about summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
committerMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
commit7514efc2f38c9ace4557d4e69d68e7d380389030 (patch)
tree7fb00fda86cfcc2ca377f191633a7cfdbfea7ca3 /src/devices
parentd6201f5d8daada3d64a0a3e0038e14eebec683ce (diff)
Imported Upstream version 1.4.0 upstream/1.4.0
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/Makefile.in14
-rw-r--r--src/devices/adsl/Makefile.am1
-rw-r--r--src/devices/adsl/Makefile.in15
-rw-r--r--src/devices/adsl/nm-atm-manager.h4
-rw-r--r--src/devices/adsl/nm-device-adsl.h7
-rw-r--r--src/devices/bluetooth/Makefile.am1
-rw-r--r--src/devices/bluetooth/Makefile.in15
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c49
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h3
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c17
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.h4
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c16
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.h5
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c18
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.h8
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.h2
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c22
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.h7
-rw-r--r--src/devices/bluetooth/nm-device-bt.c21
-rw-r--r--src/devices/bluetooth/nm-device-bt.h4
-rw-r--r--src/devices/nm-arping-manager.h4
-rw-r--r--src/devices/nm-device-bond.c154
-rw-r--r--src/devices/nm-device-bond.h6
-rw-r--r--src/devices/nm-device-bridge.h7
-rw-r--r--src/devices/nm-device-ethernet-utils.c28
-rw-r--r--src/devices/nm-device-ethernet-utils.h4
-rw-r--r--src/devices/nm-device-ethernet.c304
-rw-r--r--src/devices/nm-device-ethernet.h19
-rw-r--r--src/devices/nm-device-factory.h4
-rw-r--r--src/devices/nm-device-generic.h6
-rw-r--r--src/devices/nm-device-infiniband.c13
-rw-r--r--src/devices/nm-device-infiniband.h6
-rw-r--r--src/devices/nm-device-ip-tunnel.c9
-rw-r--r--src/devices/nm-device-ip-tunnel.h6
-rw-r--r--src/devices/nm-device-logging.h1
-rw-r--r--src/devices/nm-device-macvlan.c36
-rw-r--r--src/devices/nm-device-macvlan.h6
-rw-r--r--src/devices/nm-device-private.h15
-rw-r--r--src/devices/nm-device-tun.c10
-rw-r--r--src/devices/nm-device-tun.h6
-rw-r--r--src/devices/nm-device-veth.c55
-rw-r--r--src/devices/nm-device-veth.h10
-rw-r--r--src/devices/nm-device-vlan.c62
-rw-r--r--src/devices/nm-device-vlan.h6
-rw-r--r--src/devices/nm-device-vxlan.c19
-rw-r--r--src/devices/nm-device-vxlan.h6
-rw-r--r--src/devices/nm-device.c1945
-rw-r--r--src/devices/nm-device.h31
-rw-r--r--src/devices/nm-lldp-listener.c9
-rw-r--r--src/devices/nm-lldp-listener.h4
-rw-r--r--src/devices/team/Makefile.am1
-rw-r--r--src/devices/team/Makefile.in15
-rw-r--r--src/devices/team/nm-device-team.c118
-rw-r--r--src/devices/team/nm-device-team.h9
-rw-r--r--src/devices/team/nm-team-factory.h2
-rw-r--r--src/devices/tests/Makefile.in14
-rw-r--r--src/devices/tests/test-lldp.c2
-rw-r--r--src/devices/wifi/Makefile.am1
-rw-r--r--src/devices/wifi/Makefile.in15
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.h6
-rw-r--r--src/devices/wifi/nm-device-wifi.c451
-rw-r--r--src/devices/wifi/nm-device-wifi.h47
-rw-r--r--src/devices/wifi/nm-wifi-ap.c210
-rw-r--r--src/devices/wifi/nm-wifi-ap.h10
-rw-r--r--src/devices/wifi/tests/Makefile.in14
-rw-r--r--src/devices/wifi/tests/test-wifi-ap-utils.c2
-rw-r--r--src/devices/wwan/Makefile.am13
-rw-r--r--src/devices/wwan/Makefile.in51
-rw-r--r--src/devices/wwan/nm-device-modem.c33
-rw-r--r--src/devices/wwan/nm-modem-broadband.c158
-rw-r--r--src/devices/wwan/nm-modem-broadband.h6
-rw-r--r--src/devices/wwan/nm-modem-enum-types.c1
-rw-r--r--src/devices/wwan/nm-modem-manager.c372
-rw-r--r--src/devices/wwan/nm-modem-ofono.c1202
-rw-r--r--src/devices/wwan/nm-modem-ofono.h53
-rw-r--r--src/devices/wwan/nm-modem.c8
-rw-r--r--src/devices/wwan/nm-modem.h5
-rw-r--r--src/devices/wwan/nm-wwan-factory.c4
-rw-r--r--src/devices/wwan/nm-wwan-factory.h2
79 files changed, 4131 insertions, 1718 deletions
diff --git a/src/devices/Makefile.in b/src/devices/Makefile.in
index d6848e53..4791015d 100644
--- a/src/devices/Makefile.in
+++ b/src/devices/Makefile.in
@@ -89,8 +89,9 @@ build_triplet = @build@
 host_triplet = @host@
 subdir = src/devices
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -225,6 +226,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -281,13 +283,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -299,6 +302,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -359,6 +364,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -394,6 +401,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
diff --git a/src/devices/adsl/Makefile.am b/src/devices/adsl/Makefile.am
index 5f981740..9075021c 100644
--- a/src/devices/adsl/Makefile.am
+++ b/src/devices/adsl/Makefile.am
@@ -40,6 +40,7 @@ if ENABLE_TESTS
 
 check-local:
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-adsl.so $(SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-adsl.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 endif
 
diff --git a/src/devices/adsl/Makefile.in b/src/devices/adsl/Makefile.in
index b6805537..f3fbad4c 100644
--- a/src/devices/adsl/Makefile.in
+++ b/src/devices/adsl/Makefile.in
@@ -90,8 +90,9 @@ build_triplet = @build@
 host_triplet = @host@
 subdir = src/devices/adsl
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -252,6 +253,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -308,13 +310,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -326,6 +329,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -386,6 +391,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -421,6 +428,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -838,6 +846,7 @@ include $(GLIB_MAKEFILE)
 
 @ENABLE_TESTS_TRUE@check-local:
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-adsl.so $(SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-adsl.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/devices/adsl/nm-atm-manager.h b/src/devices/adsl/nm-atm-manager.h
index fe0bc14c..bcbc6c77 100644
--- a/src/devices/adsl/nm-atm-manager.h
+++ b/src/devices/adsl/nm-atm-manager.h
@@ -22,10 +22,6 @@
 #ifndef __NETWORKMANAGER_ATM_MANAGER_H__
 #define __NETWORKMANAGER_ATM_MANAGER_H__
 
-#include "nm-default.h"
-
-G_BEGIN_DECLS
-
 #define NM_TYPE_ATM_MANAGER            (nm_atm_manager_get_type ())
 #define NM_ATM_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ATM_MANAGER, NMAtmManager))
 
diff --git a/src/devices/adsl/nm-device-adsl.h b/src/devices/adsl/nm-device-adsl.h
index efcdaf69..cfde2b35 100644
--- a/src/devices/adsl/nm-device-adsl.h
+++ b/src/devices/adsl/nm-device-adsl.h
@@ -23,11 +23,8 @@
 #define __NETWORKMANAGER_DEVICE_ADSL_H__
 
 // Parent class
-#include "nm-default.h"
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_ADSL            (nm_device_adsl_get_type ())
 #define NM_DEVICE_ADSL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_ADSL, NMDeviceAdsl))
 #define NM_DEVICE_ADSL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_ADSL, NMDeviceAdslClass))
@@ -52,6 +49,4 @@ NMDevice *nm_device_adsl_new (const char *udi,
                               const char *driver,
                               int atm_index);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_ADSL_H */
+#endif /* NM_DEVICE_ADSL_H */
diff --git a/src/devices/bluetooth/Makefile.am b/src/devices/bluetooth/Makefile.am
index 2ff9a572..8454702e 100644
--- a/src/devices/bluetooth/Makefile.am
+++ b/src/devices/bluetooth/Makefile.am
@@ -76,6 +76,7 @@ if ENABLE_TESTS
 
 check-local:
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-bluetooth.so $(SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-bluetooth.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 endif
 
diff --git a/src/devices/bluetooth/Makefile.in b/src/devices/bluetooth/Makefile.in
index d2b0b865..c010f4c7 100644
--- a/src/devices/bluetooth/Makefile.in
+++ b/src/devices/bluetooth/Makefile.in
@@ -96,8 +96,9 @@ host_triplet = @host@
 @WITH_BLUEZ5_DUN_TRUE@am__append_3 = $(BLUEZ5_LIBS)
 subdir = src/devices/bluetooth
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -273,6 +274,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -329,13 +331,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -347,6 +350,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -407,6 +412,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -442,6 +449,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -872,6 +880,7 @@ include $(GLIB_MAKEFILE)
 
 @ENABLE_TESTS_TRUE@check-local:
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-bluetooth.so $(SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-bluetooth.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index dcfa5eec..3463da6b 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -28,6 +28,7 @@
 #include "nm-bt-error.h"
 #include "nm-bluez-common.h"
 #include "nm-bluez-device.h"
+#include "nm-settings.h"
 #include "nm-settings-connection.h"
 #include "NetworkManagerUtils.h"
 
@@ -67,7 +68,7 @@ typedef struct {
 	NMBluez5DunContext *b5_dun_context;
 #endif
 
-	NMConnectionProvider *provider;
+	NMSettings *settings;
 	GSList *connections;
 
 	NMConnection *pan_connection;
@@ -96,7 +97,7 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 
-static void cp_connection_added (NMConnectionProvider *provider,
+static void cp_connection_added (NMSettings *settings,
                                  NMConnection *connection, NMBluezDevice *self);
 static gboolean connection_compatible (NMBluezDevice *self, NMConnection *connection);
 
@@ -233,9 +234,9 @@ pan_connection_check_create (NMBluezDevice *self)
 	/* 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->provider, cp_connection_added, self);
-	added = nm_connection_provider_add_connection (priv->provider, connection, FALSE, &error);
-	g_signal_handlers_unblock_by_func (priv->provider, cp_connection_added, self);
+	g_signal_handlers_block_by_func (priv->settings, cp_connection_added, self);
+	added = NM_CONNECTION (nm_settings_add_connection (priv->settings, connection, FALSE, &error));
+	g_signal_handlers_unblock_by_func (priv->settings, cp_connection_added, self);
 
 	if (added) {
 		g_assert (!g_slist_find (priv->connections, added));
@@ -367,7 +368,7 @@ _internal_track_connection (NMBluezDevice *self, NMConnection *connection, gbool
 }
 
 static void
-cp_connection_added (NMConnectionProvider *provider,
+cp_connection_added (NMSettings *settings,
                      NMConnection *connection,
                      NMBluezDevice *self)
 {
@@ -378,7 +379,7 @@ cp_connection_added (NMConnectionProvider *provider,
 }
 
 static void
-cp_connection_removed (NMConnectionProvider *provider,
+cp_connection_removed (NMSettings *settings,
                        NMConnection *connection,
                        NMBluezDevice *self)
 {
@@ -387,8 +388,9 @@ cp_connection_removed (NMConnectionProvider *provider,
 }
 
 static void
-cp_connection_updated (NMConnectionProvider *provider,
+cp_connection_updated (NMSettings *settings,
                        NMConnection *connection,
+                       gboolean by_user,
                        NMBluezDevice *self)
 {
 	if (_internal_track_connection (self, connection,
@@ -400,12 +402,13 @@ static void
 load_connections (NMBluezDevice *self)
 {
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	const GSList *connections, *iter;
+	NMSettingsConnection *const*connections;
+	guint i;
 	gboolean changed = FALSE;
 
-	connections = nm_connection_provider_get_connections (priv->provider);
-	for (iter = connections; iter; iter = g_slist_next (iter)) {
-		NMConnection *connection = iter->data;
+	connections = nm_settings_get_connections (priv->settings, NULL);
+	for (i = 0; connections[i]; i++) {
+		NMConnection *connection = (NMConnection *) connections[i];
 
 		if (connection_compatible (self, connection))
 			changed |= _internal_track_connection (self, connection, TRUE);
@@ -1029,7 +1032,7 @@ on_bus_acquired (GObject *object, GAsyncResult *res, NMBluezDevice *self)
 NMBluezDevice *
 nm_bluez_device_new (const char *path,
                      const char *adapter_address,
-                     NMConnectionProvider *provider,
+                     NMSettings *settings,
                      int bluez_version)
 {
 	NMBluezDevice *self;
@@ -1037,7 +1040,7 @@ nm_bluez_device_new (const char *path,
 	const char *interface_name = NULL;
 
 	g_return_val_if_fail (path != NULL, NULL);
-	g_return_val_if_fail (NM_IS_CONNECTION_PROVIDER (provider), 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,
@@ -1051,14 +1054,14 @@ nm_bluez_device_new (const char *path,
 	priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
 
 	priv->bluez_version = bluez_version;
-	priv->provider = g_object_ref (provider);
+	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->provider, NM_CP_SIGNAL_CONNECTION_ADDED,   G_CALLBACK (cp_connection_added),   self);
-	g_signal_connect (priv->provider, NM_CP_SIGNAL_CONNECTION_REMOVED, G_CALLBACK (cp_connection_removed), self);
-	g_signal_connect (priv->provider, NM_CP_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (cp_connection_updated), self);
+	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,
@@ -1116,10 +1119,10 @@ dispose (GObject *object)
 	}
 #endif
 
-	if (priv->provider) {
-		g_signal_handlers_disconnect_by_func (priv->provider, cp_connection_added, self);
-		g_signal_handlers_disconnect_by_func (priv->provider, cp_connection_removed, self);
-		g_signal_handlers_disconnect_by_func (priv->provider, cp_connection_updated, self);
+	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);
@@ -1137,7 +1140,7 @@ dispose (GObject *object)
 		g_object_unref (to_delete);
 	}
 
-	g_clear_object (&priv->provider);
+	g_clear_object (&priv->settings);
 }
 
 static void
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
index c9560540..66a349e0 100644
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ b/src/devices/bluetooth/nm-bluez-device.h
@@ -22,7 +22,6 @@
 #define __NETWORKMANAGER_BLUEZ_DEVICE_H__
 
 #include "nm-connection.h"
-#include "nm-connection-provider.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))
@@ -59,7 +58,7 @@ GType nm_bluez_device_get_type (void);
 
 NMBluezDevice *nm_bluez_device_new (const char *path,
                                     const char *adapter_address,
-                                    NMConnectionProvider *provider,
+                                    NMSettings *settings,
                                     int bluez_version);
 
 const char *nm_bluez_device_get_path (NMBluezDevice *self);
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 766ecc0a..0d2e302c 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -20,19 +20,20 @@
 
 #include "nm-default.h"
 
+#include "nm-bluez-manager.h"
+
 #include <signal.h>
 #include <string.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
-#include "nm-bluez-manager.h"
 #include "nm-device-factory.h"
 #include "nm-setting-bluetooth.h"
+#include "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-connection-provider.h"
 #include "nm-device-bt.h"
 #include "nm-core-internal.h"
 #include "nm-platform.h"
@@ -51,7 +52,7 @@
 typedef struct {
 	int bluez_version;
 
-	NMConnectionProvider *provider;
+	NMSettings *settings;
 	NMBluez4Manager *manager4;
 	NMBluez5Manager *manager5;
 
@@ -190,7 +191,7 @@ setup_bluez4 (NMBluezManager *self)
 	g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version);
 
 	setup_version_number (self, 4);
-	priv->manager4 = manager = nm_bluez4_manager_new (priv->provider);
+	priv->manager4 = manager = nm_bluez4_manager_new (priv->settings);
 
 	g_signal_connect (manager,
 	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
@@ -209,7 +210,7 @@ setup_bluez5 (NMBluezManager *self)
 	g_return_if_fail (!priv->manager4 && !priv->manager5 && !priv->bluez_version);
 
 	setup_version_number (self, 5);
-	priv->manager5 = manager = nm_bluez5_manager_new (priv->provider);
+	priv->manager5 = manager = nm_bluez5_manager_new (priv->settings);
 
 	g_signal_connect (manager,
 	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
@@ -407,9 +408,9 @@ dispose (GObject *object)
 
 	priv->bluez_version = 0;
 
-	g_clear_object (&priv->provider);
-
 	G_OBJECT_CLASS (nm_bluez_manager_parent_class)->dispose (object);
+
+	g_clear_object (&priv->settings);
 }
 
 static void
@@ -417,7 +418,7 @@ nm_bluez_manager_init (NMBluezManager *self)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
 
-	priv->provider = g_object_ref (nm_connection_provider_get ());
+	priv->settings = g_object_ref (NM_SETTINGS_GET);
 }
 
 static NMDevice *
diff --git a/src/devices/bluetooth/nm-bluez-manager.h b/src/devices/bluetooth/nm-bluez-manager.h
index 1f5597d8..d23b33a7 100644
--- a/src/devices/bluetooth/nm-bluez-manager.h
+++ b/src/devices/bluetooth/nm-bluez-manager.h
@@ -22,10 +22,6 @@
 #ifndef __NETWORKMANAGER_BLUEZ_MANAGER_H__
 #define __NETWORKMANAGER_BLUEZ_MANAGER_H__
 
-#include "nm-default.h"
-
-G_BEGIN_DECLS
-
 #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))
 
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index 9d8fd210..2ef071a8 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -20,13 +20,15 @@
 
 #include "nm-default.h"
 
+#include "nm-bluez4-adapter.h"
+
 #include <string.h>
 
 #include "nm-dbus-interface.h"
-#include "nm-bluez4-adapter.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "nm-core-internal.h"
+#include "nm-settings.h"
 
 G_DEFINE_TYPE (NMBluez4Adapter, nm_bluez4_adapter, G_TYPE_OBJECT)
 
@@ -41,7 +43,7 @@ typedef struct {
 	GHashTable *devices;
 
 	/* Cached for devices */
-	NMConnectionProvider *provider;
+	NMSettings *settings;
 } NMBluez4AdapterPrivate;
 
 
@@ -160,7 +162,7 @@ device_created (GDBusProxy *proxy, const char *path, gpointer user_data)
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
-	device = nm_bluez_device_new (path, priv->address, priv->provider, 4);
+	device = nm_bluez_device_new (path, priv->address, priv->settings, 4);
 	g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self);
 	g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self);
 	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
@@ -234,17 +236,19 @@ query_properties (NMBluez4Adapter *self)
 /***********************************************************/
 
 NMBluez4Adapter *
-nm_bluez4_adapter_new (const char *path, NMConnectionProvider *provider)
+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->provider = provider;
+	priv->settings = g_object_ref (settings);
 
 	priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
 	                                             G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
@@ -301,6 +305,8 @@ finalize (GObject *object)
 	g_object_unref (priv->proxy);
 
 	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->finalize (object);
+
+	g_object_unref (priv->settings);
 }
 
 static void
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h
index a8a2bfb4..ee61112f 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.h
+++ b/src/devices/bluetooth/nm-bluez4-adapter.h
@@ -21,10 +21,7 @@
 #ifndef __NETWORKMANAGER_BLUEZ4_ADAPTER_H__
 #define __NETWORKMANAGER_BLUEZ4_ADAPTER_H__
 
-
-#include "nm-default.h"
 #include "nm-bluez-device.h"
-#include "nm-connection-provider.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))
@@ -54,7 +51,7 @@ typedef struct {
 GType nm_bluez4_adapter_get_type (void);
 
 NMBluez4Adapter *nm_bluez4_adapter_new (const char *path,
-                                        NMConnectionProvider *provider);
+                                        NMSettings *settings);
 
 const char *nm_bluez4_adapter_get_path (NMBluez4Adapter *self);
 
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index 146612a8..36bc30e5 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -21,20 +21,22 @@
 
 #include "nm-default.h"
 
+#include "nm-bluez4-manager.h"
+
 #include <signal.h>
 #include <string.h>
 #include <stdlib.h>
 
-#include "nm-bluez-manager.h"
-#include "nm-bluez4-manager.h"
 #include "nm-bluez4-adapter.h"
+#include "nm-bluez-manager.h"
 #include "nm-bluez-common.h"
 #include "nm-core-internal.h"
+#include "nm-settings.h"
 
 typedef struct {
 	gulong name_owner_changed_id;
 
-	NMConnectionProvider *provider;
+	NMSettings *settings;
 
 	GDBusProxy *proxy;
 
@@ -155,7 +157,7 @@ default_adapter_changed (GDBusProxy *proxy, const char *path, NMBluez4Manager *s
 
 	/* Add the new default adapter */
 	if (path) {
-		priv->adapter = nm_bluez4_adapter_new (path, priv->provider);
+		priv->adapter = nm_bluez4_adapter_new (path, priv->settings);
 		g_signal_connect (priv->adapter, "initialized", G_CALLBACK (adapter_initialized), self);
 	}
 }
@@ -223,12 +225,14 @@ name_owner_changed_cb (GObject *object,
 /****************************************************************/
 
 NMBluez4Manager *
-nm_bluez4_manager_new (NMConnectionProvider *provider)
+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)->provider = provider;
+	NM_BLUEZ4_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings);
 	return instance;
 }
 
@@ -264,6 +268,8 @@ dispose (GObject *object)
 	g_clear_object (&priv->adapter);
 
 	G_OBJECT_CLASS (nm_bluez4_manager_parent_class)->dispose (object);
+
+	g_clear_object (&priv->settings);
 }
 
 static void
diff --git a/src/devices/bluetooth/nm-bluez4-manager.h b/src/devices/bluetooth/nm-bluez4-manager.h
index f6bf7658..8afe6924 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.h
+++ b/src/devices/bluetooth/nm-bluez4-manager.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -22,11 +21,6 @@
 #ifndef __NETWORKMANAGER_BLUEZ4_MANAGER_H__
 #define __NETWORKMANAGER_BLUEZ4_MANAGER_H__
 
-#include "nm-default.h"
-#include "nm-connection-provider.h"
-
-G_BEGIN_DECLS
-
 #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))
@@ -51,7 +45,7 @@ typedef struct {
 
 GType nm_bluez4_manager_get_type (void);
 
-NMBluez4Manager *nm_bluez4_manager_new (NMConnectionProvider *provider);
+NMBluez4Manager *nm_bluez4_manager_new (NMSettings *settings);
 
 void nm_bluez4_manager_query_devices (NMBluez4Manager *manager);
 
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h
index c59cbb80..124c1a05 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/devices/bluetooth/nm-bluez5-dun.h
@@ -21,8 +21,6 @@
 #ifndef _NM_BLUEZ5_UTILS_H_
 #define _NM_BLUEZ5_UTILS_H_
 
-#include "nm-default.h"
-
 typedef struct _NMBluez5DunContext NMBluez5DunContext;
 
 typedef void (*NMBluez5DunFunc) (NMBluez5DunContext *context,
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index 66d171bf..a014d0c1 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -22,19 +22,21 @@
 
 #include "nm-default.h"
 
+#include "nm-bluez5-manager.h"
+
 #include <signal.h>
 #include <string.h>
 #include <stdlib.h>
 
+#include "nm-core-internal.h"
+
 #include "nm-bluez-manager.h"
-#include "nm-bluez5-manager.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
-
-#include "nm-core-internal.h"
+#include "nm-settings.h"
 
 typedef struct {
-	NMConnectionProvider *provider;
+	NMSettings *settings;
 
 	GDBusProxy *proxy;
 
@@ -140,7 +142,7 @@ device_added (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
-	device = nm_bluez_device_new (path, NULL, priv->provider, 5);
+	device = nm_bluez_device_new (path, NULL, priv->settings, 5);
 	g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self);
 	g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self);
 	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
@@ -180,7 +182,7 @@ object_manager_interfaces_removed (GDBusProxy       *proxy,
                                    const char      **ifaces,
                                    NMBluez5Manager  *self)
 {
-	if (_nm_utils_string_in_list (BLUEZ5_DEVICE_INTERFACE, ifaces))
+	if (ifaces && g_strv_contains (ifaces, BLUEZ5_DEVICE_INTERFACE))
 		device_removed (proxy, path, self);
 }
 
@@ -309,12 +311,14 @@ bluez_cleanup (NMBluez5Manager *self, gboolean do_signal)
 /****************************************************************/
 
 NMBluez5Manager *
-nm_bluez5_manager_new (NMConnectionProvider *provider)
+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)->provider = provider;
+	NM_BLUEZ5_MANAGER_GET_PRIVATE (instance)->settings = g_object_ref (settings);
 	return instance;
 }
 
@@ -347,6 +351,8 @@ finalize (GObject *object)
 	g_hash_table_destroy (priv->devices);
 
 	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->finalize (object);
+
+	g_object_unref (priv->settings);
 }
 
 static void
diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h
index 1c531090..0e309d33 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.h
+++ b/src/devices/bluetooth/nm-bluez5-manager.h
@@ -22,11 +22,6 @@
 #ifndef __NETWORKMANAGER_BLUEZ5_MANAGER_H__
 #define __NETWORKMANAGER_BLUEZ5_MANAGER_H__
 
-#include "nm-default.h"
-#include "nm-connection-provider.h"
-
-G_BEGIN_DECLS
-
 #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))
@@ -51,7 +46,7 @@ typedef struct {
 
 GType nm_bluez5_manager_get_type (void);
 
-NMBluez5Manager *nm_bluez5_manager_new (NMConnectionProvider *provider);
+NMBluez5Manager *nm_bluez5_manager_new (NMSettings *settings);
 
 void nm_bluez5_manager_query_devices (NMBluez5Manager *manager);
 
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index eef4ed0a..67ae15bc 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -362,6 +362,7 @@ static void
 ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceBt *self = NM_DEVICE_BT (user_data);
 
 	switch (nm_device_get_state (device)) {
 	case NM_DEVICE_STATE_PREPARE:
@@ -375,7 +376,18 @@ ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
 			nm_device_activate_schedule_ip4_config_timeout (device);
-		else {
+		else if (nm_device_activate_ip6_state_in_conf (device))
+			nm_device_activate_schedule_ip6_config_timeout (device);
+		else if (nm_device_activate_ip4_state_done (device)) {
+			nm_device_ip_method_failed (device,
+			                            AF_INET,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		} else if (nm_device_activate_ip6_state_done (device)) {
+			nm_device_ip_method_failed (device,
+			                            AF_INET6,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		} else {
+			_LOGW (LOGD_MB, "PPP failure in unexpected state %u", (guint) nm_device_get_state (device));
 			nm_device_state_changed (device,
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
@@ -502,8 +514,9 @@ modem_ip4_config_result (NMModem *modem,
 		_LOGW (LOGD_MB | LOGD_IP4 | LOGD_BT,
 		       "retrieving IP4 configuration failed: %s",
 		       error->message);
-
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		nm_device_ip_method_failed (device,
+		                            AF_INET,
+		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else
 		nm_device_activate_schedule_ip4_config_result (device, config);
 }
@@ -989,7 +1002,7 @@ nm_device_bt_new (NMBluezDevice *bt_device,
 	                                  NM_DEVICE_UDI, udi,
 	                                  NM_DEVICE_IFACE, bdaddr,
 	                                  NM_DEVICE_DRIVER, "bluez",
-	                                  NM_DEVICE_HW_ADDRESS, bdaddr,
+	                                  NM_DEVICE_PERM_HW_ADDRESS, bdaddr,
 	                                  NM_DEVICE_BT_DEVICE, bt_device,
 	                                  NM_DEVICE_BT_NAME, name,
 	                                  NM_DEVICE_BT_CAPABILITIES, capabilities,
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h
index c565745f..c97cca01 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/devices/bluetooth/nm-device-bt.h
@@ -25,8 +25,6 @@
 #include "nm-bluez-device.h"
 #include "nm-modem.h"
 
-G_BEGIN_DECLS
-
 #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))
 #define NM_DEVICE_BT_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_BT, NMDeviceBtClass))
@@ -63,6 +61,4 @@ gboolean nm_device_bt_modem_added (NMDeviceBt *device,
                                    NMModem *modem,
                                    const char *driver);
 
-G_END_DECLS
-
 #endif /* __NETWORKMANAGER_DEVICE_BT_H__ */
diff --git a/src/devices/nm-arping-manager.h b/src/devices/nm-arping-manager.h
index 4a4f8d4b..78965a0d 100644
--- a/src/devices/nm-arping-manager.h
+++ b/src/devices/nm-arping-manager.h
@@ -19,8 +19,6 @@
 
 #include <netinet/in.h>
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_ARPING_MANAGER            (nm_arping_manager_get_type ())
 #define NM_ARPING_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ARPING_MANAGER, NMArpingManager))
 #define NM_ARPING_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_ARPING_MANAGER, NMArpingManagerClass))
@@ -48,6 +46,4 @@ gboolean nm_arping_manager_check_address (NMArpingManager *self, in_addr_t addre
 void nm_arping_manager_announce_addresses (NMArpingManager *self);
 void nm_arping_manager_reset (NMArpingManager *self);
 
-G_END_DECLS
-
 #endif /* __NM_ARPING_MANAGER__ */
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 9c98c8ea..3f17849b 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -15,7 +15,7 @@
  * 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.
+ * Copyright 2011 - 2016 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -118,12 +118,15 @@ complete_connection (NMDevice *device,
 /******************************************************************/
 
 static gboolean
-set_bond_attr (NMDevice *device, const char *attr, const char *value)
+set_bond_attr (NMDevice *device, NMBondMode mode, const char *attr, const char *value)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
 	gboolean ret;
 	int ifindex = nm_device_get_ifindex (device);
 
+	if (!_nm_setting_bond_option_supported (attr, mode))
+		return FALSE;
+
 	ret = nm_platform_sysctl_master_set_option (NM_PLATFORM_GET, ifindex, attr, value);
 	if (!ret)
 		_LOGW (LOGD_HW, "failed to set bonding attribute '%s' to '%s'", attr, value);
@@ -134,10 +137,10 @@ set_bond_attr (NMDevice *device, const char *attr, const char *value)
 static gboolean
 ignore_if_zero (const char *option, const char *value)
 {
-	if (strcmp (option, "arp_interval") &&
-	    strcmp (option, "miimon") &&
-	    strcmp (option, "downdelay") &&
-	    strcmp (option, "updelay"))
+	if (!NM_IN_STRSET (option, NM_SETTING_BOND_OPTION_ARP_INTERVAL,
+	                           NM_SETTING_BOND_OPTION_DOWNDELAY,
+	                           NM_SETTING_BOND_OPTION_MIIMON,
+	                           NM_SETTING_BOND_OPTION_UPDELAY))
 		return FALSE;
 
 	return g_strcmp0 (value, "0") == 0 ? TRUE : FALSE;
@@ -173,7 +176,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 		    && !ignore_if_zero (*options, value)
 		    && !nm_streq0 (value, defvalue)) {
 			/* Replace " " with "," for arp_ip_targets from the kernel */
-			if (strcmp (*options, "arp_ip_target") == 0) {
+			if (strcmp (*options, NM_SETTING_BOND_OPTION_ARP_IP_TARGET) == 0) {
 				for (p = value; *p; p++) {
 					if (*p == ' ')
 						*p = ',';
@@ -201,6 +204,7 @@ master_update_slave_connection (NMDevice *self,
 
 static void
 set_arp_targets (NMDevice *device,
+                 NMBondMode mode,
                  const char *value,
                  const char *delim,
                  const char *prefix)
@@ -214,7 +218,7 @@ set_arp_targets (NMDevice *device,
 	for (iter = items; iter && *iter; iter++) {
 		if (*iter[0]) {
 			tmp = g_strdup_printf ("%s%s", prefix, *iter);
-			set_bond_attr (device, "arp_ip_target", tmp);
+			set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, tmp);
 			g_free (tmp);
 		}
 	}
@@ -223,7 +227,7 @@ set_arp_targets (NMDevice *device,
 
 static void
 set_simple_option (NMDevice *device,
-                   const char *attr,
+                   NMBondMode mode,
                    NMSettingBond *s_bond,
                    const char *opt)
 {
@@ -232,18 +236,20 @@ set_simple_option (NMDevice *device,
 	value = nm_setting_bond_get_option_by_name (s_bond, opt);
 	if (!value)
 		value = nm_setting_bond_get_option_default (s_bond, opt);
-	set_bond_attr (device, attr, value);
+	set_bond_attr (device, mode, opt, value);
 }
 
 static NMActStageReturn
 apply_bonding_config (NMDevice *device)
 {
+	NMDeviceBond *self = NM_DEVICE_BOND (device);
 	NMConnection *connection;
 	NMSettingBond *s_bond;
 	int ifindex = nm_device_get_ifindex (device);
-	const char *mode, *value;
+	const char *mode_str, *value;
 	char *contents;
 	gboolean set_arp_interval = TRUE;
+	NMBondMode mode;
 
 	/* Option restrictions:
 	 *
@@ -263,98 +269,90 @@ apply_bonding_config (NMDevice *device)
 	s_bond = nm_connection_get_setting_bond (connection);
 	g_assert (s_bond);
 
-	mode = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
-	if (mode == NULL)
-		mode = "balance-rr";
+	mode_str = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
+	if (!mode_str)
+		mode_str = "balance-rr";
+
+	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;
+	}
+
+	/* Set mode first, as some other options (e.g. arp_interval) are valid
+	 * only for certain modes.
+	 */
+
+	set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_MODE, mode_str);
 
 	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MIIMON);
 	if (value && atoi (value)) {
 		/* clear arp interval */
-		set_bond_attr (device, "arp_interval", "0");
+		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_INTERVAL, "0");
 		set_arp_interval = FALSE;
 
-		set_bond_attr (device, "miimon", value);
-		set_simple_option (device, "updelay", s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
-		set_simple_option (device, "downdelay", s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
+		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_MIIMON, value);
+		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
+		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
 	} else if (!value) {
-		/* If not given, and arp_interval is not given, default to 100 */
-		long int val_int;
-		char *end;
-
+		/* If not given, and arp_interval is not given or disabled, default to 100 */
 		value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		errno = 0;
-		val_int = strtol (value ? value : "0", &end, 10);
-		if (!value || (val_int == 0 && errno == 0 && *end == '\0'))
-			set_bond_attr (device, "miimon", "100");
+		if (_nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, 0) == 0)
+			set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_MIIMON, "100");
 	}
 
-	/* The stuff after 'mode' requires the given mode or doesn't care */
-	set_bond_attr (device, "mode", mode);
-
-	/* arp_interval not compatible with ALB, TLB */
-	if (NM_IN_STRSET (mode, "balance-alb", "balance-tlb"))
-		set_arp_interval = FALSE;
-
 	if (set_arp_interval) {
-		set_simple_option (device, "arp_interval", s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-
+		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
 		/* Just let miimon get cleared automatically; even setting miimon to
 		 * 0 (disabled) clears arp_interval.
 		 */
 	}
 
+	/* ARP validate: value > 0 only valid in active-backup mode */
 	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE);
-	/* arp_validate > 0 only valid in active-backup mode */
 	if (   value
 	    && !nm_streq (value, "0")
 	    && !nm_streq (value, "none")
-	    && nm_streq (mode, "active-backup"))
-		set_bond_attr (device, "arp_validate", value);
+	    && mode == NM_BOND_MODE_ACTIVEBACKUP)
+		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_VALIDATE, value);
 	else
-		set_bond_attr (device, "arp_validate", "0");
-
-	if (NM_IN_STRSET (mode, "active-backup", "balance-alb", "balance-tlb")) {
-		value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
-		set_bond_attr (device, "primary", value ? value : "");
-		set_simple_option (device, "lp_interval", s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL);
-	}
+		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_VALIDATE, "0");
 
-	/* Clear ARP targets */
-	contents = nm_platform_sysctl_master_get_option (NM_PLATFORM_GET, ifindex, "arp_ip_target");
-	set_arp_targets (device, contents, " \n", "-");
-	g_free (contents);
+	/* Primary */
+	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
+	set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_PRIMARY, value ? value : "");
 
-	/* Add new ARP targets */
+	/* ARP targets: clear and initialize the list */
+	contents = nm_platform_sysctl_master_get_option (NM_PLATFORM_GET, ifindex,
+	                                                 NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+	set_arp_targets (device, mode, contents, " \n", "-");
 	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-	set_arp_targets (device, value, ",", "+");
-
-	set_simple_option (device, "primary_reselect", s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT);
-	set_simple_option (device, "fail_over_mac", s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC);
-	set_simple_option (device, "use_carrier", s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER);
-	set_simple_option (device, "ad_select", s_bond, NM_SETTING_BOND_OPTION_AD_SELECT);
-	set_simple_option (device, "xmit_hash_policy", s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY);
-	set_simple_option (device, "resend_igmp", s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP);
-	set_simple_option (device, "active_slave", s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
-	set_simple_option (device, "all_slaves_active", s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE);
-	set_simple_option (device, "num_grat_arp", s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
-	set_simple_option (device, "num_unsol_na", s_bond, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
-
-	if (nm_streq (mode, "802.3ad")) {
-		set_simple_option (device, "lacp_rate", s_bond, NM_SETTING_BOND_OPTION_LACP_RATE);
-		set_simple_option (device, "ad_actor_sys_prio", s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO);
-		set_simple_option (device, "ad_actor_system", s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM);
-		set_simple_option (device, "ad_user_port_key", s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY);
-		set_simple_option (device, "min_links", s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS);
-	}
-
-	if (nm_streq (mode, "active-backup"))
-		set_simple_option (device, "arp_all_targets", s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS);
-
-	if (nm_streq (mode, "balance-rr"))
-		set_simple_option (device, "packets_per_slave", s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE);
+	set_arp_targets (device, mode, value, ",", "+");
+	g_free (contents);
 
-	if (nm_streq (mode, "balance-tlb"))
-		set_simple_option (device, "tlb_dynamic_lb", s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB);
+	/* AD actor system: don't set if empty */
+	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM);
+	if (value)
+		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, value);
+
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_AD_SELECT);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_LACP_RATE);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER);
+	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY);
 
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
diff --git a/src/devices/nm-device-bond.h b/src/devices/nm-device-bond.h
index 9e86719e..02b6c8e5 100644
--- a/src/devices/nm-device-bond.h
+++ b/src/devices/nm-device-bond.h
@@ -23,8 +23,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_BOND            (nm_device_bond_get_type ())
 #define NM_DEVICE_BOND(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BOND, NMDeviceBond))
 #define NM_DEVICE_BOND_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_BOND, NMDeviceBondClass))
@@ -37,6 +35,4 @@ typedef NMDeviceClass NMDeviceBondClass;
 
 GType nm_device_bond_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_BOND_H */
+#endif /* NM_DEVICE_BOND_H */
diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h
index 46482675..05da9ed7 100644
--- a/src/devices/nm-device-bridge.h
+++ b/src/devices/nm-device-bridge.h
@@ -21,11 +21,8 @@
 #ifndef __NETWORKMANAGER_DEVICE_BRIDGE_H__
 #define __NETWORKMANAGER_DEVICE_BRIDGE_H__
 
-#include "nm-default.h"
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_BRIDGE            (nm_device_bridge_get_type ())
 #define NM_DEVICE_BRIDGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BRIDGE, NMDeviceBridge))
 #define NM_DEVICE_BRIDGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_BRIDGE, NMDeviceBridgeClass))
@@ -38,6 +35,4 @@ typedef NMDeviceClass NMDeviceBridgeClass;
 
 GType nm_device_bridge_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_BRIDGE_H */
+#endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */
diff --git a/src/devices/nm-device-ethernet-utils.c b/src/devices/nm-device-ethernet-utils.c
index d034ddfc..298e6dff 100644
--- a/src/devices/nm-device-ethernet-utils.c
+++ b/src/devices/nm-device-ethernet-utils.c
@@ -25,30 +25,26 @@
 #include "nm-device-ethernet-utils.h"
 
 char *
-nm_device_ethernet_utils_get_default_wired_name (const GSList *connections)
+nm_device_ethernet_utils_get_default_wired_name (NMConnection *const *connections)
 {
-	const GSList *iter;
-	char *cname = NULL;
-	int i = 0;
+	char *temp;
+	guint j;
+	int i;
 
 	/* Find the next available unique connection name */
-	while (!cname && (i++ < 10000)) {
-		char *temp;
-		gboolean found = FALSE;
-
+	for (i = 1; i <= 10000; i++) {
 		temp = g_strdup_printf (_("Wired connection %d"), i);
-		for (iter = connections; iter; iter = iter->next) {
-			if (g_strcmp0 (nm_connection_get_id (NM_CONNECTION (iter->data)), temp) == 0) {
-				found = TRUE;
+		for (j = 0; connections[j]; j++) {
+			if (nm_streq0 (nm_connection_get_id (connections[j]), temp)) {
 				g_free (temp);
-				break;
+				goto next;
 			}
 		}
-
-		if (found == FALSE)
-			cname = temp;
+		return temp;
+next:
+		;
 	}
 
-	return cname;
+	return NULL;
 }
 
diff --git a/src/devices/nm-device-ethernet-utils.h b/src/devices/nm-device-ethernet-utils.h
index 31d645d8..197d0a9e 100644
--- a/src/devices/nm-device-ethernet-utils.h
+++ b/src/devices/nm-device-ethernet-utils.h
@@ -19,8 +19,6 @@
 #ifndef __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__
 #define __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__
 
-#include "nm-default.h"
-
-char *nm_device_ethernet_utils_get_default_wired_name (const GSList *connections);
+char *nm_device_ethernet_utils_get_default_wired_name (NMConnection *const *connections);
 
 #endif  /* NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H */
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index d27f7dca..90d472da 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -21,6 +21,8 @@
 
 #include "nm-default.h"
 
+#include "nm-device-ethernet.h"
+
 #include <netinet/in.h>
 #include <string.h>
 #include <stdlib.h>
@@ -29,7 +31,6 @@
 
 #include <gudev/gudev.h>
 
-#include "nm-device-ethernet.h"
 #include "nm-device-private.h"
 #include "nm-activation-request.h"
 #include "NetworkManagerUtils.h"
@@ -44,7 +45,7 @@
 #include "nm-settings-connection.h"
 #include "nm-config.h"
 #include "nm-device-ethernet-utils.h"
-#include "nm-connection-provider.h"
+#include "nm-settings.h"
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
@@ -54,15 +55,15 @@
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceEthernet);
 
-G_DEFINE_TYPE (NMDeviceEthernet, nm_device_ethernet, NM_TYPE_DEVICE)
-
-#define NM_DEVICE_ETHERNET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetPrivate))
+/*****************************************************************************/
 
 #define WIRED_SECRETS_TRIES "wired-secrets-tries"
 
 #define PPPOE_RECONNECT_DELAY 7
 #define PPPOE_ENCAP_OVERHEAD  8 /* 2 bytes for PPP, 6 for PPPoE */
 
+/*****************************************************************************/
+
 typedef struct Supplicant {
 	NMSupplicantManager *mgr;
 	NMSupplicantInterface *iface;
@@ -90,7 +91,7 @@ typedef enum {
 	DCB_WAIT_CARRIER_POSTCONFIG_UP,
 } DcbWait;
 
-typedef struct {
+typedef struct _NMDeviceEthernetPrivate {
 	guint32             speed;
 
 	Supplicant          supplicant;
@@ -117,15 +118,29 @@ typedef struct {
 	gulong        dcb_carrier_id;
 } NMDeviceEthernetPrivate;
 
-enum {
-	PROP_0,
-	PROP_PERM_HW_ADDRESS,
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceEthernet,
 	PROP_SPEED,
 	PROP_S390_SUBCHANNELS,
+);
 
-	LAST_PROP
-};
+/*****************************************************************************/
+
+G_DEFINE_TYPE (NMDeviceEthernet, nm_device_ethernet, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_ETHERNET_GET_PRIVATE(self) \
+	({ \
+		/* preserve the const-ness of self. Unfortunately, that
+		 * way, @self cannot be a void pointer */ \
+		typeof (self) _self = (self); \
+		\
+		/* Get compiler error if variable is of wrong type */ \
+		_nm_unused const NMDeviceEthernet *_self2 = (_self); \
+		\
+		nm_assert (NM_IS_DEVICE_ETHERNET (_self)); \
+		_self->_priv; \
+	})
 
+/*****************************************************************************/
 
 static char *
 get_link_basename (const char *parent_path, const char *name, GError **error)
@@ -147,21 +162,29 @@ static void
 _update_s390_subchannels (NMDeviceEthernet *self)
 {
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	GUdevDevice *dev;
-	GUdevDevice *parent = NULL;
-	const char *parent_path, *item, *driver;
+	gs_unref_object GUdevDevice *dev = NULL;
+	gs_unref_object GUdevDevice *parent = NULL;
+	const char *parent_path, *item;
 	int ifindex;
 	GDir *dir;
 	GError *error = NULL;
 
-	ifindex = nm_device_get_ifindex (NM_DEVICE (self));
-	dev = (GUdevDevice *) nm_platform_link_get_udev_device (NM_PLATFORM_GET, ifindex);
+	if (priv->subchannels) {
+		/* only read the subchannels once. For one, we don't expect them to change
+		 * on multiple invocations. Second, we didn't implement proper reloading.
+		 * Proper reloading might also be complicated, because the subchannels are
+		 * used to match on devices based on a device-spec. Thus, it's not clear
+		 * what it means to change afterwards. */
+		return;
+	}
+
+	ifindex = nm_device_get_ifindex ((NMDevice *) self);
+	dev = (GUdevDevice *) nm_g_object_ref (nm_platform_link_get_udev_device (NM_PLATFORM_GET, ifindex));
 	if (!dev) {
-		_LOGW (LOGD_DEVICE | LOGD_HW, "failed to find device %d '%s' with udev",
-		       ifindex, nm_device_get_iface (NM_DEVICE (self)) ?: "(null)");
-		goto out;
+		/* we only call _update_s390_subchannels() when platform claims the device to be initialized.
+		 * Thus, we expect to successfully lookup a GUdevDevice. */
+		g_return_if_reached ();
 	}
-	g_object_ref (dev);
 
 	/* Try for the "ccwgroup" parent */
 	parent = g_udev_device_get_parent_with_subsystem (dev, "ccwgroup", NULL);
@@ -169,17 +192,17 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 		/* FIXME: whatever 'lcs' devices' subsystem is here... */
 		if (!parent) {
 			/* Not an s390 device */
-			goto out;
+			return;
 		}
 	}
 
 	parent_path = g_udev_device_get_sysfs_path (parent);
 	dir = g_dir_open (parent_path, 0, &error);
 	if (!dir) {
-		_LOGW (LOGD_DEVICE | LOGD_HW, "failed to open directory '%s': %s",
+		_LOGW (LOGD_DEVICE | LOGD_HW, "update-s390: failed to open directory '%s': %s",
 		       parent_path, error->message);
 		g_clear_error (&error);
-		goto out;
+		return;
 	}
 
 	while ((item = g_dir_read_name (dir))) {
@@ -206,11 +229,11 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 				g_hash_table_insert (priv->s390_options, g_strdup (item), value);
 				value = NULL;
 			} else
-				_LOGW (LOGD_DEVICE | LOGD_HW, "error reading %s", path);
+				_LOGW (LOGD_DEVICE | LOGD_HW, "update-s390: error reading %s", path);
 		}
 
 		if (error) {
-			_LOGW (LOGD_DEVICE | LOGD_HW, "%s", error->message);
+			_LOGW (LOGD_DEVICE | LOGD_HW, "update-s390: failed reading sysfs for %s (%s)", item, error->message);
 			g_clear_error (&error);
 		}
 	}
@@ -235,42 +258,23 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 	priv->subchannels_dbus[2] = g_strdup (priv->subchan3);
 	priv->subchannels_dbus[3] = NULL;
 
-	driver = nm_device_get_driver (NM_DEVICE (self));
-	_LOGI (LOGD_DEVICE | LOGD_HW, "found s390 '%s' subchannels [%s]",
-	       driver ? driver : "(unknown driver)", priv->subchannels);
+	_LOGI (LOGD_DEVICE | LOGD_HW, "update-s390: found s390 '%s' subchannels [%s]",
+	       nm_device_get_driver ((NMDevice *) self) ?: "(unknown driver)",
+	       priv->subchannels);
 
-out:
-	if (parent)
-		g_object_unref (parent);
-	if (dev)
-		g_object_unref (dev);
+	_notify (self, PROP_S390_SUBCHANNELS);
 }
 
-static GObject*
-constructor (GType type,
-             guint n_construct_params,
-             GObjectConstructParam *construct_params)
+static void
+constructed (GObject *object)
 {
-	GObject *object;
-
-	object = G_OBJECT_CLASS (nm_device_ethernet_parent_class)->constructor (type,
-	                                                                        n_construct_params,
-	                                                                        construct_params);
-	if (object) {
-#ifndef G_DISABLE_ASSERT
-		int ifindex = nm_device_get_ifindex (NM_DEVICE (object));
-		NMLinkType link_type = nm_platform_link_get_type (NM_PLATFORM_GET, ifindex);
-
-		g_assert (   link_type == NM_LINK_TYPE_ETHERNET
-		          || link_type == NM_LINK_TYPE_VETH
-		          || link_type == NM_LINK_TYPE_NONE);
-#endif
-
-		/* s390 stuff */
-		_update_s390_subchannels (NM_DEVICE_ETHERNET (object));
-	}
+	const NMPlatformLink *pllink;
+
+	G_OBJECT_CLASS (nm_device_ethernet_parent_class)->constructed (object);
 
-	return object;
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, nm_device_get_ifindex ((NMDevice *) object));
+	if (pllink && pllink->initialized)
+		_update_s390_subchannels ((NMDeviceEthernet *) object);
 }
 
 static void
@@ -302,16 +306,12 @@ device_state_changed (NMDevice *device,
 static void
 nm_device_ethernet_init (NMDeviceEthernet *self)
 {
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	priv->s390_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-}
+	NMDeviceEthernetPrivate *priv;
 
-static void
-realize_start_notify (NMDevice *device, const NMPlatformLink *plink)
-{
-	NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->realize_start_notify (device, plink);
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetPrivate);
+	self->_priv = priv;
 
-	g_object_notify (G_OBJECT (device), NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS);
+	priv->s390_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 }
 
 static NMDeviceCapabilities
@@ -407,7 +407,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		if (!match_subchans (self, s_wired, &try_mac))
 			return FALSE;
 
-		perm_hw_addr = nm_device_get_permanent_hw_address (device);
+		perm_hw_addr = nm_device_get_permanent_hw_address (device, TRUE);
 		mac = nm_setting_wired_get_mac_address (s_wired);
 		if (perm_hw_addr) {
 			if (try_mac && mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
@@ -812,21 +812,17 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (dev);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	NMSettingWired *s_wired;
-	const char *cloned_mac;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
 
 	ret = NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage1_prepare (dev, reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
-		s_wired = (NMSettingWired *) nm_device_get_applied_setting (dev, NM_TYPE_SETTING_WIRED);
-		if (s_wired) {
-			/* Set device MAC address if the connection wants to change it */
-			cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
-			nm_device_set_hw_addr (dev, cloned_mac, "set", LOGD_ETHER);
-		}
+		if (!nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE))
+			ret = NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
+	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		/* 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
@@ -946,7 +942,7 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *reas
 	req = nm_device_get_act_request (NM_DEVICE (self));
 	g_assert (req);
 
-	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_PPPOE);
+	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
 	g_assert (s_pppoe);
 
 	priv->ppp_manager = nm_ppp_manager_new (nm_device_get_iface (NM_DEVICE (self)));
@@ -972,22 +968,6 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *reas
 
 /****************************************************************/
 
-static void
-dcb_timeout_cleanup (NMDevice *device)
-{
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
-
-	nm_clear_g_source (&priv->dcb_timeout_id);
-}
-
-static void
-dcb_carrier_cleanup (NMDevice *device)
-{
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
-
-	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
-}
-
 static void dcb_state (NMDevice *device, gboolean timeout);
 
 static gboolean
@@ -1011,12 +991,12 @@ dcb_carrier_timeout (gpointer user_data)
 static gboolean
 dcb_configure (NMDevice *device)
 {
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
+	NMDeviceEthernet *self = (NMDeviceEthernet *) device;
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMSettingDcb *s_dcb;
 	GError *error = NULL;
 
-	dcb_timeout_cleanup (device);
+	nm_clear_g_source (&priv->dcb_timeout_id);
 
 	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
 	g_assert (s_dcb);
@@ -1043,7 +1023,7 @@ dcb_enable (NMDevice *device)
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	GError *error = NULL;
 
-	dcb_timeout_cleanup (device);
+	nm_clear_g_source (&priv->dcb_timeout_id);
 	if (!nm_dcb_enable (nm_device_get_iface (device), TRUE, &error)) {
 		_LOGW (LOGD_DCB, "Activation: (ethernet) failed to enable DCB/FCoE: %s",
 		       error->message);
@@ -1081,9 +1061,9 @@ dcb_state (NMDevice *device, gboolean timeout)
 	case DCB_WAIT_CARRIER_PREENABLE_UP:
 		if (timeout || carrier) {
 			_LOGD (LOGD_DCB, "dcb_state() enabling DCB");
-			dcb_timeout_cleanup (device);
+			nm_clear_g_source (&priv->dcb_timeout_id);
 			if (!dcb_enable (device)) {
-				dcb_carrier_cleanup (device);
+				nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
 				nm_device_state_changed (device,
 				                         NM_DEVICE_STATE_FAILED,
 				                         NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
@@ -1091,7 +1071,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 		}
 		break;
 	case DCB_WAIT_CARRIER_PRECONFIG_DOWN:
-		dcb_timeout_cleanup (device);
+		nm_clear_g_source (&priv->dcb_timeout_id);
 		priv->dcb_wait = DCB_WAIT_CARRIER_PRECONFIG_UP;
 
 		if (!carrier) {
@@ -1105,9 +1085,9 @@ dcb_state (NMDevice *device, gboolean timeout)
 	case DCB_WAIT_CARRIER_PRECONFIG_UP:
 		if (timeout || carrier) {
 			_LOGD (LOGD_DCB, "dcb_state() preconfig up configuring DCB");
-			dcb_timeout_cleanup (device);
+			nm_clear_g_source (&priv->dcb_timeout_id);
 			if (!dcb_configure (device)) {
-				dcb_carrier_cleanup (device);
+				nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
 				nm_device_state_changed (device,
 				                         NM_DEVICE_STATE_FAILED,
 				                         NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
@@ -1115,7 +1095,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 		}
 		break;
 	case DCB_WAIT_CARRIER_POSTCONFIG_DOWN:
-		dcb_timeout_cleanup (device);
+		nm_clear_g_source (&priv->dcb_timeout_id);
 		priv->dcb_wait = DCB_WAIT_CARRIER_POSTCONFIG_UP;
 
 		if (!carrier) {
@@ -1129,8 +1109,8 @@ dcb_state (NMDevice *device, gboolean timeout)
 	case DCB_WAIT_CARRIER_POSTCONFIG_UP:
 		if (timeout || carrier) {
 			_LOGD (LOGD_DCB, "dcb_state() postconfig up starting IP");
-			dcb_timeout_cleanup (device);
-			dcb_carrier_cleanup (device);
+			nm_clear_g_source (&priv->dcb_timeout_id);
+			nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
 			priv->dcb_wait = DCB_WAIT_UNKNOWN;
 			nm_device_activate_schedule_stage3_ip_config_start (device);
 		}
@@ -1200,7 +1180,7 @@ found:
 static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
 {
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
+	NMDeviceEthernet *self = (NMDeviceEthernet *) device;
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMSettingConnection *s_con;
 	const char *connection_type;
@@ -1213,8 +1193,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
 	                                                              NM_TYPE_SETTING_CONNECTION));
 	g_assert (s_con);
 
-	dcb_timeout_cleanup (device);
-	dcb_carrier_cleanup (device);
+	nm_clear_g_source (&priv->dcb_timeout_id);
+	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
 
 	/* 802.1x has to run before any IP configuration since the 802.1x auth
 	 * process opens the port up for normal traffic.
@@ -1311,7 +1291,7 @@ ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 	guint32 mtu;
 
 	/* MTU only set for plain ethernet */
-	if (NM_DEVICE_ETHERNET_GET_PRIVATE (device)->ppp_manager)
+	if (NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device)->ppp_manager)
 		return;
 
 	connection = nm_device_get_applied_connection (device);
@@ -1348,8 +1328,8 @@ deactivate (NMDevice *device)
 	supplicant_interface_release (self);
 
 	priv->dcb_wait = DCB_WAIT_UNKNOWN;
-	dcb_timeout_cleanup (device);
-	dcb_carrier_cleanup (device);
+	nm_clear_g_source (&priv->dcb_timeout_id);
+	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
 
 	/* Tear down DCB/FCoE if it was enabled */
 	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
@@ -1363,11 +1343,7 @@ deactivate (NMDevice *device)
 
 	/* Set last PPPoE connection time */
 	if (nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE))
-		NM_DEVICE_ETHERNET_GET_PRIVATE (device)->last_pppoe_time = nm_utils_get_monotonic_timestamp_s ();
-
-	/* Reset MAC address back to initial address */
-	if (nm_device_get_initial_hw_address (device))
-		nm_device_set_hw_addr (device, nm_device_get_initial_hw_address (device), "reset", LOGD_ETHER);
+		priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_s ();
 }
 
 static gboolean
@@ -1408,7 +1384,7 @@ complete_connection (NMDevice *device,
 		nm_connection_add_setting (connection, NM_SETTING (s_wired));
 	}
 
-	perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	perm_hw_addr = nm_device_get_permanent_hw_address (device, FALSE);
 	if (perm_hw_addr) {
 		setting_mac = nm_setting_wired_get_mac_address (s_wired);
 		if (setting_mac) {
@@ -1435,9 +1411,9 @@ static NMConnection *
 new_default_connection (NMDevice *self)
 {
 	NMConnection *connection;
-	const GSList *connections;
+	NMSettingsConnection *const*connections;
 	NMSetting *setting;
-	const char *hw_address;
+	const char *perm_hw_addr;
 	gs_free char *defname = NULL;
 	gs_free char *uuid = NULL;
 	gs_free char *machine_id = NULL;
@@ -1445,16 +1421,16 @@ new_default_connection (NMDevice *self)
 	if (nm_config_get_no_auto_default_for_device (nm_config_get (), self))
 		return NULL;
 
-	hw_address = nm_device_get_hw_address (self);
-	if (!hw_address)
+	perm_hw_addr = nm_device_get_permanent_hw_address (self, TRUE);
+	if (!perm_hw_addr)
 		return NULL;
 
 	connection = nm_simple_connection_new ();
 	setting = nm_setting_connection_new ();
 	nm_connection_add_setting (connection, setting);
 
-	connections = nm_connection_provider_get_connections (nm_connection_provider_get ());
-	defname = nm_device_ethernet_utils_get_default_wired_name (connections);
+	connections = nm_settings_get_connections (nm_device_get_settings (self), NULL);
+	defname = nm_device_ethernet_utils_get_default_wired_name ((NMConnection *const*) connections);
 	if (!defname)
 		return NULL;
 
@@ -1465,7 +1441,7 @@ new_default_connection (NMDevice *self)
 	uuid = _nm_utils_uuid_generate_from_strings ("default-wired",
 	                                             machine_id ?: "",
 	                                             defname,
-	                                             hw_address,
+	                                             perm_hw_addr,
 	                                             NULL);
 
 	g_object_set (setting,
@@ -1479,7 +1455,7 @@ new_default_connection (NMDevice *self)
 
 	/* Lock the connection to the device */
 	setting = nm_setting_wired_new ();
-	g_object_set (setting, NM_SETTING_WIRED_MAC_ADDRESS, hw_address, NULL);
+	g_object_set (setting, NM_SETTING_WIRED_MAC_ADDRESS, perm_hw_addr, NULL);
 	nm_connection_add_setting (connection, setting);
 
 	return connection;
@@ -1489,7 +1465,7 @@ static NMMatchSpecMatchType
 spec_match_list (NMDevice *device, const GSList *specs)
 {
 	NMMatchSpecMatchType matched = NM_MATCH_SPEC_NO_MATCH, m;
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device);
 
 	if (priv->subchannels)
 		matched = nm_match_spec_s390_subchannels (specs, priv->subchannels);
@@ -1503,9 +1479,9 @@ spec_match_list (NMDevice *device, const GSList *specs)
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device);
 	NMSettingWired *s_wired = nm_connection_get_setting_wired (connection);
-	const char *perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	const char *perm_hw_addr = nm_device_get_permanent_hw_address (device, FALSE);
 	const char *mac = nm_device_get_hw_address (device);
 	const char *mac_prop = NM_SETTING_WIRED_MAC_ADDRESS;
 	GHashTableIter iter;
@@ -1562,7 +1538,7 @@ get_link_speed (NMDevice *device)
 		return;
 
 	priv->speed = speed;
-	g_object_notify (G_OBJECT (device), "speed");
+	_notify (self, PROP_SPEED);
 
 	_LOGD (LOGD_HW | LOGD_ETHER, "speed is now %d Mb/s", speed);
 }
@@ -1579,37 +1555,9 @@ carrier_changed (NMDevice *device, gboolean carrier)
 static void
 link_changed (NMDevice *device, NMPlatformLink *info)
 {
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	static const guint8 zero_hwaddr[ETH_ALEN];
-	const guint8 *hwaddr;
-	gsize hwaddrlen = 0;
-
 	NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->link_changed (device, info);
-	if (!priv->subchan1 && info->initialized)
-		_update_s390_subchannels (self);
-
-	if (!nm_device_get_initial_hw_address (device)) {
-		hwaddr = nm_platform_link_get_address (NM_PLATFORM_GET,
-		                                       nm_device_get_ifindex (self),
-		                                       &hwaddrlen);
-		if (!nm_utils_hwaddr_matches (hwaddr, hwaddrlen, zero_hwaddr, ETH_ALEN)) {
-			_LOGD (LOGD_DEVICE, "device got a valid hw address");
-			nm_device_update_hw_address (self);
-			nm_device_update_initial_hw_address (self);
-			if (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE) {
-				/*
-				 * If the device is UNAVAILABLE, any previous try to
-				 * bring it up probably has failed because of the
-				 * invalid hardware address; try again.
-				 */
-				nm_device_bring_up (self, TRUE, NULL);
-				nm_device_queue_recheck_available (device,
-				                                   NM_DEVICE_STATE_REASON_NONE,
-				                                   NM_DEVICE_STATE_REASON_NONE);
-			}
-		}
-	}
+	if (info->initialized)
+		_update_s390_subchannels ((NMDeviceEthernet *) device);
 }
 
 static gboolean
@@ -1631,8 +1579,8 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
-	dcb_timeout_cleanup (NM_DEVICE (self));
-	dcb_carrier_cleanup (NM_DEVICE (self));
+	nm_clear_g_source (&priv->dcb_timeout_id);
+	nm_clear_g_signal_handler (self, &priv->dcb_carrier_id);
 
 	G_OBJECT_CLASS (nm_device_ethernet_parent_class)->dispose (object);
 }
@@ -1663,9 +1611,6 @@ get_property (GObject *object, guint prop_id,
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 
 	switch (prop_id) {
-	case PROP_PERM_HW_ADDRESS:
-		g_value_set_string (value, nm_device_get_permanent_hw_address (NM_DEVICE (object)));
-		break;
 	case PROP_SPEED:
 		g_value_set_uint (value, priv->speed);
 		break;
@@ -1700,14 +1645,13 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRED_SETTING_NAME, NM_LINK_TYPE_ETHERNET)
 
 	/* virtual methods */
-	object_class->constructor = constructor;
+	object_class->constructed = constructed;
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->realize_start_notify = realize_start_notify;
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->complete_connection = complete_connection;
 	parent_class->new_default_connection = new_default_connection;
@@ -1725,27 +1669,19 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 
 	parent_class->state_changed = device_state_changed;
 
-	/* properties */
-	g_object_class_install_property
-		(object_class, PROP_PERM_HW_ADDRESS,
-		 g_param_spec_string (NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS, "", "",
-		                      NULL,
-		                      G_PARAM_READABLE |
-		                      G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_SPEED,
-		 g_param_spec_uint (NM_DEVICE_ETHERNET_SPEED, "", "",
-		                    0, G_MAXUINT32, 0,
-		                    G_PARAM_READABLE |
-		                    G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_S390_SUBCHANNELS,
-		 g_param_spec_boxed (NM_DEVICE_ETHERNET_S390_SUBCHANNELS, "", "",
-		                     G_TYPE_STRV,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS));
+	obj_properties[PROP_SPEED] =
+	    g_param_spec_uint (NM_DEVICE_ETHERNET_SPEED, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_S390_SUBCHANNELS] =
+	    g_param_spec_boxed (NM_DEVICE_ETHERNET_S390_SUBCHANNELS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_ETHERNET_SKELETON,
diff --git a/src/devices/nm-device-ethernet.h b/src/devices/nm-device-ethernet.h
index 2d284822..3ce6f0ca 100644
--- a/src/devices/nm-device-ethernet.h
+++ b/src/devices/nm-device-ethernet.h
@@ -24,8 +24,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_ETHERNET			(nm_device_ethernet_get_type ())
 #define NM_DEVICE_ETHERNET(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernet))
 #define NM_DEVICE_ETHERNET_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass))
@@ -33,15 +31,20 @@ G_BEGIN_DECLS
 #define NM_IS_DEVICE_ETHERNET_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_ETHERNET))
 #define NM_DEVICE_ETHERNET_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetClass))
 
-#define NM_DEVICE_ETHERNET_PERMANENT_HW_ADDRESS "perm-hw-address"
 #define NM_DEVICE_ETHERNET_SPEED "speed"
 #define NM_DEVICE_ETHERNET_S390_SUBCHANNELS "s390-subchannels"
 
-typedef NMDevice NMDeviceEthernet;
-typedef NMDeviceClass NMDeviceEthernetClass;
+struct _NMDeviceEthernetPrivate;
 
-GType nm_device_ethernet_get_type (void);
+typedef struct {
+	NMDevice parent;
+	struct _NMDeviceEthernetPrivate *_priv;
+} NMDeviceEthernet;
 
-G_END_DECLS
+typedef struct {
+	NMDeviceClass parent_class;
+} NMDeviceEthernetClass;
+
+GType nm_device_ethernet_get_type (void);
 
-#endif	/* NM_DEVICE_ETHERNET_H */
+#endif /* __NETWORKMANAGER_DEVICE_ETHERNET_H__ */
diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h
index ae78968a..4a62468c 100644
--- a/src/devices/nm-device-factory.h
+++ b/src/devices/nm-device-factory.h
@@ -92,10 +92,10 @@ typedef struct {
 	 * @connection: the #NMConnection to return the parent name for, if supported
 	 *
 	 * Given a connection, returns the a parent interface name, parent connection
-	 * UUID, or parent device hardware address for @connection.
+	 * UUID, or parent device permanent hardware address for @connection.
 	 *
 	 * Returns: the parent interface name, parent connection UUID, parent
-	 *   device hardware address, or %NULL
+	 *   device permenent hardware address, or %NULL
 	 */
 	const char * (*get_connection_parent) (NMDeviceFactory *factory,
 	                                       NMConnection *connection);
diff --git a/src/devices/nm-device-generic.h b/src/devices/nm-device-generic.h
index 9303a278..9a5ff091 100644
--- a/src/devices/nm-device-generic.h
+++ b/src/devices/nm-device-generic.h
@@ -23,8 +23,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_GENERIC            (nm_device_generic_get_type ())
 #define NM_DEVICE_GENERIC(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_GENERIC, NMDeviceGeneric))
 #define NM_DEVICE_GENERIC_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_GENERIC, NMDeviceGenericClass))
@@ -47,6 +45,4 @@ GType nm_device_generic_get_type (void);
 
 NMDevice *nm_device_generic_new (const NMPlatformLink *plink);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_GENERIC_H */
+#endif /* __NETWORKMANAGER_DEVICE_GENERIC_H__ */
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index ae9543e4..17f50ba2 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -147,10 +147,15 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 
 	if (nm_device_is_real (device)) {
 		const char *mac;
+		const char *hw_addr;
 
 		mac = nm_setting_infiniband_get_mac_address (s_infiniband);
-		if (mac && !nm_utils_hwaddr_matches (mac, -1, nm_device_get_hw_address (device), -1))
-			return FALSE;
+		if (mac) {
+			hw_addr = nm_device_get_permanent_hw_address (device, TRUE);
+			if (   !hw_addr
+			    || !nm_utils_hwaddr_matches (mac, -1, hw_addr, -1))
+				return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -183,7 +188,7 @@ complete_connection (NMDevice *device,
 	}
 
 	setting_mac = nm_setting_infiniband_get_mac_address (s_infiniband);
-	hw_address = nm_device_get_hw_address (device);
+	hw_address = nm_device_get_permanent_hw_address (device, TRUE);
 	if (setting_mac) {
 		/* Make sure the setting MAC (if any) matches the device's MAC */
 		if (!nm_utils_hwaddr_matches (setting_mac, -1, hw_address, -1)) {
@@ -209,7 +214,7 @@ static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMSettingInfiniband *s_infiniband = nm_connection_get_setting_infiniband (connection);
-	const char *mac = nm_device_get_hw_address (device);
+	const char *mac = nm_device_get_permanent_hw_address (device, TRUE);
 	const char *transport_mode = "datagram";
 	int ifindex;
 
diff --git a/src/devices/nm-device-infiniband.h b/src/devices/nm-device-infiniband.h
index cac9a79d..a74d07a5 100644
--- a/src/devices/nm-device-infiniband.h
+++ b/src/devices/nm-device-infiniband.h
@@ -23,8 +23,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_INFINIBAND			(nm_device_infiniband_get_type ())
 #define NM_DEVICE_INFINIBAND(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfiniband))
 #define NM_DEVICE_INFINIBAND_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_INFINIBAND, NMDeviceInfinibandClass))
@@ -37,6 +35,4 @@ typedef NMDeviceClass NMDeviceInfinibandClass;
 
 GType nm_device_infiniband_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_INFINIBAND_H */
+#endif /* __NETWORKMANAGER_DEVICE_INFINIBAND_H__ */
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 089b1035..785010eb 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -20,19 +20,20 @@
 
 #include "nm-default.h"
 
+#include "nm-device-ip-tunnel.h"
+
 #include <string.h>
 #include <netinet/in.h>
 #include <linux/if.h>
 #include <linux/ip.h>
 #include <linux/if_tunnel.h>
 
-#include "nm-device-ip-tunnel.h"
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-core-internal.h"
-#include "nm-connection-provider.h"
+#include "nm-settings.h"
 #include "nm-activation-request.h"
 #include "nm-ip4-config.h"
 
@@ -385,8 +386,8 @@ update_connection (NMDevice *device, NMConnection *connection)
 			NMConnection *parent_connection;
 
 			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = nm_connection_provider_get_connection_by_uuid (nm_connection_provider_get (),
-			                                                                   setting_parent);
+			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device),
+			                                                                         setting_parent);
 			if (parent_connection && nm_device_check_connection_compatible (parent, parent_connection))
 				new_parent = NULL;
 		}
diff --git a/src/devices/nm-device-ip-tunnel.h b/src/devices/nm-device-ip-tunnel.h
index 11cb5bce..94cc7375 100644
--- a/src/devices/nm-device-ip-tunnel.h
+++ b/src/devices/nm-device-ip-tunnel.h
@@ -24,8 +24,6 @@
 #include "nm-core-types.h"
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_IP_TUNNEL            (nm_device_ip_tunnel_get_type ())
 #define NM_DEVICE_IP_TUNNEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_IP_TUNNEL, NMDeviceIPTunnel))
 #define NM_DEVICE_IP_TUNNEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_IP_TUNNEL, NMDeviceIPTunnelClass))
@@ -55,6 +53,4 @@ typedef struct {
 
 GType nm_device_ip_tunnel_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_IP_TUNNEL_H */
+#endif /* __NETWORKMANAGER_DEVICE_IP_TUNNEL_H__ */
diff --git a/src/devices/nm-device-logging.h b/src/devices/nm-device-logging.h
index 1be17073..fac92eac 100644
--- a/src/devices/nm-device-logging.h
+++ b/src/devices/nm-device-logging.h
@@ -21,7 +21,6 @@
 #ifndef __NETWORKMANAGER_DEVICE_LOGGING_H__
 #define __NETWORKMANAGER_DEVICE_LOGGING_H__
 
-#include "nm-default.h"
 #include "nm-device.h"
 
 #define _LOG_DECLARE_SELF(t) \
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index c431fe89..2bfc65cb 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -20,11 +20,12 @@
 
 #include "nm-default.h"
 
+#include "nm-device-macvlan.h"
+
 #include <string.h>
 
-#include "nm-device-macvlan.h"
 #include "nm-device-private.h"
-#include "nm-connection-provider.h"
+#include "nm-settings.h"
 #include "nm-activation-request.h"
 #include "nm-manager.h"
 #include "nm-platform.h"
@@ -372,9 +373,8 @@ match_hwaddr (NMDevice *device, NMConnection *connection, gboolean fail_if_no_hw
 	if (!priv->parent)
 		return !fail_if_no_hwaddr;
 
-	parent_mac = nm_device_get_hw_address (priv->parent);
-
-	return nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
+	parent_mac = nm_device_get_permanent_hw_address (priv->parent, FALSE);
+	return parent_mac && nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
 }
 
 static gboolean
@@ -488,7 +488,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 			NMConnection *parent_connection;
 
 			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = nm_connection_provider_get_connection_by_uuid (nm_connection_provider_get (), setting_parent);
+			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device), setting_parent);
 			if (parent_connection && nm_device_check_connection_compatible (priv->parent, parent_connection))
 				new_parent = NULL;
 		}
@@ -502,8 +502,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 static NMActStageReturn
 act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 {
-	NMSettingWired *s_wired;
-	const char *cloned_mac;
 	NMActStageReturn ret;
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
@@ -512,14 +510,9 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (dev, NM_TYPE_SETTING_WIRED);
-	if (s_wired) {
-		/* Set device MAC address if the connection wants to change it */
-		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
-		nm_device_set_hw_addr (dev, cloned_mac, "set", LOGD_HW);
-	}
-
-	return TRUE;
+	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
@@ -548,16 +541,6 @@ realize_start_notify (NMDevice *device, const NMPlatformLink *plink)
 	update_properties (device);
 }
 
-static void
-deactivate (NMDevice *device)
-{
-	/* Reset MAC address back to initial address */
-	if (nm_device_get_initial_hw_address (device)) {
-		nm_device_set_hw_addr (device, nm_device_get_initial_hw_address (device),
-		                       "reset", LOGD_DEVICE);
-	}
-}
-
 /******************************************************************/
 
 static void
@@ -638,7 +621,6 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	device_class->complete_connection = complete_connection;
 	device_class->connection_type = NM_SETTING_MACVLAN_SETTING_NAME;
 	device_class->create_and_realize = create_and_realize;
-	device_class->deactivate = deactivate;
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->ip4_config_pre_commit = ip4_config_pre_commit;
 	device_class->is_available = is_available;
diff --git a/src/devices/nm-device-macvlan.h b/src/devices/nm-device-macvlan.h
index 89f670bd..5756f696 100644
--- a/src/devices/nm-device-macvlan.h
+++ b/src/devices/nm-device-macvlan.h
@@ -23,8 +23,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_MACVLAN            (nm_device_macvlan_get_type ())
 #define NM_DEVICE_MACVLAN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlan))
 #define NM_DEVICE_MACVLAN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_MACVLAN, NMDeviceMacvlanClass))
@@ -42,6 +40,4 @@ typedef NMDeviceClass NMDeviceMacvlanClass;
 
 GType nm_device_macvlan_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_MACVLAN_H */
+#endif /* __NETWORKMANAGER_DEVICE_MACVLAN_H__ */
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 602c2c85..c85cb969 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -24,8 +24,6 @@
 
 #include "nm-device.h"
 
-#include "nm-default.h"
-
 /* This file should only be used by subclasses of NMDevice */
 
 enum NMActStageReturn {
@@ -42,6 +40,8 @@ enum NMActStageReturn {
 
 #define NM_DEVICE_CAP_INTERNAL_MASK 0xc0000000
 
+NMSettings *nm_device_get_settings (NMDevice *self);
+
 void nm_device_set_ip_iface (NMDevice *self, const char *iface);
 
 void nm_device_activate_schedule_stage3_ip_config_start (NMDevice *device);
@@ -54,8 +54,9 @@ gboolean nm_device_bring_up (NMDevice *self, gboolean wait, gboolean *no_firmwar
 
 void nm_device_take_down (NMDevice *self, gboolean block);
 
-gboolean nm_device_set_hw_addr (NMDevice *device, const char *addr,
-                                const char *detail, guint64 hw_log_domain);
+gboolean nm_device_hw_addr_set (NMDevice *device, const char *addr, const char *detail);
+gboolean nm_device_hw_addr_set_cloned (NMDevice *device, NMConnection *connection, gboolean is_wifi);
+gboolean nm_device_hw_addr_reset (NMDevice *device, const char *detail);
 
 void nm_device_set_firmware_missing (NMDevice *self, gboolean missing);
 
@@ -70,9 +71,11 @@ void nm_device_activate_schedule_ip6_config_timeout (NMDevice *device);
 
 gboolean nm_device_activate_ip4_state_in_conf (NMDevice *device);
 gboolean nm_device_activate_ip4_state_in_wait (NMDevice *device);
+gboolean nm_device_activate_ip4_state_done (NMDevice *device);
 
 gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device);
 gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device);
+gboolean nm_device_activate_ip6_state_done (NMDevice *device);
 
 void nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout);
 void nm_device_set_dhcp_anycast_address (NMDevice *device, const char *addr);
@@ -103,6 +106,10 @@ void nm_device_queue_recheck_available (NMDevice *device,
 void nm_device_set_wwan_ip4_config (NMDevice *device, NMIP4Config *config);
 void nm_device_set_wwan_ip6_config (NMDevice *device, NMIP6Config *config);
 
+gboolean nm_device_hw_addr_is_explict (NMDevice *device);
+
+void nm_device_ip_method_failed (NMDevice *self, int family, NMDeviceStateReason reason);
+
 gboolean nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value);
 
 #define NM_DEVICE_CLASS_DECLARE_TYPES(klass, conn_type, ...) \
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 01cd8ea6..1f42d060 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -291,8 +291,6 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	NMSettingWired *s_wired;
-	const char *cloned_mac;
 	NMActStageReturn ret;
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
@@ -305,12 +303,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 	if (g_strcmp0 (priv->mode, "tap"))
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
-	if (s_wired) {
-		/* Set device MAC address if the connection wants to change it */
-		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
-		nm_device_set_hw_addr (device, cloned_mac, "set", LOGD_DEVICE);
-	}
+	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;
 }
diff --git a/src/devices/nm-device-tun.h b/src/devices/nm-device-tun.h
index 32dcfd47..d62b0c37 100644
--- a/src/devices/nm-device-tun.h
+++ b/src/devices/nm-device-tun.h
@@ -23,8 +23,6 @@
 
 #include "nm-device-generic.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_TUN            (nm_device_tun_get_type ())
 #define NM_DEVICE_TUN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_TUN, NMDeviceTun))
 #define NM_DEVICE_TUN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_TUN, NMDeviceTunClass))
@@ -44,6 +42,4 @@ typedef NMDeviceClass NMDeviceTunClass;
 
 GType nm_device_tun_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_TUN_H */
+#endif /* __NETWORKMANAGER_DEVICE_TUN_H__ */
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index ff198a7d..cca86fbe 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -38,23 +38,44 @@
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceVeth);
 
-G_DEFINE_TYPE (NMDeviceVeth, nm_device_veth, NM_TYPE_DEVICE_ETHERNET)
-
-#define NM_DEVICE_VETH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_VETH, NMDeviceVethPrivate))
+/*****************************************************************************/
 
 typedef struct {
 	NMDevice *peer;
 	gboolean ever_had_peer;
 } NMDeviceVethPrivate;
 
-enum {
-	PROP_0,
-	PROP_PEER,
+struct _NMDeviceVeth {
+	NMDeviceEthernet parent;
+	NMDeviceVethPrivate _priv;
+};
 
-	LAST_PROP
+struct _NMDeviceVethClass {
+	NMDeviceEthernetClass parent_class;
 };
 
-/**************************************************************/
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceVeth,
+	PROP_PEER,
+);
+
+/*****************************************************************************/
+
+G_DEFINE_TYPE (NMDeviceVeth, nm_device_veth, NM_TYPE_DEVICE_ETHERNET)
+
+#define NM_DEVICE_VETH_GET_PRIVATE(self) \
+	({ \
+		/* preserve the const-ness of self. Unfortunately, that
+		 * way, @self cannot be a void pointer */ \
+		typeof (self) _self = (self); \
+		\
+		/* Get compiler error if variable is of wrong type */ \
+		_nm_unused const NMDeviceVeth *_self2 = (_self); \
+		\
+		nm_assert (NM_IS_DEVICE_VETH (_self)); \
+		&_self->_priv; \
+	})
+
+/*****************************************************************************/
 
 static void
 set_peer (NMDeviceVeth *self, NMDevice *peer)
@@ -66,7 +87,7 @@ set_peer (NMDeviceVeth *self, NMDevice *peer)
 		priv->peer = peer;
 		g_object_add_weak_pointer (G_OBJECT (peer), (gpointer *) &priv->peer);
 
-		g_object_notify (G_OBJECT (self), NM_DEVICE_VETH_PEER);
+		_notify (self, PROP_PEER);
 	}
 }
 
@@ -150,8 +171,6 @@ nm_device_veth_class_init (NMDeviceVethClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	g_type_class_add_private (klass, sizeof (NMDeviceVethPrivate));
-
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_VETH)
 
 	object_class->get_property = get_property;
@@ -159,13 +178,13 @@ nm_device_veth_class_init (NMDeviceVethClass *klass)
 
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
 
-	/* properties */
-	g_object_class_install_property
-		(object_class, PROP_PEER,
-		 g_param_spec_string (NM_DEVICE_VETH_PEER, "", "",
-		                      NULL,
-		                      G_PARAM_READABLE |
-		                      G_PARAM_STATIC_STRINGS));
+	obj_properties[PROP_PEER] =
+	    g_param_spec_string (NM_DEVICE_VETH_PEER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_VETH_SKELETON,
diff --git a/src/devices/nm-device-veth.h b/src/devices/nm-device-veth.h
index c7b8ec3a..53ccf925 100644
--- a/src/devices/nm-device-veth.h
+++ b/src/devices/nm-device-veth.h
@@ -23,8 +23,6 @@
 
 #include "nm-device-ethernet.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_VETH            (nm_device_veth_get_type ())
 #define NM_DEVICE_VETH(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VETH, NMDeviceVeth))
 #define NM_DEVICE_VETH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_VETH, NMDeviceVethClass))
@@ -34,11 +32,9 @@ G_BEGIN_DECLS
 
 #define NM_DEVICE_VETH_PEER "peer"
 
-typedef NMDeviceEthernet NMDeviceVeth;
-typedef NMDeviceEthernetClass NMDeviceVethClass;
+typedef struct _NMDeviceVeth NMDeviceVeth;
+typedef struct _NMDeviceVethClass NMDeviceVethClass;
 
 GType nm_device_veth_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_VETH_H */
+#endif /* __NETWORKMANAGER_DEVICE_VETH_H__ */
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index eb6527de..16edc0d2 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -20,15 +20,16 @@
 
 #include "nm-default.h"
 
+#include "nm-device-vlan.h"
+
 #include <sys/socket.h>
 
-#include "nm-device-vlan.h"
 #include "nm-manager.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
 #include "nm-enum-types.h"
-#include "nm-connection-provider.h"
+#include "nm-settings.h"
 #include "nm-activation-request.h"
 #include "nm-ip4-config.h"
 #include "nm-platform.h"
@@ -86,7 +87,6 @@ parent_hwaddr_maybe_changed (NMDevice *parent,
 {
 	NMDeviceVlan *self = NM_DEVICE_VLAN (user_data);
 	NMConnection *connection;
-	NMSettingWired *s_wired;
 	const char *new_mac, *old_mac;
 	NMSettingIPConfig *s_ip6;
 
@@ -99,11 +99,8 @@ parent_hwaddr_maybe_changed (NMDevice *parent,
 		return;
 
 	/* Update the VLAN MAC only if configuration does not specify one */
-	s_wired = nm_connection_get_setting_wired (connection);
-	if (s_wired) {
-		if (nm_setting_wired_get_cloned_mac_address (s_wired))
-			return;
-	}
+	if (nm_device_hw_addr_is_explict (self))
+		return;
 
 	old_mac = nm_device_get_hw_address (self);
 	new_mac = nm_device_get_hw_address (parent);
@@ -113,7 +110,7 @@ parent_hwaddr_maybe_changed (NMDevice *parent,
 	_LOGD (LOGD_VLAN, "parent hardware address changed to %s%s%s",
 	       NM_PRINT_FMT_QUOTE_STRING (new_mac));
 	if (new_mac) {
-		nm_device_set_hw_addr (self, new_mac, "set", LOGD_VLAN);
+		nm_device_hw_addr_set (self, new_mac, "vlan-parent");
 		/* When changing the hw address the interface is taken down,
 		 * removing the IPv6 configuration; reapply it.
 		 */
@@ -377,21 +374,25 @@ match_parent (NMDeviceVlan *self, const char *parent)
 static gboolean
 match_hwaddr (NMDevice *device, NMConnection *connection, gboolean fail_if_no_hwaddr)
 {
-	  NMSettingWired *s_wired;
-	  const char *setting_mac;
-	  const char *device_mac;
+	NMDeviceVlanPrivate *priv;
+	NMSettingWired *s_wired;
+	const char *setting_mac;
+	const char *parent_mac;
 
-	  s_wired = nm_connection_get_setting_wired (connection);
-	  if (!s_wired)
-		  return !fail_if_no_hwaddr;
+	s_wired = nm_connection_get_setting_wired (connection);
+	if (!s_wired)
+		return !fail_if_no_hwaddr;
 
-	  setting_mac = nm_setting_wired_get_mac_address (s_wired);
-	  if (!setting_mac)
-		  return !fail_if_no_hwaddr;
+	setting_mac = nm_setting_wired_get_mac_address (s_wired);
+	if (!setting_mac)
+		return !fail_if_no_hwaddr;
 
-	  device_mac = nm_device_get_hw_address (device);
+	priv = NM_DEVICE_VLAN_GET_PRIVATE (device);
+	if (!priv->parent)
+		return !fail_if_no_hwaddr;
 
-	  return nm_utils_hwaddr_matches (setting_mac, -1, device_mac, -1);
+	parent_mac = nm_device_get_permanent_hw_address (priv->parent, FALSE);
+	return parent_mac && nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
 }
 
 static gboolean
@@ -515,7 +516,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 			NMConnection *parent_connection;
 
 			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = nm_connection_provider_get_connection_by_uuid (nm_connection_provider_get (), setting_parent);
+			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device), setting_parent);
 			if (parent_connection && nm_device_check_connection_compatible (priv->parent, parent_connection))
 				new_parent = NULL;
 		}
@@ -549,8 +550,6 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 {
 	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (dev);
 	NMSettingVlan *s_vlan;
-	NMSettingWired *s_wired;
-	const char *cloned_mac;
 	NMActStageReturn ret;
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
@@ -559,12 +558,8 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (dev, NM_TYPE_SETTING_WIRED);
-	if (s_wired) {
-		/* Set device MAC address if the connection wants to change it */
-		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
-		nm_device_set_hw_addr (dev, cloned_mac, "set", LOGD_VLAN);
-	}
+	if (!nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE))
+		return NM_ACT_STAGE_RETURN_FAILURE;
 
 	/* Change MAC address to parent's one if needed */
 	if (priv->parent)
@@ -618,14 +613,6 @@ ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 	}
 }
 
-static void
-deactivate (NMDevice *device)
-{
-	/* Reset MAC address back to initial address */
-	if (nm_device_get_initial_hw_address (device))
-		nm_device_set_hw_addr (device, nm_device_get_initial_hw_address (device), "reset", LOGD_VLAN);
-}
-
 /******************************************************************/
 
 static void
@@ -689,7 +676,6 @@ nm_device_vlan_class_init (NMDeviceVlanClass *klass)
 	parent_class->bring_up = bring_up;
 	parent_class->act_stage1_prepare = act_stage1_prepare;
 	parent_class->ip4_config_pre_commit = ip4_config_pre_commit;
-	parent_class->deactivate = deactivate;
 	parent_class->is_available = is_available;
 	parent_class->notify_new_device_added = notify_new_device_added;
 
diff --git a/src/devices/nm-device-vlan.h b/src/devices/nm-device-vlan.h
index e31e171d..71ca8030 100644
--- a/src/devices/nm-device-vlan.h
+++ b/src/devices/nm-device-vlan.h
@@ -23,8 +23,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_VLAN            (nm_device_vlan_get_type ())
 #define NM_DEVICE_VLAN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VLAN, NMDeviceVlan))
 #define NM_DEVICE_VLAN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_VLAN, NMDeviceVlanClass))
@@ -47,6 +45,4 @@ typedef NMDeviceClass NMDeviceVlanClass;
 
 GType nm_device_vlan_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_VLAN_H */
+#endif /* __NETWORKMANAGER_DEVICE_VLAN_H__ */
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index 45f33bdd..674c2a78 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -20,9 +20,10 @@
 
 #include "nm-default.h"
 
+#include "nm-device-vxlan.h"
+
 #include <string.h>
 
-#include "nm-device-vxlan.h"
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "nm-platform.h"
@@ -30,7 +31,7 @@
 #include "nm-device-factory.h"
 #include "nm-setting-vxlan.h"
 #include "nm-setting-wired.h"
-#include "nm-connection-provider.h"
+#include "nm-settings.h"
 #include "nm-activation-request.h"
 #include "nm-ip4-config.h"
 
@@ -417,8 +418,8 @@ update_connection (NMDevice *device, NMConnection *connection)
 			NMConnection *parent_connection;
 
 			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = nm_connection_provider_get_connection_by_uuid (nm_connection_provider_get (),
-			                                                                   setting_parent);
+			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device),
+			                                                                         setting_parent);
 			if (parent_connection && nm_device_check_connection_compatible (parent, parent_connection))
 				new_parent = NULL;
 		}
@@ -510,8 +511,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 {
-	NMSettingWired *s_wired;
-	const char *cloned_mac;
 	NMActStageReturn ret;
 
 	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
@@ -520,12 +519,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
-	if (s_wired) {
-		/* Set device MAC address if the connection wants to change it */
-		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
-		nm_device_set_hw_addr (device, cloned_mac, "set", LOGD_DEVICE);
-	}
+	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;
 }
diff --git a/src/devices/nm-device-vxlan.h b/src/devices/nm-device-vxlan.h
index 7de04a12..71204d3b 100644
--- a/src/devices/nm-device-vxlan.h
+++ b/src/devices/nm-device-vxlan.h
@@ -23,8 +23,6 @@
 
 #include "nm-device-generic.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_VXLAN            (nm_device_vxlan_get_type ())
 #define NM_DEVICE_VXLAN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VXLAN, NMDeviceVxlan))
 #define NM_DEVICE_VXLAN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_VXLAN, NMDeviceVxlanClass))
@@ -54,6 +52,4 @@ typedef NMDeviceClass NMDeviceVxlanClass;
 
 GType nm_device_vxlan_get_type (void);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_VXLAN_H */
+#endif /* __NETWORKMANAGER_DEVICE_VXLAN_H__ */
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 62259bc4..199acc66 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -21,6 +21,8 @@
 
 #include "nm-default.h"
 
+#include "nm-device.h"
+
 #include <netinet/in.h>
 #include <string.h>
 #include <unistd.h>
@@ -52,7 +54,7 @@
 #include "nm-firewall-manager.h"
 #include "nm-enum-types.h"
 #include "nm-settings-connection.h"
-#include "nm-connection-provider.h"
+#include "nm-settings.h"
 #include "nm-auth-utils.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
@@ -69,10 +71,22 @@
 _LOG_DECLARE_SELF (NMDevice);
 
 #include "nmdbus-device.h"
+#include "nmdbus-device-statistics.h"
 
 G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
 
-#define NM_DEVICE_GET_PRIVATE(o) ((o)->priv)
+#define NM_DEVICE_GET_PRIVATE(self) \
+	({ \
+		/* preserve the const-ness of self. Unfortunately, that
+		 * way, @self cannot be a void pointer */ \
+		typeof (self) _self = (self); \
+		\
+		/* Get compiler error if variable is of wrong type */ \
+		_nm_unused const NMDevice *_self2 = (_self); \
+		\
+		nm_assert (NM_IS_DEVICE (_self)); \
+		_self->priv; \
+	})
 
 enum {
 	STATE_CHANGED,
@@ -119,11 +133,15 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_IS_MASTER,
 	PROP_MASTER,
 	PROP_HW_ADDRESS,
+	PROP_PERM_HW_ADDRESS,
 	PROP_HAS_PENDING_ACTION,
 	PROP_METERED,
 	PROP_LLDP_NEIGHBORS,
 	PROP_REAL,
 	PROP_SLAVES,
+	PROP_REFRESH_RATE_MS,
+	PROP_TX_BYTES,
+	PROP_RX_BYTES,
 );
 
 #define DEFAULT_AUTOCONNECT TRUE
@@ -134,6 +152,9 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 #define PENDING_ACTION_DHCP6 "dhcp6"
 #define PENDING_ACTION_AUTOCONF6 "autoconf6"
 
+#define DHCP_RESTART_TIMEOUT   120
+#define DHCP_NUM_TRIES_MAX     3
+
 typedef void (*ActivationHandleFunc) (NMDevice *self);
 
 typedef struct {
@@ -142,9 +163,9 @@ typedef struct {
 } ActivationHandleData;
 
 typedef enum {
-	CLEANUP_TYPE_DECONFIGURE,
 	CLEANUP_TYPE_KEEP,
 	CLEANUP_TYPE_REMOVED,
+	CLEANUP_TYPE_DECONFIGURE,
 } CleanupType;
 
 typedef enum {
@@ -163,9 +184,9 @@ typedef struct {
 
 typedef struct {
 	NMDevice *slave;
-	gboolean slave_is_enslaved;
-	gboolean configure;
 	gulong watch_id;
+	bool slave_is_enslaved;
+	bool configure;
 } SlaveInfo;
 
 typedef struct {
@@ -192,8 +213,15 @@ typedef struct {
 	NMIP4Config **configs;
 } ArpingData;
 
+typedef enum {
+	HW_ADDR_TYPE_UNSET = 0,
+	HW_ADDR_TYPE_PERMANENT,
+	HW_ADDR_TYPE_EXPLICIT,
+	HW_ADDR_TYPE_GENERATED,
+} HwAddrType;
+
 typedef struct _NMDevicePrivate {
-	gboolean in_state_changed;
+	bool in_state_changed;
 
 	guint device_link_changed_id;
 	guint device_ip_link_changed_id;
@@ -209,7 +237,12 @@ typedef struct _NMDevicePrivate {
 	char *        udi;
 	char *        iface;   /* may change, could be renamed by user */
 	int           ifindex;
-	gboolean      real;
+
+	guint         hw_addr_len;
+	guint8 /*HwAddrType*/ hw_addr_type;
+
+	bool          real;
+
 	char *        ip_iface;
 	int           ip_ifindex;
 	NMDeviceType  type;
@@ -221,19 +254,19 @@ typedef struct _NMDevicePrivate {
 	char *        driver_version;
 	char *        firmware_version;
 	RfKillType    rfkill_type;
-	gboolean      firmware_missing;
-	gboolean      nm_plugin_missing;
+	bool          firmware_missing:1;
+	bool          nm_plugin_missing:1;
+	bool          hw_addr_perm_fake:1; /* whether the permanent HW address could not be read and is a fake */
 	GHashTable *  available_connections;
 	char *        hw_addr;
-	guint         hw_addr_len;
-	char *        perm_hw_addr;
-	char *        initial_hw_addr;
+	char *        hw_addr_perm;
+	char *        hw_addr_initial;
 	char *        physical_port_id;
 	guint         dev_id;
 
 	NMUnmanagedFlags        unmanaged_mask;
 	NMUnmanagedFlags        unmanaged_flags;
-	gboolean                is_nm_owned; /* whether the device is a device owned and created by NM */
+	bool                    is_nm_owned; /* whether the device is a device owned and created by NM */
 	DeleteOnDeactivateData *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */
 
 	GCancellable *deactivating_cancellable;
@@ -241,7 +274,7 @@ typedef struct _NMDevicePrivate {
 	guint32         ip4_address;
 
 	NMActRequest *  queued_act_request;
-	gboolean        queued_act_request_is_waiting_for_carrier;
+	bool            queued_act_request_is_waiting_for_carrier;
 	NMActRequest *  act_request;
 	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
 	ActivationHandleData act_handle6;
@@ -261,11 +294,12 @@ typedef struct _NMDevicePrivate {
 	guint           link_connected_id;
 	guint           link_disconnected_id;
 	guint           carrier_defer_id;
-	gboolean        carrier;
+	bool            carrier;
 	guint           carrier_wait_id;
-	gboolean        ignore_carrier;
+	bool            ignore_carrier;
+	gulong          ignore_carrier_id;
 	guint32         mtu;
-	gboolean        up;   /* IFF_UP */
+	bool            up;   /* IFF_UP */
 
 	/* Generic DHCP stuff */
 	guint32         dhcp_timeout;
@@ -288,14 +322,17 @@ typedef struct _NMDevicePrivate {
 		NMPlatformIP6Route v6;
 	} default_route;
 
-	gboolean v4_commit_first_time;
-	gboolean v6_commit_first_time;
+	bool v4_commit_first_time;
+	bool v6_commit_first_time;
 
 	/* DHCPv4 tracking */
-	NMDhcpClient *  dhcp4_client;
-	gulong          dhcp4_state_sigid;
-	NMDhcp4Config * dhcp4_config;
-	guint           dhcp4_restart_id;
+	struct {
+		NMDhcpClient *  client;
+		gulong          state_sigid;
+		NMDhcp4Config * config;
+		guint           restart_id;
+		guint           num_tries_left;
+	} dhcp4;
 
 	PingInfo        gw_ping;
 
@@ -304,7 +341,7 @@ typedef struct _NMDevicePrivate {
 	gulong            dnsmasq_state_id;
 
 	/* Firewall */
-	gboolean       fw_ready;
+	bool fw_ready;
 	NMFirewallManagerCallId fw_call;
 
 	/* IPv4LL stuff */
@@ -325,8 +362,9 @@ typedef struct _NMDevicePrivate {
 	NMIP6Config *  ext_ip6_config; /* Stuff added outside NM */
 	NMIP6Config *  ext_ip6_config_captured; /* Configuration captured from platform. */
 	GSList *       vpn6_configs;   /* VPNs which use this device */
-	gboolean       nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
+	bool           nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 	guint32        ip6_mtu;
+	NMIP6Config *  dad6_ip6_config;
 
 	NMRDisc *      rdisc;
 	gulong         rdisc_changed_id;
@@ -340,35 +378,47 @@ typedef struct _NMDevicePrivate {
 
 	GHashTable *   ip6_saved_properties;
 
-	NMDhcpClient *  dhcp6_client;
-	NMRDiscDHCPLevel dhcp6_mode;
-	gulong          dhcp6_state_sigid;
-	NMDhcp6Config * dhcp6_config;
-	/* IP6 config from DHCP */
-	NMIP6Config *   dhcp6_ip6_config;
-	/* Event ID of the current IP6 config from DHCP */
-	char *          dhcp6_event_id;
-	guint           dhcp6_restart_id;
+	struct {
+		NMDhcpClient *   client;
+		NMRDiscDHCPLevel mode;
+		gulong           state_sigid;
+		NMDhcp6Config *  config;
+		/* IP6 config from DHCP */
+		NMIP6Config *    ip6_config;
+		/* Event ID of the current IP6 config from DHCP */
+		char *           event_id;
+		guint            restart_id;
+		guint            num_tries_left;
+	} dhcp6;
 
 	/* allow autoconnect feature */
-	gboolean        autoconnect;
+	bool autoconnect;
 
 	/* master interface for bridge/bond/team slave */
 	NMDevice *      master;
-	gboolean        is_enslaved;
-	gboolean        master_ready_handled;
+	bool            is_enslaved;
+	bool            master_ready_handled;
 	gulong          master_ready_id;
 
 	/* slave management */
-	gboolean        is_master;
+	bool            is_master;
 	GSList *        slaves;    /* list of SlaveInfo */
 
 	NMMetered       metered;
 
-	NMConnectionProvider *con_provider;
+	NMSettings *settings;
+
 	NMLldpListener *lldp_listener;
 
 	guint check_delete_unrealized_id;
+
+	struct {
+		guint timeout_id;
+		guint refresh_rate_ms;
+		guint64 tx_bytes;
+		guint64 rx_bytes;
+	} stats;
+
 } NMDevicePrivate;
 
 static gboolean nm_device_set_ip4_config (NMDevice *self,
@@ -417,6 +467,8 @@ static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceState
 static void nm_device_start_ip_check (NMDevice *self);
 static void realize_start_setup (NMDevice *self, const NMPlatformLink *plink);
 static void nm_device_set_mtu (NMDevice *self, guint32 mtu);
+static void dhcp_schedule_restart (NMDevice *self, int family, const char *reason);
+static void _cancel_activation (NMDevice *self);
 
 /***********************************************************/
 
@@ -524,6 +576,12 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_reason_to_string, NMDeviceStateReason,
 
 /***********************************************************/
 
+NMSettings *
+nm_device_get_settings (NMDevice *self)
+{
+	return NM_DEVICE_GET_PRIVATE (self)->settings;
+}
+
 static void
 init_ip4_config_dns_priority (NMDevice *self, NMIP4Config *config)
 {
@@ -570,6 +628,41 @@ nm_device_has_capability (NMDevice *self, NMDeviceCapabilities caps)
 	return NM_FLAGS_ANY (NM_DEVICE_GET_PRIVATE (self)->capabilities, caps);
 }
 
+static void
+_add_capabilities (NMDevice *self, NMDeviceCapabilities capabilities)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!NM_FLAGS_ALL (priv->capabilities, capabilities)) {
+		priv->capabilities |= capabilities;
+		_notify (self, PROP_CAPABILITIES);
+	}
+}
+
+/***********************************************************/
+
+static const char *
+_get_stable_id (NMConnection *connection, NMUtilsStableType *out_stable_type)
+{
+	NMSettingConnection *s_con;
+	const char *stable_id;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (out_stable_type);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_return_val_if_fail (s_con, NULL);
+
+	stable_id = nm_setting_connection_get_stable_id (s_con);
+	if (!stable_id) {
+		*out_stable_type = NM_UTILS_STABLE_TYPE_UUID;
+		return nm_connection_get_uuid (connection);
+	}
+
+	*out_stable_type = NM_UTILS_STABLE_TYPE_STABLE_ID;
+	return stable_id;
+}
+
 /***********************************************************/
 
 const char *
@@ -688,43 +781,175 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
 	g_free (old_ip_iface);
 }
 
+/*****************************************************************************/
+
+static void
+_stats_update_counters (NMDevice *self,
+                        guint64 tx_bytes,
+                        guint64 rx_bytes)
+{
+	NMDevicePrivate *priv;
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->stats.tx_bytes != tx_bytes) {
+		priv->stats.tx_bytes = tx_bytes;
+		_notify (self, PROP_TX_BYTES);
+	}
+	if (priv->stats.rx_bytes != rx_bytes) {
+		priv->stats.rx_bytes = rx_bytes;
+		_notify (self, PROP_RX_BYTES);
+	}
+}
+
+static void
+_stats_update_counters_from_pllink (NMDevice *self, const NMPlatformLink *pllink)
+{
+	_stats_update_counters (self, pllink->tx_bytes, pllink->rx_bytes);
+}
+
+static gboolean
+_stats_timeout_cb (gpointer user_data)
+{
+	NMDevice *self = user_data;
+	int ifindex;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+
+	_LOGT (LOGD_DEVICE, "stats: refresh %d", ifindex);
+
+	if (ifindex > 0)
+		nm_platform_link_refresh (NM_PLATFORM_GET, ifindex);
+
+	return G_SOURCE_CONTINUE;
+}
+
+static guint
+_stats_refresh_rate_real (guint refresh_rate_ms)
+{
+	const guint STATS_REFRESH_RATE_MS_MIN = 200;
+
+	if (refresh_rate_ms == 0)
+		return 0;
+
+	if (refresh_rate_ms < STATS_REFRESH_RATE_MS_MIN) {
+		/* you cannot set the refresh-rate arbitrarly small. E.g.
+		 * setting to 1ms is just killing. Have a lowest number. */
+		return STATS_REFRESH_RATE_MS_MIN;
+	}
+
+	return refresh_rate_ms;
+}
+
+static void
+_stats_set_refresh_rate (NMDevice *self, guint refresh_rate_ms)
+{
+	NMDevicePrivate *priv;
+	int ifindex;
+	guint old_rate;
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->stats.refresh_rate_ms == refresh_rate_ms)
+		return;
+
+	old_rate = priv->stats.refresh_rate_ms;
+	priv->stats.refresh_rate_ms = refresh_rate_ms;
+	_notify (self, PROP_REFRESH_RATE_MS);
+
+	_LOGD (LOGD_DEVICE, "stats: set refresh to %u ms", priv->stats.refresh_rate_ms);
+
+	if (!nm_device_is_real (self))
+		return;
+
+	refresh_rate_ms = _stats_refresh_rate_real (refresh_rate_ms);
+	if (_stats_refresh_rate_real (old_rate) == refresh_rate_ms)
+		return;
+
+	nm_clear_g_source (&priv->stats.timeout_id);
+
+	if (!refresh_rate_ms)
+		return;
+
+	/* trigger an inital refresh of the data whenever the refresh-rate changes.
+	 * As we process the result in an idle handler with device_link_changed(),
+	 * we don't get the result right away. */
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex > 0)
+		nm_platform_link_refresh (NM_PLATFORM_GET, ifindex);
+
+	priv->stats.timeout_id = g_timeout_add (refresh_rate_ms, _stats_timeout_cb, self);
+}
+
+/*****************************************************************************/
+
 static gboolean
 get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMLinkType link_type;
-	const guint8 *hwaddr = NULL;
-	size_t hwaddr_len = 0;
+	const NMPlatformLink *pllink;
 	int ifindex;
 	gboolean success;
 
 	/* If we get here, we *must* have a kernel netdev, which implies an ifindex */
 	ifindex = nm_device_get_ip_ifindex (self);
-	g_assert (ifindex);
+	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	link_type = nm_platform_link_get_type (NM_PLATFORM_GET, ifindex);
-	g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, 0);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, ifindex);
+	if (   !pllink
+	    || NM_IN_SET (pllink->type, NM_LINK_TYPE_NONE, NM_LINK_TYPE_UNKNOWN))
+		return FALSE;
 
-	hwaddr = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &hwaddr_len);
-	if (!hwaddr_len)
+	if (pllink->addr.len <= 0)
 		return FALSE;
+	if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX)
+		g_return_val_if_reached (FALSE);
 
-	success = nm_utils_get_ipv6_interface_identifier (link_type,
-	                                                  hwaddr,
-	                                                  hwaddr_len,
+	success = nm_utils_get_ipv6_interface_identifier (pllink->type,
+	                                                  pllink->addr.data,
+	                                                  pllink->addr.len,
 	                                                  priv->dev_id,
 	                                                  out_iid);
 	if (!success) {
 		_LOGW (LOGD_HW, "failed to generate interface identifier "
-		       "for link type %u hwaddr_len %zu", link_type, hwaddr_len);
+		       "for link type %u hwaddr_len %u", pllink->type, (unsigned) pllink->addr.len);
 	}
 	return success;
 }
 
+/**
+ * nm_device_get_ip_iface_identifier:
+ * @self: an #NMDevice
+ * @iid: where to place the interface identifier
+ * @ignore_token: force creation of a non-tokenized address
+ *
+ * Return the interface's identifier for the EUI64 address generation mode.
+ * It's either a manually set token or and identifier generated in a
+ * hardware-specific way.
+ *
+ * Unless @ignore_token is set the token is preferred. That is the case
+ * for link-local addresses (to mimic kernel behavior).
+ *
+ * Returns: #TRUE if the @iid could be set
+ */
 static gboolean
-nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid)
+nm_device_get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *iid, gboolean ignore_token)
 {
-	return NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, iid);
+	NMSettingIP6Config *s_ip6;
+	const char *token = NULL;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	if (!ignore_token) {
+		s_ip6 = (NMSettingIP6Config *)
+			nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+		g_return_val_if_fail (s_ip6, FALSE);
+		token = nm_setting_ip6_config_get_token (s_ip6);
+	}
+	if (token)
+		return nm_utils_ipv6_interface_identifier_get_from_token (iid, token);
+	else
+		return NM_DEVICE_GET_CLASS (self)->get_ip_iface_identifier (self, iid);
 }
 
 const char *
@@ -1029,7 +1254,11 @@ nm_device_get_settings_connection (NMDevice *self)
 NMConnection *
 nm_device_get_applied_connection (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	return priv->act_request ? nm_act_request_get_applied_connection (priv->act_request) : NULL;
 }
@@ -1046,23 +1275,12 @@ nm_device_has_unmodified_applied_connection (NMDevice *self, NMSettingCompareFla
 }
 
 NMSetting *
-nm_device_get_applied_setting (NMDevice *device, GType setting_type)
+nm_device_get_applied_setting (NMDevice *self, GType setting_type)
 {
-	NMActRequest *req;
-	NMSetting *setting = NULL;
-
-	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
-
-	req = nm_device_get_act_request (device);
-	if (req) {
-		NMConnection *connection;
-
-		connection = nm_act_request_get_applied_connection (req);
-		if (connection)
-			setting = nm_connection_get_setting (connection, setting_type);
-	}
+	NMConnection *connection;
 
-	return setting;
+	connection = nm_device_get_applied_connection (self);
+	return connection ? nm_connection_get_setting (connection, setting_type) : NULL;
 }
 
 RfKillType
@@ -1355,7 +1573,7 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
-	if (priv->dhcp4_client) {
+	if (priv->dhcp4.client) {
 		if (!nm_device_dhcp4_renew (self, FALSE)) {
 			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
@@ -1363,7 +1581,7 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 			return;
 		}
 	}
-	if (priv->dhcp6_client) {
+	if (priv->dhcp6.client) {
 		if (!nm_device_dhcp6_renew (self, FALSE)) {
 			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
@@ -1467,8 +1685,6 @@ link_disconnect_action_cb (gpointer user_data)
 
 	priv->carrier_defer_id = 0;
 
-	_LOGI (LOGD_DEVICE, "link disconnected (calling deferred action)");
-
 	NM_DEVICE_GET_CLASS (self)->carrier_changed (self, FALSE);
 
 	return FALSE;
@@ -1508,11 +1724,11 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 			nm_device_remove_pending_action (self, "carrier wait", TRUE);
 			_carrier_wait_check_queued_act_request (self);
 		}
-	} else if (state <= NM_DEVICE_STATE_DISCONNECTED) {
-		_LOGI (LOGD_DEVICE, "link disconnected");
+	} else if (   state <= NM_DEVICE_STATE_DISCONNECTED
+	           && !priv->queued_act_request) {
+		_LOGD (LOGD_DEVICE, "link disconnected");
 		klass->carrier_changed (self, FALSE);
 	} else {
-		_LOGI (LOGD_DEVICE, "link disconnected (deferring action for %d seconds)", LINK_DISCONNECT_DELAY);
 		priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY,
 		                                                link_disconnect_action_cb, self);
 		_LOGD (LOGD_DEVICE, "link disconnected (deferring action for %d seconds) (id=%u)",
@@ -1563,13 +1779,14 @@ device_link_changed (NMDevice *self)
 {
 	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMUtilsIPv6IfaceId token_iid;
 	gboolean ip_ifname_changed = FALSE;
 	const char *udi;
 	NMPlatformLink info;
 	const NMPlatformLink *pllink;
 	int ifindex;
 	gboolean was_up;
+	gboolean update_unmanaged_specs = FALSE;
+	gboolean got_hw_addr = FALSE, had_hw_addr;
 
 	priv->device_link_changed_id = 0;
 
@@ -1607,6 +1824,14 @@ device_link_changed (NMDevice *self)
 		_notify (self, PROP_DRIVER);
 	}
 
+	if (ifindex == nm_device_get_ip_ifindex (self))
+		_stats_update_counters_from_pllink (self, &info);
+
+	had_hw_addr = (priv->hw_addr != NULL);
+	nm_device_update_hw_address (self);
+	got_hw_addr = (!had_hw_addr && priv->hw_addr);
+	nm_device_update_permanent_hw_address (self);
+
 	if (info.name[0] && strcmp (priv->iface, info.name) != 0) {
 		_LOGI (LOGD_DEVICE, "interface index %d renamed iface from '%s' to '%s'",
 		       priv->ifindex, priv->iface, info.name);
@@ -1616,6 +1841,11 @@ device_link_changed (NMDevice *self)
 		/* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */
 		ip_ifname_changed = !priv->ip_iface;
 
+		if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT))
+			nm_device_set_unmanaged_by_user_settings (self, nm_settings_get_unmanaged_specs (priv->settings));
+		else
+			update_unmanaged_specs = TRUE;
+
 		_notify (self, PROP_IFACE);
 		if (ip_ifname_changed)
 			_notify (self, PROP_IP_IFACE);
@@ -1629,10 +1859,11 @@ device_link_changed (NMDevice *self)
 		nm_device_emit_recheck_auto_activate (self);
 	}
 
-	if (priv->rdisc && nm_platform_link_get_ipv6_token (NM_PLATFORM_GET, priv->ifindex, &token_iid)) {
-		_LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
-		if (nm_rdisc_set_iid (priv->rdisc, token_iid))
+	if (priv->rdisc && info.inet6_token.id) {
+		if (nm_rdisc_set_iid (priv->rdisc, info.inet6_token)) {
+			_LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
 			nm_rdisc_start (priv->rdisc);
+		}
 	}
 
 	if (klass->link_changed)
@@ -1651,18 +1882,23 @@ device_link_changed (NMDevice *self)
 
 		nm_device_set_unmanaged_by_user_udev (self);
 
+		reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
+
 		/* If the device is a external-down candidated but no longer has external
 		 * down set, we must clear the platform-unmanaged flag with reason
 		 * "assumed". */
 		if (    nm_device_get_unmanaged_mask (self, NM_UNMANAGED_EXTERNAL_DOWN)
 		    && !nm_device_get_unmanaged_flags (self, NM_UNMANAGED_EXTERNAL_DOWN)) {
-			/* Ensure the assume check is queued before any queued state changes
-			 * from the transition to UNAVAILABLE.
-			 */
-			nm_device_queue_recheck_assume (self);
-			reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
-		} else
-			reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
+			/* actually, user-udev overwrites external-down. So we only assume the device,
+			 * when it is a external-down candidate, which is not managed via udev. */
+			if (!nm_device_get_unmanaged_mask (self, NM_UNMANAGED_USER_UDEV)) {
+				/* Ensure the assume check is queued before any queued state changes
+				 * from the transition to UNAVAILABLE.
+				 */
+				nm_device_queue_recheck_assume (self);
+				reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
+			}
+		}
 
 		nm_device_set_unmanaged_by_flags (self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
 	}
@@ -1684,6 +1920,23 @@ device_link_changed (NMDevice *self)
 		}
 	}
 
+	if (update_unmanaged_specs)
+		nm_device_set_unmanaged_by_user_settings (self, nm_settings_get_unmanaged_specs (priv->settings));
+
+	if (   got_hw_addr
+	    && !priv->up
+	    && nm_device_get_state (self) == NM_DEVICE_STATE_UNAVAILABLE) {
+		/*
+		 * If the device is UNAVAILABLE, any previous try to
+		 * bring it up probably has failed because of the
+		 * invalid hardware address; try again.
+		 */
+		nm_device_bring_up (self, TRUE, NULL);
+		nm_device_queue_recheck_available (self,
+		                                   NM_DEVICE_STATE_REASON_NONE,
+		                                   NM_DEVICE_STATE_REASON_NONE);
+	}
+
 	return G_SOURCE_REMOVE;
 }
 
@@ -1702,6 +1955,8 @@ device_ip_link_changed (NMDevice *self)
 	if (!pllink)
 		return G_SOURCE_REMOVE;
 
+	_stats_update_counters_from_pllink (self, pllink);
+
 	if (pllink->name[0] && g_strcmp0 (priv->ip_iface, pllink->name)) {
 		_LOGI (LOGD_DEVICE, "interface index %d renamed ip_iface (%d) from '%s' to '%s'",
 		       priv->ifindex, nm_device_get_ip_ifindex (self),
@@ -1712,6 +1967,7 @@ device_ip_link_changed (NMDevice *self)
 		_notify (self, PROP_IP_IFACE);
 		nm_device_update_dynamic_ip_setup (self);
 	}
+
 	return G_SOURCE_REMOVE;
 }
 
@@ -1817,6 +2073,8 @@ nm_device_realize_start (NMDevice *self,
                          gboolean *out_compatible,
                          GError **error)
 {
+	NMPlatformLink plink_copy;
+
 	NM_SET_OUT (out_compatible, TRUE);
 
 	if (plink) {
@@ -1831,6 +2089,10 @@ nm_device_realize_start (NMDevice *self,
 			return FALSE;
 	}
 
+	if (plink) {
+		plink_copy = *plink;
+		plink = &plink_copy;
+	}
 	realize_start_setup (self, plink);
 
 	return TRUE;
@@ -1962,6 +2224,9 @@ realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
 	NMDevicePrivate *priv;
 	NMDeviceClass *klass;
 	static guint32 id = 0;
+	NMDeviceCapabilities capabilities = 0;
+	NMConfig *config;
+	guint real_rate;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -1985,6 +2250,7 @@ realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
 	if (plink) {
 		g_return_if_fail (link_type_compatible (self, plink->type, NULL, NULL));
 		update_device_from_platform_link (self, plink);
+		_stats_update_counters_from_pllink (self, plink);
 	}
 
 	if (priv->ifindex > 0) {
@@ -1994,7 +2260,7 @@ realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
 		priv->dev_id = nm_platform_link_get_dev_id (NM_PLATFORM_GET, priv->ifindex);
 
 		if (nm_platform_link_is_software (NM_PLATFORM_GET, priv->ifindex))
-			priv->capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
+			capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
 
 		priv->mtu = nm_platform_link_get_mtu (NM_PLATFORM_GET, priv->ifindex);
 		_notify (self, PROP_MTU);
@@ -2014,7 +2280,9 @@ realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
 	}
 
 	if (klass->get_generic_capabilities)
-		priv->capabilities |= klass->get_generic_capabilities (self);
+		capabilities |= klass->get_generic_capabilities (self);
+
+	_add_capabilities (self, capabilities);
 
 	if (!priv->udi) {
 		/* Use a placeholder UDI until we get a real one */
@@ -2028,17 +2296,19 @@ realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
 
 	nm_device_update_hw_address (self);
 	nm_device_update_initial_hw_address (self);
+	nm_device_update_permanent_hw_address (self);
 
 	/* Note: initial hardware address must be read before calling get_ignore_carrier() */
-	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
-		NMConfig *config = nm_config_get ();
-
-		priv->ignore_carrier = nm_config_data_get_ignore_carrier (nm_config_get_data (config), self);
-		g_signal_connect (G_OBJECT (config),
-		                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
-		                  G_CALLBACK (config_changed_update_ignore_carrier),
-		                  self);
+	config = nm_config_get ();
+	priv->ignore_carrier = nm_config_data_get_ignore_carrier (nm_config_get_data (config), self);
+	if (!priv->ignore_carrier_id) {
+		priv->ignore_carrier_id = g_signal_connect (config,
+		                                            NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+		                                            G_CALLBACK (config_changed_update_ignore_carrier),
+		                                            self);
+	}
 
+	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
 		check_carrier (self);
 		_LOGD (LOGD_HW,
 		       "carrier is %s%s",
@@ -2049,7 +2319,10 @@ realize_start_setup (NMDevice *self, const NMPlatformLink *plink)
 		priv->carrier = TRUE;
 	}
 
-	_notify (self, PROP_CAPABILITIES);
+	nm_assert (!priv->stats.timeout_id);
+	real_rate = _stats_refresh_rate_real (priv->stats.refresh_rate_ms);
+	if (real_rate)
+		priv->stats.timeout_id = g_timeout_add (real_rate, _stats_timeout_cb, self);
 
 	klass->realize_start_notify (self, plink);
 
@@ -2214,6 +2487,7 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		_notify (self, PROP_UDI);
 	}
 	if (priv->hw_addr) {
+		priv->hw_addr_len = 0;
 		g_clear_pointer (&priv->hw_addr, g_free);
 		_notify (self, PROP_HW_ADDRESS);
 	}
@@ -2222,14 +2496,21 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		_notify (self, PROP_PHYSICAL_PORT_ID);
 	}
 
-	g_clear_pointer (&priv->perm_hw_addr, g_free);
-	g_clear_pointer (&priv->initial_hw_addr, g_free);
+	nm_clear_g_source (&priv->stats.timeout_id);
+	_stats_update_counters (self, 0, 0);
+
+	priv->hw_addr_type = HW_ADDR_TYPE_UNSET;
+	g_clear_pointer (&priv->hw_addr_perm, g_free);
+	_notify (self, PROP_PERM_HW_ADDRESS);
+	g_clear_pointer (&priv->hw_addr_initial, g_free);
 
 	priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
 	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
 		priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
 	_notify (self, PROP_CAPABILITIES);
 
+	nm_clear_g_signal_handler (nm_config_get (), &priv->ignore_carrier_id);
+
 	priv->real = FALSE;
 	_notify (self, PROP_REAL);
 
@@ -2660,6 +2941,8 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 		       nm_connection_get_id (connection),
 		       master_status);
 
+		/* Cancel any pending activation sources */
+		_cancel_activation (self);
 		nm_device_queue_state (self, new_state, reason);
 	} else
 		_LOGI (LOGD_DEVICE, "released from master device %s", nm_device_get_iface (priv->master));
@@ -2999,6 +3282,7 @@ nm_device_generate_connection (NMDevice *self, NMDevice *master)
 	gs_free char *uuid = NULL;
 	const char *ip4_method, *ip6_method;
 	GError *error = NULL;
+	const NMPlatformLink *pllink;
 
 	/* If update_connection() is not implemented, just fail. */
 	if (!klass->update_connection)
@@ -3045,6 +3329,15 @@ nm_device_generate_connection (NMDevice *self, NMDevice *master)
 
 		s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
 		nm_connection_add_setting (connection, s_ip6);
+
+		pllink = nm_platform_link_get (NM_PLATFORM_GET, priv->ifindex);
+		if (pllink && pllink->inet6_token.id) {
+			_LOGD (LOGD_IP6, "IPv6 tokenized identifier present");
+			g_object_set (s_ip6,
+			              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, NM_IN6_ADDR_GEN_MODE_EUI64,
+			              NM_SETTING_IP6_CONFIG_TOKEN, nm_utils_inet6_interface_identifier_to_token (pllink->inet6_token, NULL),
+			              NULL);
+		}
 	}
 
 	klass->update_connection (self, connection);
@@ -3202,7 +3495,7 @@ nm_device_check_slave_connection_compatible (NMDevice *self, NMConnection *slave
 static gboolean
 nm_device_can_assume_connections (NMDevice *self)
 {
-	return   !!NM_DEVICE_GET_CLASS (self)->update_connection;
+	return !!NM_DEVICE_GET_CLASS (self)->update_connection;
 }
 
 /**
@@ -3218,7 +3511,7 @@ nm_device_can_assume_connections (NMDevice *self)
  * if there is no active connection or the active connection cannot be
  * assumed.
  */
-gboolean
+static gboolean
 nm_device_can_assume_active_connection (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -3255,16 +3548,55 @@ nm_device_can_assume_active_connection (NMDevice *self)
 		return FALSE;
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-	if (!_nm_utils_string_in_list (method, assumable_ip6_methods))
+	if (!g_strv_contains (assumable_ip6_methods, method))
 		return FALSE;
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-	if (!_nm_utils_string_in_list (method, assumable_ip4_methods))
+	if (!g_strv_contains (assumable_ip4_methods, method))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+unmanaged_on_quit (NMDevice *self)
+{
+	/* Leave certain devices alone when quitting so their configuration
+	 * can be taken over when NM restarts.  This ensures connectivity while
+	 * NM is stopped.
+	 */
+	if (nm_device_uses_assumed_connection (self)) {
+		/* An assume connection must be left alone */
+		return FALSE;
+	}
+
+	if (!nm_device_get_act_request (self)) {
+		/* a device without any active connection is either UNAVAILABLE or DISCONNECTED
+		 * state. Since we don't know whether the device was upped by NetworkManager,
+		 * we must leave it up on exit.
+		 */
+		return FALSE;
+	}
+
+	if (!nm_platform_link_can_assume (NM_PLATFORM_GET, nm_device_get_ifindex (self))) {
+		/* The device has no layer 3 configuration. Leave it up. */
+		return FALSE;
+	}
+
+	if (nm_device_can_assume_active_connection (self))
 		return FALSE;
 
 	return TRUE;
 }
 
+gboolean
+nm_device_unmanage_on_quit (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	return NM_DEVICE_GET_CLASS (self)->unmanaged_on_quit (self);
+}
+
 static gboolean
 nm_device_emit_recheck_assume (gpointer user_data)
 {
@@ -3351,7 +3683,7 @@ dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer us
 
 	switch (status) {
 	case NM_DNSMASQ_STATUS_DEAD:
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
+		nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
 		break;
 	default:
 		break;
@@ -3753,15 +4085,21 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 
 	if (!priv->master_ready_handled) {
 		NMActiveConnection *active = NM_ACTIVE_CONNECTION (priv->act_request);
+		NMActiveConnection *master;
+
+		master = nm_active_connection_get_master (active);
 
-		if (!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 {
+			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) {
@@ -3815,6 +4153,26 @@ check_ip_failed (NMDevice *self, gboolean may_fail)
 	                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 }
 
+void
+nm_device_ip_method_failed (NMDevice *self, int family, NMDeviceStateReason reason)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (family == AF_INET || family == AF_INET6);
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (family == AF_INET)
+		priv->ip4_state = IP_FAIL;
+	else
+		priv->ip6_state = IP_FAIL;
+
+	if (get_ip_config_may_fail (self, family))
+		check_ip_failed (self, FALSE);
+	else
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+}
+
 /*
  * check_ip_done
  *
@@ -4050,14 +4408,14 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 
 	memset (&address, 0, sizeof (address));
 	nm_platform_ip4_address_set_addr (&address, lla, 16);
-	address.source = NM_IP_CONFIG_SOURCE_IP4LL;
+	address.addr_source = NM_IP_CONFIG_SOURCE_IP4LL;
 	nm_ip4_config_add_address (config, &address);
 
 	/* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */
 	memset (&route, 0, sizeof (route));
 	route.network = htonl (0xE0000000L);
 	route.plen = 4;
-	route.source = NM_IP_CONFIG_SOURCE_IP4LL;
+	route.rt_source = NM_IP_CONFIG_SOURCE_IP4LL;
 	route.metric = nm_device_get_ip4_route_metric (self);
 	nm_ip4_config_add_route (config, &route);
 
@@ -4094,23 +4452,20 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 		r = sd_ipv4ll_get_address (ll, &address);
 		if (r < 0) {
 			_LOGE (LOGD_AUTOIP4, "invalid IPv4 link-local address received, error %d.", r);
-			priv->ip4_state = IP_FAIL;
-			check_ip_failed (self, FALSE);
+			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
 			return;
 		}
 
 		if ((address.s_addr & IPV4LL_NETMASK) != IPV4LL_NETWORK) {
 			_LOGE (LOGD_AUTOIP4, "invalid address %08x received (not link-local).", address.s_addr);
-			priv->ip4_state = IP_FAIL;
-			check_ip_failed (self, FALSE);
+			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
 			return;
 		}
 
 		config = ipv4ll_get_ip4_config (self, address.s_addr);
 		if (config == NULL) {
 			_LOGE (LOGD_AUTOIP4, "failed to get IPv4LL config");
-			priv->ip4_state = IP_FAIL;
-			check_ip_failed (self, FALSE);
+			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
 			return;
 		}
 
@@ -4120,8 +4475,7 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 		} else if (priv->ip4_state == IP_DONE) {
 			if (!ip4_config_merge_and_apply (self, config, TRUE, NULL)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
-				priv->ip4_state = IP_FAIL;
-				check_ip_failed (self, FALSE);
+				nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
 			}
 		} else
 			g_assert_not_reached ();
@@ -4130,8 +4484,7 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 		break;
 	default:
 		_LOGW (LOGD_AUTOIP4, "IPv4LL address no longer valid after event %d.", event);
-		priv->ip4_state = IP_FAIL;
-		check_ip_failed (self, FALSE);
+		nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
 	}
 }
 
@@ -4188,9 +4541,9 @@ ipv4ll_start (NMDevice *self, NMDeviceStateReason *reason)
 		goto fail;
 	}
 
-	r = sd_ipv4ll_set_index (priv->ipv4ll, ifindex);
+	r = sd_ipv4ll_set_ifindex (priv->ipv4ll, ifindex);
 	if (r < 0) {
-		_LOGE (LOGD_AUTOIP4, "IPv4LL: set_index() failed with error %d", r);
+		_LOGE (LOGD_AUTOIP4, "IPv4LL: set_ifindex() failed with error %d", r);
 		goto fail;
 	}
 
@@ -4325,23 +4678,23 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->dhcp4_restart_id);
+	nm_clear_g_source (&priv->dhcp4.restart_id);
 
-	if (priv->dhcp4_client) {
+	if (priv->dhcp4.client) {
 		/* Stop any ongoing DHCP transaction on this device */
-		nm_clear_g_signal_handler (priv->dhcp4_client, &priv->dhcp4_state_sigid);
+		nm_clear_g_signal_handler (priv->dhcp4.client, &priv->dhcp4.state_sigid);
 
 		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE);
 
 		if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 		    || cleanup_type == CLEANUP_TYPE_REMOVED)
-			nm_dhcp_client_stop (priv->dhcp4_client, release);
+			nm_dhcp_client_stop (priv->dhcp4.client, release);
 
-		g_clear_object (&priv->dhcp4_client);
+		g_clear_object (&priv->dhcp4.client);
 	}
 
-	if (priv->dhcp4_config) {
-		nm_exported_object_clear_and_unexport (&priv->dhcp4_config);
+	if (priv->dhcp4.config) {
+		nm_exported_object_clear_and_unexport (&priv->dhcp4.config);
 		_notify (self, PROP_DHCP4_CONFIG);
 	}
 }
@@ -4495,7 +4848,7 @@ ip4_config_merge_and_apply (NMDevice *self,
 
 	priv->default_route.v4_has = TRUE;
 	memset (&priv->default_route.v4, 0, sizeof (priv->default_route.v4));
-	priv->default_route.v4.source = NM_IP_CONFIG_SOURCE_USER;
+	priv->default_route.v4.rt_source = NM_IP_CONFIG_SOURCE_USER;
 	priv->default_route.v4.gateway = gateway;
 	priv->default_route.v4.metric = default_route_metric;
 	priv->default_route.v4.mss = nm_ip4_config_get_mss (composite);
@@ -4541,28 +4894,30 @@ END_ADD_DEFAULT_ROUTE:
 	return success;
 }
 
-static void
+static gboolean
 dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 {
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
-	g_return_if_fail (config != NULL);
+	g_return_val_if_fail (config != NULL, FALSE);
 
 	if (!ip4_config_merge_and_apply (self, config, TRUE, &reason)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-	} else {
-		/* Notify dispatcher scripts of new DHCP4 config */
-		nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE,
-		                    nm_device_get_settings_connection (self),
-		                    nm_device_get_applied_connection (self),
-		                    self,
-		                    NULL,
-		                    NULL,
-		                    NULL);
-
-		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE);
+		return FALSE;
 	}
+
+	/* Notify dispatcher scripts of new DHCP4 config */
+	nm_dispatcher_call (DISPATCHER_ACTION_DHCP4_CHANGE,
+	                    nm_device_get_settings_connection (self),
+	                    nm_device_get_applied_connection (self),
+	                    self,
+	                    NULL,
+	                    NULL,
+	                    NULL);
+
+	nm_device_remove_pending_action (self, PENDING_ACTION_DHCP4, FALSE);
+
+	return TRUE;
 }
 
 static gboolean
@@ -4576,11 +4931,11 @@ dhcp4_restart_cb (gpointer user_data)
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	priv->dhcp4_restart_id = 0;
+	priv->dhcp4.restart_id = 0;
 	connection = nm_device_get_applied_connection (self);
 
 	if (dhcp4_start (self, connection, &reason) == NM_ACT_STAGE_RETURN_FAILURE)
-		priv->dhcp4_restart_id = g_timeout_add_seconds (120, dhcp4_restart_cb, self);
+		dhcp_schedule_restart (self, AF_INET, NULL);
 
 	return FALSE;
 }
@@ -4590,6 +4945,9 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	_LOGD (LOGD_DHCP4, "DHCPv4 failed: timeout %d, num tries left %u",
+	       timeout, priv->dhcp4.num_tries_left);
+
 	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 
 	/* Don't fail if there are static addresses configured on
@@ -4598,8 +4956,7 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 	if (   priv->ip4_state == IP_DONE
 	    && priv->con_ip4_config
 	    && nm_ip4_config_get_num_addresses (priv->con_ip4_config) > 0) {
-		_LOGI (LOGD_DHCP4, "Scheduling DHCPv4 restart because device has IP addresses");
-		priv->dhcp4_restart_id = g_timeout_add_seconds (120, dhcp4_restart_cb, self);
+		dhcp_schedule_restart (self, AF_INET, "device has IP addresses");
 		return;
 	}
 
@@ -4608,16 +4965,23 @@ dhcp4_fail (NMDevice *self, gboolean timeout)
 	 * retry DHCP again.
 	 */
 	if (nm_device_uses_assumed_connection (self)) {
-		_LOGI (LOGD_DHCP4, "Scheduling DHCPv4 restart because the connection is assumed");
-		priv->dhcp4_restart_id = g_timeout_add_seconds (120, dhcp4_restart_cb, self);
+		dhcp_schedule_restart (self, AF_INET, "connection is assumed");
 		return;
 	}
 
-	if (timeout || (priv->ip4_state == IP_CONF))
+	if (   priv->dhcp4.num_tries_left == DHCP_NUM_TRIES_MAX
+	    && (timeout || (priv->ip4_state == IP_CONF)))
 		nm_device_activate_schedule_ip4_config_timeout (self);
-	else if (priv->ip4_state == IP_DONE)
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-	else
+	else if (priv->ip4_state == IP_DONE) {
+		/* Don't fail immediately when the lease expires but try to
+		 * restart DHCP for a predefined number of times.
+		 */
+		if (priv->dhcp4.num_tries_left) {
+			priv->dhcp4.num_tries_left--;
+			dhcp_schedule_restart (self, AF_INET, "lease expired");
+		} else
+			nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	} else
 		g_warn_if_reached ();
 }
 
@@ -4654,14 +5018,13 @@ dhcp4_state_changed (NMDhcpClient *client,
 	case NM_DHCP_STATE_BOUND:
 		if (!ip4_config) {
 			_LOGW (LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
-			nm_device_state_changed (self,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+			dhcp4_fail (self, FALSE);
 			break;
 		}
 
-		nm_dhcp4_config_set_options (priv->dhcp4_config, options);
+		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
+		priv->dhcp4.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 		if (priv->ip4_state == IP_CONF) {
 			connection = nm_device_get_applied_connection (self);
@@ -4678,8 +5041,10 @@ dhcp4_state_changed (NMDhcpClient *client,
 
 			ipv4_dad_start (self, configs, dhcp4_dad_cb);
 		} else if (priv->ip4_state == IP_DONE) {
-			dhcp4_lease_change (self, ip4_config);
-			nm_device_update_metered (self);
+			if (dhcp4_lease_change (self, ip4_config))
+				nm_device_update_metered (self);
+			else
+				dhcp4_fail (self, FALSE);
 		}
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
@@ -4735,8 +5100,8 @@ dhcp4_start (NMDevice *self,
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
 	/* Clear old exported DHCP options */
-	nm_exported_object_clear_and_unexport (&priv->dhcp4_config);
-	priv->dhcp4_config = nm_dhcp4_config_new ();
+	nm_exported_object_clear_and_unexport (&priv->dhcp4.config);
+	priv->dhcp4.config = nm_dhcp4_config_new ();
 
 	hw_addr = nm_platform_link_get_address (NM_PLATFORM_GET, nm_device_get_ip_ifindex (self), &hw_addr_len);
 	if (hw_addr_len) {
@@ -4745,8 +5110,8 @@ dhcp4_start (NMDevice *self,
 	}
 
 	/* Begin DHCP on the interface */
-	g_warn_if_fail (priv->dhcp4_client == NULL);
-	priv->dhcp4_client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
+	g_warn_if_fail (priv->dhcp4.client == NULL);
+	priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                tmp,
@@ -4763,12 +5128,12 @@ dhcp4_start (NMDevice *self,
 	if (tmp)
 		g_byte_array_free (tmp, TRUE);
 
-	if (!priv->dhcp4_client) {
+	if (!priv->dhcp4.client) {
 		*reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	priv->dhcp4_state_sigid = g_signal_connect (priv->dhcp4_client,
+	priv->dhcp4.state_sigid = g_signal_connect (priv->dhcp4.client,
 	                                            NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
 	                                            G_CALLBACK (dhcp4_state_changed),
 	                                            self);
@@ -4787,7 +5152,7 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 	NMDeviceStateReason reason;
 	NMConnection *connection;
 
-	g_return_val_if_fail (priv->dhcp4_client != NULL, FALSE);
+	g_return_val_if_fail (priv->dhcp4.client != NULL, FALSE);
 
 	_LOGI (LOGD_DHCP4, "DHCPv4 lease renewal requested");
 
@@ -4862,7 +5227,7 @@ shared4_new_config (NMDevice *self, NMConnection *connection, NMDeviceStateReaso
 	}
 
 	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
-	address.source = NM_IP_CONFIG_SOURCE_SHARED;
+	address.addr_source = NM_IP_CONFIG_SOURCE_SHARED;
 	nm_ip4_config_add_address (config, &address);
 
 	/* Remove the address lock when the object gets disposed */
@@ -4888,7 +5253,7 @@ connection_ip4_method_requires_carrier (NMConnection *connection,
 
 	if (out_ip4_enabled)
 		*out_ip4_enabled = !!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
-	return _nm_utils_string_in_list (method, ip4_carrier_methods);
+	return g_strv_contains (ip4_carrier_methods, method);
 }
 
 static gboolean
@@ -4905,7 +5270,7 @@ connection_ip6_method_requires_carrier (NMConnection *connection,
 
 	if (out_ip6_enabled)
 		*out_ip6_enabled = !!strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
-	return _nm_utils_string_in_list (method, ip6_carrier_methods);
+	return g_strv_contains (ip6_carrier_methods, method);
 }
 
 static gboolean
@@ -5013,6 +5378,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 	}
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	priv->dhcp4.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 	/* Start IPv4 addressing based on the method requested */
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
@@ -5056,25 +5422,25 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
-	g_clear_object (&priv->dhcp6_ip6_config);
-	g_clear_pointer (&priv->dhcp6_event_id, g_free);
-	nm_clear_g_source (&priv->dhcp6_restart_id);
+	priv->dhcp6.mode = NM_RDISC_DHCP_LEVEL_NONE;
+	g_clear_object (&priv->dhcp6.ip6_config);
+	g_clear_pointer (&priv->dhcp6.event_id, g_free);
+	nm_clear_g_source (&priv->dhcp6.restart_id);
 
-	if (priv->dhcp6_client) {
-		nm_clear_g_signal_handler (priv->dhcp6_client, &priv->dhcp6_state_sigid);
+	if (priv->dhcp6.client) {
+		nm_clear_g_signal_handler (priv->dhcp6.client, &priv->dhcp6.state_sigid);
 
 		if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 		    || cleanup_type == CLEANUP_TYPE_REMOVED)
-			nm_dhcp_client_stop (priv->dhcp6_client, release);
+			nm_dhcp_client_stop (priv->dhcp6.client, release);
 
-		g_clear_object (&priv->dhcp6_client);
+		g_clear_object (&priv->dhcp6.client);
 	}
 
 	nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
 
-	if (priv->dhcp6_config) {
-		nm_exported_object_clear_and_unexport (&priv->dhcp6_config);
+	if (priv->dhcp6.config) {
+		nm_exported_object_clear_and_unexport (&priv->dhcp6.config);
 		_notify (self, PROP_DHCP6_CONFIG);
 	}
 }
@@ -5104,6 +5470,7 @@ ip6_config_merge_and_apply (NMDevice *self,
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
 	gboolean auto_method = FALSE;
+	const char *token = NULL;
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
@@ -5111,9 +5478,14 @@ ip6_config_merge_and_apply (NMDevice *self,
 		NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config (connection);
 
 		if (s_ip6) {
+			NMSettingIP6Config *ip6 = NM_SETTING_IP6_CONFIG (s_ip6);
+
 			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip6);
 			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip6);
 
+			if (nm_setting_ip6_config_get_addr_gen_mode (ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
+				token = nm_setting_ip6_config_get_token (ip6);
+
 			if (NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
 			                  NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 			                  NM_SETTING_IP6_CONFIG_METHOD_DHCP))
@@ -5136,8 +5508,8 @@ ip6_config_merge_and_apply (NMDevice *self,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
 		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
 	}
-	if (priv->dhcp6_ip6_config) {
-		nm_ip6_config_merge (composite, priv->dhcp6_ip6_config,
+	if (priv->dhcp6.ip6_config) {
+		nm_ip6_config_merge (composite, priv->dhcp6.ip6_config,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
 		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
 	}
@@ -5230,7 +5602,7 @@ ip6_config_merge_and_apply (NMDevice *self,
 
 	priv->default_route.v6_has = TRUE;
 	memset (&priv->default_route.v6, 0, sizeof (priv->default_route.v6));
-	priv->default_route.v6.source = NM_IP_CONFIG_SOURCE_USER;
+	priv->default_route.v6.rt_source = NM_IP_CONFIG_SOURCE_USER;
 	priv->default_route.v6.gateway = *gateway;
 	priv->default_route.v6.metric = nm_device_get_ip6_route_metric (self);
 	priv->default_route.v6.mss = nm_ip6_config_get_mss (composite);
@@ -5261,6 +5633,14 @@ END_ADD_DEFAULT_ROUTE:
 
 	/* Allow setting MTU etc */
 	if (commit) {
+		NMUtilsIPv6IfaceId iid;
+
+		if (token && nm_utils_ipv6_interface_identifier_get_from_token (&iid, token)) {
+			nm_platform_link_set_ipv6_token (NM_PLATFORM_GET,
+			                                 nm_device_get_ip_ifindex (self),
+			                                 iid);
+		}
+
 		if (NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit)
 			NM_DEVICE_GET_CLASS (self)->ip6_config_pre_commit (self, composite);
 	}
@@ -5276,37 +5656,38 @@ END_ADD_DEFAULT_ROUTE:
 	return success;
 }
 
-static void
+static gboolean
 dhcp6_lease_change (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingsConnection *settings_connection;
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
 
-	if (priv->dhcp6_ip6_config == NULL) {
+	if (priv->dhcp6.ip6_config == NULL) {
 		_LOGW (LOGD_DHCP6, "failed to get DHCPv6 config for rebind");
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-		return;
+		return FALSE;
 	}
 
-	g_assert (priv->dhcp6_client);  /* sanity check */
+	g_assert (priv->dhcp6.client);  /* sanity check */
 
 	settings_connection = nm_device_get_settings_connection (self);
 	g_assert (settings_connection);
 
 	/* Apply the updated config */
-	if (ip6_config_merge_and_apply (self, TRUE, &reason) == FALSE) {
-		_LOGW (LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event.");
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
-	} else {
-		/* Notify dispatcher scripts of new DHCPv6 config */
-		nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE,
-		                    settings_connection,
-		                    nm_device_get_applied_connection (self),
-		                    self, NULL, NULL, NULL);
-
-		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
+	if (!ip6_config_merge_and_apply (self, TRUE, &reason)) {
+		_LOGW (LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event");
+		return FALSE;
 	}
+
+	/* Notify dispatcher scripts of new DHCPv6 config */
+	nm_dispatcher_call (DISPATCHER_ACTION_DHCP6_CHANGE,
+	                    settings_connection,
+	                    nm_device_get_applied_connection (self),
+	                    self, NULL, NULL, NULL);
+
+	nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
+
+	return TRUE;
 }
 
 static gboolean
@@ -5319,30 +5700,63 @@ dhcp6_restart_cb (gpointer user_data)
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
-	priv->dhcp6_restart_id = 0;
+	priv->dhcp6.restart_id = 0;
 
 	if (!dhcp6_start (self, FALSE, &reason))
-		priv->dhcp6_restart_id = g_timeout_add_seconds (120, dhcp6_restart_cb, self);
+		dhcp_schedule_restart (self, AF_INET6, NULL);
 
 	return FALSE;
 }
 
 static void
+dhcp_schedule_restart (NMDevice *self, int family, const char *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean inet4;
+	guint tries_left;
+	gs_free char *tries_str = NULL;
+
+	g_return_if_fail (family == AF_INET || family == AF_INET6);
+	inet4 = family == AF_INET;
+
+	tries_left = inet4 ? priv->dhcp4.num_tries_left : priv->dhcp6.num_tries_left;
+	if (tries_left != DHCP_NUM_TRIES_MAX)
+		tries_str = g_strdup_printf (", %u tries left", tries_left + 1);
+
+	_LOGI (inet4 ? LOGD_DHCP4 : LOGD_DHCP6,
+	       "scheduling DHCPv%c restart in %u seconds%s%s%s%s",
+	       inet4 ? '4' : '6',
+	       DHCP_RESTART_TIMEOUT,
+	       tries_str ? tries_str : "",
+	       NM_PRINT_FMT_QUOTED (reason, " (reason: ", reason, ")", ""));
+
+	if (inet4) {
+		priv->dhcp4.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
+		                                                dhcp4_restart_cb, self);
+	} else {
+		priv->dhcp6.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
+		                                                dhcp6_restart_cb, self);
+	}
+}
+
+static void
 dhcp6_fail (NMDevice *self, gboolean timeout)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	_LOGD (LOGD_DHCP6, "DHCPv6 failed: timeout %d, num tries left %u",
+           timeout, priv->dhcp6.num_tries_left);
+
 	dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
 
-	if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
+	if (priv->dhcp6.mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
 		/* Don't fail if there are static addresses configured on
 		 * the device, instead retry after some time.
 		 */
 		if (   priv->ip6_state == IP_DONE
 		    && priv->con_ip6_config
 		    && nm_ip6_config_get_num_addresses (priv->con_ip6_config)) {
-			_LOGI (LOGD_DHCP6, "Scheduling DHCPv6 restart because device has IP addresses");
-			priv->dhcp6_restart_id = g_timeout_add_seconds (120, dhcp6_restart_cb, self);
+			dhcp_schedule_restart (self, AF_INET6, "device has IP addresses");
 			return;
 		}
 
@@ -5351,16 +5765,23 @@ dhcp6_fail (NMDevice *self, gboolean timeout)
 		 * retry DHCP again.
 		 */
 		if (nm_device_uses_assumed_connection (self)) {
-			_LOGI (LOGD_DHCP6, "Scheduling DHCPv6 restart because the connection is assumed");
-			priv->dhcp6_restart_id = g_timeout_add_seconds (120, dhcp6_restart_cb, self);
+			dhcp_schedule_restart (self, AF_INET6, "connection is assumed");
 			return;
 		}
 
-		if (timeout || (priv->ip6_state == IP_CONF))
+		if (   priv->dhcp6.num_tries_left == DHCP_NUM_TRIES_MAX
+		    && (timeout || (priv->ip6_state == IP_CONF)))
 			nm_device_activate_schedule_ip6_config_timeout (self);
-		else if (priv->ip6_state == IP_DONE)
-			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-		else
+		else if (priv->ip6_state == IP_DONE) {
+			/* Don't fail immediately when the lease expires but try to
+			 * restart DHCP for a predefined number of times.
+			 */
+			if (priv->dhcp6.num_tries_left) {
+				priv->dhcp6.num_tries_left--;
+				dhcp_schedule_restart (self, AF_INET6, "lease expired");
+			} else
+				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+		} else
 			g_warn_if_reached ();
 	} else {
 		/* not a hard failure; just live with the RA info */
@@ -5374,7 +5795,7 @@ dhcp6_timeout (NMDevice *self, NMDhcpClient *client)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED)
+	if (priv->dhcp6.mode == NM_RDISC_DHCP_LEVEL_MANAGED)
 		dhcp6_fail (self, TRUE);
 	else {
 		/* not a hard failure; just live with the RA info */
@@ -5409,32 +5830,34 @@ dhcp6_state_changed (NMDhcpClient *client,
 		 */
 		if (   ip6_config
 		    && event_id
-		    && priv->dhcp6_event_id
-		    && !strcmp (event_id, priv->dhcp6_event_id)) {
+		    && priv->dhcp6.event_id
+		    && !strcmp (event_id, priv->dhcp6.event_id)) {
 			for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
-				nm_ip6_config_add_address (priv->dhcp6_ip6_config,
+				nm_ip6_config_add_address (priv->dhcp6.ip6_config,
 				                           nm_ip6_config_get_address (ip6_config, i));
 			}
 		} else {
-			g_clear_object (&priv->dhcp6_ip6_config);
-			g_clear_pointer (&priv->dhcp6_event_id, g_free);
+			g_clear_object (&priv->dhcp6.ip6_config);
+			g_clear_pointer (&priv->dhcp6.event_id, g_free);
 			if (ip6_config) {
-				priv->dhcp6_ip6_config = g_object_ref (ip6_config);
-				priv->dhcp6_event_id = g_strdup (event_id);
-				nm_dhcp6_config_set_options (priv->dhcp6_config, options);
+				priv->dhcp6.ip6_config = g_object_ref (ip6_config);
+				priv->dhcp6.event_id = g_strdup (event_id);
+				nm_dhcp6_config_set_options (priv->dhcp6.config, options);
 				_notify (self, PROP_DHCP6_CONFIG);
 			}
 		}
 
+		priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
+
 		if (priv->ip6_state == IP_CONF) {
-			if (priv->dhcp6_ip6_config == NULL) {
-				/* FIXME: Initial DHCP failed; should we fail IPv6 entirely then? */
-				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_DHCP_FAILED);
+			if (priv->dhcp6.ip6_config == NULL) {
+				nm_device_ip_method_failed (self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
 				break;
 			}
 			nm_device_activate_schedule_ip6_config_result (self);
 		} else if (priv->ip6_state == IP_DONE)
-			dhcp6_lease_change (self);
+			if (!dhcp6_lease_change (self))
+				dhcp6_fail (self, FALSE);
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
 		dhcp6_timeout (self, client);
@@ -5449,7 +5872,7 @@ dhcp6_state_changed (NMDhcpClient *client,
 		 * may exit right after getting a response from the server.  That's
 		 * normal.  In that case we just ignore the exit.
 		 */
-		if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF)
+		if (priv->dhcp6.mode == NM_RDISC_DHCP_LEVEL_OTHERCONF)
 			break;
 		/* Otherwise, fall through */
 	case NM_DHCP_STATE_FAIL:
@@ -5485,7 +5908,7 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 
 	g_return_val_if_fail (ll_addr, FALSE);
 
-	priv->dhcp6_client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
+	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                tmp,
@@ -5496,19 +5919,19 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
 	                                                priv->dhcp_timeout,
 	                                                priv->dhcp_anycast_address,
-	                                                (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
+	                                                (priv->dhcp6.mode == NM_RDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
 	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)));
 	if (tmp)
 		g_byte_array_free (tmp, TRUE);
 
-	if (priv->dhcp6_client) {
-		priv->dhcp6_state_sigid = g_signal_connect (priv->dhcp6_client,
+	if (priv->dhcp6.client) {
+		priv->dhcp6.state_sigid = g_signal_connect (priv->dhcp6.client,
 		                                            NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
 		                                            G_CALLBACK (dhcp6_state_changed),
 		                                            self);
 	}
 
-	return !!priv->dhcp6_client;
+	return !!priv->dhcp6.client;
 }
 
 static gboolean
@@ -5518,12 +5941,12 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip6;
 
-	nm_exported_object_clear_and_unexport (&priv->dhcp6_config);
-	priv->dhcp6_config = nm_dhcp6_config_new ();
+	nm_exported_object_clear_and_unexport (&priv->dhcp6.config);
+	priv->dhcp6.config = nm_dhcp6_config_new ();
 
-	g_warn_if_fail (priv->dhcp6_ip6_config == NULL);
-	g_clear_object (&priv->dhcp6_ip6_config);
-	g_clear_pointer (&priv->dhcp6_event_id, g_free);
+	g_warn_if_fail (priv->dhcp6.ip6_config == NULL);
+	g_clear_object (&priv->dhcp6.ip6_config);
+	g_clear_pointer (&priv->dhcp6.event_id, g_free);
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -5559,7 +5982,7 @@ nm_device_dhcp6_renew (NMDevice *self, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	g_return_val_if_fail (priv->dhcp6_client != NULL, FALSE);
+	g_return_val_if_fail (priv->dhcp6.client != NULL, FALSE);
 
 	_LOGI (LOGD_DHCP6, "DHCPv6 lease renewal requested");
 
@@ -5668,11 +6091,17 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 		s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
 
 	if (s_ip6 && nm_setting_ip6_config_get_addr_gen_mode (s_ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
-		if (!nm_utils_ipv6_addr_set_stable_privacy (&lladdr,
-		                                            nm_device_get_iface (self),
-		                                            nm_connection_get_uuid (connection),
-		                                            priv->linklocal6_dad_counter++,
-		                                            &error)) {
+		NMUtilsStableType stable_type;
+		const char *stable_id;
+
+		stable_id = _get_stable_id (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)) {
 			_LOGW (LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message);
 			g_clear_error (&error);
 			linklocal6_failed (self);
@@ -5690,13 +6119,13 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 			return;
 		}
 
-		if (!nm_device_get_ip_iface_identifier (self, &iid)) {
+		if (!nm_device_get_ip_iface_identifier (self, &iid, TRUE)) {
 			_LOGW (LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
 			return;
 		}
 		_LOGD (LOGD_IP6, "linklocal6: using EUI-64 identifier to generate IPv6LL address");
 
-		nm_utils_ipv6_addr_set_interface_identfier (&lladdr, iid);
+		nm_utils_ipv6_addr_set_interface_identifier (&lladdr, iid);
 	}
 
 	_LOGD (LOGD_IP6, "linklocal6: adding IPv6LL address %s", nm_utils_inet6_ntop (&lladdr, NULL));
@@ -5799,8 +6228,9 @@ nm_device_ipv6_set_mtu (NMDevice *self, guint32 mtu)
 }
 
 static void
-rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
+rdisc_config_changed (NMRDisc *rdisc, const NMRDiscData *rdata, guint changed_int, NMDevice *self)
 {
+	NMRDiscConfigMap changed = changed_int;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int i;
 	int system_support;
@@ -5830,11 +6260,9 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 
 	if (changed & NM_RDISC_CONFIG_GATEWAYS) {
 		/* Use the first gateway as ordered in router discovery cache. */
-		if (rdisc->gateways->len) {
-			NMRDiscGateway *gateway = &g_array_index (rdisc->gateways, NMRDiscGateway, 0);
-
-			nm_ip6_config_set_gateway (priv->ac_ip6_config, &gateway->address);
-		} else
+		if (rdata->gateways_n)
+			nm_ip6_config_set_gateway (priv->ac_ip6_config, &rdata->gateways[0].address);
+		else
 			nm_ip6_config_set_gateway (priv->ac_ip6_config, NULL);
 	}
 
@@ -5847,8 +6275,8 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 		 * also counts static and temporary addresses when checking
 		 * max_addresses.
 		 **/
-		for (i = 0; i < rdisc->addresses->len; i++) {
-			NMRDiscAddress *discovered_address = &g_array_index (rdisc->addresses, NMRDiscAddress, i);
+		for (i = 0; i < rdata->addresses_n; i++) {
+			const NMRDiscAddress *discovered_address = &rdata->addresses[i];
 			NMPlatformIP6Address address;
 
 			memset (&address, 0, sizeof (address));
@@ -5859,7 +6287,7 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 			address.preferred = discovered_address->preferred;
 			if (address.preferred > address.lifetime)
 				address.preferred = address.lifetime;
-			address.source = NM_IP_CONFIG_SOURCE_RDISC;
+			address.addr_source = NM_IP_CONFIG_SOURCE_RDISC;
 			address.n_ifa_flags = ifa_flags;
 
 			nm_ip6_config_add_address (priv->ac_ip6_config, &address);
@@ -5870,25 +6298,17 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 		/* Rebuild route list from router discovery cache. */
 		nm_ip6_config_reset_routes (priv->ac_ip6_config);
 
-		for (i = 0; i < rdisc->routes->len; i++) {
-			NMRDiscRoute *discovered_route = &g_array_index (rdisc->routes, NMRDiscRoute, i);
-			NMPlatformIP6Route route;
-
-			/* Only accept non-default routes.  The router has no idea what the
-			 * local configuration or user preferences are, so sending routes
-			 * with a prefix length of 0 is quite rude and thus ignored.
-			 */
-			if (discovered_route->plen > 0) {
-				memset (&route, 0, sizeof (route));
-				route.network = discovered_route->network;
-				nm_assert (discovered_route->plen <= 128);
-				route.plen = discovered_route->plen;
-				route.gateway = discovered_route->gateway;
-				route.source = NM_IP_CONFIG_SOURCE_RDISC;
-				route.metric = nm_device_get_ip6_route_metric (self);
-
-				nm_ip6_config_add_route (priv->ac_ip6_config, &route);
-			}
+		for (i = 0; i < rdata->routes_n; i++) {
+			const NMRDiscRoute *discovered_route = &rdata->routes[i];
+			const NMPlatformIP6Route route = {
+				.network    = discovered_route->network,
+				.plen       = discovered_route->plen,
+				.gateway    = discovered_route->gateway,
+				.rt_source  = NM_IP_CONFIG_SOURCE_RDISC,
+				.metric     = nm_device_get_ip6_route_metric (self),
+			};
+
+			nm_ip6_config_add_route (priv->ac_ip6_config, &route);
 		}
 	}
 
@@ -5896,36 +6316,30 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 		/* Rebuild DNS server list from router discovery cache. */
 		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
 
-		for (i = 0; i < rdisc->dns_servers->len; i++) {
-			NMRDiscDNSServer *discovered_server = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i);
-
-			nm_ip6_config_add_nameserver (priv->ac_ip6_config, &discovered_server->address);
-		}
+		for (i = 0; i < rdata->dns_servers_n; i++)
+			nm_ip6_config_add_nameserver (priv->ac_ip6_config, &rdata->dns_servers[i].address);
 	}
 
 	if (changed & NM_RDISC_CONFIG_DNS_DOMAINS) {
 		/* Rebuild domain list from router discovery cache. */
 		nm_ip6_config_reset_domains (priv->ac_ip6_config);
 
-		for (i = 0; i < rdisc->dns_domains->len; i++) {
-			NMRDiscDNSDomain *discovered_domain = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i);
-
-			nm_ip6_config_add_domain (priv->ac_ip6_config, discovered_domain->domain);
-		}
+		for (i = 0; i < rdata->dns_domains_n; i++)
+			nm_ip6_config_add_domain (priv->ac_ip6_config, rdata->dns_domains[i].domain);
 	}
 
 	if (changed & NM_RDISC_CONFIG_DHCP_LEVEL) {
 		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, TRUE);
 
-		priv->dhcp6_mode = rdisc->dhcp_level;
-		if (priv->dhcp6_mode != NM_RDISC_DHCP_LEVEL_NONE) {
+		priv->dhcp6.mode = rdata->dhcp_level;
+		if (priv->dhcp6.mode != NM_RDISC_DHCP_LEVEL_NONE) {
 			NMDeviceStateReason reason;
 
 			_LOGD (LOGD_DEVICE | LOGD_DHCP6,
 			       "Activation: Stage 3 of 5 (IP Configure Start) starting DHCPv6"
 			       " as requested by IPv6 router...");
 			if (!dhcp6_start (self, FALSE, &reason)) {
-				if (priv->dhcp6_mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
+				if (priv->dhcp6.mode == NM_RDISC_DHCP_LEVEL_MANAGED) {
 					nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
 					return;
 				}
@@ -5934,10 +6348,10 @@ rdisc_config_changed (NMRDisc *rdisc, NMRDiscConfigMap changed, NMDevice *self)
 	}
 
 	if (changed & NM_RDISC_CONFIG_HOP_LIMIT)
-		nm_platform_sysctl_set_ip6_hop_limit_safe (NM_PLATFORM_GET, nm_device_get_ip_iface (self), rdisc->hop_limit);
+		nm_platform_sysctl_set_ip6_hop_limit_safe (NM_PLATFORM_GET, nm_device_get_ip_iface (self), rdata->hop_limit);
 
 	if (changed & NM_RDISC_CONFIG_MTU)
-		priv->ip6_mtu = rdisc->mtu;
+		priv->ip6_mtu = rdata->mtu;
 
 	nm_device_activate_schedule_ip6_config_result (self);
 }
@@ -5975,10 +6389,7 @@ addrconf6_start_with_link_ready (NMDevice *self)
 
 	g_assert (priv->rdisc);
 
-	if (nm_platform_link_get_ipv6_token (NM_PLATFORM_GET, priv->ifindex, &iid)) {
-		_LOGD (LOGD_IP6, "addrconf6: IPv6 tokenized identifier present");
-		nm_rdisc_set_iid (priv->rdisc, iid);
-	} else if (nm_device_get_ip_iface_identifier (self, &iid)) {
+	if (nm_device_get_ip_iface_identifier (self, &iid, FALSE)) {
 		_LOGD (LOGD_IP6, "addrconf6: using the device EUI-64 identifier");
 		nm_rdisc_set_iid (priv->rdisc, iid);
 	} else {
@@ -6017,6 +6428,8 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	NMActStageReturn ret;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
+	NMUtilsStableType stable_type;
+	const char *stable_id;
 
 	connection = nm_device_get_applied_connection (self);
 	g_assert (connection);
@@ -6030,12 +6443,16 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
 	g_assert (s_ip6);
 
-	priv->rdisc = nm_lndp_rdisc_new (NM_PLATFORM_GET,
-	                                 nm_device_get_ip_ifindex (self),
-	                                 nm_device_get_ip_iface (self),
-	                                 nm_connection_get_uuid (connection),
-	                                 nm_setting_ip6_config_get_addr_gen_mode (s_ip6),
-	                                 &error);
+	stable_id = _get_stable_id (connection, &stable_type);
+	if (stable_id) {
+		priv->rdisc = nm_lndp_rdisc_new (NM_PLATFORM_GET,
+		                                 nm_device_get_ip_ifindex (self),
+		                                 nm_device_get_ip_iface (self),
+		                                 stable_type,
+		                                 stable_id,
+		                                 nm_setting_ip6_config_get_addr_gen_mode (s_ip6),
+		                                 &error);
+	}
 	if (!priv->rdisc) {
 		_LOGE (LOGD_IP6, "addrconf6: failed to start router discovery: %s", error->message);
 		g_error_free (error);
@@ -6147,11 +6564,18 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 
 	priv->nm_ipv6ll = enable;
 	if (ifindex > 0) {
+		NMPlatformError plerr;
 		const char *detail = enable ? "enable" : "disable";
 
 		_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
-		if (!nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, ifindex, enable))
-			_LOGW (LOGD_IP6, "failed to %s userspace IPv6LL address handling", detail);
+		plerr = nm_platform_link_set_user_ipv6ll_enabled (NM_PLATFORM_GET, ifindex, enable);
+		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+			_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
+			        LOGD_IP6,
+			        "failed to %s userspace IPv6LL address handling (%s)",
+			        detail,
+			        nm_platform_error_to_string (plerr));
+		}
 
 		if (enable) {
 			/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
@@ -6286,7 +6710,8 @@ act_stage3_ip6_config_start (NMDevice *self,
 		}
 	}
 
-	priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_NONE;
+	priv->dhcp6.mode = NM_RDISC_DHCP_LEVEL_NONE;
+	priv->dhcp6.num_tries_left = DHCP_NUM_TRIES_MAX;
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
@@ -6334,7 +6759,7 @@ act_stage3_ip6_config_start (NMDevice *self,
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0) {
 		ret = linklocal6_start (self);
 	} else if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0) {
-		priv->dhcp6_mode = NM_RDISC_DHCP_LEVEL_MANAGED;
+		priv->dhcp6.mode = NM_RDISC_DHCP_LEVEL_MANAGED;
 		if (!dhcp6_start (self, TRUE, reason)) {
 			/* IPv6 might be disabled; allow IPv4 to proceed */
 			ret = NM_ACT_STAGE_RETURN_STOP;
@@ -6611,7 +7036,6 @@ act_stage4_ip4_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
-
 /*
  * nm_device_activate_stage4_ip4_config_timeout
  *
@@ -6639,7 +7063,6 @@ activate_stage4_ip4_config_timeout (NMDevice *self)
 	check_ip_failed (self, FALSE);
 }
 
-
 /*
  * nm_device_activate_schedule_ip4_config_timeout
  *
@@ -6659,7 +7082,6 @@ nm_device_activate_schedule_ip4_config_timeout (NMDevice *self)
 	activation_source_schedule (self, activate_stage4_ip4_config_timeout, AF_INET);
 }
 
-
 static NMActStageReturn
 act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 {
@@ -6671,7 +7093,6 @@ act_stage4_ip6_config_timeout (NMDevice *self, NMDeviceStateReason *reason)
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
-
 /*
  * activate_stage4_ip6_config_timeout
  *
@@ -6699,7 +7120,6 @@ activate_stage4_ip6_config_timeout (NMDevice *self)
 	check_ip_failed (self, FALSE);
 }
 
-
 /*
  * nm_device_activate_schedule_ip6_config_timeout
  *
@@ -6747,19 +7167,12 @@ share_init (void)
 	return TRUE;
 }
 
-static void
-add_share_rule (NMActRequest *req, const char *table, const char *fmt, ...)
-{
-	va_list args;
-	char *cmd;
-
-	va_start (args, fmt);
-	cmd = g_strdup_vprintf (fmt, args);
-	va_end (args);
-
-	nm_act_request_add_share_rule (req, table, cmd);
-	g_free (cmd);
-}
+#define add_share_rule(req, table, ...) \
+	G_STMT_START { \
+		char *_cmd = g_strdup_printf (__VA_ARGS__); \
+		nm_act_request_add_share_rule (req, table, _cmd); \
+		g_free (_cmd); \
+	} G_STMT_END
 
 static gboolean
 start_sharing (NMDevice *self, NMIP4Config *config)
@@ -6906,7 +7319,7 @@ activate_stage5_ip4_config_commit (NMDevice *self)
 	/* NULL to use the existing priv->dev_ip4_config */
 	if (!ip4_config_merge_and_apply (self, NULL, TRUE, &reason)) {
 		_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed");
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		nm_device_ip_method_failed (self, AF_INET, reason);
 		return;
 	}
 
@@ -6924,7 +7337,7 @@ activate_stage5_ip4_config_commit (NMDevice *self)
 	/* If IPv4 wasn't the first to complete, and DHCP was used, then ensure
 	 * dispatcher scripts get the DHCP lease information.
 	 */
-	if (   priv->dhcp4_client
+	if (   priv->dhcp4.client
 	    && nm_device_activate_ip4_state_in_conf (self)
 	    && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) {
 		/* Notify dispatcher scripts of new DHCP4 config */
@@ -6975,6 +7388,64 @@ nm_device_activate_ip4_state_in_wait (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_WAIT;
 }
 
+gboolean
+nm_device_activate_ip4_state_done (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, FALSE);
+	return NM_DEVICE_GET_PRIVATE (self)->ip4_state == IP_DONE;
+}
+
+/*
+ * Returns a NMIP6Config containing NM-configured addresses which
+ * have the tentative flag, or NULL if none is present.
+ */
+static NMIP6Config *
+dad6_get_pending_addresses (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMIP6Config *confs[] = { priv->ac_ip6_config,
+	                         priv->dhcp6.ip6_config,
+	                         priv->con_ip6_config,
+	                         priv->wwan_ip6_config };
+	const NMPlatformIP6Address *addr, *pl_addr;
+	NMIP6Config *dad6_config = NULL;
+	guint i, j, num;
+	int ifindex;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	/* We are interested only in addresses that we have explicitly configured,
+	 * not in externally added ones.
+	 */
+	for (i = 0; i < G_N_ELEMENTS (confs); i++) {
+		if (confs[i]) {
+			num = nm_ip6_config_get_num_addresses (confs[i]);
+			for (j = 0; j < num; j++) {
+				addr = nm_ip6_config_get_address (confs[i], j);
+				pl_addr = nm_platform_ip6_address_get (NM_PLATFORM_GET,
+				                                       ifindex,
+				                                       addr->address,
+				                                       addr->plen);
+				if (   pl_addr
+				    && NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
+				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_DADFAILED)
+				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
+					_LOGt (LOGD_DEVICE, "IPv6 DAD: pending address %s",
+					       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
+
+					if (!dad6_config)
+						dad6_config = nm_ip6_config_new (ifindex);
+
+					nm_ip6_config_add_address (dad6_config, pl_addr);
+				}
+			}
+		}
+	}
+
+	return dad6_config;
+}
+
 static void
 activate_stage5_ip6_config_commit (NMDevice *self)
 {
@@ -6998,9 +7469,9 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 	}
 
 	if (ip6_config_merge_and_apply (self, TRUE, &reason)) {
-		if (   priv->dhcp6_mode != NM_RDISC_DHCP_LEVEL_NONE
+		if (   priv->dhcp6.mode != NM_RDISC_DHCP_LEVEL_NONE
 		    && priv->ip6_state == IP_CONF) {
-			if (priv->dhcp6_ip6_config) {
+			if (priv->dhcp6.ip6_config) {
 				/* If IPv6 wasn't the first IP to complete, and DHCP was used,
 				 * then ensure dispatcher scripts get the DHCP lease information.
 				 */
@@ -7016,16 +7487,23 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 				return;
 			}
 		}
-
 		nm_device_remove_pending_action (self, PENDING_ACTION_DHCP6, FALSE);
 		nm_device_remove_pending_action (self, PENDING_ACTION_AUTOCONF6, FALSE);
 
-		/* Enter the IP_CHECK state if this is the first method to complete */
-		priv->ip6_state = IP_DONE;
-		check_ip_done (self);
+		/* Check if we have to wait for DAD */
+		if (priv->ip6_state == IP_CONF && !priv->dad6_ip6_config) {
+			priv->dad6_ip6_config = dad6_get_pending_addresses (self);
+			if (priv->dad6_ip6_config) {
+				_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: waiting termination");
+			} else {
+				/* No tentative addresses, proceed right away */
+				priv->ip6_state = IP_DONE;
+				check_ip_done (self);
+			}
+		}
 	} else {
 		_LOGW (LOGD_DEVICE | LOGD_IP6, "Activation: Stage 5 of 5 (IPv6 Commit) failed");
-		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		nm_device_ip_method_failed (self, AF_INET6, reason);
 	}
 }
 
@@ -7059,6 +7537,13 @@ nm_device_activate_ip6_state_in_wait (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_WAIT;
 }
 
+gboolean
+nm_device_activate_ip6_state_done (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, FALSE);
+	return NM_DEVICE_GET_PRIVATE (self)->ip6_state == IP_DONE;
+}
+
 static void
 clear_act_request (NMDevice *self)
 {
@@ -7223,6 +7708,7 @@ _cleanup_ip6_pre (NMDevice *self, CleanupType cleanup_type)
 	if (nm_clear_g_source (&priv->queued_ip6_config_id))
 		_LOGD (LOGD_DEVICE, "clearing queued IP6 config change");
 
+	g_clear_object (&priv->dad6_ip6_config);
 	dhcp6_cleanup (self, cleanup_type, FALSE);
 	linklocal6_cleanup (self);
 	addrconf6_cleanup (self);
@@ -7891,7 +8377,7 @@ nm_device_steal_connection (NMDevice *self, NMSettingsConnection *connection)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGW (LOGD_DEVICE, "disconnecting connection '%s' for new activation request.",
+	_LOGI (LOGD_DEVICE, "disconnecting connection '%s' for new activation request",
 	       nm_settings_connection_get_id (connection));
 
 	if (   priv->queued_act_request
@@ -7970,7 +8456,7 @@ nm_device_get_dhcp4_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->dhcp4_config;
+	return NM_DEVICE_GET_PRIVATE (self)->dhcp4.config;
 }
 
 NMIP4Config *
@@ -8287,7 +8773,7 @@ nm_device_get_dhcp6_config (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->dhcp6_config;
+	return NM_DEVICE_GET_PRIVATE (self)->dhcp6.config;
 }
 
 NMIP6Config *
@@ -8595,6 +9081,7 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean device_is_up = FALSE;
+	NMDeviceCapabilities capabilities;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
@@ -8605,6 +9092,10 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 			return FALSE;
 	}
 
+	/* Store carrier immediately. */
+	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT))
+		check_carrier (self);
+
 	device_is_up = nm_device_is_up (self);
 	if (block && !device_is_up) {
 		int ifindex = nm_device_get_ip_ifindex (self);
@@ -8626,6 +9117,13 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 		return FALSE;
 	}
 
+	/* some ethernet devices fail to report capabilities unless the device
+	 * is up. Re-read the capabilities. */
+	capabilities = 0;
+	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
+		capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
+	_add_capabilities (self, capabilities);
+
 	/* Devices that support carrier detect must be IFF_UP to report carrier
 	 * changes; so after setting the device IFF_UP we must suppress startup
 	 * complete (via a pending action) until either the carrier turns on, or
@@ -8669,10 +9167,6 @@ bring_up (NMDevice *self, gboolean *no_firmware)
 
 	result = nm_platform_link_set_up (NM_PLATFORM_GET, ifindex, no_firmware);
 
-	/* Store carrier immediately. */
-	if (result && nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT))
-		check_carrier (self);
-
 	return result;
 }
 
@@ -8808,7 +9302,7 @@ capture_lease_config (NMDevice *self,
                       NMIP6Config **out_ip6_config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const GSList *connections, *citer;
+	NMSettingsConnection *const*connections;
 	guint i;
 	gboolean dhcp_used = FALSE;
 
@@ -8841,9 +9335,9 @@ capture_lease_config (NMDevice *self,
 	if (!dhcp_used)
 		return;
 
-	connections = nm_connection_provider_get_connections (priv->con_provider);
-	for (citer = connections; citer; citer = citer->next) {
-		NMConnection *candidate = citer->data;
+	connections = nm_settings_get_connections (priv->settings, NULL);
+	for (i = 0; connections[i]; i++) {
+		NMConnection *candidate = (NMConnection *) connections[i];
 		const char *method;
 
 		if (!nm_device_check_connection_compatible (self, candidate))
@@ -9016,8 +9510,8 @@ update_ip6_config (NMDevice *self, gboolean initial)
 			nm_ip6_config_intersect (priv->con_ip6_config, priv->ext_ip6_config);
 		if (priv->ac_ip6_config)
 			nm_ip6_config_intersect (priv->ac_ip6_config, priv->ext_ip6_config);
-		if (priv->dhcp6_ip6_config)
-			nm_ip6_config_intersect (priv->dhcp6_ip6_config, priv->ext_ip6_config);
+		if (priv->dhcp6.ip6_config)
+			nm_ip6_config_intersect (priv->dhcp6.ip6_config, priv->ext_ip6_config);
 		if (priv->wwan_ip6_config)
 			nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config);
 		g_slist_foreach (priv->vpn6_configs, _ip6_config_intersect, priv->ext_ip6_config);
@@ -9029,8 +9523,8 @@ update_ip6_config (NMDevice *self, gboolean initial)
 			nm_ip6_config_subtract (priv->ext_ip6_config, priv->con_ip6_config);
 		if (priv->ac_ip6_config)
 			nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config);
-		if (priv->dhcp6_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6_ip6_config);
+		if (priv->dhcp6.ip6_config)
+			nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6.ip6_config);
 		if (priv->wwan_ip6_config)
 			nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config);
 		g_slist_foreach (priv->vpn6_configs, _ip6_config_subtract, priv->ext_ip6_config);
@@ -9070,9 +9564,7 @@ queued_ip4_config_change (gpointer user_data)
 		return TRUE;
 
 	priv->queued_ip4_config_id = 0;
-	g_object_ref (self);
 	update_ip4_config (self, FALSE);
-	g_object_unref (self);
 
 	set_unmanaged_external_down (self, TRUE);
 
@@ -9096,16 +9588,15 @@ queued_ip6_config_change (gpointer user_data)
 		return TRUE;
 
 	priv->queued_ip6_config_id = 0;
-	g_object_ref (self);
 	update_ip6_config (self, FALSE);
 
-	if (   priv->state < NM_DEVICE_STATE_DEACTIVATING
+	if (priv->state < NM_DEVICE_STATE_DEACTIVATING
 	    && nm_platform_link_get (NM_PLATFORM_GET, priv->ifindex)) {
 		/* Handle DAD failures */
 		for (iter = priv->dad6_failed_addrs; iter; iter = g_slist_next (iter)) {
 			NMPlatformIP6Address *addr = iter->data;
 
-			if (addr->source >= NM_IP_CONFIG_SOURCE_USER)
+			if (addr->addr_source >= NM_IP_CONFIG_SOURCE_USER)
 				continue;
 
 			_LOGI (LOGD_IP6, "ipv6: duplicate address check failed for the %s address",
@@ -9132,7 +9623,18 @@ queued_ip6_config_change (gpointer user_data)
 	g_slist_free_full (priv->dad6_failed_addrs, g_free);
 	priv->dad6_failed_addrs = NULL;
 
-	g_object_unref (self);
+	/* Check if DAD is still pending */
+	if (   priv->ip6_state == IP_CONF
+	    && priv->dad6_ip6_config
+	    && priv->ext_ip6_config_captured) {
+		if (!nm_ip6_config_has_any_dad_pending (priv->ext_ip6_config_captured,
+		                                        priv->dad6_ip6_config)) {
+			_LOGD (LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
+			g_clear_object (&priv->dad6_ip6_config);
+			priv->ip6_state = IP_DONE;
+			check_ip_done (self);
+		}
+	}
 
 	set_unmanaged_external_down (self, TRUE);
 
@@ -9256,7 +9758,7 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 	 * Some flags are authoritative, meaning they always cause
 	 * the device to be unmanaged (e.g. @NM_UNMANAGED_PLATFORM_INIT).
 	 *
-	 * OTOH, some flags can be overwritten. For example NM_UNMANAGED_USER_SETTINGS
+	 * OTOH, some flags can be overwritten. For example NM_UNMANAGED_USER_UDEV
 	 * is ignored once NM_UNMANAGED_USER_EXPLICIT is set. The idea is that
 	 * the flag from the configuration has no effect once the user explicitly
 	 * touches the unmanaged flags. */
@@ -9290,6 +9792,9 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 		/* configuration from udev or nm-config overwrites the by-default flag
 		 * which is based on the device type. */
 		flags &= ~NM_UNMANAGED_BY_DEFAULT;
+
+		/* configuration from udev overwrites external-down */
+		flags &= ~NM_UNMANAGED_EXTERNAL_DOWN;
 	}
 
 	if (   NM_FLAGS_HAS (mask, NM_UNMANAGED_IS_SLAVE)
@@ -9528,7 +10033,7 @@ nm_device_set_unmanaged_by_flags_queue (NMDevice *self,
 }
 
 void
-nm_device_set_unmanaged_by_user_config (NMDevice *self, const GSList *unmanaged_specs)
+nm_device_set_unmanaged_by_user_settings (NMDevice *self, const GSList *unmanaged_specs)
 {
 	NMDevicePrivate *priv;
 	gboolean unmanaged;
@@ -9880,50 +10385,45 @@ void
 nm_device_recheck_available_connections (NMDevice *self)
 {
 	NMDevicePrivate *priv;
-	const GSList *connections, *iter;
+	NMSettingsConnection *const*connections;
 	gboolean changed = FALSE;
 	GHashTableIter h_iter;
 	NMConnection *connection;
+	guint i;
+	gs_unref_hashtable GHashTable *prune_list = NULL;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	priv = NM_DEVICE_GET_PRIVATE(self);
 
-	if (priv->con_provider) {
-		gs_unref_hashtable GHashTable *prune_list = NULL;
-
-		if (g_hash_table_size (priv->available_connections) > 0) {
-			prune_list = g_hash_table_new (g_direct_hash, g_direct_equal);
-			g_hash_table_iter_init (&h_iter, priv->available_connections);
-			while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL))
-				g_hash_table_add (prune_list, connection);
-		}
-
-		connections = nm_connection_provider_get_connections (priv->con_provider);
-		for (iter = connections; iter; iter = g_slist_next (iter)) {
-			connection = NM_CONNECTION (iter->data);
-
-			if (nm_device_check_connection_available (self,
-			                                          connection,
-			                                          NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
-			                                          NULL)) {
-				if (available_connections_add (self, connection))
-					changed = TRUE;
-				if (prune_list)
-					g_hash_table_remove (prune_list, connection);
-			}
+	if (g_hash_table_size (priv->available_connections) > 0) {
+		prune_list = g_hash_table_new (g_direct_hash, g_direct_equal);
+		g_hash_table_iter_init (&h_iter, priv->available_connections);
+		while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL))
+			g_hash_table_add (prune_list, connection);
+	}
+
+	connections = nm_settings_get_connections (priv->settings, NULL);
+	for (i = 0; connections[i]; i++) {
+		connection = (NMConnection *) connections[i];
+
+		if (nm_device_check_connection_available (self,
+		                                          connection,
+		                                          NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
+		                                          NULL)) {
+			if (available_connections_add (self, connection))
+				changed = TRUE;
+			if (prune_list)
+				g_hash_table_remove (prune_list, connection);
 		}
+	}
 
-		if (prune_list) {
-			g_hash_table_iter_init (&h_iter, prune_list);
-			while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL)) {
-				if (available_connections_del (self, connection))
-					changed = TRUE;
-			}
+	if (prune_list) {
+		g_hash_table_iter_init (&h_iter, prune_list);
+		while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL)) {
+			if (available_connections_del (self, connection))
+				changed = TRUE;
 		}
-	} else {
-		if (available_connections_del_all (self))
-			changed = TRUE;
 	}
 
 	if (changed)
@@ -9984,10 +10484,9 @@ nm_device_get_best_connection (NMDevice *self,
 }
 
 static void
-cp_connection_added_or_updated (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+cp_connection_added_or_updated (NMDevice *self, NMConnection *connection)
 {
 	gboolean changed;
-	NMDevice *self = user_data;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
@@ -10007,6 +10506,18 @@ cp_connection_added_or_updated (NMConnectionProvider *cp, NMConnection *connecti
 }
 
 static void
+cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
+{
+	cp_connection_added_or_updated (user_data, connection);
+}
+
+static void
+cp_connection_updated (NMConnectionProvider *cp, NMConnection *connection, gboolean by_user, gpointer user_data)
+{
+	cp_connection_added_or_updated (user_data, connection);
+}
+
+static void
 cp_connection_removed (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data)
 {
 	NMDevice *self = user_data;
@@ -10213,6 +10724,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	g_clear_object (&priv->ext_ip6_config_captured);
 	g_clear_object (&priv->wwan_ip6_config);
 	g_clear_object (&priv->ip6_config);
+	g_clear_object (&priv->dad6_ip6_config);
 
 	g_slist_free_full (priv->vpn4_configs, g_object_unref);
 	priv->vpn4_configs = NULL;
@@ -10276,44 +10788,48 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	if (NM_DEVICE_GET_CLASS (self)->deactivate)
 		NM_DEVICE_GET_CLASS (self)->deactivate (self);
 
-	/* master: release slaves */
-	nm_device_master_release_slaves (self);
+	if (cleanup_type != CLEANUP_TYPE_KEEP) {
+		/* master: release slaves */
+		nm_device_master_release_slaves (self);
 
-	/* slave: mark no longer enslaved */
-	if (   priv->master
-	    && nm_platform_link_get_master (NM_PLATFORM_GET, priv->ifindex) <= 0)
-		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+		/* slave: mark no longer enslaved */
+		if (   priv->master
+		    && nm_platform_link_get_master (NM_PLATFORM_GET, priv->ifindex) <= 0)
+			nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 
-	/* Take out any entries in the routing table and any IP address the device had. */
-	ifindex = nm_device_get_ip_ifindex (self);
-	if (ifindex > 0) {
-		nm_route_manager_route_flush (nm_route_manager_get (), ifindex);
-		nm_platform_address_flush (NM_PLATFORM_GET, ifindex);
+		/* Take out any entries in the routing table and any IP address the device had. */
+		ifindex = nm_device_get_ip_ifindex (self);
+		if (ifindex > 0) {
+			nm_route_manager_route_flush (nm_route_manager_get (), ifindex);
+			nm_platform_address_flush (NM_PLATFORM_GET, ifindex);
+		}
 	}
 
 	if (priv->lldp_listener)
 		nm_lldp_listener_stop (priv->lldp_listener);
 
 	nm_device_update_metered (self);
+
+	/* during device cleanup, we want to reset the MAC address of the device
+	 * to the initial state.
+	 *
+	 * We certainly want to do that when reaching the UNMANAGED state... */
+	if (nm_device_get_state (self) <= NM_DEVICE_STATE_UNMANAGED)
+		nm_device_hw_addr_reset (self, "unmanage");
+	else {
+		/* for other device states (UNAVAILABLE, DISCONNECTED), allow the
+		 * device to overwrite the reset behavior, so that Wi-Fi can set
+		 * a randomized MAC address used during scanning. */
+		NM_DEVICE_GET_CLASS (self)->deactivate_reset_hw_addr (self);
+	}
+
 	_cleanup_generic_post (self, cleanup_type);
 }
 
-static char *
-bin2hexstr (const char *bytes, gsize len)
+static void
+deactivate_reset_hw_addr (NMDevice *self)
 {
-	GString *str;
-	int i;
-
-	g_return_val_if_fail (bytes != NULL, NULL);
-	g_return_val_if_fail (len > 0, NULL);
-
-	str = g_string_sized_new (len * 2 + 1);
-	for (i = 0; i < len; i++) {
-		if (str->len)
-			g_string_append_c (str, ':');
-		g_string_append_printf (str, "%02x", (guint8) bytes[i]);
-	}
-	return g_string_free (str, FALSE);
+	nm_device_hw_addr_reset (self, "deactivate");
 }
 
 static char *
@@ -10329,7 +10845,7 @@ find_dhcp4_address (NMDevice *self)
 	for (i = 0; i < n; i++) {
 		const NMPlatformIP4Address *a = nm_ip4_config_get_address (priv->ip4_config, i);
 
-		if (a->source == NM_IP_CONFIG_SOURCE_DHCP)
+		if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
 			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
 	}
 	return NULL;
@@ -10346,6 +10862,8 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	GPtrArray *argv;
 	gs_free char *dhcp4_address = NULL;
 	char *logging_backend;
+	NMUtilsStableType stable_type;
+	const char *stable_id;
 
 	if (priv->state != NM_DEVICE_STATE_ACTIVATED)
 		return;
@@ -10364,6 +10882,12 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	g_ptr_array_add (argv, g_strdup ("--uuid"));
 	g_ptr_array_add (argv, g_strdup (nm_connection_get_uuid (connection)));
 
+	stable_id = _get_stable_id (connection, &stable_type);
+	if (stable_id && 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));
+	}
+
 	logging_backend = nm_config_get_is_debug (nm_config_get ())
 	                  ? g_strdup ("debug")
 	                  : nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
@@ -10380,7 +10904,6 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
 	if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
 		NMSettingIPConfig *s_ip4;
-		char *hex_client_id;
 
 		s_ip4 = nm_connection_get_setting_ip4_config (connection);
 		g_assert (s_ip4);
@@ -10393,25 +10916,26 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		if (nm_setting_ip_config_get_may_fail (s_ip4) == FALSE)
 			g_ptr_array_add (argv, g_strdup ("--dhcp4-required"));
 
-		if (priv->dhcp4_client) {
+		if (priv->dhcp4.client) {
 			const char *hostname, *fqdn;
 			GBytes *client_id;
 
-			client_id = nm_dhcp_client_get_client_id (priv->dhcp4_client);
+			client_id = nm_dhcp_client_get_client_id (priv->dhcp4.client);
 			if (client_id) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
-				hex_client_id = bin2hexstr (g_bytes_get_data (client_id, NULL),
-				                            g_bytes_get_size (client_id));
-				g_ptr_array_add (argv, hex_client_id);
+				g_ptr_array_add (argv,
+				                 _nm_utils_bin2str (g_bytes_get_data (client_id, NULL),
+				                                    g_bytes_get_size (client_id),
+				                                    FALSE));
 			}
 
-			hostname = nm_dhcp_client_get_hostname (priv->dhcp4_client);
+			hostname = nm_dhcp_client_get_hostname (priv->dhcp4.client);
 			if (hostname) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-hostname"));
 				g_ptr_array_add (argv, g_strdup (hostname));
 			}
 
-			fqdn = nm_dhcp_client_get_fqdn (priv->dhcp4_client);
+			fqdn = nm_dhcp_client_get_fqdn (priv->dhcp4.client);
 			if (fqdn) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-fqdn"));
 				g_ptr_array_add (argv, g_strdup (fqdn));
@@ -10424,7 +10948,6 @@ nm_device_spawn_iface_helper (NMDevice *self)
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 	if (g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0) {
 		NMSettingIPConfig *s_ip6;
-		char *hex_iid;
 		NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
 
 		s_ip6 = nm_connection_get_setting_ip6_config (connection);
@@ -10441,10 +10964,12 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		g_ptr_array_add (argv, g_strdup ("--slaac-tempaddr"));
 		g_ptr_array_add (argv, g_strdup_printf ("%d", priv->rdisc_use_tempaddr));
 
-		if (nm_device_get_ip_iface_identifier (self, &iid)) {
+		if (nm_device_get_ip_iface_identifier (self, &iid, FALSE)) {
 			g_ptr_array_add (argv, g_strdup ("--iid"));
-			hex_iid = bin2hexstr ((const char *) iid.id_u8, sizeof (NMUtilsIPv6IfaceId));
-			g_ptr_array_add (argv, hex_iid);
+			g_ptr_array_add (argv,
+			                 _nm_utils_bin2str (iid.id_u8,
+			                                    sizeof (NMUtilsIPv6IfaceId),
+			                                    FALSE));
 		}
 
 		g_ptr_array_add (argv, g_strdup ("--addr-gen-mode"));
@@ -10654,11 +11179,14 @@ _set_state_full (NMDevice *self,
 		if (old_state > NM_DEVICE_STATE_UNMANAGED) {
 			if (reason == NM_DEVICE_STATE_REASON_REMOVED) {
 				nm_device_cleanup (self, reason, CLEANUP_TYPE_REMOVED);
+			} else if (reason == NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED) {
+				nm_device_cleanup (self, reason, CLEANUP_TYPE_KEEP);
 			} else {
 				/* Clean up if the device is now unmanaged but was activated */
 				if (nm_device_get_act_request (self))
 					nm_device_cleanup (self, reason, CLEANUP_TYPE_DECONFIGURE);
 				nm_device_take_down (self, TRUE);
+				nm_device_hw_addr_reset (self, "unmanage");
 				set_nm_ipv6ll (self, FALSE);
 				restore_ip6_properties (self);
 			}
@@ -10706,6 +11234,9 @@ _set_state_full (NMDevice *self,
 			}
 		}
 		break;
+	case NM_DEVICE_STATE_PREPARE:
+		nm_device_update_initial_hw_address (self);
+		break;
 	case NM_DEVICE_STATE_NEED_AUTH:
 		if (old_state > NM_DEVICE_STATE_NEED_AUTH) {
 			/* Clean up any half-done IP operations if the device's layer2
@@ -10726,7 +11257,7 @@ _set_state_full (NMDevice *self,
 
 	_notify (self, PROP_STATE);
 	_notify (self, PROP_STATE_REASON);
-	g_signal_emit_by_name (self, NM_DEVICE_STATE_CHANGED, state, old_state, reason);
+	g_signal_emit (self, signals[STATE_CHANGED], 0, state, old_state, reason);
 
 	/* Post-process the event after internal notification */
 
@@ -10749,11 +11280,9 @@ _set_state_full (NMDevice *self,
 	case NM_DEVICE_STATE_DEACTIVATING:
 		_cancel_activation (self);
 
-		if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
-			/* We cache the ignore_carrier state to not react on config-reloads while the connection
-			 * is active. But on deactivating, reset the ignore-carrier flag to the current state. */
-			priv->ignore_carrier = nm_config_data_get_ignore_carrier (NM_CONFIG_GET_DATA, self);
-		}
+		/* We cache the ignore_carrier state to not react on config-reloads while the connection
+		 * is active. But on deactivating, reset the ignore-carrier flag to the current state. */
+		priv->ignore_carrier = nm_config_data_get_ignore_carrier (NM_CONFIG_GET_DATA, self);
 
 		if (quitting) {
 			nm_dispatcher_call_sync (DISPATCHER_ACTION_PRE_DOWN,
@@ -11026,25 +11555,27 @@ nm_device_get_hw_address (NMDevice *self)
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->hw_addr_len ? priv->hw_addr : NULL;
+	nm_assert ((!priv->hw_addr) ^ (priv->hw_addr_len > 0));
+
+	return priv->hw_addr;
 }
 
 void
 nm_device_update_hw_address (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex = nm_device_get_ifindex (self);
+	NMDevicePrivate *priv;
 	const guint8 *hwaddr;
 	gsize hwaddrlen = 0;
-	static const guint8 zero_hwaddr[ETH_ALEN];
 
-	if (ifindex <= 0)
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (priv->ifindex <= 0)
 		return;
 
-	hwaddr = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &hwaddrlen);
+	hwaddr = nm_platform_link_get_address (NM_PLATFORM_GET, priv->ifindex, &hwaddrlen);
 
 	if (   priv->type == NM_DEVICE_TYPE_ETHERNET
-	    && nm_utils_hwaddr_matches (hwaddr, hwaddrlen, zero_hwaddr, sizeof (zero_hwaddr)))
+	    && hwaddr
+	    && nm_utils_hwaddr_matches (hwaddr, hwaddrlen, nm_ip_addr_zero.addr_eth, sizeof (nm_ip_addr_zero.addr_eth)))
 		hwaddrlen = 0;
 
 	if (hwaddrlen) {
@@ -11053,17 +11584,28 @@ nm_device_update_hw_address (NMDevice *self)
 			g_free (priv->hw_addr);
 			priv->hw_addr = nm_utils_hwaddr_ntoa (hwaddr, hwaddrlen);
 
-			_LOGD (LOGD_HW | LOGD_DEVICE, "hardware address now %s", priv->hw_addr);
+			_LOGD (LOGD_HW | LOGD_DEVICE, "hw-addr: hardware address now %s", priv->hw_addr);
 			_notify (self, PROP_HW_ADDRESS);
+
+			if (   !priv->hw_addr_initial
+			    || (   priv->hw_addr_type == HW_ADDR_TYPE_UNSET
+			        && priv->state < NM_DEVICE_STATE_PREPARE
+			        && !nm_device_is_activating (self))) {
+				/* when we get a hw_addr the first time or while the device
+				 * is not activated (with no explict hw address set), always
+				 * update our inital hw-address as well. */
+				nm_device_update_initial_hw_address (self);
+			}
 		}
 	} else {
 		/* Invalid or no hardware address */
 		if (priv->hw_addr_len != 0) {
-			g_clear_pointer (&priv->hw_addr, g_free);
-			priv->hw_addr_len = 0;
 			_LOGD (LOGD_HW | LOGD_DEVICE,
-			       "previous hardware address is no longer valid");
-			_notify (self, PROP_HW_ADDRESS);
+			       "hw-addr: failed reading current MAC address (stay with %s)",
+			       priv->hw_addr);
+		} else {
+			_LOGD (LOGD_HW | LOGD_DEVICE,
+			       "hw-addr: failed reading current MAC address");
 		}
 	}
 }
@@ -11073,84 +11615,365 @@ nm_device_update_initial_hw_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->hw_addr_len) {
-		priv->initial_hw_addr = g_strdup (priv->hw_addr);
-		_LOGD (LOGD_DEVICE | LOGD_HW, "read initial MAC address %s", priv->initial_hw_addr);
+	if (   priv->hw_addr
+	    && !nm_streq0 (priv->hw_addr_initial, priv->hw_addr)) {
+		if (   priv->hw_addr_initial
+		    && priv->hw_addr_type != HW_ADDR_TYPE_UNSET) {
+			/* once we have the initial hw address set, we only allow
+			 * update if the currenty type is "unset". */
+			return;
+		}
+		g_free (priv->hw_addr_initial);
+		priv->hw_addr_initial = g_strdup (priv->hw_addr);
+		_LOGD (LOGD_DEVICE, "hw-addr: update initial MAC address %s",
+		       priv->hw_addr_initial);
+	}
+}
 
-		if (priv->ifindex > 0) {
-			guint8 buf[NM_UTILS_HWADDR_LEN_MAX];
-			size_t len = 0;
+void
+nm_device_update_permanent_hw_address (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint8 buf[NM_UTILS_HWADDR_LEN_MAX];
+	size_t len = 0;
+	gboolean success_read;
 
-			if (nm_platform_link_get_permanent_address (NM_PLATFORM_GET, priv->ifindex, buf, &len)) {
-				g_warn_if_fail (len == priv->hw_addr_len);
-				priv->perm_hw_addr = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
-				_LOGD (LOGD_DEVICE | LOGD_HW, "read permanent MAC address %s",
-				       priv->perm_hw_addr);
-			} else {
-				/* Fall back to current address */
-				_LOGD (LOGD_HW | LOGD_ETHER, "unable to read permanent MAC address");
-				priv->perm_hw_addr = g_strdup (priv->hw_addr);
+	if (priv->hw_addr_perm) {
+		/* the permanent hardware address is only read once and not
+		 * re-read later.
+		 *
+		 * Except during unrealize/realize cycles, where we clear the permanent
+		 * hardware address during unrealization. */
+		return;
+	}
+
+	if (priv->ifindex <= 0)
+		return;
+
+	if (!priv->hw_addr_len) {
+		nm_device_update_hw_address (self);
+		if (!priv->hw_addr_len)
+			return;
+	}
+
+	success_read = nm_platform_link_get_permanent_address (NM_PLATFORM_GET, priv->ifindex, buf, &len);
+	if (!success_read || len != priv->hw_addr_len) {
+		/* Fall back to current address. We use the fake address and keep it
+		 * until the device unrealizes.
+		 *
+		 * In some cases it might be necessary to know whether this is a "real" or
+		 * a temporary address (fake). */
+		_LOGD (LOGD_HW | LOGD_ETHER, "hw-addr: %s (use current: %s)",
+		       success_read
+		           ? "read HW addr length of permanent MAC address differs"
+		           : "unable to read permanent MAC address",
+		       priv->hw_addr);
+		priv->hw_addr_perm_fake = TRUE;
+		priv->hw_addr_perm = g_strdup (priv->hw_addr);
+	} else {
+		priv->hw_addr_perm_fake = FALSE;
+		priv->hw_addr_perm = nm_utils_hwaddr_ntoa (buf, len);
+		_LOGD (LOGD_DEVICE, "hw-addr: read permanent MAC address '%s'",
+		       priv->hw_addr_perm);
+	}
+	_notify (self, PROP_PERM_HW_ADDRESS);
+}
+
+static const char *
+_get_cloned_mac_address_setting (NMDevice *self, NMConnection *connection, gboolean is_wifi, char **out_addr)
+{
+	NMSetting *setting;
+	const char *addr = NULL;
+
+	nm_assert (out_addr && !*out_addr);
+
+	setting = nm_connection_get_setting (connection,
+	                                     is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
+	if (setting) {
+		addr = is_wifi
+		       ? nm_setting_wireless_get_cloned_mac_address ((NMSettingWireless *) setting)
+		       : nm_setting_wired_get_cloned_mac_address ((NMSettingWired *) setting);
+	}
+
+	if (!addr) {
+		gs_free char *a = NULL;
+
+		a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+		                                           is_wifi ? "wifi.cloned-mac-address" : "ethernet.cloned-mac-address",
+		                                           self);
+		/* default is permanent. */
+		addr = NM_CLONED_MAC_PERMANENT;
+
+		if (!a) {
+			if (is_wifi) {
+				NMSettingMacRandomization v;
+
+				/* for backward compatibility, read the deprecated wifi.mac-address-randomization setting. */
+				a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+				                                           "wifi." NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
+				                                           self);
+				v = _nm_utils_ascii_str_to_int64 (a, 10,
+				                                  NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
+				                                  NM_SETTING_MAC_RANDOMIZATION_ALWAYS,
+				                                  NM_SETTING_MAC_RANDOMIZATION_DEFAULT);
+				if (v == NM_SETTING_MAC_RANDOMIZATION_ALWAYS)
+					addr = NM_CLONED_MAC_RANDOM;
 			}
-		}
+		} else if (   NM_CLONED_MAC_IS_SPECIAL (a)
+		           || nm_utils_hwaddr_valid (a, ETH_ALEN))
+			addr = *out_addr = g_steal_pointer (&a);
 	}
+
+	return addr;
+}
+
+static const char *
+_get_generate_mac_address_mask_setting (NMDevice *self, NMConnection *connection, gboolean is_wifi, char **out_value)
+{
+	NMSetting *setting;
+	const char *value = NULL;
+	char *a;
+
+	nm_assert (out_value && !*out_value);
+
+	setting = nm_connection_get_setting (connection,
+	                                     is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
+	if (setting) {
+		value = is_wifi
+		        ? nm_setting_wireless_get_generate_mac_address_mask ((NMSettingWireless *) setting)
+		        : nm_setting_wired_get_generate_mac_address_mask ((NMSettingWired *) setting);
+		if (value)
+			return value;
+	}
+
+	a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+	                                           is_wifi ? "wifi.generate-mac-address-mask" : "ethernet.generate-mac-mac-address-mask",
+	                                           self);
+	if (!a)
+		return NULL;
+	*out_value = a;
+	return a;
 }
 
 gboolean
-nm_device_set_hw_addr (NMDevice *self, const char *addr,
-                       const char *detail, guint64 hw_log_domain)
+nm_device_hw_addr_is_explict (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	return !NM_IN_SET (priv->hw_addr_type, HW_ADDR_TYPE_PERMANENT, HW_ADDR_TYPE_UNSET);
+}
+
+static gboolean
+_hw_addr_set (NMDevice *self,
+              const char *addr,
+              const char *operation,
+              const char *detail)
+{
+	NMDevicePrivate *priv;
 	gboolean success = FALSE;
-	const char *cur_addr = nm_device_get_hw_address (self);
+	NMPlatformError plerr;
+	const char *cur_addr;
 	guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
+	guint hw_addr_len;
+	gboolean was_up;
 
-	/* Fall back to the permanent address */
-	if (!addr)
-		addr = priv->perm_hw_addr;
-	if (!addr)
-		return FALSE;
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert (addr);
+	nm_assert (operation);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	cur_addr = nm_device_get_hw_address (self);
 
 	/* Do nothing if current MAC is same */
 	if (cur_addr && nm_utils_hwaddr_matches (cur_addr, -1, addr, -1)) {
-		_LOGD (LOGD_DEVICE | hw_log_domain, "no MAC address change needed");
+		_LOGT (LOGD_DEVICE, "set-hw-addr: no MAC address change needed (%s)", cur_addr);
 		return TRUE;
 	}
-	if (!nm_utils_hwaddr_aton (addr, addr_bytes, priv->hw_addr_len)) {
-		_LOGW (LOGD_DEVICE | hw_log_domain, "invalid MAC address %s", addr);
-		return FALSE;
-	}
 
-	/* Can't change MAC address while device is up */
-	nm_device_take_down (self, FALSE);
+	hw_addr_len = priv->hw_addr_len;
+	if (!hw_addr_len)
+		hw_addr_len = _nm_utils_hwaddr_length (addr);
+	if (   !hw_addr_len
+	    || !nm_utils_hwaddr_aton (addr, addr_bytes, hw_addr_len))
+		g_return_val_if_reached (FALSE);
 
-	success = nm_platform_link_set_address (NM_PLATFORM_GET, nm_device_get_ip_ifindex (self), addr_bytes, priv->hw_addr_len);
+	_LOGT (LOGD_DEVICE, "set-hw-addr: setting MAC address to '%s' (%s, %s)...", addr, operation, detail);
+
+	was_up = nm_device_is_up (self);
+	if (was_up) {
+		/* Can't change MAC address while device is up */
+		nm_device_take_down (self, FALSE);
+	}
+
+	plerr = nm_platform_link_set_address (NM_PLATFORM_GET, nm_device_get_ip_ifindex (self), addr_bytes, hw_addr_len);
+	success = (plerr == NM_PLATFORM_ERROR_SUCCESS);
 	if (success) {
 		/* MAC address succesfully changed; update the current MAC to match */
 		nm_device_update_hw_address (self);
 		cur_addr = nm_device_get_hw_address (self);
 		if (cur_addr && nm_utils_hwaddr_matches (cur_addr, -1, addr, -1)) {
-			_LOGI (LOGD_DEVICE | hw_log_domain, "%s MAC address to %s",
-			       detail, addr);
+			_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
+			       operation, addr, detail);
 		} else {
-			_LOGW (LOGD_DEVICE | hw_log_domain,
-			       "new MAC address %s not successfully set", addr);
+			_LOGW (LOGD_DEVICE,
+			       "set-hw-addr: new MAC address %s not successfully set to %s (%s)",
+			       addr, operation, detail);
 			success = FALSE;
 		}
 	} else {
-		_LOGW (LOGD_DEVICE | hw_log_domain, "failed to %s MAC address to %s",
-		       detail, addr);
+		_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
+		        LOGD_DEVICE, "set-hw-addr: failed to %s MAC address to %s (%s) (%s)",
+		        operation, addr, detail,
+		        nm_platform_error_to_string (plerr));
+	}
+
+	if (was_up) {
+		if (!nm_device_bring_up (self, TRUE, NULL))
+			return FALSE;
 	}
-	nm_device_bring_up (self, TRUE, NULL);
 
 	return success;
 }
 
+gboolean
+nm_device_hw_addr_set (NMDevice *self, const char *addr, const char *detail)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!addr)
+		g_return_val_if_reached (FALSE);
+
+	/* this is called by NMDeviceVlan to take the MAC address from the parent
+	 * and by NMDeviceWifi to set a random MAC address during scanning.
+	 * In this case, it's like setting it to PERMANENT. */
+	priv->hw_addr_type = HW_ADDR_TYPE_PERMANENT;
+
+	return _hw_addr_set (self, addr, "set", detail);
+}
+
+gboolean
+nm_device_hw_addr_set_cloned (NMDevice *self, NMConnection *connection, gboolean is_wifi)
+{
+	NMDevicePrivate *priv;
+	gs_free char *hw_addr_tmp = NULL;
+	gs_free char *hw_addr_generated = NULL;
+	gs_free char *generate_mac_address_mask_tmp = NULL;
+	const char *addr, *addr_setting;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!connection)
+		g_return_val_if_reached (FALSE);
+
+	addr = addr_setting = _get_cloned_mac_address_setting (self, connection, is_wifi, &hw_addr_tmp);
+
+	if (nm_streq (addr, NM_CLONED_MAC_PRESERVE)) {
+		/* "preserve" means to reset the initial MAC address. */
+		return nm_device_hw_addr_reset (self, addr_setting);
+	}
+
+	if (nm_streq (addr, NM_CLONED_MAC_PERMANENT)) {
+		addr = nm_device_get_permanent_hw_address (self, TRUE);
+		if (!addr)
+			return FALSE;
+		priv->hw_addr_type = HW_ADDR_TYPE_PERMANENT;
+	} else if (NM_IN_STRSET (addr, NM_CLONED_MAC_RANDOM)) {
+		if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) {
+			/* hm, we already use a generate MAC address. Most certainly, that is from the same
+			 * activation request, so we should not create a new random address, instead keep
+			 * the current. */
+			return TRUE;
+		}
+		hw_addr_generated = nm_utils_hw_addr_gen_random_eth (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) {
+			_LOGW (LOGD_DEVICE, "set-hw-addr: failed to generate %s MAC address", "random");
+			return FALSE;
+		}
+		priv->hw_addr_type = HW_ADDR_TYPE_GENERATED;
+		addr = hw_addr_generated;
+	} else if (NM_IN_STRSET (addr, NM_CLONED_MAC_STABLE)) {
+		NMUtilsStableType stable_type;
+		const char *stable_id;
+
+		if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) {
+			/* hm, we already use a generate MAC address. Most certainly, that is from the same
+			 * activation request, so let's skip creating the stable address anew. */
+			return TRUE;
+		}
+
+		stable_id = _get_stable_id (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));
+		}
+		if (!hw_addr_generated) {
+			_LOGW (LOGD_DEVICE, "set-hw-addr: failed to generate %s MAC address", "stable");
+			return FALSE;
+		}
+		priv->hw_addr_type = HW_ADDR_TYPE_GENERATED;
+		addr = hw_addr_generated;
+	} else {
+		/* this must be a valid address. Otherwise, we shouldn't come here. */
+		if (_nm_utils_hwaddr_length (addr) <= 0) {
+			g_return_val_if_reached (FALSE);
+		}
+		priv->hw_addr_type = HW_ADDR_TYPE_EXPLICIT;
+	}
+
+	return _hw_addr_set (self, addr, "set-cloned", addr_setting);
+}
+
+gboolean
+nm_device_hw_addr_reset (NMDevice *self, const char *detail)
+{
+	NMDevicePrivate *priv;
+	const char *addr;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->hw_addr_type == HW_ADDR_TYPE_UNSET)
+		return TRUE;
+
+	priv->hw_addr_type = HW_ADDR_TYPE_UNSET;
+	addr = nm_device_get_initial_hw_address (self);
+	if (!addr) {
+		/* as hw_addr_type is not UNSET, we expect that we can get an
+		 * initial address to which to reset. */
+		g_return_val_if_reached (FALSE);
+	}
+
+	return _hw_addr_set (self, addr, "reset", detail);
+}
+
 const char *
-nm_device_get_permanent_hw_address (NMDevice *self)
+nm_device_get_permanent_hw_address (NMDevice *self, gboolean fallback_fake)
 {
+	NMDevicePrivate *priv;
+
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->perm_hw_addr;
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (!priv->hw_addr_perm)
+		return NULL;
+	if (   priv->hw_addr_perm_fake
+	    && !fallback_fake)
+		return NULL;
+	return priv->hw_addr_perm;
 }
 
 const char *
@@ -11158,7 +11981,7 @@ nm_device_get_initial_hw_address (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->initial_hw_addr;
+	return NM_DEVICE_GET_PRIVATE (self)->hw_addr_initial;
 }
 
 /**
@@ -11196,9 +12019,9 @@ nm_device_spec_match_list (NMDevice *self, const GSList *specs)
 static NMMatchSpecMatchType
 spec_match_list (NMDevice *self, const GSList *specs)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMMatchSpecMatchType matched = NM_MATCH_SPEC_NO_MATCH, m;
 	const GSList *iter;
+	const char *hw_addr_perm;
 
 	for (iter = specs; iter; iter = g_slist_next (iter)) {
 		if (!strcmp ((const char *) iter->data, "*")) {
@@ -11206,8 +12029,10 @@ spec_match_list (NMDevice *self, const GSList *specs)
 			break;
 		}
 	}
-	if (priv->hw_addr_len && priv->hw_addr) {
-		m = nm_match_spec_hwaddr (specs, priv->hw_addr);
+
+	hw_addr_perm = nm_device_get_permanent_hw_address (self, FALSE);
+	if (hw_addr_perm) {
+		m = nm_match_spec_hwaddr (specs, hw_addr_perm);
 		matched = MAX (matched, m);
 	}
 	if (matched != NM_MATCH_SPEC_NEG_MATCH) {
@@ -11290,7 +12115,8 @@ constructor (GType type,
 	self = NM_DEVICE (object);
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->iface) {
+	if (   priv->iface
+	    && G_LIKELY (!nm_utils_get_testing ())) {
 		pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, priv->iface);
 
 		if (pllink && link_type_compatible (self, pllink->type, NULL, NULL)) {
@@ -11299,6 +12125,17 @@ constructor (GType type,
 		}
 	}
 
+	if (priv->hw_addr_perm) {
+		priv->hw_addr_len = _nm_utils_hwaddr_length (priv->hw_addr_perm);
+		if (!priv->hw_addr_len) {
+			g_clear_pointer (&priv->hw_addr_perm, g_free);
+			g_return_val_if_reached (object);
+		}
+
+		priv->hw_addr = g_strdup (priv->hw_addr_perm);
+		_LOGT (LOGD_DEVICE, "hw-addr: has permanent hw-address '%s'", priv->hw_addr_perm);
+	}
+
 	return object;
 }
 
@@ -11309,33 +12146,31 @@ constructed (GObject *object)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPlatform *platform;
 
-	platform = nm_platform_get ();
-
 	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
 		priv->capabilities |= NM_DEVICE_GET_CLASS (self)->get_generic_capabilities (self);
 
 	/* Watch for external IP config changes */
+	platform = NM_PLATFORM_GET;
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
 
-	priv->con_provider = nm_connection_provider_get ();
-	g_assert (priv->con_provider);
-	g_signal_connect (priv->con_provider,
-	                  NM_CP_SIGNAL_CONNECTION_ADDED,
-	                  G_CALLBACK (cp_connection_added_or_updated),
-	                  self);
+	priv->settings = g_object_ref (NM_SETTINGS_GET);
+	g_assert (priv->settings);
 
-	g_signal_connect (priv->con_provider,
-	                  NM_CP_SIGNAL_CONNECTION_REMOVED,
-	                  G_CALLBACK (cp_connection_removed),
+	g_signal_connect (priv->settings,
+	                  NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
+	                  G_CALLBACK (cp_connection_added),
 	                  self);
-
-	g_signal_connect (priv->con_provider,
-	                  NM_CP_SIGNAL_CONNECTION_UPDATED,
-	                  G_CALLBACK (cp_connection_added_or_updated),
+	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);
 
 	G_OBJECT_CLASS (nm_device_parent_class)->constructed (object);
@@ -11352,7 +12187,7 @@ dispose (GObject *object)
 
 	_LOGD (LOGD_DEVICE, "disposing");
 
-	platform = nm_platform_get ();
+	platform = NM_PLATFORM_GET;
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ipx_changed), self);
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self);
 
@@ -11361,7 +12196,7 @@ dispose (GObject *object)
 
 	arp_cleanup (self);
 
-	g_signal_handlers_disconnect_by_func (nm_config_get (), config_changed_update_ignore_carrier, self);
+	nm_clear_g_signal_handler (nm_config_get (), &priv->ignore_carrier_id);
 
 	dispatcher_cleanup (self);
 
@@ -11382,12 +12217,14 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->check_delete_unrealized_id);
 
+	nm_clear_g_source (&priv->stats.timeout_id);
+
 	link_disconnect_action_cancel (self);
 
-	if (priv->con_provider) {
-		g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_added_or_updated, self);
-		g_signal_handlers_disconnect_by_func (priv->con_provider, cp_connection_removed, self);
-		priv->con_provider = NULL;
+	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_updated, self);
+		g_signal_handlers_disconnect_by_func (priv->settings, cp_connection_removed, self);
 	}
 
 	available_connections_del_all (self);
@@ -11426,8 +12263,8 @@ finalize (GObject *object)
 	_LOGD (LOGD_DEVICE, "finalize(): %s", G_OBJECT_TYPE_NAME (self));
 
 	g_free (priv->hw_addr);
-	g_free (priv->perm_hw_addr);
-	g_free (priv->initial_hw_addr);
+	g_free (priv->hw_addr_perm);
+	g_free (priv->hw_addr_initial);
 	g_slist_free_full (priv->pending_actions, g_free);
 	g_slist_free_full (priv->dad6_failed_addrs, g_free);
 	g_clear_pointer (&priv->physical_port_id, g_free);
@@ -11445,16 +12282,19 @@ finalize (GObject *object)
 	g_hash_table_unref (priv->available_connections);
 
 	G_OBJECT_CLASS (nm_device_parent_class)->finalize (object);
+
+	/* for testing, NMDeviceTest does not invoke NMDevice::constructed,
+	 * and thus @settings might be unset. */
+	if (priv->settings)
+		g_object_unref (priv->settings);
 }
 
 static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDevice *self = NM_DEVICE (object);
+	NMDevice *self = (NMDevice *) object;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *hw_addr, *p;
-	guint count;
 
 	switch (prop_id) {
 	case PROP_UDI:
@@ -11476,11 +12316,11 @@ set_property (GObject *object, guint prop_id,
 		break;
 	case PROP_DRIVER_VERSION:
 		g_free (priv->driver_version);
-		priv->driver_version = g_strdup (g_value_get_string (value));
+		priv->driver_version = g_value_dup_string (value);
 		break;
 	case PROP_FIRMWARE_VERSION:
 		g_free (priv->firmware_version);
-		priv->firmware_version = g_strdup (g_value_get_string (value));
+		priv->firmware_version = g_value_dup_string (value);
 		break;
 	case PROP_MTU:
 		priv->mtu = g_value_get_uint (value);
@@ -11532,32 +12372,12 @@ set_property (GObject *object, guint prop_id,
 	case PROP_IS_MASTER:
 		priv->is_master = g_value_get_boolean (value);
 		break;
-	case PROP_HW_ADDRESS:
+	case PROP_PERM_HW_ADDRESS:
 		/* construct only */
-		p = hw_addr = g_value_get_string (value);
-
-		/* Hardware address length is the number of ':' plus 1 */
-		count = 1;
-		while (p && *p) {
-			if (*p++ == ':')
-				count++;
-		}
-		if (count < ETH_ALEN || count > NM_UTILS_HWADDR_LEN_MAX) {
-			if (hw_addr && *hw_addr) {
-				_LOGW (LOGD_DEVICE, "ignoring hardware address '%s' with unexpected length %d",
-				       hw_addr, count);
-			}
-			break;
-		}
-
-		priv->hw_addr_len = count;
-		g_free (priv->hw_addr);
-		if (nm_utils_hwaddr_valid (hw_addr, priv->hw_addr_len))
-			priv->hw_addr = g_strdup (hw_addr);
-		else {
-			_LOGW (LOGD_DEVICE, "could not parse hw-address '%s'", hw_addr);
-			priv->hw_addr = NULL;
-		}
+		priv->hw_addr_perm = g_value_dup_string (value);
+		break;
+	case PROP_REFRESH_RATE_MS:
+		_stats_set_refresh_rate (self, g_value_get_uint (value));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -11617,13 +12437,13 @@ get_property (GObject *object, guint prop_id,
 		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip4_config : NULL);
 		break;
 	case PROP_DHCP4_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4_config : NULL);
+		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4.config : NULL);
 		break;
 	case PROP_IP6_CONFIG:
 		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip6_config : NULL);
 		break;
 	case PROP_DHCP6_CONFIG:
-		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6_config : NULL);
+		nm_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6.config : NULL);
 		break;
 	case PROP_STATE:
 		g_value_set_uint (value, priv->state);
@@ -11680,6 +12500,10 @@ get_property (GObject *object, guint prop_id,
 	case PROP_HW_ADDRESS:
 		g_value_set_string (value, priv->hw_addr);
 		break;
+	case PROP_PERM_HW_ADDRESS:
+		/* this property is exposed on D-Bus for NMDeviceEthernet and NMDeviceWifi. */
+		g_value_set_string (value, nm_device_get_permanent_hw_address (self, FALSE));
+		break;
 	case PROP_HAS_PENDING_ACTION:
 		g_value_set_boolean (value, nm_device_has_pending_action (self));
 		break;
@@ -11717,6 +12541,15 @@ get_property (GObject *object, guint prop_id,
 		g_value_take_boxed (value, slave_list);
 		break;
 	}
+	case PROP_REFRESH_RATE_MS:
+		g_value_set_uint (value, priv->stats.refresh_rate_ms);
+		break;
+	case PROP_TX_BYTES:
+		g_value_set_uint64 (value, priv->stats.tx_bytes);
+		break;
+	case PROP_RX_BYTES:
+		g_value_set_uint64 (value, priv->stats.rx_bytes);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -11765,6 +12598,8 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->take_down = take_down;
 	klass->carrier_changed = carrier_changed;
 	klass->get_ip_iface_identifier = get_ip_iface_identifier;
+	klass->unmanaged_on_quit = unmanaged_on_quit;
+	klass->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 
 	/* Properties */
 	obj_properties[PROP_UDI] =
@@ -11923,6 +12758,11 @@ nm_device_class_init (NMDeviceClass *klass)
 	obj_properties[PROP_HW_ADDRESS] =
 	    g_param_spec_string (NM_DEVICE_HW_ADDRESS, "", "",
 	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_PERM_HW_ADDRESS] =
+	    g_param_spec_string (NM_DEVICE_PERM_HW_ADDRESS, "", "",
+	                         NULL,
 	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_HAS_PENDING_ACTION] =
@@ -11960,6 +12800,23 @@ nm_device_class_init (NMDeviceClass *klass)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
+	/* Statistics */
+	obj_properties[PROP_REFRESH_RATE_MS] =
+	    g_param_spec_uint (NM_DEVICE_STATISTICS_REFRESH_RATE_MS, "", "",
+	                       0, UINT32_MAX, 0,
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_TX_BYTES] =
+	    g_param_spec_uint64 (NM_DEVICE_STATISTICS_TX_BYTES, "", "",
+	                         0, UINT64_MAX, 0,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_RX_BYTES] =
+	    g_param_spec_uint64 (NM_DEVICE_STATISTICS_RX_BYTES, "", "",
+	                         0, UINT64_MAX, 0,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	/* Signals */
@@ -12030,4 +12887,8 @@ nm_device_class_init (NMDeviceClass *klass)
 	                                        "Disconnect", impl_device_disconnect,
 	                                        "Delete", impl_device_delete,
 	                                        NULL);
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_STATISTICS_SKELETON,
+	                                        NULL);
 }
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 21804396..34d31cad 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -26,7 +26,6 @@
 
 #include "nm-exported-object.h"
 #include "nm-dbus-interface.h"
-#include "nm-default.h"
 #include "nm-connection.h"
 #include "nm-rfkill-manager.h"
 #include "NetworkManagerUtils.h"
@@ -58,6 +57,11 @@
 #define NM_DEVICE_PHYSICAL_PORT_ID "physical-port-id"
 #define NM_DEVICE_MTU              "mtu"
 #define NM_DEVICE_HW_ADDRESS       "hw-address"
+
+/* "perm-hw-address" is exposed on D-Bus both for NMDeviceEthernet
+ * and NMDeviceWifi. */
+#define NM_DEVICE_PERM_HW_ADDRESS  "perm-hw-address"
+
 #define NM_DEVICE_METERED          "metered"
 #define NM_DEVICE_LLDP_NEIGHBORS  "lldp-neighbors"
 #define NM_DEVICE_REAL             "real"
@@ -84,7 +88,9 @@
 #define NM_DEVICE_STATE_CHANGED         "state-changed"
 #define NM_DEVICE_LINK_INITIALIZED      "link-initialized"
 
-G_BEGIN_DECLS
+#define NM_DEVICE_STATISTICS_REFRESH_RATE_MS "refresh-rate-ms"
+#define NM_DEVICE_STATISTICS_TX_BYTES        "tx-bytes"
+#define NM_DEVICE_STATISTICS_RX_BYTES        "rx-bytes"
 
 #define NM_TYPE_DEVICE            (nm_device_get_type ())
 #define NM_DEVICE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE, NMDevice))
@@ -275,6 +281,8 @@ typedef struct {
 	                                             GAsyncResult *res,
 	                                             GError **error);
 
+	void            (* deactivate_reset_hw_addr) (NMDevice *self);
+
 	/* Sync deactivating (in the DISCONNECTED phase) */
 	void            (* deactivate) (NMDevice *self);
 
@@ -324,6 +332,8 @@ typedef struct {
 	gboolean        (* owns_iface) (NMDevice *self, const char *iface);
 
 	NMConnection *  (* new_default_connection) (NMDevice *self);
+
+	gboolean        (* unmanaged_on_quit) (NMDevice *self);
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
@@ -354,7 +364,8 @@ guint32         nm_device_get_ip4_route_metric  (NMDevice *dev);
 guint32         nm_device_get_ip6_route_metric  (NMDevice *dev);
 
 const char *    nm_device_get_hw_address        (NMDevice *dev);
-const char *    nm_device_get_permanent_hw_address (NMDevice *dev);
+const char *    nm_device_get_permanent_hw_address (NMDevice *dev,
+                                                    gboolean fallback_fake);
 const char *    nm_device_get_initial_hw_address (NMDevice *dev);
 
 NMDhcp4Config * nm_device_get_dhcp4_config      (NMDevice *dev);
@@ -412,7 +423,7 @@ gboolean nm_device_check_slave_connection_compatible (NMDevice *device, NMConnec
 
 gboolean nm_device_uses_assumed_connection (NMDevice *device);
 
-gboolean nm_device_can_assume_active_connection (NMDevice *device);
+gboolean nm_device_unmanage_on_quit (NMDevice *self);
 
 gboolean nm_device_spec_match_list (NMDevice *device, const GSList *specs);
 
@@ -440,7 +451,10 @@ RfKillType nm_device_get_rfkill_type (NMDevice *device);
  *   (e.g. via a D-Bus command)
  * @NM_UNMANAGED_USER_SETTINGS: %TRUE when unmanaged by user decision via
  *   the settings plugin (for example keyfile.unmanaged-devices or ifcfg-rh's
- *   NM_CONTROLLED=no)
+ *   NM_CONTROLLED=no). Although this is user-configuration (provided from
+ *   the settings plugins, such as NM_CONTROLLED=no in ifcfg-rh), it cannot
+ *   be overruled and is authorative. That is because users may depend on
+ *   dropping a ifcfg-rh file to ensure the device is unmanaged.
  * @NM_UNMANAGED_BY_DEFAULT: %TRUE for certain device types where we unmanage
  *   them by default
  * @NM_UNMANAGED_USER_UDEV: %TRUE when unmanaged by user decision (via UDev rule)
@@ -493,7 +507,7 @@ void nm_device_set_unmanaged_by_flags_queue (NMDevice *self,
                                              NMUnmanagedFlags flags,
                                              NMUnmanFlagOp set_op,
                                              NMDeviceStateReason reason);
-void nm_device_set_unmanaged_by_user_config (NMDevice *self, const GSList *unmanaged_specs);
+void nm_device_set_unmanaged_by_user_settings (NMDevice *self, const GSList *unmanaged_specs);
 void nm_device_set_unmanaged_by_user_udev (NMDevice *self);
 void nm_device_set_unmanaged_by_quitting (NMDevice *device);
 
@@ -576,8 +590,7 @@ void nm_device_reactivate_ip6_config (NMDevice *device,
 
 void nm_device_update_hw_address (NMDevice *self);
 void nm_device_update_initial_hw_address (NMDevice *self);
+void nm_device_update_permanent_hw_address (NMDevice *self);
 void nm_device_update_dynamic_ip_setup (NMDevice *self);
 
-G_END_DECLS
-
-#endif /* NM_DEVICE_H */
+#endif /* __NETWORKMANAGER_DEVICE_H__ */
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index 9cc1d086..f4663405 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -725,13 +725,20 @@ nm_lldp_listener_start (NMLldpListener *self, int ifindex, GError **error)
 		return FALSE;
 	}
 
-	ret = sd_lldp_new (&priv->lldp_handle, ifindex);
+	ret = sd_lldp_new (&priv->lldp_handle);
 	if (ret < 0) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		                     "initialization failed");
 		return FALSE;
 	}
 
+	ret = sd_lldp_set_ifindex (priv->lldp_handle, ifindex);
+	if (ret < 0) {
+		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		                     "failed setting ifindex");
+		goto err;
+	}
+
 	ret = sd_lldp_set_callback (priv->lldp_handle, lldp_event_handler, self);
 	if (ret < 0) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
diff --git a/src/devices/nm-lldp-listener.h b/src/devices/nm-lldp-listener.h
index df02fd63..870ca8a5 100644
--- a/src/devices/nm-lldp-listener.h
+++ b/src/devices/nm-lldp-listener.h
@@ -21,8 +21,6 @@
 #ifndef __NM_LLDP_LISTENER__
 #define __NM_LLDP_LISTENER__
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_LLDP_LISTENER            (nm_lldp_listener_get_type ())
 #define NM_LLDP_LISTENER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_LLDP_LISTENER, NMLldpListener))
 #define NM_LLDP_LISTENER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_LLDP_LISTENER, NMLldpListenerClass))
@@ -48,6 +46,4 @@ gboolean nm_lldp_listener_is_running (NMLldpListener *self);
 
 GVariant *nm_lldp_listener_get_neighbors (NMLldpListener *self);
 
-G_END_DECLS
-
 #endif /* __NM_LLDP_LISTENER__ */
diff --git a/src/devices/team/Makefile.am b/src/devices/team/Makefile.am
index 3609ace4..db756b4c 100644
--- a/src/devices/team/Makefile.am
+++ b/src/devices/team/Makefile.am
@@ -49,6 +49,7 @@ if ENABLE_TESTS
 
 check-local:
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-team.so $(SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-team.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 endif
 
diff --git a/src/devices/team/Makefile.in b/src/devices/team/Makefile.in
index 4ca3014f..922ec593 100644
--- a/src/devices/team/Makefile.in
+++ b/src/devices/team/Makefile.in
@@ -92,8 +92,9 @@ host_triplet = @host@
 @WITH_TEAMDCTL_TRUE@am__append_2 = $(LIBTEAMDCTL_LIBS)
 subdir = src/devices/team
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -256,6 +257,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -312,13 +314,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -330,6 +333,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -390,6 +395,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -425,6 +432,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -835,6 +843,7 @@ include $(GLIB_MAKEFILE)
 
 @ENABLE_TESTS_TRUE@check-local:
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-team.so $(SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-team.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index b4b522d8..09eefe70 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -45,12 +45,18 @@ G_DEFINE_TYPE (NMDeviceTeam, nm_device_team, NM_TYPE_DEVICE)
 
 #define NM_DEVICE_TEAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_TEAM, NMDeviceTeamPrivate))
 
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceTeam,
+	PROP_CONFIG,
+);
+
 typedef struct {
 	struct teamdctl *tdc;
 	GPid teamd_pid;
 	guint teamd_process_watch;
 	guint teamd_timeout;
+	guint teamd_read_timeout;
 	guint teamd_dbus_watch;
+	char *config;
 } NMDeviceTeamPrivate;
 
 static gboolean teamd_start (NMDevice *device, NMSettingTeam *s_team);
@@ -147,39 +153,74 @@ ensure_teamd_connection (NMDevice *device)
 	return !!priv->tdc;
 }
 
+static gboolean
+teamd_read_config (NMDevice *device)
+{
+	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
+	char *config = NULL;
+	int err;
+
+	if (priv->tdc) {
+		err = teamdctl_config_actual_get_raw_direct (priv->tdc, &config);
+		if (err)
+			return FALSE;
+	}
+
+	if (!nm_streq0 (config, priv->config)) {
+		g_free (priv->config);
+		priv->config = g_strdup (config);
+		_notify (self, PROP_CONFIG);
+	}
+
+	return TRUE;
+}
+
+static gboolean
+teamd_read_timeout_cb (gpointer user_data)
+{
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (user_data);
+
+	teamd_read_config ((NMDevice *) user_data);
+	priv->teamd_read_timeout = 0;
+
+	return G_SOURCE_REMOVE;
+}
+
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMSettingTeam *s_team = nm_connection_get_setting_team (connection);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
+	struct teamdctl *tdc = priv->tdc;
 
 	if (!s_team) {
 		s_team = (NMSettingTeam *) nm_setting_team_new ();
 		nm_connection_add_setting (connection, (NMSetting *) s_team);
 	}
-	g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_CONFIG, NULL, NULL);
 
-	if (priv->tdc) {
-		const char *config = NULL;
-		int err;
+	/* Read the configuration only if not already set */
+	if (!priv->config && ensure_teamd_connection (device))
+		teamd_read_config (device);
 
-		err = teamdctl_config_get_raw_direct (NM_DEVICE_TEAM_GET_PRIVATE (device)->tdc,
-		                                      (char **)&config);
-		if (err == 0)
-			g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_CONFIG, config, NULL);
-		else
-			_LOGE (LOGD_TEAM, "failed to read teamd config (err=%d)", err);
+	/* Restore previous tdc state */
+	if (priv->tdc && !tdc) {
+		teamdctl_disconnect (priv->tdc);
+		teamdctl_free (priv->tdc);
+		priv->tdc = NULL;
 	}
+
+	g_object_set (G_OBJECT (s_team), NM_SETTING_TEAM_CONFIG, priv->config, NULL);
 }
 
 /******************************************************************/
 
 static gboolean
 master_update_slave_connection (NMDevice *self,
-                                   NMDevice *slave,
-                                   NMConnection *connection,
-                                   GError **error)
+                                NMDevice *slave,
+                                NMConnection *connection,
+                                GError **error)
 {
 	NMSettingTeamPort *s_port;
 	char *port_config = NULL;
@@ -249,6 +290,7 @@ teamd_cleanup (NMDevice *device, gboolean free_tdc)
 
 	nm_clear_g_source (&priv->teamd_process_watch);
 	nm_clear_g_source (&priv->teamd_timeout);
+	nm_clear_g_source (&priv->teamd_read_timeout);
 
 	if (priv->teamd_pid > 0) {
 		nm_utils_kill_child_async (priv->teamd_pid, SIGTERM, LOGD_TEAM, "teamd", 2000, NULL, NULL);
@@ -279,6 +321,14 @@ teamd_timeout_cb (gpointer user_data)
 
 		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);
+	} else {
+		/* Read again the configuration after the timeout since it might
+		 * have changed.
+		 */
+		if (!teamd_read_config (device)) {
+			_LOGW (LOGD_TEAM, "failed to read teamd configuration");
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		}
 	}
 
 	return G_SOURCE_REMOVE;
@@ -339,6 +389,8 @@ teamd_dbus_appeared (GDBusConnection *connection,
 	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_uses_assumed_connection (device))
 			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
@@ -523,7 +575,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 		 * have a PID, then we must fail.
 		 */
 		cfg = teamdctl_config_get_raw (priv->tdc);
-		if (cfg && strcmp (cfg,  nm_setting_team_get_config (s_team)) == 0) {
+		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;
 		}
@@ -626,6 +678,11 @@ enslave_slave (NMDevice *device,
 		if (!success)
 			return FALSE;
 
+		nm_clear_g_source (&priv->teamd_read_timeout);
+		priv->teamd_read_timeout = g_timeout_add_seconds (5,
+		                                                  teamd_read_timeout_cb,
+		                                                  self);
+
 		_LOGI (LOGD_TEAM, "enslaved team port %s", slave_iface);
 	} else
 		_LOGI (LOGD_TEAM, "team port %s was enslaved", slave_iface);
@@ -639,6 +696,7 @@ release_slave (NMDevice *device,
                gboolean configure)
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (device);
 	gboolean success, no_firmware = FALSE;
 
 	if (configure) {
@@ -658,6 +716,11 @@ release_slave (NMDevice *device,
 		if (!nm_device_bring_up (slave, TRUE, &no_firmware))
 			_LOGW (LOGD_TEAM, "released team port %s could not be brought up",
 			       nm_device_get_ip_iface (slave));
+
+		nm_clear_g_source (&priv->teamd_read_timeout);
+		priv->teamd_read_timeout = g_timeout_add_seconds (5,
+		                                                  teamd_read_timeout_cb,
+		                                                  self);
 	} else
 		_LOGI (LOGD_TEAM, "team port %s was released", nm_device_get_ip_iface (slave));
 }
@@ -701,6 +764,23 @@ nm_device_team_new (const char *iface)
 }
 
 static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceTeam *self = NM_DEVICE_TEAM (object);
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_CONFIG:
+		g_value_set_string (value, priv->config);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
 nm_device_team_init (NMDeviceTeam * self)
 {
 }
@@ -738,6 +818,7 @@ dispose (GObject *object)
 	}
 
 	teamd_cleanup (device, TRUE);
+	g_clear_pointer (&priv->config, g_free);
 
 	G_OBJECT_CLASS (nm_device_team_parent_class)->dispose (object);
 }
@@ -754,6 +835,7 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
+	object_class->get_property = get_property;
 
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
@@ -770,6 +852,14 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 	parent_class->enslave_slave = enslave_slave;
 	parent_class->release_slave = release_slave;
 
+	obj_properties[PROP_CONFIG] =
+	    g_param_spec_string (NM_DEVICE_TEAM_CONFIG, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_TEAM_SKELETON,
 	                                        NULL);
diff --git a/src/devices/team/nm-device-team.h b/src/devices/team/nm-device-team.h
index 0e81afa7..43e8aab4 100644
--- a/src/devices/team/nm-device-team.h
+++ b/src/devices/team/nm-device-team.h
@@ -23,8 +23,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_TEAM            (nm_device_team_get_type ())
 #define NM_DEVICE_TEAM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_TEAM, NMDeviceTeam))
 #define NM_DEVICE_TEAM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_TEAM, NMDeviceTeamClass))
@@ -32,6 +30,9 @@ G_BEGIN_DECLS
 #define NM_IS_DEVICE_TEAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_TEAM))
 #define NM_DEVICE_TEAM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_TEAM, NMDeviceTeamClass))
 
+/* Properties */
+#define NM_DEVICE_TEAM_CONFIG          "config"
+
 typedef NMDevice NMDeviceTeam;
 typedef NMDeviceClass NMDeviceTeamClass;
 
@@ -39,6 +40,4 @@ GType nm_device_team_get_type (void);
 
 NMDevice *nm_device_team_new (const char *iface);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_TEAM_H */
+#endif /* __NETWORKMANAGER_DEVICE_TEAM_H__ */
diff --git a/src/devices/team/nm-team-factory.h b/src/devices/team/nm-team-factory.h
index 9049478d..4155870c 100644
--- a/src/devices/team/nm-team-factory.h
+++ b/src/devices/team/nm-team-factory.h
@@ -21,8 +21,6 @@
 #ifndef __NETWORKMANAGER_TEAM_FACTORY_H__
 #define __NETWORKMANAGER_TEAM_FACTORY_H__
 
-#include "nm-default.h"
-
 #define NM_TYPE_TEAM_FACTORY (nm_team_factory_get_type ())
 #define NM_TEAM_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_TEAM_FACTORY, NMTeamFactory))
 
diff --git a/src/devices/tests/Makefile.in b/src/devices/tests/Makefile.in
index b07e6dfd..7c25dc11 100644
--- a/src/devices/tests/Makefile.in
+++ b/src/devices/tests/Makefile.in
@@ -92,8 +92,9 @@ noinst_PROGRAMS = test-lldp$(EXEEXT) test-arping$(EXEEXT)
 TESTS = test-lldp$(EXEEXT) test-arping$(EXEEXT)
 subdir = src/devices/tests
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -428,6 +429,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -484,13 +486,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -502,6 +505,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -562,6 +567,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -597,6 +604,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index bff85e6c..ff6f42a9 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -33,7 +33,7 @@
 
 #include "test-common.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 /*****************************************************************************/
 
diff --git a/src/devices/wifi/Makefile.am b/src/devices/wifi/Makefile.am
index e35a84b4..add0a007 100644
--- a/src/devices/wifi/Makefile.am
+++ b/src/devices/wifi/Makefile.am
@@ -52,6 +52,7 @@ if ENABLE_TESTS
 
 check-local:
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wifi.so $(SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-wifi.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 endif
 
diff --git a/src/devices/wifi/Makefile.in b/src/devices/wifi/Makefile.in
index 34aa4b97..134c4012 100644
--- a/src/devices/wifi/Makefile.in
+++ b/src/devices/wifi/Makefile.in
@@ -90,8 +90,9 @@ build_triplet = @build@
 host_triplet = @host@
 subdir = src/devices/wifi
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -295,6 +296,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -351,13 +353,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -369,6 +372,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -429,6 +434,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -464,6 +471,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -967,6 +975,7 @@ include $(GLIB_MAKEFILE)
 
 @ENABLE_TESTS_TRUE@check-local:
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wifi.so $(SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-wifi.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/devices/wifi/nm-device-olpc-mesh.h b/src/devices/wifi/nm-device-olpc-mesh.h
index 63895a88..135226d5 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.h
+++ b/src/devices/wifi/nm-device-olpc-mesh.h
@@ -30,8 +30,6 @@
 
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_DEVICE_OLPC_MESH            (nm_device_olpc_mesh_get_type ())
 #define NM_DEVICE_OLPC_MESH(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMesh))
 #define NM_DEVICE_OLPC_MESH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OLPC_MESH, NMDeviceOlpcMeshClass))
@@ -67,6 +65,4 @@ GType nm_device_olpc_mesh_get_type (void);
 
 NMDevice *nm_device_olpc_mesh_new (const char *iface);
 
-G_END_DECLS
-
-#endif  /* NM_DEVICE_OLPC_MESH_H */
+#endif /* __NETWORKMANAGER_DEVICE_OLPC_MESH_H__ */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 82b12a11..49c380af 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -21,6 +21,8 @@
 
 #include "nm-default.h"
 
+#include "nm-device-wifi.h"
+
 #include <netinet/in.h>
 #include <string.h>
 #include <unistd.h>
@@ -28,7 +30,6 @@
 
 #include "nm-common-macros.h"
 #include "nm-device.h"
-#include "nm-device-wifi.h"
 #include "nm-device-private.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
@@ -46,8 +47,8 @@
 #include "nm-platform.h"
 #include "nm-auth-utils.h"
 #include "nm-settings-connection.h"
+#include "nm-settings.h"
 #include "nm-enum-types.h"
-#include "nm-connection-provider.h"
 #include "nm-core-internal.h"
 #include "nm-config.h"
 
@@ -61,25 +62,20 @@ _LOG_DECLARE_SELF(NMDeviceWifi);
 #define SCAN_INTERVAL_STEP 20
 #define SCAN_INTERVAL_MAX 120
 
-#define WIRELESS_SECRETS_TRIES "wireless-secrets-tries"
+#define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
 
-G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
-
-#define NM_DEVICE_WIFI_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_WIFI, NMDeviceWifiPrivate))
+#define WIRELESS_SECRETS_TRIES "wireless-secrets-tries"
 
+/*****************************************************************************/
 
-enum {
-	PROP_0,
-	PROP_PERM_HW_ADDRESS,
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 	PROP_MODE,
 	PROP_BITRATE,
 	PROP_ACCESS_POINTS,
 	PROP_ACTIVE_ACCESS_POINT,
 	PROP_CAPABILITIES,
 	PROP_SCANNING,
-
-	LAST_PROP
-};
+);
 
 enum {
 	ACCESS_POINT_ADDED,
@@ -91,26 +87,26 @@ enum {
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-struct _NMDeviceWifiPrivate {
+typedef struct {
 	gint8             invalid_strength_counter;
 
 	GHashTable *      aps;
 	NMAccessPoint *   current_ap;
 	guint32           rate;
-	gboolean          enabled; /* rfkilled or not */
+	bool              enabled:1; /* rfkilled or not */
+	bool              requested_scan:1;
+	bool              ssid_found:1;
 
 	gint32            last_scan;
 	gint32            scheduled_scan_time;
 	guint8            scan_interval; /* seconds */
 	guint             pending_scan_id;
 	guint             ap_dump_id;
-	bool              requested_scan;
 
 	NMSupplicantManager   *sup_mgr;
 	NMSupplicantInterface *sup_iface;
 	guint                  sup_timeout_id; /* supplicant association timeout */
 
-	bool              ssid_found;
 	NM80211Mode       mode;
 
 	guint             periodic_source_id;
@@ -119,8 +115,44 @@ struct _NMDeviceWifiPrivate {
 	guint             reacquire_iface_id;
 
 	NMDeviceWifiCapabilities capabilities;
+
+	gint32 hw_addr_scan_expire;
+	char *hw_addr_scan;
+} NMDeviceWifiPrivate;
+
+struct _NMDeviceWifi
+{
+	NMDevice parent;
+	NMDeviceWifiPrivate _priv;
 };
 
+struct _NMDeviceWifiClass
+{
+	NMDeviceClass parent;
+
+	/* Signals */
+	gboolean (*scanning_allowed) (NMDeviceWifi *device);
+};
+
+/*****************************************************************************/
+
+G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_WIFI_GET_PRIVATE(self) \
+	({ \
+		/* preserve the const-ness of self. Unfortunately, that
+		 * way, @self cannot be a void pointer */ \
+		typeof (self) _self = (self); \
+		\
+		/* Get compiler error if variable is of wrong type */ \
+		_nm_unused const NMDeviceWifi *_self2 = (_self); \
+		\
+		nm_assert (NM_IS_DEVICE_WIFI (_self)); \
+		&_self->_priv; \
+	})
+
+/*****************************************************************************/
+
 static gboolean check_scanning_allowed (NMDeviceWifi *self);
 
 static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
@@ -169,7 +201,9 @@ static void ap_add_remove (NMDeviceWifi *self,
 
 static void remove_supplicant_interface_error_handler (NMDeviceWifi *self);
 
-/*****************************************************************/
+static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
+
+/*****************************************************************************/
 
 static void
 constructed (GObject *object)
@@ -187,6 +221,18 @@ constructed (GObject *object)
 }
 
 static gboolean
+unmanaged_on_quit (NMDevice *self)
+{
+	/* Wi-Fi devices cannot be assumed and are always taken down.
+	 * However, also when being disconnected, we scan and thus
+	 * set the MAC address to a random value.
+	 *
+	 * We must restore the original MAC address when quitting, thus
+	 * signal to unmanage the device. */
+	return TRUE;
+}
+
+static gboolean
 supplicant_interface_acquire (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
@@ -238,6 +284,24 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 }
 
 static void
+_requested_scan_set (NMDeviceWifi *self, gboolean value)
+{
+	NMDeviceWifiPrivate *priv;
+
+	value = !!value;
+
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	if (priv->requested_scan == value)
+		return;
+
+	priv->requested_scan = value;
+	if (value)
+		nm_device_add_pending_action ((NMDevice *) self, "scan", TRUE);
+	else
+		nm_device_remove_pending_action ((NMDevice *) self, "scan", TRUE);
+}
+
+static void
 supplicant_interface_release (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv;
@@ -246,6 +310,8 @@ supplicant_interface_release (NMDeviceWifi *self)
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
+	_requested_scan_set (self, FALSE);
+
 	nm_clear_g_source (&priv->pending_scan_id);
 
 	/* Reset the scan interval to be pretty frequent when disconnected */
@@ -340,7 +406,7 @@ set_current_ap (NMDeviceWifi *self, NMAccessPoint *new_ap, gboolean recheck_avai
 		g_object_unref (old_ap);
 	}
 
-	g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT);
+	_notify (self, PROP_ACTIVE_ACCESS_POINT);
 }
 
 static void
@@ -386,7 +452,7 @@ periodic_update (NMDeviceWifi *self)
 	new_rate = nm_platform_wifi_get_rate (NM_PLATFORM_GET, ifindex);
 	if (new_rate != priv->rate) {
 		priv->rate = new_rate;
-		g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_BITRATE);
+		_notify (self, PROP_BITRATE);
 	}
 }
 
@@ -397,18 +463,10 @@ periodic_update_cb (gpointer user_data)
 	return TRUE;
 }
 
-static void
-realize_start_notify (NMDevice *device, const NMPlatformLink *plink)
-{
-	NM_DEVICE_CLASS (nm_device_wifi_parent_class)->realize_start_notify (device, plink);
-
-	g_object_notify (G_OBJECT (device), NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS);
-}
-
 static gboolean
 bring_up (NMDevice *device, gboolean *no_firmware)
 {
-	if (!NM_DEVICE_WIFI_GET_PRIVATE (device)->enabled)
+	if (!NM_DEVICE_WIFI_GET_PRIVATE ((NMDeviceWifi *) device)->enabled)
 		return FALSE;
 
 	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->bring_up (device, no_firmware);
@@ -431,7 +489,7 @@ ap_add_remove (NMDeviceWifi *self,
 	}
 
 	g_signal_emit (self, signals[signum], 0, ap);
-	g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACCESS_POINTS);
+	_notify (self, PROP_ACCESS_POINTS);
 
 	if (signum == ACCESS_POINT_REMOVED) {
 		g_hash_table_remove (priv->aps, nm_exported_object_get_path ((NMExportedObject *) ap));
@@ -485,10 +543,6 @@ deactivate (NMDevice *device)
 	/* Clear any critical protocol notification in the Wi-Fi stack */
 	nm_platform_wifi_indicate_addressing_running (NM_PLATFORM_GET, ifindex, FALSE);
 
-	/* Reset MAC address back to initial address */
-	if (nm_device_get_initial_hw_address (device))
-		nm_device_set_hw_addr (device, nm_device_get_initial_hw_address (device), "reset", LOGD_WIFI);
-
 	/* Ensure we're in infrastructure mode after deactivation; some devices
 	 * (usually older ones) don't scan well in adhoc mode.
 	 */
@@ -500,7 +554,7 @@ deactivate (NMDevice *device)
 
 	if (priv->mode != NM_802_11_MODE_INFRA) {
 		priv->mode = NM_802_11_MODE_INFRA;
-		g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_MODE);
+		_notify (self, PROP_MODE);
 	}
 
 	/* Ensure we trigger a scan after deactivating a Hotspot */
@@ -510,6 +564,12 @@ deactivate (NMDevice *device)
 	}
 }
 
+static void
+deactivate_reset_hw_addr (NMDevice *device)
+{
+	_hw_addr_set_scanning ((NMDeviceWifi *) device, TRUE);
+}
+
 static gboolean
 is_adhoc_wpa (NMConnection *connection)
 {
@@ -566,7 +626,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	if (!s_wireless)
 		return FALSE;
 
-	perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	perm_hw_addr = nm_device_get_permanent_hw_address (device, FALSE);
 	mac = nm_setting_wireless_get_mac_address (s_wireless);
 	if (perm_hw_addr) {
 		if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
@@ -679,35 +739,31 @@ check_connection_available (NMDevice *device,
 	return !!find_first_compatible_ap (NM_DEVICE_WIFI (device), connection, TRUE);
 }
 
-/*
- * List of manufacturer default SSIDs that are often unchanged by users.
- *
- * NOTE: this list should *not* contain networks that you would like to
- * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot".
- */
-static const char *
-manf_defaults[] = {
-	"linksys",
-	"linksys-a",
-	"linksys-g",
-	"default",
-	"belkin54g",
-	"NETGEAR",
-	"o2DSL",
-	"WLAN",
-	"ALICE-WLAN",
-	"Speedport W 501V",
-	"TURBONETT",
-};
-
-#define ARRAY_SIZE(a)  (sizeof (a) / sizeof (a[0]))
-
 static gboolean
 is_manf_default_ssid (const GByteArray *ssid)
 {
 	int i;
-
-	for (i = 0; i < ARRAY_SIZE (manf_defaults); i++) {
+	/*
+	 * List of manufacturer default SSIDs that are often unchanged by users.
+	 *
+	 * NOTE: this list should *not* contain networks that you would like to
+	 * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot".
+	 */
+	static const char *manf_defaults[] = {
+		"linksys",
+		"linksys-a",
+		"linksys-g",
+		"default",
+		"belkin54g",
+		"NETGEAR",
+		"o2DSL",
+		"WLAN",
+		"ALICE-WLAN",
+		"Speedport W 501V",
+		"TURBONETT",
+	};
+
+	for (i = 0; i < G_N_ELEMENTS (manf_defaults); i++) {
 		if (ssid->len == strlen (manf_defaults[i])) {
 			if (memcmp (manf_defaults[i], ssid->data, ssid->len) == 0)
 				return TRUE;
@@ -862,7 +918,7 @@ complete_connection (NMDevice *device,
 	if (hidden)
 		g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
 
-	perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	perm_hw_addr = nm_device_get_permanent_hw_address (device, FALSE);
 	if (perm_hw_addr) {
 		setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
 		if (setting_mac) {
@@ -1022,6 +1078,60 @@ impl_device_wifi_get_all_access_points (NMDeviceWifi *self,
 }
 
 static void
+_hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
+{
+	NMDevice *device = (NMDevice *) self;
+	NMDeviceWifiPrivate *priv;
+	guint32 now;
+	gboolean randomize;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI (self));
+
+	if (   nm_device_is_activating (device)
+	    || nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED)
+		return;
+
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	randomize = nm_config_data_get_device_config_boolean (NM_CONFIG_GET_DATA,
+	                                                      "wifi.scan-rand-mac-address",
+	                                                      device,
+	                                                      TRUE, TRUE);
+
+	if (!randomize) {
+		g_clear_pointer (&priv->hw_addr_scan, g_free);
+		if (do_reset)
+			nm_device_hw_addr_reset (device, "scanning");
+		return;
+	}
+
+	now = nm_utils_get_monotonic_timestamp_s ();
+
+	if (   !priv->hw_addr_scan
+	    || now >= priv->hw_addr_scan_expire) {
+		gs_free char *generate_mac_address_mask = NULL;
+
+		/* the random MAC address for scanning expires after a while.
+		 *
+		 * We don't bother with to update the MAC address exactly when
+		 * it expires, instead on the next scan request, we will generate
+		 * a new one.*/
+		priv->hw_addr_scan_expire = now + (SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN * 60);
+
+		generate_mac_address_mask = nm_config_data_get_device_config (NM_CONFIG_GET_DATA,
+		                                                              "wifi.scan-generate-mac-address-mask",
+		                                                              device,
+		                                                              NULL);
+
+		g_free (priv->hw_addr_scan);
+		priv->hw_addr_scan = nm_utils_hw_addr_gen_random_eth (nm_device_get_initial_hw_address (device),
+		                                                      generate_mac_address_mask);
+	}
+
+	nm_device_hw_addr_set (device, priv->hw_addr_scan, "scanning");
+}
+
+static void
 request_scan_cb (NMDevice *device,
                  GDBusMethodInvocation *context,
                  NMAuthSubject *subject,
@@ -1199,7 +1309,7 @@ check_scanning_allowed (NMDeviceWifi *self)
 }
 
 static gboolean
-hidden_filter_func (NMConnectionProvider *provider,
+hidden_filter_func (NMSettings *settings,
                     NMConnection *connection,
                     gpointer user_data)
 {
@@ -1226,12 +1336,12 @@ build_hidden_probe_list (NMDeviceWifi *self)
 	if (G_UNLIKELY (nullssid == NULL))
 		nullssid = g_byte_array_new ();
 
-	connections = nm_connection_provider_get_best_connections (nm_connection_provider_get (),
-	                                                           max_scan_ssids - 1,
-	                                                           NM_SETTING_WIRELESS_SETTING_NAME,
-	                                                           NULL,
-	                                                           hidden_filter_func,
-	                                                           NULL);
+	connections = nm_settings_get_best_connections (nm_device_get_settings ((NMDevice *) self),
+	                                                max_scan_ssids - 1,
+	                                                NM_SETTING_WIRELESS_SETTING_NAME,
+	                                                NULL,
+	                                                hidden_filter_func,
+	                                                NULL);
 	if (connections && connections->data) {
 		ssids = g_ptr_array_new_full (max_scan_ssids - 1, (GDestroyNotify) g_byte_array_unref);
 		g_ptr_array_add (ssids, g_byte_array_ref (nullssid));  /* Add wildcard SSID */
@@ -1330,11 +1440,12 @@ request_wireless_scan (NMDeviceWifi *self, GVariant *scan_options)
 				_LOGD (LOGD_WIFI_SCAN, "no SSIDs to probe scan");
 		}
 
+		_hw_addr_set_scanning (self, FALSE);
+
 		if (nm_supplicant_interface_request_scan (priv->sup_iface, ssids)) {
 			/* success */
 			backoff = TRUE;
-			priv->requested_scan = TRUE;
-			nm_device_add_pending_action (NM_DEVICE (self), "scan", TRUE);
+			_requested_scan_set (self, TRUE);
 		}
 
 		if (ssids)
@@ -1349,10 +1460,11 @@ request_wireless_scan (NMDeviceWifi *self, GVariant *scan_options)
 static gboolean
 request_wireless_scan_periodic (gpointer user_data)
 {
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (user_data);
+	NMDeviceWifi *self = user_data;
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->pending_scan_id = 0;
-	request_wireless_scan (user_data, NULL);
+	request_wireless_scan (self, NULL);
 	return FALSE;
 }
 
@@ -1416,10 +1528,7 @@ supplicant_iface_scan_done_cb (NMSupplicantInterface *iface,
 	priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
 	schedule_scan (self, success);
 
-	if (priv->requested_scan) {
-		priv->requested_scan = FALSE;
-		nm_device_remove_pending_action (NM_DEVICE (self), "scan", TRUE);
-	}
+	_requested_scan_set (self, FALSE);
 }
 
 /****************************************************************************
@@ -1458,10 +1567,12 @@ schedule_ap_list_dump (NMDeviceWifi *self)
 }
 
 static void
-try_fill_ssid_for_hidden_ap (NMAccessPoint *ap)
+try_fill_ssid_for_hidden_ap (NMDeviceWifi *self,
+                             NMAccessPoint *ap)
 {
 	const char *bssid;
-	const GSList *connections, *iter;
+	NMSettingsConnection *const*connections;
+	guint i;
 
 	g_return_if_fail (nm_ap_get_ssid (ap) == NULL);
 
@@ -1470,9 +1581,9 @@ try_fill_ssid_for_hidden_ap (NMAccessPoint *ap)
 
 	/* Look for this AP's BSSID in the seen-bssids list of a connection,
 	 * and if a match is found, copy over the SSID */
-	connections = nm_connection_provider_get_connections (nm_connection_provider_get ());
-	for (iter = connections; iter; iter = g_slist_next (iter)) {
-		NMConnection *connection = NM_CONNECTION (iter->data);
+	connections = nm_settings_get_connections (nm_device_get_settings ((NMDevice *) self), NULL);
+	for (i = 0; connections[i]; i++) {
+		NMConnection *connection = (NMConnection *) connections[i];
 		NMSettingWireless *s_wifi;
 
 		s_wifi = nm_connection_get_setting_wireless (connection);
@@ -1522,7 +1633,7 @@ supplicant_iface_new_bss_cb (NMSupplicantInterface *iface,
 	ssid = nm_ap_get_ssid (ap);
 	if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) {
 		/* Try to fill the SSID from the AP database */
-		try_fill_ssid_for_hidden_ap (ap);
+		try_fill_ssid_for_hidden_ap (self, ap);
 
 		ssid = nm_ap_get_ssid (ap);
 		if (ssid && (nm_utils_is_empty_ssid (ssid->data, ssid->len) == FALSE)) {
@@ -1951,6 +2062,11 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		else
 			_LOGI (LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up");
 		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
+		_requested_scan_set (self, FALSE);
+		nm_clear_g_source (&priv->pending_scan_id);
+		request_wireless_scan (self, NULL);
+		break;
 	default:
 		break;
 	}
@@ -1964,7 +2080,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	/* Signal scanning state changes */
 	if (   new_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
 	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		g_object_notify (G_OBJECT (self), "scanning");
+		_notify (self, PROP_SCANNING);
 }
 
 static void
@@ -2008,7 +2124,7 @@ supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
 	scanning = nm_supplicant_interface_get_scanning (iface);
 	_LOGD (LOGD_WIFI_SCAN, "now %s", scanning ? "scanning" : "idle");
 
-	g_object_notify (G_OBJECT (self), "scanning");
+	_notify (self, PROP_SCANNING);
 
 	/* Run a quick update of current AP when coming out of a scan */
 	state = nm_device_get_state (NM_DEVICE (self));
@@ -2197,9 +2313,6 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSupplicantConfig *config = NULL;
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
-	NMSupplicantFeature mac_randomization_support;
-	NMSettingMacRandomization mac_randomization_fallback;
-	gs_free char *svalue = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
 
@@ -2214,20 +2327,9 @@ build_supplicant_config (NMDeviceWifi *self,
 		_LOGW (LOGD_WIFI, "Supplicant may not support AP mode; connection may time out.");
 	}
 
-	mac_randomization_support = nm_supplicant_interface_get_mac_randomization_support (priv->sup_iface);
-	svalue = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                                "wifi." NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
-	                                                NM_DEVICE (self));
-	mac_randomization_fallback = _nm_utils_ascii_str_to_int64 (svalue, 10,
-	                                                           NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
-	                                                           NM_SETTING_MAC_RANDOMIZATION_ALWAYS,
-	                                                           NM_SETTING_MAC_RANDOMIZATION_DEFAULT);
-
 	if (!nm_supplicant_config_add_setting_wireless (config,
 	                                                s_wireless,
 	                                                fixed_freq,
-	                                                mac_randomization_support,
-	                                                mac_randomization_fallback,
 	                                                error)) {
 		g_prefix_error (error, "802-11-wireless: ");
 		goto error;
@@ -2277,7 +2379,6 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 	NMActRequest *req;
 	NMConnection *connection;
 	NMSettingWireless *s_wireless;
-	const char *cloned_mac;
 	const char *mode;
 	const char *ap_path;
 
@@ -2305,7 +2406,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 		/* Scanning not done in AP mode; clear the scan list */
 		remove_all_aps (self);
 	}
-	g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_MODE);
+	_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
@@ -2317,9 +2418,12 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
+	/* forget the temporary MAC address used during scanning */
+	g_clear_pointer (&priv->hw_addr_scan, g_free);
+
 	/* Set spoof MAC to the interface */
-	cloned_mac = nm_setting_wireless_get_cloned_mac_address (s_wireless);
-	nm_device_set_hw_addr (device, cloned_mac, "set", LOGD_WIFI);
+	if (!nm_device_hw_addr_set_cloned (device, connection, TRUE))
+		return NM_ACT_STAGE_RETURN_FAILURE;
 
 	/* AP mode never uses a specific object or existing scanned AP */
 	if (priv->mode != NM_802_11_MODE_AP) {
@@ -2859,6 +2963,8 @@ set_enabled (NMDevice *device, gboolean enabled)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDeviceState state;
 
+	enabled = !!enabled;
+
 	if (priv->enabled == enabled)
 		return;
 
@@ -2959,6 +3065,8 @@ finalize (GObject *object)
 
 	g_hash_table_unref (priv->aps);
 
+	g_free (priv->hw_addr_scan);
+
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->finalize (object);
 }
 
@@ -2973,9 +3081,6 @@ get_property (GObject *object, guint prop_id,
 	GPtrArray *array;
 
 	switch (prop_id) {
-	case PROP_PERM_HW_ADDRESS:
-		g_value_set_string (value, nm_device_get_permanent_hw_address (NM_DEVICE (device)));
-		break;
 	case PROP_MODE:
 		g_value_set_uint (value, priv->mode);
 		break;
@@ -3030,8 +3135,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
-	g_type_class_add_private (object_class, sizeof (NMDeviceWifiPrivate));
-
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
 
 	object_class->constructed = constructed;
@@ -3040,7 +3143,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
-	parent_class->realize_start_notify = realize_start_notify;
 	parent_class->bring_up = bring_up;
 	parent_class->can_auto_connect = can_auto_connect;
 	parent_class->is_available = is_available;
@@ -3057,90 +3159,81 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	parent_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
 	parent_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
 	parent_class->deactivate = deactivate;
+	parent_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
+	parent_class->unmanaged_on_quit = unmanaged_on_quit;
 
 	parent_class->state_changed = device_state_changed;
 
 	klass->scanning_allowed = scanning_allowed;
 
 	/* Properties */
-	g_object_class_install_property
-		(object_class, PROP_PERM_HW_ADDRESS,
-		 g_param_spec_string (NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS, "", "",
-		                      NULL,
-		                      G_PARAM_READABLE |
-		                      G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_MODE,
-		 g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
-		                    NM_802_11_MODE_UNKNOWN,
-		                    NM_802_11_MODE_AP,
-		                    NM_802_11_MODE_INFRA,
-		                    G_PARAM_READABLE |
-		                    G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_BITRATE,
-		 g_param_spec_uint (NM_DEVICE_WIFI_BITRATE, "", "",
-		                    0, G_MAXUINT32, 0,
-		                    G_PARAM_READABLE |
-		                    G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_ACCESS_POINTS,
-		 g_param_spec_boxed (NM_DEVICE_WIFI_ACCESS_POINTS, "", "",
-		                     G_TYPE_STRV,
-		                     G_PARAM_READABLE |
-		                     G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_ACTIVE_ACCESS_POINT,
-		 g_param_spec_string (NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT, "", "",
-		                      NULL,
-		                      G_PARAM_READABLE |
-		                      G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_CAPABILITIES,
-		 g_param_spec_uint (NM_DEVICE_WIFI_CAPABILITIES, "", "",
-		                    0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE,
-		                    G_PARAM_READWRITE |
-		                    G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-		(object_class, PROP_SCANNING,
-		 g_param_spec_boolean (NM_DEVICE_WIFI_SCANNING, "", "",
-		                       FALSE,
-		                       G_PARAM_READABLE |
-		                       G_PARAM_STATIC_STRINGS));
+	obj_properties[PROP_MODE] =
+	    g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
+	                       NM_802_11_MODE_UNKNOWN,
+	                       NM_802_11_MODE_AP,
+	                       NM_802_11_MODE_INFRA,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_BITRATE] =
+	    g_param_spec_uint (NM_DEVICE_WIFI_BITRATE, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ACCESS_POINTS] =
+	    g_param_spec_boxed (NM_DEVICE_WIFI_ACCESS_POINTS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ACTIVE_ACCESS_POINT] =
+	    g_param_spec_string (NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_CAPABILITIES] =
+	    g_param_spec_uint (NM_DEVICE_WIFI_CAPABILITIES, "", "",
+	                       0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE,
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SCANNING] =
+	    g_param_spec_boolean (NM_DEVICE_WIFI_SCANNING, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	/* Signals */
 	signals[ACCESS_POINT_ADDED] =
-		g_signal_new ("access-point-added",
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_FIRST,
-		              G_STRUCT_OFFSET (NMDeviceWifiClass, access_point_added),
-		              NULL, NULL, NULL,
-		              G_TYPE_NONE, 1,
-		              NM_TYPE_AP);
+	    g_signal_new ("access-point-added",
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1,
+	                  NM_TYPE_AP);
 
 	signals[ACCESS_POINT_REMOVED] =
-		g_signal_new ("access-point-removed",
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_FIRST,
-		              0,
-		              NULL, NULL, NULL,
-		              G_TYPE_NONE, 1,
-		              NM_TYPE_AP);
+	    g_signal_new ("access-point-removed",
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1,
+	                  NM_TYPE_AP);
 
 	signals[SCANNING_ALLOWED] =
-		g_signal_new ("scanning-allowed",
-		              G_OBJECT_CLASS_TYPE (object_class),
-		              G_SIGNAL_RUN_LAST,
-		              G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed),
-		              scanning_allowed_accumulator, NULL, NULL,
-		              G_TYPE_BOOLEAN, 0);
+	    g_signal_new ("scanning-allowed",
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed),
+	                  scanning_allowed_accumulator, NULL, NULL,
+	                  G_TYPE_BOOLEAN, 0);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 7e0d06f5..ba9d60e2 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2010 Red Hat, Inc.
+ * Copyright (C) 2005 - 2016 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -25,18 +25,13 @@
 #include "nm-device.h"
 #include "nm-wifi-ap.h"
 
-struct NMAccessPointList;
+#define NM_TYPE_DEVICE_WIFI             (nm_device_wifi_get_type ())
+#define NM_DEVICE_WIFI(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi))
+#define NM_DEVICE_WIFI_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass))
+#define NM_IS_DEVICE_WIFI(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI))
+#define NM_IS_DEVICE_WIFI_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIFI))
+#define NM_DEVICE_WIFI_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass))
 
-G_BEGIN_DECLS
-
-#define NM_TYPE_DEVICE_WIFI			(nm_device_wifi_get_type ())
-#define NM_DEVICE_WIFI(obj)			(G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI, NMDeviceWifi))
-#define NM_DEVICE_WIFI_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass))
-#define NM_IS_DEVICE_WIFI(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI))
-#define NM_IS_DEVICE_WIFI_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIFI))
-#define NM_DEVICE_WIFI_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIFI, NMDeviceWifiClass))
-
-#define NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS "perm-hw-address"
 #define NM_DEVICE_WIFI_MODE                "mode"
 #define NM_DEVICE_WIFI_BITRATE             "bitrate"
 #define NM_DEVICE_WIFI_ACCESS_POINTS       "access-points"
@@ -44,37 +39,11 @@ G_BEGIN_DECLS
 #define NM_DEVICE_WIFI_CAPABILITIES        "wireless-capabilities"
 #define NM_DEVICE_WIFI_SCANNING            "scanning"
 
-#ifndef NM_DEVICE_WIFI_DEFINED
-#define NM_DEVICE_WIFI_DEFINED
 typedef struct _NMDeviceWifi NMDeviceWifi;
-#endif
-
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
-typedef struct _NMDeviceWifiPrivate NMDeviceWifiPrivate;
-
-struct _NMDeviceWifi
-{
-	NMDevice parent;
-
-	/*< private >*/
-	NMDeviceWifiPrivate *priv;
-};
-
-struct _NMDeviceWifiClass
-{
-	NMDeviceClass parent;
-
-	/* Signals */
-	void (*access_point_added)   (NMDeviceWifi *device, NMAccessPoint *ap);
-	void (*access_point_removed) (NMDeviceWifi *device, NMAccessPoint *ap);
-	gboolean (*scanning_allowed) (NMDeviceWifi *device);
-};
-
 
 GType nm_device_wifi_get_type (void);
 
 NMDevice * nm_device_wifi_new (const char *iface, NMDeviceWifiCapabilities capabilities);
 
-G_END_DECLS
-
-#endif	/* NM_DEVICE_WIFI_H */
+#endif /* __NETWORKMANAGER_DEVICE_WIFI_H__ */
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index a0c25709..e1beb85b 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -42,29 +42,48 @@ typedef struct
 	char *supplicant_path;   /* D-Bus object path of this AP from wpa_supplicant */
 
 	/* Scanned or cached values */
-	GByteArray *	ssid;
-	char *          address;
-	NM80211Mode		mode;
-	guint8			strength;
-	guint32			freq;		/* Frequency in MHz; ie 2412 (== 2.412 GHz) */
-	guint32			max_bitrate;/* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */
+	GByteArray *       ssid;
+	char *             address;
+	NM80211Mode        mode;
+	guint8             strength;
+	guint32            freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
+	guint32            max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */
 
 	NM80211ApFlags         flags;      /* General flags */
 	NM80211ApSecurityFlags wpa_flags;  /* WPA-related flags */
 	NM80211ApSecurityFlags rsn_flags;  /* RSN (WPA2) -related flags */
 
 	/* Non-scanned attributes */
-	gboolean			fake;	/* Whether or not the AP is from a scan */
-	gboolean            hotspot;    /* Whether the AP is a local device's hotspot network */
+	bool                fake;       /* Whether or not the AP is from a scan */
+	bool                hotspot;    /* 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()) */
 } NMAccessPointPrivate;
 
-#define NM_AP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_AP, NMAccessPointPrivate))
+struct _NMAccessPoint {
+	NMExportedObject parent;
+	NMAccessPointPrivate _priv;
+};
+
+struct _NMAccessPointClass{
+	NMExportedObjectClass parent;
+};
+
+#define NM_AP_GET_PRIVATE(self) \
+	({ \
+		/* preserve the const-ness of self. Unfortunately, that
+		 * way, @self cannot be a void pointer */ \
+		typeof (self) _self = (self); \
+		\
+		/* Get compiler error if variable is of wrong type */ \
+		_nm_unused const NMAccessPoint *_self2 = (_self); \
+		\
+		nm_assert (NM_IS_AP (_self)); \
+		&_self->_priv; \
+	})
 
 G_DEFINE_TYPE (NMAccessPoint, nm_ap, NM_TYPE_EXPORTED_OBJECT)
 
-enum {
-	PROP_0,
+NM_GOBJECT_PROPERTIES_DEFINE (NMAccessPoint,
 	PROP_FLAGS,
 	PROP_WPA_FLAGS,
 	PROP_RSN_FLAGS,
@@ -75,8 +94,7 @@ enum {
 	PROP_MAX_BITRATE,
 	PROP_STRENGTH,
 	PROP_LAST_SEEN,
-	LAST_PROP
-};
+);
 
 /*****************************************************************/
 
@@ -130,7 +148,7 @@ nm_ap_set_ssid (NMAccessPoint *ap, const guint8 *ssid, gsize len)
 		g_byte_array_append (priv->ssid, ssid, len);
 	}
 
-	g_object_notify (G_OBJECT (ap), NM_AP_SSID);
+	_notify (ap, PROP_SSID);
 }
 
 static void
@@ -144,7 +162,7 @@ nm_ap_set_flags (NMAccessPoint *ap, NM80211ApFlags flags)
 
 	if (priv->flags != flags) {
 		priv->flags = flags;
-		g_object_notify (G_OBJECT (ap), NM_AP_FLAGS);
+		_notify (ap, PROP_FLAGS);
 	}
 }
 
@@ -158,7 +176,7 @@ nm_ap_set_wpa_flags (NMAccessPoint *ap, NM80211ApSecurityFlags flags)
 	priv = NM_AP_GET_PRIVATE (ap);
 	if (priv->wpa_flags != flags) {
 		priv->wpa_flags = flags;
-		g_object_notify (G_OBJECT (ap), NM_AP_WPA_FLAGS);
+		_notify (ap, PROP_WPA_FLAGS);
 	}
 }
 
@@ -172,7 +190,7 @@ nm_ap_set_rsn_flags (NMAccessPoint *ap, NM80211ApSecurityFlags flags)
 	priv = NM_AP_GET_PRIVATE (ap);
 	if (priv->rsn_flags != flags) {
 		priv->rsn_flags = flags;
-		g_object_notify (G_OBJECT (ap), NM_AP_RSN_FLAGS);
+		_notify (ap, PROP_RSN_FLAGS);
 	}
 }
 
@@ -198,7 +216,7 @@ nm_ap_set_address (NMAccessPoint *ap, const char *addr)
 	if (!priv->address || !nm_utils_hwaddr_matches (addr, -1, priv->address, -1)) {
 		g_free (priv->address);
 		priv->address = g_strdup (addr);
-		g_object_notify (G_OBJECT (ap), NM_AP_HW_ADDRESS);
+		_notify (ap, PROP_HW_ADDRESS);
 	}
 }
 
@@ -223,7 +241,7 @@ nm_ap_set_mode (NMAccessPoint *ap, const NM80211Mode mode)
 
 	if (priv->mode != mode) {
 		priv->mode = mode;
-		g_object_notify (G_OBJECT (ap), NM_AP_MODE);
+		_notify (ap, PROP_MODE);
 	}
 }
 
@@ -254,7 +272,7 @@ nm_ap_set_strength (NMAccessPoint *ap, const gint8 strength)
 
 	if (priv->strength != strength) {
 		priv->strength = strength;
-		g_object_notify (G_OBJECT (ap), NM_AP_STRENGTH);
+		_notify (ap, PROP_STRENGTH);
 	}
 }
 
@@ -278,7 +296,7 @@ nm_ap_set_freq (NMAccessPoint *ap,
 
 	if (priv->freq != freq) {
 		priv->freq = freq;
-		g_object_notify (G_OBJECT (ap), NM_AP_FREQUENCY);
+		_notify (ap, PROP_FREQUENCY);
 	}
 }
 
@@ -302,7 +320,7 @@ nm_ap_set_max_bitrate (NMAccessPoint *ap, guint32 bitrate)
 
 	if (priv->max_bitrate != bitrate) {
 		priv->max_bitrate = bitrate;
-		g_object_notify (G_OBJECT (ap), NM_AP_MAX_BITRATE);
+		_notify (ap, PROP_MAX_BITRATE);
 	}
 }
 
@@ -333,7 +351,7 @@ nm_ap_set_last_seen (NMAccessPoint *ap, gint32 last_seen)
 
 	if (priv->last_seen != last_seen) {
 		priv->last_seen = last_seen;
-		g_object_notify (G_OBJECT (ap), NM_AP_LAST_SEEN);
+		_notify (ap, PROP_LAST_SEEN);
 	}
 }
 
@@ -347,18 +365,20 @@ security_from_vardict (GVariant *security)
 
 	g_return_val_if_fail (g_variant_is_of_type (security, G_VARIANT_TYPE_VARDICT), NM_802_11_AP_SEC_NONE);
 
-	if (g_variant_lookup (security, "KeyMgmt", "^a&s", &array)) {
-		if (_nm_utils_string_in_list ("wpa-psk", array))
+	if (   g_variant_lookup (security, "KeyMgmt", "^a&s", &array)
+	    && array) {
+		if (g_strv_contains (array, "wpa-psk"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
-		if (_nm_utils_string_in_list ("wpa-eap", array))
+		if (g_strv_contains (array, "wpa-eap"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
 		g_free (array);
 	}
 
-	if (g_variant_lookup (security, "Pairwise", "^a&s", &array)) {
-		if (_nm_utils_string_in_list ("tkip", array))
+	if (   g_variant_lookup (security, "Pairwise", "^a&s", &array)
+	    && array) {
+		if (g_strv_contains (array, "tkip"))
 			flags |= NM_802_11_AP_SEC_PAIR_TKIP;
-		if (_nm_utils_string_in_list ("ccmp", array))
+		if (g_strv_contains (array, "ccmp"))
 			flags |= NM_802_11_AP_SEC_PAIR_CCMP;
 		g_free (array);
 	}
@@ -879,7 +899,7 @@ nm_ap_init (NMAccessPoint *ap)
 static void
 finalize (GObject *object)
 {
-	NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE (object);
+	NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE ((NMAccessPoint *) object);
 
 	g_free (priv->supplicant_path);
 	if (priv->ssid)
@@ -891,16 +911,16 @@ finalize (GObject *object)
 
 static void
 set_property (GObject *object, guint prop_id,
-		    const GValue *value, GParamSpec *pspec)
+              const GValue *value, GParamSpec *pspec)
 {
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 }
 
 static void
 get_property (GObject *object, guint prop_id,
-			  GValue *value, GParamSpec *pspec)
+              GValue *value, GParamSpec *pspec)
 {
-	NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE (object);
+	NMAccessPointPrivate *priv = NM_AP_GET_PRIVATE ((NMAccessPoint *) object);
 	GVariant *ssid;
 
 	switch (prop_id) {
@@ -965,8 +985,6 @@ nm_ap_class_init (NMAccessPointClass *ap_class)
 	                                             | NM_802_11_AP_SEC_KEY_MGMT_PSK
 	                                             | NM_802_11_AP_SEC_KEY_MGMT_802_1X;
 
-	g_type_class_add_private (ap_class, sizeof (NMAccessPointPrivate));
-
 	exported_object_class->export_path = NM_DBUS_PATH_ACCESS_POINT "/%u";
 
 	/* virtual methods */
@@ -975,72 +993,64 @@ nm_ap_class_init (NMAccessPointClass *ap_class)
 	object_class->finalize = finalize;
 
 	/* properties */
-	g_object_class_install_property
-	    (object_class, PROP_FLAGS,
-	     g_param_spec_uint (NM_AP_FLAGS, "", "",
-	                        NM_802_11_AP_FLAGS_NONE,
-	                        NM_802_11_AP_FLAGS_PRIVACY,
-	                        NM_802_11_AP_FLAGS_NONE,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_WPA_FLAGS,
-	     g_param_spec_uint (NM_AP_WPA_FLAGS, "", "",
-	                        NM_802_11_AP_SEC_NONE,
-	                        all_sec_flags,
-	                        NM_802_11_AP_SEC_NONE,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_RSN_FLAGS,
-	     g_param_spec_uint (NM_AP_RSN_FLAGS, "", "",
-	                        NM_802_11_AP_SEC_NONE,
-	                        all_sec_flags,
-	                        NM_802_11_AP_SEC_NONE,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_SSID,
-	     g_param_spec_variant (NM_AP_SSID, "", "",
-	                           G_VARIANT_TYPE ("ay"),
-	                           NULL,
-	                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_FREQUENCY,
-	     g_param_spec_uint (NM_AP_FREQUENCY, "", "",
-	                        0, 10000, 0,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_HW_ADDRESS,
-	     g_param_spec_string (NM_AP_HW_ADDRESS, "", "",
+	obj_properties[PROP_FLAGS] =
+	    g_param_spec_uint (NM_AP_FLAGS, "", "",
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       NM_802_11_AP_FLAGS_PRIVACY,
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_WPA_FLAGS] =
+	    g_param_spec_uint (NM_AP_WPA_FLAGS, "", "",
+	                       NM_802_11_AP_SEC_NONE,
+	                       all_sec_flags,
+	                       NM_802_11_AP_SEC_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_RSN_FLAGS] =
+	    g_param_spec_uint (NM_AP_RSN_FLAGS, "", "",
+	                       NM_802_11_AP_SEC_NONE,
+	                       all_sec_flags,
+	                       NM_802_11_AP_SEC_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SSID] =
+	    g_param_spec_variant (NM_AP_SSID, "", "",
+	                          G_VARIANT_TYPE ("ay"),
 	                          NULL,
-	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_MODE,
-	     g_param_spec_uint (NM_AP_MODE, "", "",
-	                        NM_802_11_MODE_ADHOC, NM_802_11_MODE_INFRA, NM_802_11_MODE_INFRA,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_MAX_BITRATE,
-	     g_param_spec_uint (NM_AP_MAX_BITRATE, "", "",
-	                        0, G_MAXUINT16, 0,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_STRENGTH,
-	     g_param_spec_uchar (NM_AP_STRENGTH, "", "",
-	                         0, G_MAXINT8, 0,
-	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_LAST_SEEN,
-	     g_param_spec_int (NM_AP_LAST_SEEN, "", "",
-	                       -1, G_MAXINT, -1,
-	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_FREQUENCY] =
+	    g_param_spec_uint (NM_AP_FREQUENCY, "", "",
+	                       0, 10000, 0,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_HW_ADDRESS] =
+	    g_param_spec_string (NM_AP_HW_ADDRESS, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODE] =
+	    g_param_spec_uint (NM_AP_MODE, "", "",
+	                       NM_802_11_MODE_ADHOC, NM_802_11_MODE_INFRA, NM_802_11_MODE_INFRA,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MAX_BITRATE] =
+	    g_param_spec_uint (NM_AP_MAX_BITRATE, "", "",
+	                       0, G_MAXUINT16, 0,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_STRENGTH] =
+	    g_param_spec_uchar (NM_AP_STRENGTH, "", "",
+	                        0, G_MAXINT8, 0,
+	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_LAST_SEEN] =
+	    g_param_spec_int (NM_AP_LAST_SEEN, "", "",
+	                      -1, G_MAXINT, -1,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (ap_class),
 	                                        NMDBUS_TYPE_ACCESS_POINT_SKELETON,
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 84aff983..035d1b5e 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -44,14 +44,8 @@
 #define NM_AP_STRENGTH "strength"
 #define NM_AP_LAST_SEEN "last-seen"
 
-typedef struct {
-	NMExportedObject parent;
-} NMAccessPoint;
-
-typedef struct {
-	NMExportedObjectClass parent;
-
-} NMAccessPointClass;
+typedef struct _NMAccessPoint NMAccessPoint;
+typedef struct _NMAccessPointClass NMAccessPointClass;
 
 GType nm_ap_get_type (void);
 
diff --git a/src/devices/wifi/tests/Makefile.in b/src/devices/wifi/tests/Makefile.in
index 956730f8..64113a21 100644
--- a/src/devices/wifi/tests/Makefile.in
+++ b/src/devices/wifi/tests/Makefile.in
@@ -92,8 +92,9 @@ noinst_PROGRAMS = test-wifi-ap-utils$(EXEEXT)
 TESTS = test-wifi-ap-utils$(EXEEXT)
 subdir = src/devices/wifi/tests
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -423,6 +424,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -479,13 +481,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -497,6 +500,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -557,6 +562,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -592,6 +599,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
diff --git a/src/devices/wifi/tests/test-wifi-ap-utils.c b/src/devices/wifi/tests/test-wifi-ap-utils.c
index 838368c4..dbe185b3 100644
--- a/src/devices/wifi/tests/test-wifi-ap-utils.c
+++ b/src/devices/wifi/tests/test-wifi-ap-utils.c
@@ -26,7 +26,7 @@
 
 #include "nm-core-internal.h"
 
-#include "nm-test-utils.h"
+#include "nm-test-utils-core.h"
 
 #define DEBUG 1
 
diff --git a/src/devices/wwan/Makefile.am b/src/devices/wwan/Makefile.am
index 1777eb36..0d061a79 100644
--- a/src/devices/wwan/Makefile.am
+++ b/src/devices/wwan/Makefile.am
@@ -16,6 +16,7 @@ AM_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\""NetworkManager-wwan"\" \
 	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	$(GLIB_CFLAGS) \
+	$(LIBSYSTEMD_CFLAGS) \
 	$(MM_GLIB_CFLAGS)
 
 BUILT_SOURCES = $(null)
@@ -26,7 +27,7 @@ pkglib_LTLIBRARIES = libnm-wwan.la libnm-device-plugin-wwan.la
 
 GLIB_GENERATED = nm-modem-enum-types.h nm-modem-enum-types.c
 GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM
-GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
+GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM --fhead '\#include "nm-default.h"\n'
 nm_modem_enum_types_sources = $(srcdir)/nm-modem.h
 
 BUILT_SOURCES += $(GLIB_GENERATED)
@@ -41,6 +42,13 @@ libnm_wwan_la_SOURCES = \
 	\
 	$(GLIB_GENERATED)
 
+if WITH_OFONO
+libnm_wwan_la_SOURCES += \
+	nm-modem-ofono.c \
+	nm-modem-ofono.h \
+	$(NULL)
+endif
+
 WWAN_SYMBOL_VIS_FILE=$(srcdir)/wwan-exports.ver
 
 libnm_wwan_la_LDFLAGS = \
@@ -49,6 +57,7 @@ libnm_wwan_la_LDFLAGS = \
 libnm_wwan_la_LIBADD = \
 	$(top_builddir)/introspection/libnmdbus.la \
 	$(GLIB_LIBS) \
+	$(LIBSYSTEMD_LIBS) \
 	$(MM_GLIB_LIBS)
 
 ###########################################################
@@ -79,7 +88,9 @@ if ENABLE_TESTS
 
 check-local:
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wwan.so $(SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-wwan.so $(WWAN_SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 endif
 
diff --git a/src/devices/wwan/Makefile.in b/src/devices/wwan/Makefile.in
index 89328665..6d395567 100644
--- a/src/devices/wwan/Makefile.in
+++ b/src/devices/wwan/Makefile.in
@@ -88,10 +88,16 @@ PRE_UNINSTALL = :
 POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
+@WITH_OFONO_TRUE@am__append_1 = \
+@WITH_OFONO_TRUE@	nm-modem-ofono.c \
+@WITH_OFONO_TRUE@	nm-modem-ofono.h \
+@WITH_OFONO_TRUE@	$(NULL)
+
 subdir = src/devices/wwan
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -157,10 +163,16 @@ libnm_device_plugin_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
 	$(LDFLAGS) -o $@
 libnm_wwan_la_DEPENDENCIES =  \
 	$(top_builddir)/introspection/libnmdbus.la \
-	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_1)
+am__libnm_wwan_la_SOURCES_DIST = nm-modem-broadband.c \
+	nm-modem-broadband.h nm-modem-manager.c nm-modem-manager.h \
+	nm-modem.c nm-modem.h nm-modem-enum-types.h \
+	nm-modem-enum-types.c nm-modem-ofono.c nm-modem-ofono.h
 am__objects_1 = nm-modem-enum-types.lo
+@WITH_OFONO_TRUE@am__objects_2 = nm-modem-ofono.lo
 am_libnm_wwan_la_OBJECTS = nm-modem-broadband.lo nm-modem-manager.lo \
-	nm-modem.lo $(am__objects_1)
+	nm-modem.lo $(am__objects_1) $(am__objects_2)
 libnm_wwan_la_OBJECTS = $(am_libnm_wwan_la_OBJECTS)
 libnm_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
 	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
@@ -202,7 +214,7 @@ am__v_CCLD_1 =
 SOURCES = $(libnm_device_plugin_wwan_la_SOURCES) \
 	$(libnm_wwan_la_SOURCES)
 DIST_SOURCES = $(libnm_device_plugin_wwan_la_SOURCES) \
-	$(libnm_wwan_la_SOURCES)
+	$(am__libnm_wwan_la_SOURCES_DIST)
 am__can_run_installinfo = \
   case $$AM_UPDATE_INFO_DIR in \
     n|no|NO) false;; \
@@ -264,6 +276,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -320,13 +333,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -338,6 +352,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -398,6 +414,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -433,6 +451,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -508,6 +527,7 @@ AM_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\""NetworkManager-wwan"\" \
 	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	$(GLIB_CFLAGS) \
+	$(LIBSYSTEMD_CFLAGS) \
 	$(MM_GLIB_CFLAGS)
 
 BUILT_SOURCES = $(null) $(GLIB_GENERATED)
@@ -516,18 +536,11 @@ pkglib_LTLIBRARIES = libnm-wwan.la libnm-device-plugin-wwan.la
 ###########################################################
 GLIB_GENERATED = nm-modem-enum-types.h nm-modem-enum-types.c
 GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM
-GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
+GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM --fhead '\#include "nm-default.h"\n'
 nm_modem_enum_types_sources = $(srcdir)/nm-modem.h
-libnm_wwan_la_SOURCES = \
-	nm-modem-broadband.c \
-	nm-modem-broadband.h \
-	nm-modem-manager.c \
-	nm-modem-manager.h \
-	nm-modem.c \
-	nm-modem.h \
-	\
-	$(GLIB_GENERATED)
-
+libnm_wwan_la_SOURCES = nm-modem-broadband.c nm-modem-broadband.h \
+	nm-modem-manager.c nm-modem-manager.h nm-modem.c nm-modem.h \
+	$(GLIB_GENERATED) $(am__append_1)
 WWAN_SYMBOL_VIS_FILE = $(srcdir)/wwan-exports.ver
 libnm_wwan_la_LDFLAGS = \
 	-avoid-version \
@@ -536,6 +549,7 @@ libnm_wwan_la_LDFLAGS = \
 libnm_wwan_la_LIBADD = \
 	$(top_builddir)/introspection/libnmdbus.la \
 	$(GLIB_LIBS) \
+	$(LIBSYSTEMD_LIBS) \
 	$(MM_GLIB_LIBS)
 
 
@@ -646,6 +660,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-broadband.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-enum-types.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-manager.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-ofono.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wwan-factory.Plo@am__quote@
 
@@ -897,7 +912,9 @@ include $(GLIB_MAKEFILE)
 
 @ENABLE_TESTS_TRUE@check-local:
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wwan.so $(SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-wwan.so $(WWAN_SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index a8361c49..0f96dafb 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -60,6 +60,7 @@ static void
 ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceModem *self = NM_DEVICE_MODEM (user_data);
 
 	switch (nm_device_get_state (device)) {
 	case NM_DEVICE_STATE_PREPARE:
@@ -73,7 +74,18 @@ ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
 			nm_device_activate_schedule_ip4_config_timeout (device);
-		else {
+		else if (nm_device_activate_ip6_state_in_conf (device))
+			nm_device_activate_schedule_ip6_config_timeout (device);
+		else if (nm_device_activate_ip4_state_done (device)) {
+			nm_device_ip_method_failed (device,
+			                            AF_INET,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		} else if (nm_device_activate_ip6_state_done (device)) {
+			nm_device_ip_method_failed (device,
+			                            AF_INET6,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		} else {
+			_LOGW (LOGD_MB, "PPP failure in unexpected state %u", (guint) nm_device_get_state (device));
 			nm_device_state_changed (device,
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
@@ -159,8 +171,9 @@ modem_ip4_config_result (NMModem *modem,
 	if (error) {
 		_LOGW (LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s",
 		       error->message);
-
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		nm_device_ip_method_failed (device,
+		                            AF_INET,
+		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else {
 		nm_device_set_wwan_ip4_config (device, config);
 		nm_device_activate_schedule_ip4_config_result (device, NULL);
@@ -184,9 +197,11 @@ modem_ip6_config_result (NMModem *modem,
 	g_return_if_fail (nm_device_activate_ip6_state_in_conf (device) == TRUE);
 
 	if (error) {
-		_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s", error->message);
-
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s",
+		       error->message);
+		nm_device_ip_method_failed (device,
+		                            AF_INET6,
+		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return;
 	}
 
@@ -201,7 +216,9 @@ modem_ip6_config_result (NMModem *modem,
 			nm_device_activate_schedule_ip6_config_result (device);
 		else {
 			_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: SLAAC not requested and no addresses");
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+			nm_device_ip_method_failed (device,
+			                            AF_INET6,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		}
 		return;
 	}
@@ -211,7 +228,7 @@ modem_ip6_config_result (NMModem *modem,
 	g_assert (ignored == NULL);
 	switch (ret) {
 	case NM_ACT_STAGE_RETURN_FAILURE:
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		nm_device_ip_method_failed (device, AF_INET6, reason);
 		break;
 	case NM_ACT_STAGE_RETURN_STOP:
 		/* all done */
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 1a6e6b25..727c8409 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -30,6 +30,8 @@
 #include "nm-device-private.h"
 #include "nm-platform.h"
 
+#define NM_MODEM_BROADBAND_MODEM "modem"
+
 G_DEFINE_TYPE (NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM)
 
 typedef enum {
@@ -89,8 +91,35 @@ enum {
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG_PREFIX_NAME "modem-broadband"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel _level = (level); \
+        \
+        if (nm_logging_enabled (_level, (_NMLOG_DOMAIN))) { \
+            NMModemBroadband *const __self = (self); \
+            char __prefix_name[128]; \
+            const char *__uid; \
+            \
+            _nm_log (_level, (_NMLOG_DOMAIN), 0, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (__self \
+                         ? ({ \
+                                ((__uid = nm_modem_get_uid ((NMModem *) __self)) \
+                                    ? nm_sprintf_buf (__prefix_name, "[%s]", __uid) \
+                                    : "(null)"); \
+                            }) \
+                         : "") \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
 static NMDeviceStateReason
-translate_mm_error (GError *error)
+translate_mm_error (NMModemBroadband *self, GError *error)
 {
 	NMDeviceStateReason reason;
 
@@ -122,7 +151,7 @@ translate_mm_error (GError *error)
 		reason = NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT;
 	else {
 		/* unable to map the ModemManager error to a NM_DEVICE_STATE_REASON */
-		nm_log_dbg (LOGD_MB, "unmapped error detected: '%s'", error->message);
+		_LOGD ("unmapped error detected: '%s'", error->message);
 		reason = NM_DEVICE_STATE_REASON_UNKNOWN;
 	}
 
@@ -288,7 +317,7 @@ connect_context_clear (NMModemBroadband *self)
 
 		g_clear_error (&ctx->first_error);
 		g_clear_pointer (&ctx->ip_types, (GDestroyNotify) g_array_unref);
-		g_clear_object (&ctx->cancellable);
+		nm_clear_g_cancellable (&ctx->cancellable);
 		g_clear_object (&ctx->connection);
 		g_clear_object (&ctx->connect_properties);
 		g_clear_object (&ctx->self);
@@ -360,8 +389,7 @@ connect_ready (MMModemSimple *simple_iface,
 
 	if (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN &&
 	    ip6_method == NM_MODEM_IP_METHOD_UNKNOWN) {
-		nm_log_warn (LOGD_MB, "(%s): failed to connect modem: invalid bearer IP configuration",
-		             nm_modem_get_uid (NM_MODEM (self)));
+		_LOGW ("failed to connect modem: invalid bearer IP configuration");
 		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		connect_context_clear (self);
 		return;
@@ -389,7 +417,7 @@ send_pin_ready (MMSim *sim, GAsyncResult *result, NMModemBroadband *self)
 		return;
 
 	if (!self->priv->ctx || self->priv->ctx->step != CONNECT_STEP_UNLOCK)
-		return;
+		g_return_if_reached ();
 
 	if (error) {
 		if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) ||
@@ -397,7 +425,7 @@ send_pin_ready (MMSim *sim, GAsyncResult *result, NMModemBroadband *self)
 		     mm_modem_get_unlock_required (self->priv->modem_iface) == MM_MODEM_LOCK_SIM_PIN)) {
 			ask_for_pin (self);
 		} else {
-			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error));
+			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (self, error));
 		}
 		return;
 	}
@@ -459,9 +487,8 @@ connect_context_step (NMModemBroadband *self)
 		else if (MODEM_CAPS_3GPP2 (ctx->caps))
 			ctx->connect_properties = create_cdma_connect_properties (ctx->connection);
 		else {
-			nm_log_warn (LOGD_MB, "(%s): Failed to connect '%s': not a mobile broadband modem",
-			             nm_modem_get_uid (NM_MODEM (self)),
-			             nm_connection_get_id (ctx->connection));
+			_LOGW ("failed to connect '%s': not a mobile broadband modem",
+			       nm_connection_get_id (ctx->connection));
 
 			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
 			connect_context_clear (self);
@@ -472,10 +499,9 @@ connect_context_step (NMModemBroadband *self)
 		/* 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);
 		if (!ctx->ip_types) {
-			nm_log_warn (LOGD_MB, "(%s): Failed to connect '%s': %s",
-			             nm_modem_get_uid (NM_MODEM (self)),
-			             nm_connection_get_id (ctx->connection),
-			             error->message);
+			_LOGW ("failed to connect '%s': %s",
+			       nm_connection_get_id (ctx->connection),
+			       error->message);
 			g_clear_error (&error);
 
 			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
@@ -502,10 +528,9 @@ connect_context_step (NMModemBroadband *self)
 			else
 				g_assert_not_reached ();
 
-			nm_log_dbg (LOGD_MB, "(%s): launching connection with ip type '%s' (try %d)",
-			            nm_modem_get_uid (NM_MODEM (self)),
-			            nm_modem_ip_type_to_string (current),
-			            ctx->ip_type_tries + 1);
+			_LOGD ("launching connection with ip type '%s' (try %d)",
+			       nm_modem_ip_type_to_string (current),
+			       ctx->ip_type_tries + 1);
 
 			mm_modem_simple_connect (self->priv->simple_iface,
 			                         ctx->connect_properties,
@@ -528,10 +553,9 @@ connect_context_step (NMModemBroadband *self)
 				                                        NM_DEVICE_ERROR_INVALID_CONNECTION,
 				                                        "invalid bearer IP configuration");
 
-			nm_log_warn (LOGD_MB, "(%s): failed to connect modem: %s",
-					     nm_modem_get_uid (NM_MODEM (self)),
-					     ctx->first_error->message);
-			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (ctx->first_error));
+			_LOGW ("failed to connect modem: %s",
+			       ctx->first_error->message);
+			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (self, ctx->first_error));
 		}
 
 		connect_context_clear (self);
@@ -550,8 +574,7 @@ act_stage1_prepare (NMModem *_self,
 	if (!self->priv->simple_iface) {
 		self->priv->simple_iface = mm_object_get_modem_simple (self->priv->modem_object);
 		if (!self->priv->simple_iface) {
-			nm_log_warn (LOGD_MB, "(%s) cannot access the Simple mobile broadband modem interface",
-			             nm_modem_get_uid (NM_MODEM (self)));
+			_LOGW ("cannot access the Simple mobile broadband modem interface");
 			*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
 			return NM_ACT_STAGE_RETURN_FAILURE;
 		}
@@ -745,9 +768,8 @@ set_power_state_low_ready (MMModem *modem,
 
 	if (!mm_modem_set_power_state_finish (modem, result, &error)) {
 		/* Log but ignore errors; not all modems support low power state */
-		nm_log_dbg (LOGD_MB, "(%s): failed to set modem low power state: %s",
-		            nm_modem_get_uid (NM_MODEM (self)),
-		            NM_G_ERROR_MSG (error));
+		_LOGD ("failed to set modem low power state: %s",
+		       NM_G_ERROR_MSG (error));
 		g_clear_error (&error);
 	}
 
@@ -770,9 +792,8 @@ modem_disable_ready (MMModem *modem_iface,
 		                          (GAsyncReadyCallback) set_power_state_low_ready,
 		                          g_object_ref (self));
 	} else {
-		nm_log_warn (LOGD_MB, "(%s): failed to disable modem: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             NM_G_ERROR_MSG (error));
+		_LOGW ("failed to disable modem: %s",
+		       NM_G_ERROR_MSG (error));
 		nm_modem_set_prev_state (NM_MODEM (self), "disable failed");
 		g_clear_error (&error);
 	}
@@ -789,9 +810,8 @@ modem_enable_ready (MMModem *modem_iface,
 	GError *error = NULL;
 
 	if (!mm_modem_enable_finish (modem_iface, res, &error)) {
-		nm_log_warn (LOGD_MB, "(%s) failed to enable modem: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             NM_G_ERROR_MSG (error));
+		_LOGW ("failed to enable modem: %s",
+		       NM_G_ERROR_MSG (error));
 		nm_modem_set_prev_state (NM_MODEM (self), "enable failed");
 		g_clear_error (&error);
 	}
@@ -854,8 +874,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	g_assert (self->priv->ipv4_config);
 	g_assert (self->priv->bearer);
 
-	nm_log_info (LOGD_MB, "(%s): IPv4 static configuration:",
-	             nm_modem_get_uid (NM_MODEM (self)));
+	_LOGI ("IPv4 static configuration:");
 
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->priv->ipv4_config);
@@ -880,15 +899,15 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	address.address = address_network;
 	address.peer_address = address_network;
 	address.plen = mm_bearer_ip_config_get_prefix (self->priv->ipv4_config);
-	address.source = NM_IP_CONFIG_SOURCE_WWAN;
+	address.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
 	if (address.plen <= 32)
 		nm_ip4_config_add_address (config, &address);
 
-	nm_log_info (LOGD_MB, "  address %s/%d", address_string, address.plen);
+	_LOGI ("  address %s/%d", address_string, address.plen);
 
 	if (gw) {
 		nm_ip4_config_set_gateway (config, gw);
-		nm_log_info (LOGD_MB, "  gateway %s", gw_string);
+		_LOGI ("  gateway %s", gw_string);
 	}
 
 	/* DNS servers */
@@ -897,7 +916,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 		if (   ip4_string_to_num (dns[i], &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
-			nm_log_info (LOGD_MB, "  DNS %s", dns[i]);
+			_LOGI ("  DNS %s", dns[i]);
 		}
 	}
 
@@ -964,8 +983,7 @@ stage3_ip6_done (NMModemBroadband *self)
 		goto out;
 	}
 
-	nm_log_info (LOGD_MB, "(%s): IPv6 base configuration:",
-	             nm_modem_get_uid (NM_MODEM (self)));
+	_LOGI ("IPv6 base configuration:");
 
 	data_port = mm_bearer_get_interface (self->priv->bearer);
 	g_assert (data_port);
@@ -975,7 +993,7 @@ stage3_ip6_done (NMModemBroadband *self)
 	if (address.plen <= 128)
 		nm_ip6_config_add_address (config, &address);
 
-	nm_log_info (LOGD_MB, "  address %s/%d", address_string, address.plen);
+	_LOGI ("  address %s/%d", address_string, address.plen);
 
 	address_string = mm_bearer_ip_config_get_gateway (self->priv->ipv6_config);
 	if (address_string) {
@@ -987,7 +1005,7 @@ stage3_ip6_done (NMModemBroadband *self)
 			                     address_string);
 			goto out;
 		}
-		nm_log_info (LOGD_MB, "  gateway %s", address_string);
+		_LOGI ("  gateway %s", address_string);
 		nm_ip6_config_set_gateway (config, &address.address);
 	} else if (ip_method == NM_MODEM_IP_METHOD_STATIC) {
 		/* Gateway required for the 'static' method */
@@ -1005,7 +1023,7 @@ stage3_ip6_done (NMModemBroadband *self)
 
 		if (inet_pton (AF_INET6, dns[i], &addr)) {
 			nm_ip6_config_add_nameserver (config, &addr);
-			nm_log_info (LOGD_MB, "  DNS %s", dns[i]);
+			_LOGI ("  DNS %s", dns[i]);
 		}
 	}
 
@@ -1050,19 +1068,6 @@ disconnect_context_complete (DisconnectContext *ctx)
 }
 
 static gboolean
-disconnect_context_complete_if_cancelled (DisconnectContext *ctx)
-{
-	GError *error = NULL;
-
-	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		disconnect_context_complete (ctx);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-static gboolean
 disconnect_finish (NMModem *self,
                    GAsyncResult *res,
                    GError **error)
@@ -1079,9 +1084,10 @@ simple_disconnect_ready (MMModemSimple *modem_iface,
 
 	if (!mm_modem_simple_disconnect_finish (modem_iface, res, &error)) {
 		if (ctx->warn && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
-			nm_log_warn (LOGD_MB, "(%s) failed to disconnect modem: %s",
-			             nm_modem_get_uid (NM_MODEM (ctx->self)),
-			             error->message);
+			NMModemBroadband *self = ctx->self;
+
+			_LOGW ("failed to disconnect modem: %s",
+			       error->message);
 		}
 		g_simple_async_result_take_error (ctx->result, error);
 	}
@@ -1098,9 +1104,11 @@ disconnect (NMModem *modem,
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 	DisconnectContext *ctx;
+	GError *error = NULL;
 
 	connect_context_clear (self);
 	ctx = g_slice_new (DisconnectContext);
+	ctx->cancellable = NULL;
 	ctx->self = g_object_ref (self);
 	ctx->result = g_simple_async_result_new (G_OBJECT (self),
 	                                         callback,
@@ -1109,10 +1117,12 @@ disconnect (NMModem *modem,
 	/* Don't bother warning on FAILED since the modem is already gone */
 	ctx->warn = warn;
 
-	/* Setup cancellable */
-	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	if (disconnect_context_complete_if_cancelled (ctx))
+	/* Already cancelled? */
+	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		g_simple_async_result_take_error (ctx->result, error);
+		disconnect_context_complete (ctx);
 		return;
+	}
 
 	/* If no simple iface, we're done */
 	if (!ctx->self->priv->simple_iface) {
@@ -1120,14 +1130,13 @@ disconnect (NMModem *modem,
 		return;
 	}
 
-	nm_log_dbg (LOGD_MB, "(%s): notifying ModemManager about the modem disconnection",
-	            nm_modem_get_uid (NM_MODEM (ctx->self)));
-	mm_modem_simple_disconnect (
-		ctx->self->priv->simple_iface,
-		NULL, /* bearer path; if NULL given ALL get disconnected */
-		cancellable,
-		(GAsyncReadyCallback)simple_disconnect_ready,
-		ctx);
+	_LOGD ("notifying ModemManager about the modem disconnection");
+	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	mm_modem_simple_disconnect (ctx->self->priv->simple_iface,
+	                            NULL, /* bearer path; if NULL given ALL get disconnected */
+	                            cancellable,
+	                            (GAsyncReadyCallback) simple_disconnect_ready,
+	                            ctx);
 }
 
 /*****************************************************************************/
@@ -1274,9 +1283,10 @@ get_sim_ready (MMModem *modem,
 		if (self->priv->ctx && self->priv->ctx->step == CONNECT_STEP_WAIT_FOR_SIM)
 			connect_context_step (self);
 	} else {
-		nm_log_warn (LOGD_MB, "(%s): failed to retrieve SIM object: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             NM_G_ERROR_MSG (error));
+		_NMLOG (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND)
+		            ? LOGL_INFO : LOGL_WARN,
+		        "failed to retrieve SIM object: %s",
+		        NM_G_ERROR_MSG (error));
 	}
 	g_clear_error (&error);
 	g_object_unref (self);
diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/devices/wwan/nm-modem-broadband.h
index a54e63a6..6607f7f5 100644
--- a/src/devices/wwan/nm-modem-broadband.h
+++ b/src/devices/wwan/nm-modem-broadband.h
@@ -23,8 +23,6 @@
 
 #include "nm-modem.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_MODEM_BROADBAND            (nm_modem_broadband_get_type ())
 #define NM_MODEM_BROADBAND(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadband))
 #define NM_MODEM_BROADBAND_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass))
@@ -32,8 +30,6 @@ G_BEGIN_DECLS
 #define NM_IS_MODEM_BROADBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_MODEM_BROADBAND))
 #define NM_MODEM_BROADBAND_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass))
 
-#define NM_MODEM_BROADBAND_MODEM "modem"
-
 typedef struct _NMModemBroadband        NMModemBroadband;
 typedef struct _NMModemBroadbandClass   NMModemBroadbandClass;
 typedef struct _NMModemBroadbandPrivate NMModemBroadbandPrivate;
@@ -51,6 +47,4 @@ GType nm_modem_broadband_get_type (void);
 
 NMModem *nm_modem_broadband_new (GObject *object, GError **error);
 
-G_END_DECLS
-
 #endif /* __NETWORKMANAGER_MODEM_BROADBAND_H__ */
diff --git a/src/devices/wwan/nm-modem-enum-types.c b/src/devices/wwan/nm-modem-enum-types.c
index 2d02ede9..f999b084 100644
--- a/src/devices/wwan/nm-modem-enum-types.c
+++ b/src/devices/wwan/nm-modem-enum-types.c
@@ -6,6 +6,7 @@
 #include "config.h"
 
 #include "nm-modem-enum-types.h"
+#include "nm-default.h"
 
 #include "nm-modem.h"
 
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index e26321b0..964485fc 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -17,20 +17,30 @@
  *
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
- * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 - 2013 Canonical Ltd.
  */
 
 #include "nm-default.h"
 
-#include <string.h>
+#include "nm-modem-manager.h"
 
+#include <string.h>
 #include <libmm-glib.h>
 
-#include "nm-modem-manager.h"
+#if HAVE_LIBSYSTEMD
+#include <systemd/sd-daemon.h>
+#else
+#define sd_booted() FALSE
+#endif
+
 #include "nm-dbus-compat.h"
 #include "nm-modem.h"
 #include "nm-modem-broadband.h"
 
+#if WITH_OFONO
+#include "nm-modem-ofono.h"
+#endif
+
 #define MODEM_POKE_INTERVAL 120
 
 G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT)
@@ -39,9 +49,13 @@ struct _NMModemManagerPrivate {
 	GDBusConnection *dbus_connection;
 	MMManager *modem_manager;
 	guint mm_launch_id;
-	guint mm_name_owner_changed_id;
-	guint mm_object_added_id;
-	guint mm_object_removed_id;
+	gulong mm_name_owner_changed_id;
+	gulong mm_object_added_id;
+	gulong mm_object_removed_id;
+
+#if WITH_OFONO
+	GDBusProxy *ofono_proxy;
+#endif
 
 	/* Common */
 	GHashTable *modems;
@@ -79,34 +93,14 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
 }
 
 static void
-modem_manager_clear_signals (NMModemManager *self)
+clear_modem_manager (NMModemManager *self)
 {
 	if (!self->priv->modem_manager)
 		return;
-
-	if (self->priv->mm_name_owner_changed_id) {
-		if (g_signal_handler_is_connected (self->priv->modem_manager,
-		                                   self->priv->mm_name_owner_changed_id))
-			g_signal_handler_disconnect (self->priv->modem_manager,
-			                             self->priv->mm_name_owner_changed_id);
-		self->priv->mm_name_owner_changed_id = 0;
-	}
-
-	if (self->priv->mm_object_added_id) {
-		if (g_signal_handler_is_connected (self->priv->modem_manager,
-		                                   self->priv->mm_object_added_id))
-			g_signal_handler_disconnect (self->priv->modem_manager,
-			                             self->priv->mm_object_added_id);
-		self->priv->mm_object_added_id = 0;
-	}
-
-	if (self->priv->mm_object_removed_id) {
-		if (g_signal_handler_is_connected (self->priv->modem_manager,
-		                                   self->priv->mm_object_removed_id))
-			g_signal_handler_disconnect (self->priv->modem_manager,
-			                             self->priv->mm_object_removed_id);
-		self->priv->mm_object_removed_id = 0;
-	}
+	nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_name_owner_changed_id);
+	nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_object_added_id);
+	nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_object_removed_id);
+	g_clear_object (&self->priv->modem_manager);
 }
 
 static void
@@ -183,7 +177,7 @@ modem_manager_available (NMModemManager *self)
 
 static void schedule_modem_manager_relaunch (NMModemManager *self,
                                              guint n_seconds);
-static void ensure_client                   (NMModemManager *self);
+static void ensure_modem_manager (NMModemManager *self);
 
 static void
 modem_manager_name_owner_changed (MMManager *modem_manager,
@@ -199,10 +193,9 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	if (!name_owner) {
 		nm_log_info (LOGD_MB, "ModemManager disappeared from bus");
 
-#if !HAVE_SYSTEMD
 		/* If not managed by systemd, schedule relaunch */
-		schedule_modem_manager_relaunch (self, 0);
-#endif
+		if (!sd_booted ())
+			schedule_modem_manager_relaunch (self, 0);
 
 		return;
 	}
@@ -214,16 +207,173 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	 * nor 'object-removed' if it was created while there was no ModemManager in
 	 * the bus. This hack avoids this issue until we get a GIO with the fix
 	 * included... */
-	modem_manager_clear_signals (self);
-	g_clear_object (&self->priv->modem_manager);
-	ensure_client (self);
+	clear_modem_manager (self);
+	ensure_modem_manager (self);
 
 	/* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
 	 * modem_manager_available (self);
 	 */
 }
 
-#if !HAVE_SYSTEMD
+#if WITH_OFONO
+static void
+ofono_create_modem (NMModemManager *self, const char *path)
+{
+	NMModem *modem = NULL;
+
+	/* Ensure duplicate modems aren't created.  Because we're not using the
+	 * ObjectManager interface there's a race during oFono startup where we
+	 * receive ModemAdded signals before GetModems() returns, so some of the
+	 * modems returned from GetModems() may already have been created.
+	 */
+	if (!g_hash_table_lookup (self->priv->modems, path)) {
+		modem = nm_modem_ofono_new (path);
+		if (modem)
+			handle_new_modem (self, modem);
+		else
+			nm_log_warn (LOGD_MB, "Failed to create oFono modem for %s", path);
+	}
+}
+
+static void
+ofono_signal_cb (GDBusProxy *proxy,
+                 gchar *sender_name,
+                 gchar *signal_name,
+                 GVariant *parameters,
+                 gpointer user_data)
+{
+	NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	gchar *object_path;
+	NMModem *modem;
+
+	if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
+		g_variant_get (parameters, "(oa{sv})", &object_path, NULL);
+		nm_log_info (LOGD_MB, "oFono modem appeared: %s", object_path);
+
+		ofono_create_modem (NM_MODEM_MANAGER (user_data), object_path);
+		g_free (object_path);
+	} else if (g_strcmp0 (signal_name, "ModemRemoved") == 0) {
+		g_variant_get (parameters, "(o)", &object_path);
+		nm_log_info (LOGD_MB, "oFono modem removed: %s", object_path);
+
+		modem = (NMModem *) g_hash_table_lookup (self->priv->modems, object_path);
+		if (modem) {
+			nm_modem_emit_removed (modem);
+			g_hash_table_remove (self->priv->modems, object_path);
+		} else {
+			nm_log_warn (LOGD_MB, "could not remove modem %s, not found in table",
+			             object_path);
+		}
+		g_free (object_path);
+	}
+}
+
+static void
+ofono_enumerate_devices_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+{
+	NMModemManager *manager = NM_MODEM_MANAGER (user_data);
+	gs_free_error GError *error = NULL;
+	GVariant *results;
+	GVariantIter *iter;
+	const char *path;
+
+	results = g_dbus_proxy_call_finish (proxy, res, &error);
+	if (results) {
+		g_variant_get (results, "(a(oa{sv}))", &iter);
+		while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
+			ofono_create_modem (manager, path);
+		g_variant_iter_free (iter);
+		g_variant_unref (results);
+	}
+
+	if (error) {
+		nm_log_warn (LOGD_MB, "failed to enumerate oFono devices: %s",
+		             error->message);
+	}
+}
+
+static void
+ofono_check_name_owner (NMModemManager *self)
+{
+	gs_free char *name_owner = NULL;
+
+	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->priv->ofono_proxy));
+	if (name_owner) {
+		nm_log_info (LOGD_MB, "oFono is now available");
+
+		g_dbus_proxy_call (self->priv->ofono_proxy,
+		                   "GetModems",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   NULL,
+		                   (GAsyncReadyCallback) ofono_enumerate_devices_done,
+		                   g_object_ref (self));
+	} else {
+		GHashTableIter iter;
+		NMModem *modem;
+
+		nm_log_info (LOGD_MB, "oFono disappeared from bus");
+
+		/* Remove any oFono modems that might be left around */
+		g_hash_table_iter_init (&iter, self->priv->modems);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &modem)) {
+			if (NM_IS_MODEM_OFONO (modem)) {
+				nm_modem_emit_removed (modem);
+				g_hash_table_iter_remove (&iter);
+			}
+		}
+	}
+}
+
+static void
+ofono_name_owner_changed (GDBusProxy *ofono_proxy,
+                          GParamSpec *pspec,
+                          NMModemManager *self)
+{
+	ofono_check_name_owner (self);
+}
+
+static void
+ofono_proxy_new_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	gs_free_error GError *error = NULL;
+
+	self->priv->ofono_proxy = g_dbus_proxy_new_finish (res, &error);
+	if (error) {
+		nm_log_warn (LOGD_MB, "error getting oFono bus proxy: %s", error->message);
+		return;
+	}
+
+	g_signal_connect (self->priv->ofono_proxy,
+	                  "notify::g-name-owner",
+	                  G_CALLBACK (ofono_name_owner_changed),
+	                  self);
+
+	g_signal_connect (self->priv->ofono_proxy,
+	                  "g-signal",
+	                  G_CALLBACK (ofono_signal_cb),
+	                  self);
+
+	ofono_check_name_owner (self);
+}
+
+static void
+ensure_ofono_client (NMModemManager *self)
+{
+	g_assert (self->priv->dbus_connection);
+	g_dbus_proxy_new (self->priv->dbus_connection,
+	                  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                  NULL,
+	                  OFONO_DBUS_SERVICE,
+	                  OFONO_DBUS_PATH,
+	                  OFONO_DBUS_INTERFACE,
+	                  NULL,
+	                  (GAsyncReadyCallback) ofono_proxy_new_cb,
+	                  g_object_ref (self));
+}
+#endif
 
 static void
 modem_manager_poke_cb (GDBusConnection *connection,
@@ -235,19 +385,18 @@ modem_manager_poke_cb (GDBusConnection *connection,
 
 	result = g_dbus_connection_call_finish (connection, res, &error);
 	if (error) {
-		/* Ignore common errors when MM is not installed and such */
-		if (   !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-			nm_log_dbg (LOGD_MB, "error poking ModemManager: %s", error->message);
+		nm_log_warn (LOGD_MB, "error poking ModemManager: %s",
+					error ? error->message : "");
+
+		/* Don't reschedule poke is MM service doesn't exist. */
+		if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+
+			/* Setup timeout to relaunch */
+			schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
 		}
-		g_error_free (error);
 
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		g_error_free (error);
 	} else
 		g_variant_unref (result);
 
@@ -273,25 +422,21 @@ modem_manager_poke (NMModemManager *self)
 	                        g_object_ref (self)); /* user_data */
 }
 
-#endif /* HAVE_SYSTEMD */
-
 static void
 modem_manager_check_name_owner (NMModemManager *self)
 {
-	gchar *name_owner;
+	gs_free gchar *name_owner = NULL;
 
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->priv->modem_manager));
 	if (name_owner) {
 		/* Available! */
 		modem_manager_available (self);
-		g_free (name_owner);
 		return;
 	}
 
-#if !HAVE_SYSTEMD
 	/* If the lifecycle is not managed by systemd, poke */
-	modem_manager_poke (self);
-#endif
+	if (!sd_booted ())
+		modem_manager_poke (self);
 }
 
 static void
@@ -303,7 +448,8 @@ manager_new_ready (GObject *source,
 
 	GError *error = NULL;
 
-	g_assert (!self->priv->modem_manager);
+	g_return_if_fail (!self->priv->modem_manager);
+
 	self->priv->modem_manager = mm_manager_new_finish (res, &error);
 	if (!self->priv->modem_manager) {
 		/* We're not really supposed to get any error here. If we do get one,
@@ -316,20 +462,20 @@ manager_new_ready (GObject *source,
 	} else {
 		/* Setup signals in the GDBusObjectManagerClient */
 		self->priv->mm_name_owner_changed_id =
-			g_signal_connect (self->priv->modem_manager,
-			                  "notify::name-owner",
-			                  G_CALLBACK (modem_manager_name_owner_changed),
-			                  self);
+		    g_signal_connect (self->priv->modem_manager,
+		                      "notify::name-owner",
+		                      G_CALLBACK (modem_manager_name_owner_changed),
+		                      self);
 		self->priv->mm_object_added_id =
-			g_signal_connect (self->priv->modem_manager,
-			                  "object-added",
-			                  G_CALLBACK (modem_object_added),
-			                  self);
+		    g_signal_connect (self->priv->modem_manager,
+		                      "object-added",
+		                      G_CALLBACK (modem_object_added),
+		                      self);
 		self->priv->mm_object_removed_id =
-			g_signal_connect (self->priv->modem_manager,
-			                  "object-removed",
-			                  G_CALLBACK (modem_object_removed),
-			                  self);
+		    g_signal_connect (self->priv->modem_manager,
+		                      "object-removed",
+		                      G_CALLBACK (modem_object_removed),
+		                      self);
 
 		modem_manager_check_name_owner (self);
 	}
@@ -339,7 +485,7 @@ manager_new_ready (GObject *source,
 }
 
 static void
-ensure_client (NMModemManager *self)
+ensure_modem_manager (NMModemManager *self)
 {
 	g_assert (self->priv->dbus_connection);
 
@@ -359,46 +505,12 @@ ensure_client (NMModemManager *self)
 	modem_manager_check_name_owner (self);
 }
 
-static void
-bus_get_ready (GObject *source,
-               GAsyncResult *res,
-               NMModemManager *self)
-{
-	/* Note we always get an extra reference to self here */
-
-	GError *error = NULL;
-
-	self->priv->dbus_connection = g_bus_get_finish (res, &error);
-	if (!self->priv->dbus_connection) {
-		nm_log_warn (LOGD_CORE, "error getting bus connection: %s", error->message);
-		g_error_free (error);
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-	} else {
-		/* Got the bus, ensure client */
-		ensure_client (self);
-	}
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
 static gboolean
-ensure_bus (NMModemManager *self)
+mm_launch_cb (NMModemManager *self)
 {
-	/* Clear launch ID */
 	self->priv->mm_launch_id = 0;
-
-	if (!self->priv->dbus_connection)
-		g_bus_get (G_BUS_TYPE_SYSTEM,
-		           NULL,
-		           (GAsyncReadyCallback)bus_get_ready,
-		           g_object_ref (self));
-	else
-		/* If bus is already available, ensure client */
-		ensure_client (self);
-
-	return FALSE;
+	ensure_modem_manager (self);
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -407,11 +519,31 @@ schedule_modem_manager_relaunch (NMModemManager *self,
 {
 	/* No need to pass an extra reference to self; timeout/idle will be
 	 * cancelled if the object gets disposed. */
-
 	if (n_seconds)
-		self->priv->mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)ensure_bus, self);
+		self->priv->mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)mm_launch_cb, self);
 	else
-		self->priv->mm_launch_id = g_idle_add ((GSourceFunc)ensure_bus, self);
+		self->priv->mm_launch_id = g_idle_add ((GSourceFunc)mm_launch_cb, self);
+}
+
+static void
+bus_get_ready (GObject *source,
+               GAsyncResult *res,
+               gpointer user_data)
+{
+	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	gs_free_error GError *error = NULL;
+
+	self->priv->dbus_connection = g_bus_get_finish (res, &error);
+	if (!self->priv->dbus_connection) {
+		nm_log_warn (LOGD_MB, "error getting bus connection: %s", error->message);
+		return;
+	}
+
+	/* Got the bus, ensure clients */
+	ensure_modem_manager (self);
+#if WITH_OFONO
+	ensure_ofono_client (self);
+#endif
 }
 
 /************************************************************************/
@@ -423,7 +555,11 @@ nm_modem_manager_init (NMModemManager *self)
 
 	self->priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
 
-	schedule_modem_manager_relaunch (self, 0);
+	// FIXME: this doesn't handle bus-daemon restart
+	g_bus_get (G_BUS_TYPE_SYSTEM,
+	           NULL,
+	           (GAsyncReadyCallback)bus_get_ready,
+	           g_object_ref (self));
 }
 
 static void
@@ -433,8 +569,16 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&self->priv->mm_launch_id);
 
-	modem_manager_clear_signals (self);
-	g_clear_object (&self->priv->modem_manager);
+	clear_modem_manager (self);
+
+#if WITH_OFONO
+	if (self->priv->ofono_proxy) {
+		g_signal_handlers_disconnect_by_func (self->priv->ofono_proxy, ofono_name_owner_changed, self);
+		g_signal_handlers_disconnect_by_func (self->priv->ofono_proxy, ofono_signal_cb, self);
+		g_clear_object (&self->priv->ofono_proxy);
+	}
+#endif
+
 	g_clear_object (&self->priv->dbus_connection);
 
 	if (self->priv->modems) {
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
new file mode 100644
index 00000000..86492d3d
--- /dev/null
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -0,0 +1,1202 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* 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) 2013 - 2016 Canonical Ltd.
+ */
+
+#include "nm-default.h"
+
+#include "nm-modem-ofono.h"
+
+#include <string.h>
+
+#include "nm-core-internal.h"
+#include "nm-device-private.h"
+#include "nm-modem.h"
+#include "nm-platform.h"
+
+G_DEFINE_TYPE (NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
+
+#define NM_MODEM_OFONO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_MODEM_OFONO, NMModemOfonoPrivate))
+
+#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) ))
+#define VARIANT_IS_OF_TYPE_DICTIONARY(v)   ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_DICTIONARY) ))
+
+typedef struct {
+	GHashTable *connect_properties;
+
+	GDBusProxy *modem_proxy;
+	GDBusProxy *connman_proxy;
+	GDBusProxy *context_proxy;
+	GDBusProxy *sim_proxy;
+
+	GError *property_error;
+
+	char *context_path;
+	char *imsi;
+
+	gboolean modem_online;
+	gboolean gprs_attached;
+
+	NMIP4Config *ip4_config;
+} NMModemOfonoPrivate;
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG_PREFIX_NAME "modem-ofono"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel _level = (level); \
+        \
+        if (nm_logging_enabled (_level, (_NMLOG_DOMAIN))) { \
+            NMModemOfono *const __self = (self); \
+            char __prefix_name[128]; \
+            const char *__uid; \
+            \
+            _nm_log (_level, (_NMLOG_DOMAIN), 0, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (__self \
+                         ? ({ \
+                                ((__uid = nm_modem_get_uid ((NMModem *) __self)) \
+                                    ? nm_sprintf_buf (__prefix_name, "[%s]", __uid) \
+                                    : "(null)"); \
+                            }) \
+                         : "") \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static gboolean
+ip_string_to_network_address (const gchar *str,
+                              guint32 *out)
+{
+	guint32 addr = 0;
+	gboolean success = FALSE;
+
+	if (!str || inet_pton (AF_INET, str, &addr) != 1)
+		addr = 0;
+	else
+		success = TRUE;
+
+	*out = (guint32)addr;
+	return success;
+}
+
+static void
+get_capabilities (NMModem *_self,
+                  NMDeviceModemCapabilities *modem_caps,
+                  NMDeviceModemCapabilities *current_caps)
+{
+	/* FIXME: auto-detect capabilities to allow LTE */
+	*modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
+	*current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
+}
+
+static void
+update_modem_state (NMModemOfono *self)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMModemState state = nm_modem_get_state (NM_MODEM (self));
+	NMModemState new_state = NM_MODEM_STATE_DISABLED;
+	const char *reason = NULL;
+
+	_LOGI ("'Attached': %s 'Online': %s 'IMSI': %s",
+	       priv->gprs_attached ? "true" : "false",
+	       priv->modem_online ? "true" : "false",
+	       priv->imsi);
+
+	if (priv->modem_online == FALSE) {
+		reason = "modem 'Online=false'";
+	} else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) {
+		reason = "modem not ready";
+	} else if (priv->gprs_attached == FALSE) {
+		new_state = NM_MODEM_STATE_SEARCHING;
+		reason = "modem searching";
+	} else {
+		new_state = NM_MODEM_STATE_REGISTERED;
+		reason = "modem ready";
+	}
+
+	if (state != new_state)
+		nm_modem_set_state (NM_MODEM (self), new_state, reason);
+}
+
+/* Disconnect */
+typedef struct {
+	NMModemOfono *self;
+	GSimpleAsyncResult *result;
+	GCancellable *cancellable;
+	gboolean warn;
+} DisconnectContext;
+
+static void
+disconnect_context_complete (DisconnectContext *ctx)
+{
+	g_simple_async_result_complete_in_idle (ctx->result);
+	if (ctx->cancellable)
+		g_object_unref (ctx->cancellable);
+	g_object_unref (ctx->result);
+	g_object_unref (ctx->self);
+	g_slice_free (DisconnectContext, ctx);
+}
+
+static gboolean
+disconnect_context_complete_if_cancelled (DisconnectContext *ctx)
+{
+	GError *error = NULL;
+
+	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
+		g_simple_async_result_take_error (ctx->result, error);
+		disconnect_context_complete (ctx);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+disconnect_finish (NMModem *self,
+                   GAsyncResult *result,
+                   GError **error)
+{
+	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void
+disconnect_done (GDBusProxy *proxy,
+				 GAsyncResult *result,
+				 gpointer user_data)
+{
+	DisconnectContext *ctx = (DisconnectContext*) user_data;
+	NMModemOfono *self = ctx->self;
+	GError *error = NULL;
+
+	g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		_LOGD ("disconnect cancelled");
+		return;
+	}
+
+	if (error) {
+		if (ctx->warn)
+			_LOGW ("failed to disconnect modem: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	_LOGD ("modem disconnected");
+
+	update_modem_state (self);
+	disconnect_context_complete (ctx);
+}
+
+static void
+disconnect (NMModem *modem,
+            gboolean warn,
+            GCancellable *cancellable,
+            GAsyncReadyCallback callback,
+            gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (modem);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	DisconnectContext *ctx;
+	NMModemState state = nm_modem_get_state (NM_MODEM (self));
+
+	_LOGD ("warn: %s modem_state: %s",
+	       warn ? "TRUE" : "FALSE",
+	       nm_modem_state_to_string (state));
+
+	if (state != NM_MODEM_STATE_CONNECTED)
+		return;
+
+	ctx = g_slice_new (DisconnectContext);
+	ctx->self = g_object_ref (self);
+	ctx->warn = warn;
+
+	if (callback) {
+		ctx->result = g_simple_async_result_new (G_OBJECT (self),
+		                                         callback,
+		                                         user_data,
+		                                         disconnect);
+	}
+	/* Setup cancellable */
+	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	if (disconnect_context_complete_if_cancelled (ctx))
+		return;
+
+	nm_modem_set_state (NM_MODEM (self),
+	                    NM_MODEM_STATE_DISCONNECTING,
+	                    nm_modem_state_to_string (NM_MODEM_STATE_DISCONNECTING));
+
+	g_dbus_proxy_call (priv->context_proxy,
+	                   "SetProperty",
+	                   g_variant_new ("(sv)",
+	                                  "Active",
+	                                  g_variant_new ("b", warn)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   NULL,
+	                   (GAsyncReadyCallback) disconnect_done,
+	                   ctx);
+}
+
+static void
+deactivate_cleanup (NMModem *_self, NMDevice *device)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (_self);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	/* TODO: cancel SimpleConnect() if any */
+
+	g_clear_object (&priv->ip4_config);
+
+	/* Chain up parent's */
+	NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (_self, device);
+}
+
+
+static gboolean
+check_connection_compatible (NMModem *modem,
+                             NMConnection *connection)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (modem);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	NMSettingGsm *s_gsm;
+	const char *uuid;
+	const char *id;
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+
+	uuid = nm_connection_get_uuid (connection);
+	id = nm_connection_get_id (connection);
+
+	s_gsm = nm_connection_get_setting_gsm (connection);
+	if (!s_gsm)
+		return FALSE;
+
+	if (!priv->imsi) {
+		_LOGW ("skipping %s/%s: no IMSI", uuid, id);
+		return FALSE;
+	}
+
+	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_GSM_SETTING_NAME)) {
+		_LOGD ("skipping %s/%s: not GSM", uuid, id);
+		return FALSE;
+	}
+
+	if (!g_strrstr (id, "/context")) {
+		_LOGD ("skipping %s/%s: unexpected ID", uuid, id);
+		return FALSE;
+	}
+
+	if (!g_strrstr (id, priv->imsi)) {
+		_LOGD ("skipping %s/%s: ID doesn't contain IMSI", uuid, id);
+		return FALSE;
+	}
+
+	_LOGD ("%s/%s compatible with IMSI %s", uuid, id, priv->imsi);
+	return TRUE;
+}
+
+static void
+handle_sim_property (GDBusProxy *proxy,
+                     const char *property,
+                     GVariant *v,
+                     gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (g_strcmp0 (property, "SubscriberIdentity") == 0 && VARIANT_IS_OF_TYPE_STRING (v)) {
+		gsize length;
+		const char *value_str = g_variant_get_string (v, &length);
+
+		_LOGD ("SubscriberIdentify found");
+
+		/* Check for empty DBus string value */
+		if (length &&
+			g_strcmp0 (value_str, "(null)") != 0 &&
+			g_strcmp0 (value_str, priv->imsi) != 0) {
+
+			if (priv->imsi != NULL) {
+				_LOGW ("SimManager:'SubscriberIdentity' changed: %s", priv->imsi);
+				g_free(priv->imsi);
+			}
+
+			priv->imsi = g_strdup (value_str);
+			update_modem_state (self);
+		}
+	}
+}
+
+static void
+sim_property_changed (GDBusProxy *proxy,
+                      const char *property,
+                      GVariant *v,
+                      gpointer user_data)
+{
+	GVariant *v_child = g_variant_get_child_value (v, 0);
+
+	handle_sim_property (proxy, property, v_child, user_data);
+	g_variant_unref (v_child);
+}
+
+static void
+sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	GError *error = NULL;
+	GVariant *v_properties, *v_dict, *v;
+	GVariantIter i;
+	const char *property;
+
+	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	                                           result,
+	                                           G_VARIANT_TYPE ("(a{sv})"),
+	                                           &error);
+	if (!v_properties) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("error getting sim properties: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	_LOGD ("sim v_properties is type: %s", g_variant_get_type_string (v_properties));
+
+	v_dict = g_variant_get_child_value (v_properties, 0);
+	if (!v_dict) {
+		_LOGW ("error getting sim properties: no v_dict");
+		return;
+	}
+
+	_LOGD ("sim v_dict is type: %s", g_variant_get_type_string (v_dict));
+
+	/*
+	 * TODO:
+	 * 1) optimize by looking up properties ( Online, Interfaces ), instead
+	 *    of iterating
+	 *
+	 * 2) reduce code duplication between all of the get_properties_done
+	 *    functions in this class.
+	 */
+
+	g_variant_iter_init (&i, v_dict);
+	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+		handle_sim_property (NULL, property, v, self);
+		g_variant_unref (v);
+	}
+
+	g_variant_unref (v_dict);
+	g_variant_unref (v_properties);
+}
+
+static void
+handle_sim_iface (NMModemOfono *self, gboolean found)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	_LOGD ("SimManager interface %sfound", found ? "" : "not ");
+
+	if (!found && priv->sim_proxy) {
+		_LOGI ("SimManager interface disappeared");
+		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->sim_proxy);
+		g_clear_pointer (&priv->imsi, g_free);
+		update_modem_state (self);
+	} else if (found && !priv->sim_proxy) {
+		GError *error = NULL;
+
+		_LOGI ("found new SimManager interface");
+
+		priv->sim_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+		                                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                                                 | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                                                 NULL, /* GDBusInterfaceInfo */
+		                                                 OFONO_DBUS_SERVICE,
+		                                                 nm_modem_get_path (NM_MODEM (self)),
+		                                                 OFONO_DBUS_INTERFACE_SIM_MANAGER,
+		                                                 NULL, /* GCancellable */
+		                                                 &error);
+		if (priv->sim_proxy == NULL) {
+			_LOGW ("failed to create SimManager proxy: %s", error->message);
+			g_error_free (error);
+			return;
+		}
+
+		/* Watch for custom ofono PropertyChanged signals */
+		_nm_dbus_signal_connect (priv->sim_proxy,
+		                         "PropertyChanged",
+		                         G_VARIANT_TYPE ("(sv)"),
+		                         G_CALLBACK (sim_property_changed),
+		                         self);
+
+		g_dbus_proxy_call (priv->sim_proxy,
+		                   "GetProperties",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   20000,
+		                   NULL,
+		                   (GAsyncReadyCallback) sim_get_properties_done,
+		                   g_object_ref (self));
+	}
+}
+
+static void
+handle_connman_property (GDBusProxy *proxy,
+                         const char *property,
+                         GVariant *v,
+                         gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (g_strcmp0 (property, "Attached") == 0 && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
+		gboolean attached = g_variant_get_boolean (v);
+		gboolean old_attached = priv->gprs_attached;
+
+		_LOGD ("Attached: %s", attached ? "True" : "False");
+
+		if (priv->gprs_attached != attached) {
+			priv->gprs_attached = attached;
+
+			_LOGI ("Attached %s -> %s",
+			       old_attached ? "true" : "false",
+			       attached ? "true" : "false");
+
+			update_modem_state (self);
+		}
+	}
+}
+
+static void
+connman_property_changed (GDBusProxy *proxy,
+                        const char *property,
+                        GVariant *v,
+                        gpointer user_data)
+{
+	GVariant *v_child = g_variant_get_child_value (v, 0);
+
+	handle_connman_property (proxy, property, v_child, user_data);
+	g_variant_unref (v_child);
+}
+
+static void
+connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	GError *error = NULL;
+	GVariant *v_properties, *v_dict, *v;
+	GVariantIter i;
+	const char *property;
+
+	v_properties = _nm_dbus_proxy_call_finish (proxy,
+		                                       result,
+		                                       G_VARIANT_TYPE ("(a{sv})"),
+		                                       &error);
+	if (!v_properties) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("error getting connman properties: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	v_dict = g_variant_get_child_value (v_properties, 0);
+
+	/*
+	 * TODO:
+	 * 1) optimize by looking up properties ( Online, Interfaces ), instead
+	 *    of iterating
+	 *
+	 * 2) reduce code duplication between all of the get_properties_done
+	 *    functions in this class.
+	 */
+
+	g_variant_iter_init (&i, v_dict);
+	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+		handle_connman_property (NULL, property, v, self);
+		g_variant_unref (v);
+	}
+
+	g_variant_unref (v_dict);
+	g_variant_unref (v_properties);
+}
+
+static void
+handle_connman_iface (NMModemOfono *self, gboolean found)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	_LOGD ("ConnectionManager interface %sfound", found ? "" : "not ");
+
+	if (!found && priv->connman_proxy) {
+		_LOGI ("ConnectionManager interface disappeared");
+
+		g_signal_handlers_disconnect_by_data (priv->connman_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->connman_proxy);
+
+		/* The connection manager proxy disappeared, we should
+		 * consider the modem disabled.
+		 */
+		priv->gprs_attached = FALSE;
+
+		update_modem_state (self);
+	} else if (found && !priv->connman_proxy) {
+		GError *error = NULL;
+
+		_LOGI ("found new ConnectionManager interface");
+
+		priv->connman_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+		                                                     G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                                                     | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                                                     NULL, /* GDBusInterfaceInfo */
+		                                                     OFONO_DBUS_SERVICE,
+		                                                     nm_modem_get_path (NM_MODEM (self)),
+		                                                     OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
+		                                                     NULL, /* GCancellable */
+		                                                     &error);
+		if (priv->connman_proxy == NULL) {
+			_LOGW ("failed to create ConnectionManager proxy: %s", error->message);
+			g_error_free (error);
+			return;
+		}
+
+		/* Watch for custom ofono PropertyChanged signals */
+		_nm_dbus_signal_connect (priv->connman_proxy,
+		                         "PropertyChanged",
+		                         G_VARIANT_TYPE ("(sv)"),
+		                         G_CALLBACK (connman_property_changed),
+		                         self);
+
+		g_dbus_proxy_call (priv->connman_proxy,
+		                   "GetProperties",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   20000,
+		                   NULL,
+		                   (GAsyncReadyCallback) connman_get_properties_done,
+		                   g_object_ref (self));
+	}
+}
+
+static void
+handle_modem_property (GDBusProxy *proxy,
+                       const char *property,
+                       GVariant *v,
+                       gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if ((g_strcmp0 (property, "Online") == 0) && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
+		gboolean online = g_variant_get_boolean (v);
+
+		_LOGD ("Online: %s", online ? "True" : "False");
+
+		if (online != priv->modem_online) {
+			priv->modem_online = online;
+			_LOGI ("modem is now %s", online ? "Online" : "Offline");
+			update_modem_state (self);
+		}
+
+	} else if ((g_strcmp0 (property, "Interfaces") == 0) && VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) {
+		const char **array, **iter;
+		gboolean found_connman = FALSE;
+		gboolean found_sim = FALSE;
+
+		_LOGD ("Interfaces found");
+
+		array = g_variant_get_strv (v, NULL);
+		if (array) {
+			for (iter = array; *iter; iter++) {
+				if (g_strcmp0 (OFONO_DBUS_INTERFACE_SIM_MANAGER, *iter) == 0)
+					found_sim = TRUE;
+				else if (g_strcmp0 (OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0)
+					found_connman = TRUE;
+			}
+			g_free (array);
+		}
+
+		handle_sim_iface (self, found_sim);
+		handle_connman_iface (self, found_connman);
+	}
+}
+
+static void
+modem_property_changed (GDBusProxy *proxy,
+                        const char *property,
+                        GVariant *v,
+                        gpointer user_data)
+{
+	GVariant *v_child = g_variant_get_child_value (v, 0);
+
+	handle_modem_property (proxy, property, v_child, user_data);
+	g_variant_unref (v_child);
+}
+
+static void
+modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	GError *error = NULL;
+	GVariant *v_properties, *v_dict, *v;
+	GVariantIter i;
+	const char *property;
+
+	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	                                           result,
+	                                           G_VARIANT_TYPE ("(a{sv})"),
+	                                           &error);
+	if (!v_properties) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("error getting modem properties: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	v_dict = g_variant_get_child_value (v_properties, 0);
+	if (!v_dict) {
+		_LOGW ("error getting modem properties: no v_dict");
+		return;
+	}
+
+	/*
+	 * TODO:
+	 * 1) optimize by looking up properties ( Online, Interfaces ), instead
+	 *    of iterating
+	 *
+	 * 2) reduce code duplication between all of the get_properties_done
+	 *    functions in this class.
+	 */
+
+	g_variant_iter_init (&i, v_dict);
+	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+		handle_modem_property (NULL, property, v, self);
+		g_variant_unref (v);
+	}
+
+	g_variant_unref (v_dict);
+	g_variant_unref (v_properties);
+}
+
+NMModem *
+nm_modem_ofono_new (const char *path)
+{
+	gs_free char *basename = NULL;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	nm_log_info (LOGD_MB, "ofono: creating new Ofono modem path %s", path);
+
+	/* Use short modem name (not its object path) as the NM device name (which
+	 * comes from NM_MODEM_UID)and the device ID.
+	 */
+	basename = g_path_get_basename (path);
+
+	return (NMModem *) g_object_new (NM_TYPE_MODEM_OFONO,
+	                                 NM_MODEM_PATH, path,
+	                                 NM_MODEM_UID, basename,
+	                                 NM_MODEM_DEVICE_ID, basename,
+	                                 NM_MODEM_CONTROL_PORT, "ofono", /* mandatory */
+	                                 NM_MODEM_DRIVER, "ofono",
+	                                 NM_MODEM_STATE, NM_MODEM_STATE_INITIALIZING,
+	                                 NULL);
+}
+
+static void
+stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	g_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
+
+	g_dbus_proxy_call_finish (proxy, result, &error);
+	if (error) {
+		_LOGW ("connection failed: %s", error->message);
+
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+		                       NM_DEVICE_STATE_REASON_MODEM_BUSY);
+		/*
+		 * FIXME: add code to check for InProgress so that the
+		 * connection doesn't continue to try and activate,
+		 * leading to the connection being disabled, and a 5m
+		 * timeout...
+		 */
+
+		g_clear_error (&error);
+	}
+}
+
+static void
+context_property_changed (GDBusProxy *proxy,
+                          const char *property,
+                          GVariant *v,
+                          gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	NMPlatformIP4Address addr;
+	gboolean ret = FALSE;
+	GVariant *v_dict;
+	const gchar *s, *addr_s;
+	const gchar **array, **iter;
+	guint32 address_network, gateway_network;
+	guint prefix = 0;
+
+	_LOGD ("PropertyChanged: %s", property);
+
+	/*
+	 * TODO: might be a good idea and re-factor this to mimic bluez-device,
+	 * ie. have this function just check the key, and call a sub-func to
+	 * handle the action.
+	 */
+
+	if (g_strcmp0 (property, "Settings") != 0)
+		return;
+
+	v_dict = g_variant_get_child_value (v, 0);
+	if (!v_dict) {
+		_LOGW ("error getting IPv4 Settings: no v_dict");
+		goto out;
+	}
+
+	_LOGI ("IPv4 static Settings:");
+
+	if (g_variant_lookup (v_dict, "Interface", "&s", &s)) {
+		if (s && strlen (s)) {
+			_LOGD ("Interface: %s", s);
+			g_object_set (self,
+			              NM_MODEM_DATA_PORT, g_strdup (s),
+			              NM_MODEM_IP4_METHOD, NM_MODEM_IP_METHOD_STATIC,
+			              NULL);
+		} else {
+			_LOGW ("Settings 'Interface'; empty");
+			goto out;
+		}
+
+	} else {
+		_LOGW ("Settings 'Interface' missing");
+		goto out;
+	}
+
+	/* TODO: verify handling of ip4_config; check other places it's used... */
+	g_clear_object (&priv->ip4_config);
+
+	memset (&addr, 0, sizeof (addr));
+
+	/*
+	 * TODO:
+	 *
+	 * NM 1.2 changed the NMIP4Config constructor to take an ifindex
+	 * ( vs. void pre 1.2 ), to tie config instance to a specific
+	 * platform interface.
+	 *
+	 * This doesn't work for ofono, as the devices are created
+	 * dynamically ( eg. ril_0, ril_1 ) in NMModemManager.  The
+	 * device created doesn't really map directly to a platform
+	 * link.  The closest would be one of the devices owned by
+	 * rild ( eg. ccmin0 ), which is passed to us above as
+	 * 'Interface'.
+	 *
+	 * This needs discussion with upstream.
+	 */
+	priv->ip4_config = nm_ip4_config_new (0);
+
+	/* TODO: simply if/else error logic! */
+
+	if (g_variant_lookup (v_dict, "Address", "&s", &addr_s)) {
+		_LOGD ("Address: %s", addr_s);
+
+		if (ip_string_to_network_address (addr_s, &address_network)) {
+			addr.address = address_network;
+			addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
+		} else {
+			_LOGW ("can't convert 'Address' %s to addr", s);
+			goto out;
+		}
+
+	} else {
+		_LOGW ("Settings 'Address' missing");
+		goto out;
+	}
+
+	if (g_variant_lookup (v_dict, "Netmask", "&s", &s)) {
+		_LOGD ("Netmask: %s", s);
+
+		if (s && ip_string_to_network_address (s, &address_network)) {
+			prefix = nm_utils_ip4_netmask_to_prefix (address_network);
+			if (prefix > 0)
+				addr.plen = prefix;
+		} else {
+			_LOGW ("invalid 'Netmask': %s", s);
+			goto out;
+		}
+	} else {
+		_LOGW ("Settings 'Netmask' missing");
+		goto out;
+	}
+
+	_LOGI ("Address: %s/%d", addr_s, prefix);
+
+	nm_ip4_config_add_address (priv->ip4_config, &addr);
+
+	if (g_variant_lookup (v_dict, "Gateway", "&s", &s)) {
+		if (s && ip_string_to_network_address (s, &gateway_network)) {
+			_LOGI ("Gateway: %s", s);
+			nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+		} else {
+			_LOGW ("invalid 'Gateway': %s", s);
+			goto out;
+		}
+		nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+	} else {
+		_LOGW ("Settings 'Gateway' missing");
+		goto out;
+	}
+
+	if (g_variant_lookup (v_dict, "DomainNameServers", "^a&s", &array)) {
+		if (array) {
+			for (iter = array; *iter; iter++) {
+				if (ip_string_to_network_address (*iter, &address_network) && address_network > 0) {
+					_LOGI ("DNS: %s", *iter);
+					nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
+				} else {
+					_LOGW ("invalid NameServer: %s", *iter);
+				}
+			}
+
+			if (iter == array) {
+				_LOGW ("Settings: 'DomainNameServers': none specified");
+				g_free (array);
+				goto out;
+			}
+			g_free (array);
+		}
+	} else {
+		_LOGW ("Settings 'DomainNameServers' missing");
+		goto out;
+	}
+
+	if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
+		_LOGI ("MessageProxy: %s", s);
+		if (s && ip_string_to_network_address (s, &address_network)) {
+			NMPlatformIP4Route mms_route;
+
+			mms_route.network = address_network;
+			mms_route.plen = 32;
+			mms_route.gateway = gateway_network;
+
+			mms_route.metric = 1;
+
+			nm_ip4_config_add_route (priv->ip4_config, &mms_route);
+		} else {
+			_LOGW ("invalid MessageProxy: %s", s);
+		}
+	}
+
+	ret = TRUE;
+
+out:
+	if (nm_modem_get_state (NM_MODEM (self)) != NM_MODEM_STATE_CONNECTED) {
+		_LOGI ("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE");
+		if (!ret)
+			reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, ret, reason);
+	} else {
+		_LOGW ("MODEM_PPP_FAILED");
+		g_signal_emit_by_name (self, NM_MODEM_PPP_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
+	}
+}
+
+static NMActStageReturn
+static_stage3_ip4_config_start (NMModem *_self,
+                                NMActRequest *req,
+                                NMDeviceStateReason *reason)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (_self);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	GError *error = NULL;
+
+	if (priv->ip4_config) {
+		_LOGD ("IP4 config is done; setting modem_state -> CONNECTED");
+		g_signal_emit_by_name (self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error);
+
+		/* Signal listener takes ownership of the IP4Config */
+		priv->ip4_config = NULL;
+
+		nm_modem_set_state (NM_MODEM (self),
+		                    NM_MODEM_STATE_CONNECTED,
+		                    nm_modem_state_to_string (NM_MODEM_STATE_CONNECTED));
+		ret = NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	return ret;
+}
+
+static void
+context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	priv->context_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (error) {
+		_LOGE ("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+		                       NM_DEVICE_STATE_REASON_MODEM_BUSY);
+		return;
+	}
+
+	if (!priv->gprs_attached) {
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+		                       NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
+		return;
+	}
+
+	/* We have an old copy of the settings from a previous activation,
+	 * clear it so that we can gate getting the IP config from oFono
+	 * on whether or not we have already received them
+	 */
+	g_clear_object (&priv->ip4_config);
+
+	/* Watch for custom ofono PropertyChanged signals */
+	_nm_dbus_signal_connect (priv->context_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (context_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->context_proxy,
+	                   "SetProperty",
+	                   g_variant_new ("(sv)",
+	                                  "Active",
+	                                   g_variant_new ("b", TRUE)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   NULL,
+	                   (GAsyncReadyCallback) stage1_prepare_done,
+	                   g_object_ref (self));
+}
+
+static void
+do_context_activate (NMModemOfono *self)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_return_if_fail (NM_IS_MODEM_OFONO (self));
+
+	g_clear_object (&priv->context_proxy);
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          OFONO_DBUS_SERVICE,
+	                          priv->context_path,
+	                          OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
+	                          NULL,
+	                          (GAsyncReadyCallback) context_proxy_new_cb,
+	                          g_object_ref (self));
+}
+
+static GHashTable *
+create_connect_properties (NMConnection *connection)
+{
+	NMSettingGsm *setting;
+	GHashTable *properties;
+	const char *str;
+
+	setting = nm_connection_get_setting_gsm (connection);
+	properties = g_hash_table_new (g_str_hash, g_str_equal);
+
+	str = nm_setting_gsm_get_apn (setting);
+	if (str)
+		g_hash_table_insert (properties, "AccessPointName", g_strdup (str));
+
+	str = nm_setting_gsm_get_username (setting);
+	if (str)
+		g_hash_table_insert (properties, "Username", g_strdup (str));
+
+	str = nm_setting_gsm_get_password (setting);
+	if (str)
+		g_hash_table_insert (properties, "Password", g_strdup (str));
+
+	return properties;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMModem *modem,
+                    NMConnection *connection,
+                    NMDeviceStateReason *reason)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (modem);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	const char *context_id;
+	char **id = NULL;
+
+	context_id = nm_connection_get_id (connection);
+	id = g_strsplit (context_id, "/", 0);
+	g_assert (id[2]);
+
+	_LOGD ("trying %s %s", id[1], id[2]);
+
+	g_free (priv->context_path);
+	priv->context_path = g_strdup_printf ("%s/%s",
+	                                      nm_modem_get_path (modem),
+	                                      id[2]);
+	g_strfreev (id);
+
+	if (!priv->context_path) {
+		*reason = NM_DEVICE_STATE_REASON_GSM_APN_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	if (priv->connect_properties)
+		g_hash_table_destroy (priv->connect_properties);
+
+	priv->connect_properties = create_connect_properties (connection);
+
+	_LOGI ("activating context %s", priv->context_path);
+
+	if (nm_modem_get_state (modem) == NM_MODEM_STATE_REGISTERED) {
+		do_context_activate (self);
+	} else {
+		_LOGW ("could not activate context: modem is not registered.");
+		*reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static void
+modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	priv->modem_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (error) {
+		_LOGE ("failed to create ofono modem DBus proxy: %s", error->message);
+		return;
+	}
+
+	/* Watch for custom ofono PropertyChanged signals */
+	_nm_dbus_signal_connect (priv->modem_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (modem_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->modem_proxy,
+	                   "GetProperties",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   NULL,
+	                   (GAsyncReadyCallback) modem_get_properties_done,
+	                   g_object_ref (self));
+}
+
+static void
+nm_modem_ofono_init (NMModemOfono *self)
+{
+}
+
+static void
+constructed (GObject *object)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (object);
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          OFONO_DBUS_SERVICE,
+	                          nm_modem_get_path (NM_MODEM (self)),
+	                          OFONO_DBUS_INTERFACE_MODEM,
+	                          NULL,
+	                          (GAsyncReadyCallback) modem_proxy_new_cb,
+	                          g_object_ref (self));
+}
+
+static void
+dispose (GObject *object)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (object);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (priv->connect_properties) {
+		g_hash_table_destroy (priv->connect_properties);
+		priv->connect_properties = NULL;
+	}
+
+	g_clear_object (&priv->ip4_config);
+
+	if (priv->modem_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->modem_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->modem_proxy);
+	}
+
+	g_clear_object (&priv->connman_proxy);
+	g_clear_object (&priv->context_proxy);
+
+	if (priv->sim_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->sim_proxy);
+	}
+
+	g_free (priv->imsi);
+	priv->imsi = NULL;
+
+	G_OBJECT_CLASS (nm_modem_ofono_parent_class)->dispose (object);
+}
+
+static void
+nm_modem_ofono_class_init (NMModemOfonoClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMModemClass *modem_class = NM_MODEM_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMModemOfonoPrivate));
+
+	/* Virtual methods */
+	object_class->constructed = constructed;
+	object_class->dispose = dispose;
+
+	modem_class->get_capabilities = get_capabilities;
+	modem_class->disconnect = disconnect;
+	modem_class->disconnect_finish = disconnect_finish;
+	modem_class->deactivate_cleanup = deactivate_cleanup;
+	modem_class->check_connection_compatible = check_connection_compatible;
+
+	/* same as nm-modem-broadband */
+	modem_class->act_stage1_prepare = act_stage1_prepare;
+
+	/* same as nm-modem-broadband */
+	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
new file mode 100644
index 00000000..5965526d
--- /dev/null
+++ b/src/devices/wwan/nm-modem-ofono.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* 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) 2013 - Canonical Ltd.
+ */
+
+#ifndef NM_MODEM_OFONO_H
+#define NM_MODEM_OFONO_H
+
+#include "nm-modem.h"
+
+#define NM_TYPE_MODEM_OFONO            (nm_modem_ofono_get_type ())
+#define NM_MODEM_OFONO(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_OFONO, NMModemOfono))
+#define NM_IS_MODEM_OFONO(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_MODEM_OFONO))
+#define NM_MODEM_OFONO_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_MODEM_OFONO, NMModemOfonoClass))
+#define NM_IS_MODEM_OFONO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_MODEM_OFONO))
+#define NM_MODEM_OFONO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_MODEM_OFONO, NMModemOfonoClass))
+
+#define OFONO_DBUS_SERVICE                      "org.ofono"
+#define OFONO_DBUS_PATH                         "/"
+#define OFONO_DBUS_INTERFACE                    "org.ofono.Manager"
+#define OFONO_DBUS_INTERFACE_MODEM              "org.ofono.Modem"
+#define OFONO_DBUS_INTERFACE_CONNECTION_MANAGER "org.ofono.ConnectionManager"
+#define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext"
+#define OFONO_DBUS_INTERFACE_SIM_MANAGER        "org.ofono.SimManager"
+
+typedef struct {
+	NMModem parent;
+} NMModemOfono;
+
+typedef struct {
+	NMModemClass parent;
+} NMModemOfonoClass;
+
+GType nm_modem_ofono_get_type (void);
+
+NMModem *nm_modem_ofono_new (const char *path);
+
+#endif /* NM_MODEM_OFONO_H */
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index a6c77007..2e3d63bb 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -202,7 +202,9 @@ nm_modem_set_mm_enabled (NMModem *self,
 		return;
 	}
 
-	NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled);
+	/* Not all modem classes support set_mm_enabled */
+	if (NM_MODEM_GET_CLASS (self)->set_mm_enabled)
+		NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled);
 
 	/* Pre-empt the state change signal */
 	nm_modem_set_state (self,
@@ -573,6 +575,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 	const char *method;
 	NMActStageReturn ret;
 
+	nm_log_dbg (LOGD_MB, "ip4_config_start");
+
 	g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE);
 	g_return_val_if_fail (NM_IS_DEVICE (device), NM_ACT_STAGE_RETURN_FAILURE);
 	g_return_val_if_fail (NM_IS_DEVICE_CLASS (device_class), NM_ACT_STAGE_RETURN_FAILURE);
@@ -602,9 +606,11 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		ret = ppp_stage3_ip_config_start (self, req, reason);
 		break;
 	case NM_MODEM_IP_METHOD_STATIC:
+		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_STATIC");
 		ret = NM_MODEM_GET_CLASS (self)->static_stage3_ip4_config_start (self, req, reason);
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
+		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_AUTO");
 		ret = device_class->act_stage3_ip4_config_start (device, NULL, reason);
 		break;
 	default:
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index 60e5589b..8f797d44 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -22,12 +22,9 @@
 #ifndef __NETWORKMANAGER_MODEM_H__
 #define __NETWORKMANAGER_MODEM_H__
 
-#include "nm-default.h"
 #include "ppp-manager/nm-ppp-manager.h"
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_MODEM            (nm_modem_get_type ())
 #define NM_MODEM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM, NMModem))
 #define NM_MODEM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_MODEM, NMModemClass))
@@ -268,6 +265,4 @@ void nm_modem_emit_ip6_config_result (NMModem *self,
 
 const gchar *nm_modem_ip_type_to_string (NMModemIPType ip_type);
 
-G_END_DECLS
-
 #endif /* __NETWORKMANAGER_MODEM_H__ */
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index 805aea69..507935fc 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -90,7 +90,7 @@ modem_added_cb (NMModemManager *manager,
 
 
 NM_DEVICE_FACTORY_DECLARE_TYPES (
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_WWAN_ETHERNET)
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_WWAN_NET)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_GSM_SETTING_NAME, NM_SETTING_CDMA_SETTING_NAME)
 )
 
@@ -102,7 +102,7 @@ create_device (NMDeviceFactory *factory,
                gboolean *out_ignore)
 {
 	g_return_val_if_fail (plink, NULL);
-	g_return_val_if_fail (plink->type == NM_LINK_TYPE_WWAN_ETHERNET, NULL);
+	g_return_val_if_fail (plink->type == NM_LINK_TYPE_WWAN_NET, NULL);
 	*out_ignore = TRUE;
 	return NULL;
 }
diff --git a/src/devices/wwan/nm-wwan-factory.h b/src/devices/wwan/nm-wwan-factory.h
index 78031e60..61bdcb1e 100644
--- a/src/devices/wwan/nm-wwan-factory.h
+++ b/src/devices/wwan/nm-wwan-factory.h
@@ -21,8 +21,6 @@
 #ifndef __NETWORKMANAGER_WWAN_FACTORY_H__
 #define __NETWORKMANAGER_WWAN_FACTORY_H__
 
-#include "nm-default.h"
-
 #define NM_TYPE_WWAN_FACTORY (nm_wwan_factory_get_type ())
 #define NM_WWAN_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactory))