about summary refs log tree commit diff
path: root/src/devices/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wifi')
-rw-r--r--src/devices/wifi/meson.build51
-rw-r--r--src/devices/wifi/nm-device-iwd.c1921
-rw-r--r--src/devices/wifi/nm-device-iwd.h62
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c39
-rw-r--r--src/devices/wifi/nm-device-wifi.c386
-rw-r--r--src/devices/wifi/nm-device-wifi.h12
-rw-r--r--src/devices/wifi/nm-iwd-manager.c693
-rw-r--r--src/devices/wifi/nm-iwd-manager.h65
-rw-r--r--src/devices/wifi/nm-wifi-ap.c205
-rw-r--r--src/devices/wifi/nm-wifi-ap.h21
-rw-r--r--src/devices/wifi/nm-wifi-common.c206
-rw-r--r--src/devices/wifi/nm-wifi-common.h37
-rw-r--r--src/devices/wifi/nm-wifi-factory.c29
-rw-r--r--src/devices/wifi/nm-wifi-utils.c32
-rw-r--r--src/devices/wifi/nm-wifi-utils.h2
-rw-r--r--src/devices/wifi/tests/meson.build13
16 files changed, 3399 insertions, 375 deletions
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
new file mode 100644
index 00000000..a27f8e6a
--- /dev/null
+++ b/src/devices/wifi/meson.build
@@ -0,0 +1,51 @@
+common_sources = files(
+  'nm-wifi-ap.c',
+  'nm-wifi-utils.c'
+)
+
+sources = common_sources + files(
+  'nm-wifi-factory.c',
+  'nm-wifi-common.c',
+  'nm-device-wifi.c',
+  'nm-device-olpc-mesh.c'
+)
+
+if enable_iwd
+  sources += files(
+    'nm-device-iwd.c',
+    'nm-iwd-manager.c',
+  )
+endif
+
+deps = [
+  nm_dep
+]
+
+libnm_device_plugin_wifi = shared_module(
+  'nm-device-plugin-wifi',
+  sources: sources,
+  dependencies: deps,
+  link_args: ldflags_linker_script_devices,
+  link_depends: linker_script_devices,
+  install: true,
+  install_dir: nm_plugindir
+)
+
+core_plugins += libnm_device_plugin_wifi
+
+run_target(
+  'check-local-devices-wifi',
+  command: [check_exports, libnm_device_plugin_wifi.full_path(), linker_script_devices],
+  depends: libnm_device_plugin_wifi
+)
+
+# FIXME: check_so_symbols replacement
+'''
+check-local-devices-wifi: src/devices/wifi/libnm-device-plugin-wifi.la
+  $(srcdir)/tools/check-exports.sh $(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so "$(srcdir)/linker-script-devices.ver"
+  $(call check_so_symbols,$(builddir)/src/devices/wifi/.libs/libnm-device-plugin-wifi.so)
+'''
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
new file mode 100644
index 00000000..d3c5ae9a
--- /dev/null
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -0,0 +1,1921 @@
+/* -*- 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) 2017 Intel Corporation
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-iwd.h"
+
+#include <string.h>
+
+#include "nm-common-macros.h"
+#include "devices/nm-device.h"
+#include "devices/nm-device-private.h"
+#include "nm-utils.h"
+#include "nm-act-request.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-wireless.h"
+#include "nm-setting-wireless-security.h"
+#include "nm-setting-8021x.h"
+#include "settings/nm-settings-connection.h"
+#include "settings/nm-settings.h"
+#include "nm-wifi-utils.h"
+#include "nm-wifi-common.h"
+#include "nm-core-internal.h"
+#include "nm-config.h"
+#include "nm-iwd-manager.h"
+#include "nm-dbus-manager.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceIwd);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd,
+	PROP_MODE,
+	PROP_BITRATE,
+	PROP_ACCESS_POINTS,
+	PROP_ACTIVE_ACCESS_POINT,
+	PROP_CAPABILITIES,
+	PROP_SCANNING,
+);
+
+enum {
+	SCANNING_PROHIBITED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+	GDBusObject *   dbus_obj;
+	GDBusProxy *    dbus_proxy;
+	CList           aps_lst_head;
+	NMWifiAP *      current_ap;
+	GCancellable *  cancellable;
+	NMDeviceWifiCapabilities capabilities;
+	NMActRequestGetSecretsCallId *wifi_secrets_id;
+	GDBusMethodInvocation *secrets_request;
+	guint           periodic_scan_id;
+	bool            enabled:1;
+	bool            can_scan:1;
+	bool            can_connect:1;
+	bool            scanning:1;
+	bool            scan_requested:1;
+} NMDeviceIwdPrivate;
+
+struct _NMDeviceIwd {
+	NMDevice parent;
+	NMDeviceIwdPrivate _priv;
+};
+
+struct _NMDeviceIwdClass {
+	NMDeviceClass parent;
+
+	/* Signals */
+	gboolean (*scanning_prohibited) (NMDeviceIwd *device, gboolean periodic);
+};
+
+/*****************************************************************************/
+
+G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_IWD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD)
+
+/*****************************************************************************/
+
+static void schedule_periodic_scan (NMDeviceIwd *self,
+                                    NMDeviceState current_state);
+
+/*****************************************************************************/
+
+static void
+_ap_dump (NMDeviceIwd *self,
+          NMLogLevel log_level,
+          const NMWifiAP *ap,
+          const char *prefix,
+          gint32 now_s)
+{
+	char buf[1024];
+
+	buf[0] = '\0';
+	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s",
+	        prefix,
+	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_s));
+}
+
+/* Callers ensure we're not removing current_ap */
+static void
+ap_add_remove (NMDeviceIwd *self,
+               gboolean is_adding, /* or else is removing */
+               NMWifiAP *ap,
+               gboolean recheck_available_connections)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (is_adding) {
+		g_object_ref (ap);
+		ap->wifi_device = NM_DEVICE (self);
+		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
+		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
+	} else {
+		ap->wifi_device = NULL;
+		c_list_unlink (&ap->aps_lst);
+		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
+	}
+
+	_notify (self, PROP_ACCESS_POINTS);
+
+	if (!is_adding) {
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, FALSE);
+		nm_dbus_object_clear_and_unexport (&ap);
+	}
+
+	nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+	if (recheck_available_connections)
+		nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+static void
+set_current_ap (NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
+{
+	NMDeviceIwdPrivate *priv;
+	NMWifiAP *old_ap;
+
+	g_return_if_fail (NM_IS_DEVICE_IWD (self));
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	old_ap = priv->current_ap;
+
+	if (old_ap == new_ap)
+		return;
+
+	if (new_ap)
+		priv->current_ap = g_object_ref (new_ap);
+	else
+		priv->current_ap = NULL;
+
+	if (old_ap) {
+		if (nm_wifi_ap_get_fake (old_ap))
+			ap_add_remove (self, FALSE, old_ap, recheck_available_connections);
+		g_object_unref (old_ap);
+	}
+
+	_notify (self, PROP_ACTIVE_ACCESS_POINT);
+	_notify (self, PROP_MODE);
+}
+
+static void
+remove_all_aps (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMWifiAP *ap, *ap_safe;
+
+	if (c_list_is_empty (&priv->aps_lst_head))
+		return;
+
+	set_current_ap (self, NULL, FALSE);
+
+	c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst)
+		ap_add_remove (self, FALSE, ap, FALSE);
+
+	nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+	nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+static GVariant *
+vardict_from_network_type (const gchar *type)
+{
+	GVariantBuilder builder;
+	const gchar *key_mgmt = "";
+	const gchar *pairwise = "ccmp";
+
+	if (!strcmp (type, "psk"))
+		key_mgmt = "wpa-psk";
+	else if (!strcmp (type, "8021x"))
+		key_mgmt = "wpa-eap";
+	else
+		return NULL;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder, "{sv}", "KeyMgmt",
+	                       g_variant_new_strv (&key_mgmt, 1));
+	g_variant_builder_add (&builder, "{sv}", "Pairwise",
+	                       g_variant_new_strv (&pairwise, 1));
+	g_variant_builder_add (&builder, "{sv}", "Group",
+	                       g_variant_new_string ("ccmp"));
+	return g_variant_new ("a{sv}", &builder);
+}
+
+static void
+get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *variant = NULL;
+	GVariantIter *networks;
+	const gchar *path, *name, *type;
+	int16_t signal;
+	NMWifiAP *ap, *ap_safe, *new_ap;
+	gboolean changed = FALSE;
+	GHashTableIter ap_iter;
+	gs_unref_hashtable GHashTable *new_aps = NULL;
+
+	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                      G_VARIANT_TYPE ("(a(osns))"),
+	                                      &error);
+	if (!variant) {
+		_LOGE (LOGD_WIFI, "Device.GetOrderedNetworks failed: %s",
+		       error->message);
+		return;
+	}
+
+	new_aps = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
+
+	g_variant_get (variant, "(a(osns))", &networks);
+
+	while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type)) {
+		GVariantBuilder builder;
+		gs_unref_variant GVariant *props = NULL;
+		GVariant *rsn;
+		static uint32_t ap_id = 0;
+		uint8_t bssid[6];
+
+		/*
+		 * What we get from IWD are networks, or ESSs, that may
+		 * contain multiple APs, or BSSs, each.  We don't get
+		 * information about any specific BSSs within an ESS but
+		 * we can safely present each ESS as an individual BSS to
+		 * NM, which will be seen as ESSs comprising a single BSS
+		 * each.  NM won't be able to handle roaming but IWD already
+		 * does that.  We fake the BSSIDs as they don't play any
+		 * role either.
+		 */
+		bssid[0] = 0x00;
+		bssid[1] = 0x01;
+		bssid[2] = 0x02;
+		bssid[3] = ap_id >> 16;
+		bssid[4] = ap_id >> 8;
+		bssid[5] = ap_id++;
+
+		/* WEP not supported */
+		if (!strcmp (type, "wep"))
+			continue;
+
+		g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+		g_variant_builder_add (&builder, "{sv}", "BSSID",
+		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid, 6, 1));
+		g_variant_builder_add (&builder, "{sv}", "Mode",
+		                       g_variant_new_string ("infrastructure"));
+
+		rsn = vardict_from_network_type (type);
+		if (rsn)
+			g_variant_builder_add (&builder, "{sv}", "RSN", rsn);
+
+		props = g_variant_new ("a{sv}", &builder);
+
+		ap = nm_wifi_ap_new_from_properties (path, props);
+		if (name[0] != '\0')
+			nm_wifi_ap_set_ssid (ap, (const guint8 *) name, strlen (name));
+		nm_wifi_ap_set_strength (ap, nm_wifi_utils_level_to_quality (signal / 100));
+		nm_wifi_ap_set_freq (ap, 2417);
+		nm_wifi_ap_set_max_bitrate (ap, 65000);
+		g_hash_table_insert (new_aps,
+		                     (gpointer) nm_wifi_ap_get_supplicant_path (ap),
+		                     ap);
+	}
+
+	g_variant_iter_free (networks);
+
+	c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) {
+
+		new_ap = g_hash_table_lookup (new_aps,
+		                              nm_wifi_ap_get_supplicant_path (ap));
+		if (new_ap) {
+			if (nm_wifi_ap_set_strength (ap, nm_wifi_ap_get_strength (new_ap))) {
+				_ap_dump (self, LOGL_TRACE, ap, "updated", 0);
+				changed = TRUE;
+			}
+			g_hash_table_remove (new_aps,
+			                     nm_wifi_ap_get_supplicant_path (ap));
+			continue;
+		}
+
+		if (ap == priv->current_ap) {
+			/* Normally IWD will prevent the current AP from being
+			 * removed from the list and set a low signal strength,
+			 * but just making sure.
+			 */
+			continue;
+		}
+
+		ap_add_remove (self, FALSE, ap, FALSE);
+		changed = TRUE;
+	}
+
+	g_hash_table_iter_init (&ap_iter, new_aps);
+	while (g_hash_table_iter_next (&ap_iter, NULL, (gpointer) &ap)) {
+		ap_add_remove (self, TRUE, ap, FALSE);
+		g_hash_table_iter_remove (&ap_iter);
+		changed = TRUE;
+	}
+
+	if (changed) {
+		nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+		nm_device_recheck_available_connections (NM_DEVICE (self));
+	}
+}
+
+static void
+update_aps (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_call (priv->dbus_proxy, "GetOrderedNetworks",
+	                   g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
+	                   2000, priv->cancellable,
+	                   get_ordered_networks_cb, self);
+}
+
+static void
+send_disconnect (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
+static void
+wifi_secrets_cancel (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (priv->wifi_secrets_id)
+		nm_act_request_cancel_secrets (NULL, priv->wifi_secrets_id);
+	nm_assert (!priv->wifi_secrets_id);
+
+	if (priv->secrets_request) {
+		g_dbus_method_invocation_return_error_literal (priv->secrets_request, NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                                               "NM secrets request cancelled");
+		priv->secrets_request = NULL;
+	}
+
+}
+
+static void
+cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	wifi_secrets_cancel (self);
+
+	set_current_ap (self, NULL, TRUE);
+
+	if (disconnect && priv->dbus_obj)
+		send_disconnect (self);
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	cleanup_association_attempt (NM_DEVICE_IWD (device), TRUE);
+}
+
+static gboolean
+deactivate_async_finish (NMDevice *device, GAsyncResult *res, GError **error)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+	gs_unref_variant GVariant *variant = NULL;
+
+	variant = g_dbus_proxy_call_finish (priv->dbus_proxy, res, error);
+	return variant != NULL;
+}
+
+typedef struct {
+	NMDeviceIwd *self;
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+} DeactivateContext;
+
+static void
+disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	DeactivateContext *ctx = user_data;
+
+	ctx->callback (G_OBJECT (ctx->self), res, ctx->user_data);
+
+	g_object_unref (ctx->self);
+	g_slice_free (DeactivateContext, ctx);
+}
+
+static void
+deactivate_async (NMDevice *device,
+                  GCancellable *cancellable,
+                  GAsyncReadyCallback callback,
+                  gpointer user_data)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	DeactivateContext *ctx;
+
+	ctx = g_slice_new0 (DeactivateContext);
+	ctx->self = g_object_ref (self);
+	ctx->callback = callback;
+	ctx->user_data = user_data;
+
+	g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, ctx);
+}
+
+static NMIwdNetworkSecurity
+get_connection_iwd_security (NMConnection *connection)
+{
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const char *key_mgmt = NULL;
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+	if (!s_wireless_sec)
+		return NM_IWD_NETWORK_SECURITY_NONE;
+
+	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec);
+	nm_assert (key_mgmt);
+
+	if (!strcmp (key_mgmt, "none") || !strcmp (key_mgmt, "ieee8021x"))
+		return NM_IWD_NETWORK_SECURITY_WEP;
+
+	if (!strcmp (key_mgmt, "wpa-psk"))
+		return NM_IWD_NETWORK_SECURITY_PSK;
+
+	nm_assert (!strcmp (key_mgmt, "wpa-eap"));
+	return NM_IWD_NETWORK_SECURITY_8021X;
+}
+
+static gboolean
+is_connection_known_network (NMConnection *connection)
+{
+	NMSettingWireless *s_wireless;
+	GBytes *ssid;
+	gs_free gchar *str_ssid = NULL;
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	if (!s_wireless)
+		return FALSE;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		return FALSE;
+
+	str_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
+	                                  g_bytes_get_size (ssid));
+
+	return nm_iwd_manager_is_known_network (nm_iwd_manager_get (),
+	                                        str_ssid,
+	                                        get_connection_iwd_security (connection));
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wireless;
+	const char *mac;
+	const char * const *mac_blacklist;
+	int i;
+	const char *mode;
+	const char *perm_hw_addr;
+
+	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection))
+		return FALSE;
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+
+	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME))
+		return FALSE;
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	if (!s_wireless)
+		return FALSE;
+
+	perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	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))
+			return FALSE;
+
+		/* Check for MAC address blacklist */
+		mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
+		for (i = 0; mac_blacklist[i]; i++) {
+			if (!nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN)) {
+				g_warn_if_reached ();
+				return FALSE;
+			}
+
+			if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1))
+				return FALSE;
+		}
+	} else if (mac)
+		return FALSE;
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+		return FALSE;
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_available (NMDevice *device,
+                            NMConnection *connection,
+                            NMDeviceCheckConAvailableFlags flags,
+                            const char *specific_object)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMSettingWireless *s_wifi;
+	const char *mode;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wifi, FALSE);
+
+	/* Only Infrastrusture mode at this time */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+		return FALSE;
+
+	/* Hidden SSIDs not supported yet */
+	if (nm_setting_wireless_get_hidden (s_wifi))
+		return FALSE;
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection))
+			return FALSE;
+	}
+
+	/* a connection that is available for a certain @specific_object, MUST
+	 * also be available in general (without @specific_object). */
+
+	if (specific_object) {
+		NMWifiAP *ap;
+
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
+		return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
+	}
+
+	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
+		return TRUE;
+
+	/* Check at least one AP is compatible with this connection */
+	return !!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMSettingWireless *s_wifi;
+	const char *setting_mac;
+	char *str_ssid = NULL;
+	NMWifiAP *ap;
+	const GByteArray *ssid = NULL;
+	GByteArray *tmp_ssid = NULL;
+	GBytes *setting_ssid = NULL;
+	const char *perm_hw_addr;
+	const char *mode;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+
+	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
+
+	if (s_wifi && !nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "Only Infrastructure mode is supported.");
+		return FALSE;
+	}
+
+	if (!specific_object) {
+		/* If not given a specific object, we need at minimum an SSID */
+		if (!s_wifi) {
+			g_set_error_literal (error,
+			                     NM_DEVICE_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "A 'wireless' setting is required if no AP path was given.");
+			return FALSE;
+		}
+
+		setting_ssid = nm_setting_wireless_get_ssid (s_wifi);
+		if (!setting_ssid || g_bytes_get_size (setting_ssid) == 0) {
+			g_set_error_literal (error,
+			                     NM_DEVICE_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "A 'wireless' setting with a valid SSID is required if no AP path was given.");
+			return FALSE;
+		}
+
+		/* Find a compatible AP in the scan list */
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+		if (!ap) {
+			g_set_error_literal (error,
+			                     NM_DEVICE_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "No compatible AP in the scan list and hidden SSIDs not supported.");
+			return FALSE;
+		}
+	} else {
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
+		if (!ap) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+			             "The access point %s was not in the scan list.",
+			             specific_object);
+			return FALSE;
+		}
+	}
+
+	/* Add a wifi setting if one doesn't exist yet */
+	if (!s_wifi) {
+		s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+		nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+	}
+
+	ssid = nm_wifi_ap_get_ssid (ap);
+
+	if (ssid == NULL) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "A 'wireless' setting with a valid SSID is required.");
+		return FALSE;
+	}
+
+	if (!nm_wifi_ap_complete_connection (ap,
+	                                     connection,
+	                                     nm_wifi_utils_is_manf_default_ssid (ssid),
+	                                     error)) {
+		if (tmp_ssid)
+			g_byte_array_unref (tmp_ssid);
+		return FALSE;
+	}
+
+	str_ssid = nm_utils_ssid_to_utf8 (ssid->data, ssid->len);
+
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WIRELESS_SETTING_NAME,
+	                           existing_connections,
+	                           str_ssid,
+	                           str_ssid,
+	                           NULL,
+	                           TRUE);
+	g_free (str_ssid);
+	if (tmp_ssid)
+		g_byte_array_unref (tmp_ssid);
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection)) {
+			g_set_error_literal (error,
+			                     NM_CONNECTION_ERROR,
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "This 8021x network has not been provisioned on this machine");
+			return FALSE;
+		}
+	}
+
+	perm_hw_addr = nm_device_get_permanent_hw_address (device);
+	if (perm_hw_addr) {
+		setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
+		if (setting_mac) {
+			/* Make sure the setting MAC (if any) matches the device's permanent MAC */
+			if (!nm_utils_hwaddr_matches (setting_mac, -1, perm_hw_addr, -1)) {
+				g_set_error_literal (error,
+				                     NM_CONNECTION_ERROR,
+				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
+				                     "connection does not match device");
+				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS);
+				return FALSE;
+			}
+		} else {
+			guint8 tmp[ETH_ALEN];
+
+			/* Lock the connection to this device by default if it uses a
+			 * permanent MAC address (ie not a 'locally administered' one)
+			 */
+			nm_utils_hwaddr_aton (perm_hw_addr, tmp, ETH_ALEN);
+			if (!(tmp[0] & 0x02)) {
+				g_object_set (G_OBJECT (s_wifi),
+				              NM_SETTING_WIRELESS_MAC_ADDRESS, perm_hw_addr,
+				              NULL);
+			}
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_unref_variant GVariant *value = NULL;
+
+	if (!priv->enabled || !priv->dbus_obj)
+		return FALSE;
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "Powered");
+	return g_variant_get_boolean (value);
+}
+
+static gboolean
+get_autoconnect_allowed (NMDevice *device)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+
+	return is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)
+	       && priv->can_connect;
+}
+
+static gboolean
+can_auto_connect (NMDevice *device,
+                  NMConnection *connection,
+                  char **specific_object)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMSettingWireless *s_wifi;
+	NMWifiAP *ap;
+	const char *mode;
+	guint64 timestamp = 0;
+
+	nm_assert (!specific_object || !*specific_object);
+
+	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->can_auto_connect (device, connection, NULL))
+		return FALSE;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wifi, FALSE);
+
+	/* Only Infrastrusture mode */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+		return FALSE;
+
+	/* Don't autoconnect to networks that have been tried at least once
+	 * but haven't been successful, since these are often accidental choices
+	 * from the menu and the user may not know the password.
+	 */
+	if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp)) {
+		if (timestamp == 0)
+			return FALSE;
+	}
+
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection))
+			return FALSE;
+	}
+
+	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+	if (ap) {
+		/* All good; connection is usable */
+		NM_SET_OUT (specific_object, g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (ap))));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+const CList *
+_nm_device_iwd_get_aps (NMDeviceIwd *self)
+{
+	return &NM_DEVICE_IWD_GET_PRIVATE (self)->aps_lst_head;
+}
+
+static gboolean
+check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
+{
+	gboolean prohibited = FALSE;
+
+	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
+	return prohibited;
+}
+
+static void
+scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	gs_free_error GError *error = NULL;
+
+	if (   !_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                   G_VARIANT_TYPE ("()"), &error)
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	priv->scan_requested = FALSE;
+
+	/* On success, priv->scanning becomes true right before or right
+	 * after this callback, so the next automatic scan will be
+	 * scheduled when priv->scanning goes back to false.  On error,
+	 * schedule a retry now.
+	 */
+	if (error && !priv->scanning) {
+		NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
+
+		schedule_periodic_scan (self, state);
+	}
+}
+
+static void
+dbus_request_scan_cb (NMDevice *device,
+                      GDBusMethodInvocation *context,
+                      NMAuthSubject *subject,
+                      GError *error,
+                      gpointer user_data)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv;
+	gs_unref_variant GVariant *scan_options = user_data;
+
+	if (error) {
+		g_dbus_method_invocation_return_gerror (context, error);
+		return;
+	}
+
+	if (check_scanning_prohibited (self, FALSE)) {
+		g_dbus_method_invocation_return_error_literal (context,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+		                                               "Scanning not allowed at this time");
+		return;
+	}
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (   !priv->enabled
+	    || !priv->dbus_obj
+	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
+	    || nm_device_is_activating (device)) {
+		g_dbus_method_invocation_return_error_literal (context,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+		                                               "Scanning not allowed while unavailable");
+		return;
+	}
+
+	if (scan_options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+
+		if (val) {
+			g_dbus_method_invocation_return_error_literal (context,
+			                                               NM_DEVICE_ERROR,
+			                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+			                                               "'ssid' scan option not supported");
+			return;
+		}
+	}
+
+	if (!priv->scanning && !priv->scan_requested) {
+		g_dbus_proxy_call (priv->dbus_proxy, "Scan",
+		                   g_variant_new ("()"),
+		                   G_DBUS_CALL_FLAGS_NONE, -1,
+		                   priv->cancellable, scan_cb, self);
+		priv->scan_requested = TRUE;
+	}
+
+	g_dbus_method_invocation_return_value (context, NULL);
+}
+
+void
+_nm_device_iwd_request_scan (NMDeviceIwd *self,
+                             GVariant *options,
+                             GDBusMethodInvocation *invocation)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+
+	if (   !priv->enabled
+	    || !priv->dbus_obj
+	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
+	    || nm_device_is_activating (device)) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+		                                               "Scanning not allowed while unavailable");
+		return;
+	}
+
+	g_signal_emit_by_name (device,
+	                       NM_DEVICE_AUTH_REQUEST,
+	                       invocation,
+	                       NULL,
+	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       TRUE,
+	                       dbus_request_scan_cb,
+	                       options ? g_variant_ref (options) : NULL);
+}
+
+static gboolean
+scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_return_val_if_fail (priv->dbus_obj != NULL, TRUE);
+
+	switch (nm_device_get_state (NM_DEVICE (self))) {
+	case NM_DEVICE_STATE_UNKNOWN:
+	case NM_DEVICE_STATE_UNMANAGED:
+	case NM_DEVICE_STATE_UNAVAILABLE:
+	case NM_DEVICE_STATE_PREPARE:
+	case NM_DEVICE_STATE_CONFIG:
+	case NM_DEVICE_STATE_NEED_AUTH:
+	case NM_DEVICE_STATE_IP_CONFIG:
+	case NM_DEVICE_STATE_IP_CHECK:
+	case NM_DEVICE_STATE_SECONDARIES:
+	case NM_DEVICE_STATE_DEACTIVATING:
+		/* Prohibit scans when unusable or activating */
+		return TRUE;
+	case NM_DEVICE_STATE_DISCONNECTED:
+	case NM_DEVICE_STATE_FAILED:
+		/* Can always scan when disconnected */
+		return FALSE;
+	case NM_DEVICE_STATE_ACTIVATED:
+		break;
+	}
+
+	/* Prohibit scans if IWD is busy */
+	return !priv->can_scan;
+}
+
+static void
+wifi_secrets_cb (NMActRequest *req,
+                 NMActRequestGetSecretsCallId *call_id,
+                 NMSettingsConnection *s_connection,
+                 GError *error,
+                 gpointer user_data)
+{
+	NMDevice *device = user_data;
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const gchar *psk;
+
+	g_return_if_fail (NM_IS_DEVICE_IWD (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->wifi_secrets_id == call_id);
+
+	priv->wifi_secrets_id = NULL;
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	g_return_if_fail (priv->secrets_request);
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == s_connection);
+
+	if (nm_device_get_state (device) != NM_DEVICE_STATE_NEED_AUTH)
+		goto secrets_error;
+
+	if (error) {
+		_LOGW (LOGD_WIFI, "%s", error->message);
+		goto secrets_error;
+	}
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (nm_act_request_get_applied_connection (req));
+	if (!s_wireless_sec)
+		goto secrets_error;
+
+	psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+	if (!psk)
+		goto secrets_error;
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI,
+	       "Returning a new PSK to the IWD Agent");
+
+	g_dbus_method_invocation_return_value (priv->secrets_request,
+	                                       g_variant_new ("(s)", psk));
+	priv->secrets_request = NULL;
+
+	/* Change state back to what it was before NEED_AUTH */
+	nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+	return;
+
+secrets_error:
+	if (priv->secrets_request) {
+		g_dbus_method_invocation_return_error_literal (priv->secrets_request, NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                                               "NM secrets request failed");
+		priv->secrets_request = NULL;
+	}
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+
+	cleanup_association_attempt (self, TRUE);
+}
+
+static void
+wifi_secrets_get_secrets (NMDeviceIwd *self,
+                          const char *setting_name,
+                          NMSecretAgentGetSecretsFlags flags)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActRequest *req;
+
+	wifi_secrets_cancel (self);
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	priv->wifi_secrets_id = nm_act_request_get_secrets (req,
+	                                                    TRUE,
+	                                                    setting_name,
+	                                                    flags,
+	                                                    NULL,
+	                                                    wifi_secrets_cb,
+	                                                    self);
+}
+
+static void
+network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDevice *device = NM_DEVICE (self);
+	gs_free_error GError *error = NULL;
+	NMConnection *connection;
+	NMSettingWireless *s_wifi;
+	GBytes *ssid;
+	gs_free gchar *str_ssid = NULL;
+
+	if (!_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                 G_VARIANT_TYPE ("()"),
+	                                 &error)) {
+		gs_free gchar *dbus_error = NULL;
+
+		/* Connection failed; radio problems or if the network wasn't
+		 * open, the passwords or certificates may be wrong.
+		 */
+
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Network.Connect failed: %s",
+		       error->message);
+
+		connection = nm_device_get_applied_connection (device);
+		if (!connection)
+			goto failed;
+
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR))
+			dbus_error = g_dbus_error_get_remote_error (error);
+
+		/* If secrets were wrong, we'd be getting a net.connman.iwd.Failed */
+		if (nm_streq0 (dbus_error, "net.connman.iwd.Failed")) {
+			nm_connection_clear_secrets (connection);
+
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+		} else if (   !nm_utils_error_is_cancelled (error, TRUE)
+		           && nm_device_is_activating (device))
+			goto failed;
+
+		/* Call Disconnect to make sure IWD's autoconnect is disabled */
+		cleanup_association_attempt (self, TRUE);
+
+		return;
+	}
+
+	nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+	connection = nm_device_get_applied_connection (device);
+	if (!connection)
+		goto failed;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	if (!s_wifi)
+		goto failed;
+
+	ssid = nm_setting_wireless_get_ssid (s_wifi);
+	if (!ssid)
+		goto failed;
+
+	str_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
+	                                  g_bytes_get_size (ssid));
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Connected to '%s'.",
+	       str_ssid);
+	nm_device_activate_schedule_stage3_ip_config_start (device);
+
+	nm_iwd_manager_network_connected (nm_iwd_manager_get (), str_ssid,
+	                                  get_connection_iwd_security (connection));
+
+	return;
+
+failed:
+	cleanup_association_attempt (self, FALSE);
+	nm_device_queue_state (device, NM_DEVICE_STATE_FAILED,
+	                       NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+set_powered (NMDeviceIwd *self, gboolean powered)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_proxy,
+	                   "org.freedesktop.DBus.Properties.Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Powered",
+	                                  g_variant_new ("b", powered)),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   NULL, NULL, NULL);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMWifiAP *ap = NULL;
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingWireless *s_wireless;
+	const char *ap_path;
+
+	ret = NM_DEVICE_CLASS (nm_device_iwd_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	req = nm_device_get_act_request (device);
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
+
+	ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
+	ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL;
+	if (!ap) {
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+
+		/* TODO: assuming hidden networks aren't supported do we need
+		 * to consider the case of APs that are not in the scan list
+		 * yet, for which nm-device-wifi.c creates the temporary fake
+		 * AP object?
+		 */
+
+		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
+	                                              nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+	}
+
+	set_current_ap (self, ap, FALSE);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMActRequest *req;
+	NMWifiAP *ap;
+	NMConnection *connection;
+	GError *error = NULL;
+	GDBusProxy *network_proxy;
+
+	req = nm_device_get_act_request (device);
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	ap = priv->current_ap;
+	if (!ap) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		goto out;
+	}
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_assert (connection);
+
+	/* 802.1x networks that are not IWD Known Networks will definitely
+	 * fail, for other combinations we will let the Connect call fail
+	 * or ask us for any missing secrets through the Agent.
+	 */
+	if (   !is_connection_known_network (connection)
+	    && nm_connection_get_setting_802_1x (connection)) {
+		_LOGI (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) access point '%s' has 802.1x security, but is not configured.",
+		       nm_connection_get_id (connection));
+
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		ret = NM_ACT_STAGE_RETURN_FAILURE;
+		goto out;
+	}
+
+	/* Locate the IWD Network object */
+	network_proxy = g_dbus_proxy_new_for_bus_sync (NM_IWD_BUS_TYPE,
+	                                               G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+	                                               G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+	                                               NULL,
+	                                               NM_IWD_SERVICE,
+	                                               nm_wifi_ap_get_supplicant_path (ap),
+	                                               NM_IWD_NETWORK_INTERFACE,
+	                                               NULL, &error);
+	if (!network_proxy) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) could not get Network interface proxy for %s: %s",
+		       nm_wifi_ap_get_supplicant_path (ap),
+		       error->message);
+		g_clear_error (&error);
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		goto out;
+	}
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	/* Call Network.Connect.  No timeout because IWD already handles
+	 * timeouts.
+	 */
+	g_dbus_proxy_call (network_proxy, "Connect",
+	                   g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+	                   priv->cancellable, network_connect_cb, self);
+
+	g_object_unref (network_proxy);
+
+	/* We'll get stage3 started when the supplicant connects */
+	ret = NM_ACT_STAGE_RETURN_POSTPONE;
+
+out:
+	if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+		cleanup_association_attempt (self, FALSE);
+
+	return ret;
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, gboolean *out_is_user_config)
+{
+	NMSettingWireless *setting;
+	gint64 mtu_default;
+	guint32 mtu;
+
+	nm_assert (NM_IS_DEVICE (device));
+	nm_assert (out_is_user_config);
+
+	setting = NM_SETTING_WIRELESS (nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS));
+	if (!setting)
+		g_return_val_if_reached (0);
+
+	mtu = nm_setting_wireless_get_mtu (setting);
+	if (mtu == 0) {
+		mtu_default = nm_device_get_configured_mtu_from_connection_default (device, "wifi.mtu");
+		if (mtu_default >= 0) {
+			*out_is_user_config = TRUE;
+			return (guint32) mtu_default;
+		}
+	}
+	*out_is_user_config = (mtu != 0);
+	return mtu;
+}
+
+static gboolean
+periodic_scan_timeout_cb (gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	priv->periodic_scan_id = 0;
+
+	if (priv->scanning || priv->scan_requested)
+		return FALSE;
+
+	g_dbus_proxy_call (priv->dbus_proxy, "Scan", g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	                   priv->cancellable, scan_cb, self);
+	priv->scan_requested = TRUE;
+
+	return FALSE;
+}
+
+static void
+schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	guint interval;
+
+	if (current_state <= NM_DEVICE_STATE_UNAVAILABLE)
+		return;
+
+	if (current_state == NM_DEVICE_STATE_DISCONNECTED)
+		interval = 10;
+	else
+		interval = 20;
+
+	nm_clear_g_source (&priv->periodic_scan_id);
+	priv->periodic_scan_id = g_timeout_add_seconds (interval,
+	                                                periodic_scan_timeout_cb,
+	                                                self);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		remove_all_aps (self);
+		nm_clear_g_source (&priv->periodic_scan_id);
+	} else if (old_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		update_aps (self);
+		schedule_periodic_scan (self, new_state);
+	}
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		/*
+		 * If the device is enabled and the IWD manager is ready,
+		 * transition to DISCONNECTED because the device is now
+		 * ready to use.
+		 */
+		if (priv->enabled && priv->dbus_obj) {
+			nm_device_queue_recheck_available (device,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		}
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		break;
+	case NM_DEVICE_STATE_IP_CHECK:
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		break;
+	default:
+		break;
+	}
+}
+
+static gboolean
+get_enabled (NMDevice *device)
+{
+	return NM_DEVICE_IWD_GET_PRIVATE ((NMDeviceIwd *) device)->enabled;
+}
+
+static void
+set_enabled (NMDevice *device, gboolean enabled)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	enabled = !!enabled;
+
+	if (priv->enabled == enabled)
+		return;
+
+	priv->enabled = enabled;
+
+	_LOGD (LOGD_WIFI, "device now %s", enabled ? "enabled" : "disabled");
+
+	state = nm_device_get_state (device);
+	if (state < NM_DEVICE_STATE_UNAVAILABLE) {
+		_LOGD (LOGD_WIFI, "(%s): device blocked by UNMANAGED state",
+		       enabled ? "enable" : "disable");
+		return;
+	}
+
+	if (priv->dbus_proxy)
+		set_powered (self, enabled);
+
+	if (enabled) {
+		if (state != NM_DEVICE_STATE_UNAVAILABLE)
+			_LOGW (LOGD_CORE, "not in expected unavailable state!");
+
+		if (priv->dbus_obj)
+			nm_device_queue_recheck_available (device,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+			                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	} else {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_UNAVAILABLE,
+		                         NM_DEVICE_STATE_REASON_NONE);
+	}
+}
+
+static gboolean
+can_reapply_change (NMDevice *device,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
+{
+	NMDeviceClass *device_class;
+
+	/* Only handle wireless setting here, delegate other settings to parent class */
+	if (nm_streq (setting_name, NM_SETTING_WIRELESS_SETTING_NAME)) {
+		return nm_device_hash_check_invalid_keys (diffs,
+		                                          NM_SETTING_WIRELESS_SETTING_NAME,
+		                                          error,
+		                                          NM_SETTING_WIRELESS_MTU); /* reapplied with IP config */
+	}
+
+	device_class = NM_DEVICE_CLASS (nm_device_iwd_parent_class);
+	return device_class->can_reapply_change (device,
+	                                         setting_name,
+	                                         s_old,
+	                                         s_new,
+	                                         diffs,
+	                                         error);
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (object);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	const char **list;
+
+	switch (prop_id) {
+	case PROP_MODE:
+		if (priv->current_ap)
+			g_value_set_uint (value, NM_802_11_MODE_INFRA);
+		else
+			g_value_set_uint (value, NM_802_11_MODE_UNKNOWN);
+		break;
+	case PROP_BITRATE:
+		g_value_set_uint (value, 65000);
+		break;
+	case PROP_CAPABILITIES:
+		g_value_set_uint (value, priv->capabilities);
+		break;
+	case PROP_ACCESS_POINTS:
+		list = nm_wifi_aps_get_paths (&priv->aps_lst_head, TRUE);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
+		break;
+	case PROP_ACTIVE_ACCESS_POINT:
+		nm_dbus_utils_g_value_set_object_path (value, priv->current_ap);
+		break;
+	case PROP_SCANNING:
+		g_value_set_boolean (value, priv->scanning);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMDeviceIwd *device = NM_DEVICE_IWD (object);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (device);
+
+	switch (prop_id) {
+	case PROP_CAPABILITIES:
+		/* construct-only */
+		priv->capabilities = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+state_changed (NMDeviceIwd *self, const gchar *new_state)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceState dev_state = nm_device_get_state (device);
+	gboolean iwd_connection = FALSE;
+	gboolean can_connect;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
+
+	if (   dev_state >= NM_DEVICE_STATE_CONFIG
+	    && dev_state <= NM_DEVICE_STATE_ACTIVATED)
+		iwd_connection = TRUE;
+
+	/* Don't allow scanning while connecting, disconnecting or roaming */
+	priv->can_scan = NM_IN_STRSET (new_state, "connected", "disconnected");
+
+	/* Don't allow new connection until iwd exits disconnecting */
+	can_connect = NM_IN_STRSET (new_state, "disconnected");
+	if (can_connect != priv->can_connect) {
+		priv->can_connect = can_connect;
+		nm_device_emit_recheck_auto_activate (device);
+	}
+
+	if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) {
+		/* If we were connecting, do nothing, the confirmation of
+		 * a connection success is handled in the Device.Connect
+		 * method return callback.  Otherwise IWD must have connected
+		 * without Network Manager's will so for simplicity force a
+		 * disconnect.
+		 */
+		if (iwd_connection)
+			return;
+
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Unsolicited connection success, asking IWD to disconnect");
+		send_disconnect (self);
+
+		return;
+	} else if (NM_IN_STRSET (new_state, "disconnecting", "disconnected")) {
+		if (!iwd_connection)
+			return;
+
+		/* Call Disconnect on the IWD device object to make sure it
+		 * disables its own autoconnect.
+		 *
+		 * Note we could instead call net.connman.iwd.KnownNetworks.ForgetNetwork
+		 * and leave the device in autoconnect.  This way if NetworkManager
+		 * changes any settings for this connection, they'd be taken into
+		 * account on the next connection attempt.  But both methods are
+		 * a hack, we'll perhaps need an IWD API to "connect once" without
+		 * storing anything.
+		 */
+		send_disconnect (self);
+
+		/*
+		 * If IWD is still handling the Connect call, let our callback
+		 * for the dbus method handle the failure.
+		 */
+		if (dev_state == NM_DEVICE_STATE_CONFIG)
+			return;
+
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+	                                 NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+
+		return;
+	}
+
+	_LOGE (LOGD_WIFI, "State %s unknown", new_state);
+}
+
+static void
+scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
+
+	if (new_scanning == priv->scanning)
+		return;
+
+	priv->scanning = new_scanning;
+
+	_notify (self, PROP_SCANNING);
+
+	if (!priv->scanning) {
+		update_aps (self);
+
+		if (!priv->scan_requested)
+			schedule_periodic_scan (self, state);
+	}
+}
+
+static void
+powered_changed (NMDeviceIwd *self, gboolean new_powered)
+{
+	nm_device_queue_recheck_available (NM_DEVICE (self),
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                    GStrv invalidate_properties, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	GVariantIter *iter;
+	const gchar *key;
+	GVariant *value;
+
+	g_variant_get (changed_properties, "a{sv}", &iter);
+	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
+		if (!strcmp (key, "State"))
+			state_changed (self, g_variant_get_string (value, NULL));
+
+		if (!strcmp (key, "Scanning"))
+			scanning_changed (self, g_variant_get_boolean (value));
+
+		if (!strcmp (key, "Powered"))
+			powered_changed (self, g_variant_get_boolean (value));
+
+		g_variant_unref (value);
+	}
+
+	g_variant_iter_free (iter);
+}
+
+void
+nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GDBusInterface *interface;
+	GVariant *value;
+
+	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
+		return;
+
+	if (priv->dbus_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_proxy,
+		                                      properties_changed, self);
+
+		g_clear_object (&priv->dbus_proxy);
+	}
+
+	if (priv->enabled)
+		nm_device_queue_recheck_available (NM_DEVICE (self),
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+	if (!object) {
+		priv->can_scan = FALSE;
+
+		cleanup_association_attempt (self, FALSE);
+		return;
+	}
+
+	interface = g_dbus_object_get_interface (object, NM_IWD_DEVICE_INTERFACE);
+	priv->dbus_proxy = G_DBUS_PROXY (interface);
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "Scanning");
+	priv->scanning = g_variant_get_boolean (value);
+	g_variant_unref (value);
+	priv->scan_requested = FALSE;
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "State");
+	state_changed (self, g_variant_get_string (value, NULL));
+	g_variant_unref (value);
+
+	g_signal_connect (priv->dbus_proxy, "g-properties-changed",
+	                  G_CALLBACK (properties_changed), self);
+
+	set_powered (self, priv->enabled);
+
+	/* Call Disconnect to make sure IWD's autoconnect is disabled.
+	 * Autoconnect is the default state after device is brought UP.
+	 */
+	if (priv->enabled)
+		send_disconnect (self);
+}
+
+gboolean
+nm_device_iwd_agent_psk_query (NMDeviceIwd *self,
+                               GDBusMethodInvocation *invocation)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMActRequest *req;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const gchar *psk;
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	if (!req)
+		return FALSE;
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (nm_act_request_get_applied_connection (req));
+	if (!s_wireless_sec)
+		return FALSE;
+
+	psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+	if (psk) {
+		_LOGD (LOGD_DEVICE | LOGD_WIFI,
+		       "Returning the PSK to the IWD Agent");
+
+		g_dbus_method_invocation_return_value (invocation,
+		                                       g_variant_new ("(s)", psk));
+		return TRUE;
+	}
+
+	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH,
+	                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	wifi_secrets_get_secrets (self,
+	                          NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+	                          NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+	                            | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+
+	priv->secrets_request = invocation;
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	nm_assert (NM_IS_DEVICE_IWD (device));
+
+	return "wifi";
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_iwd_init (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	c_list_init (&priv->aps_lst_head);
+
+	/* Make sure the manager is running */
+	(void) nm_iwd_manager_get ();
+}
+
+NMDevice *
+nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities)
+{
+	return g_object_new (NM_TYPE_DEVICE_IWD,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+	                     NM_DEVICE_IWD_CAPABILITIES, (guint) capabilities,
+	                     NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceIwd *self = NM_DEVICE_IWD (object);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	nm_clear_g_cancellable (&priv->cancellable);
+
+	nm_clear_g_source (&priv->periodic_scan_id);
+
+	cleanup_association_attempt (self, TRUE);
+
+	g_clear_object (&priv->dbus_proxy);
+	g_clear_object (&priv->dbus_obj);
+
+	remove_all_aps (self);
+
+	G_OBJECT_CLASS (nm_device_iwd_parent_class)->dispose (object);
+
+	nm_assert (c_list_is_empty (&priv->aps_lst_head));
+}
+
+static void
+nm_device_iwd_class_init (NMDeviceIwdClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
+
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
+
+	object_class->get_property = get_property;
+	object_class->set_property = set_property;
+	object_class->dispose = dispose;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
+
+	parent_class->can_auto_connect = can_auto_connect;
+	parent_class->is_available = is_available;
+	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
+	parent_class->check_connection_compatible = check_connection_compatible;
+	parent_class->check_connection_available = check_connection_available;
+	parent_class->complete_connection = complete_connection;
+	parent_class->get_enabled = get_enabled;
+	parent_class->set_enabled = set_enabled;
+	parent_class->get_type_description = get_type_description;
+
+	parent_class->act_stage1_prepare = act_stage1_prepare;
+	parent_class->act_stage2_config = act_stage2_config;
+	parent_class->get_configured_mtu = get_configured_mtu;
+	parent_class->deactivate = deactivate;
+	parent_class->deactivate_async = deactivate_async;
+	parent_class->deactivate_async_finish = deactivate_async_finish;
+	parent_class->can_reapply_change = can_reapply_change;
+
+	parent_class->state_changed = device_state_changed;
+
+	klass->scanning_prohibited = scanning_prohibited;
+
+	obj_properties[PROP_MODE] =
+	    g_param_spec_uint (NM_DEVICE_IWD_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_IWD_BITRATE, "", "",
+	                       0, G_MAXUINT32, 0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ACCESS_POINTS] =
+	    g_param_spec_boxed (NM_DEVICE_IWD_ACCESS_POINTS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_ACTIVE_ACCESS_POINT] =
+	    g_param_spec_string (NM_DEVICE_IWD_ACTIVE_ACCESS_POINT, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_CAPABILITIES] =
+	    g_param_spec_uint (NM_DEVICE_IWD_CAPABILITIES, "", "",
+	                       0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE,
+	                       G_PARAM_READWRITE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SCANNING] =
+	    g_param_spec_boolean (NM_DEVICE_IWD_SCANNING, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	signals[SCANNING_PROHIBITED] =
+	    g_signal_new (NM_DEVICE_IWD_SCANNING_PROHIBITED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  G_STRUCT_OFFSET (NMDeviceIwdClass, scanning_prohibited),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+}
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
new file mode 100644
index 00000000..699ba1bb
--- /dev/null
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -0,0 +1,62 @@
+/* -*- 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) 2017 Intel Corporation
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_IWD_H__
+#define __NETWORKMANAGER_DEVICE_IWD_H__
+
+#include "devices/nm-device.h"
+#include "nm-wifi-ap.h"
+#include "nm-device-wifi.h"
+
+#define NM_TYPE_DEVICE_IWD              (nm_device_iwd_get_type ())
+#define NM_DEVICE_IWD(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_IWD, NMDeviceIwd))
+#define NM_DEVICE_IWD_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_IWD, NMDeviceIwdClass))
+#define NM_IS_DEVICE_IWD(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_IWD))
+#define NM_IS_DEVICE_IWD_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_IWD))
+#define NM_DEVICE_IWD_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_IWD, NMDeviceIwdClass))
+
+#define NM_DEVICE_IWD_MODE                NM_DEVICE_WIFI_MODE
+#define NM_DEVICE_IWD_BITRATE             NM_DEVICE_WIFI_BITRATE
+#define NM_DEVICE_IWD_ACCESS_POINTS       NM_DEVICE_WIFI_ACCESS_POINTS
+#define NM_DEVICE_IWD_ACTIVE_ACCESS_POINT NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT
+#define NM_DEVICE_IWD_CAPABILITIES        NM_DEVICE_WIFI_CAPABILITIES
+#define NM_DEVICE_IWD_SCANNING            NM_DEVICE_WIFI_SCANNING
+
+#define NM_DEVICE_IWD_SCANNING_PROHIBITED  NM_DEVICE_WIFI_SCANNING_PROHIBITED
+
+typedef struct _NMDeviceIwd NMDeviceIwd;
+typedef struct _NMDeviceIwdClass NMDeviceIwdClass;
+
+GType nm_device_iwd_get_type (void);
+
+NMDevice *nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities);
+
+void nm_device_iwd_set_dbus_object (NMDeviceIwd *device, GDBusObject *object);
+
+gboolean nm_device_iwd_agent_psk_query (NMDeviceIwd *device,
+                                        GDBusMethodInvocation *invocation);
+
+const CList *_nm_device_iwd_get_aps (NMDeviceIwd *self);
+
+void _nm_device_iwd_request_scan (NMDeviceIwd *self,
+                                  GVariant *options,
+                                  GDBusMethodInvocation *invocation);
+
+#endif /* __NETWORKMANAGER_DEVICE_IWD_H__ */
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 3a4a027a..fd7bf3f7 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -48,11 +48,6 @@
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
 
-/* This is a bug; but we can't really change API now... */
-#include "nm-vpn-dbus-interface.h"
-
-#include "introspection/org.freedesktop.NetworkManager.Device.OlpcMesh.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceOlpcMesh);
 
@@ -118,7 +113,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMSettingOlpcMesh *s_mesh;
@@ -182,7 +177,6 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       nm_device_get_iface (priv->companion));
 	}
 
-
 	/* wait with continuing configuration untill the companion device is done scanning */
 	g_object_get (priv->companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
 	if (scanning) {
@@ -392,7 +386,8 @@ static void
 find_companion (NMDeviceOlpcMesh *self)
 {
 	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
-	const GSList *list;
+	const CList *tmp_lst;
+	NMDevice *candidate;
 
 	if (priv->companion)
 		return;
@@ -400,8 +395,8 @@ find_companion (NMDeviceOlpcMesh *self)
 	nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_COMPANION, TRUE);
 
 	/* Try to find the companion if it's already known to the NMManager */
-	for (list = nm_manager_get_devices (priv->manager); list ; list = g_slist_next (list)) {
-		if (check_companion (self, NM_DEVICE (list->data))) {
+	nm_manager_for_each_device (priv->manager, candidate, tmp_lst) {
+		if (check_companion (self, candidate)) {
 			nm_device_queue_recheck_available (NM_DEVICE (self),
 			                                   NM_DEVICE_STATE_REASON_NONE,
 			                                   NM_DEVICE_STATE_REASON_NONE);
@@ -440,7 +435,7 @@ get_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_COMPANION:
-		nm_utils_g_value_set_object_path (value, priv->companion);
+		nm_dbus_utils_g_value_set_object_path (value, priv->companion);
 		break;
 	case PROP_ACTIVE_CHANNEL:
 		g_value_set_uint (value, nm_platform_mesh_get_channel (nm_device_get_platform (device), nm_device_get_ifindex (device)));
@@ -500,10 +495,26 @@ dispose (GObject *object)
 	G_OBJECT_CLASS (nm_device_olpc_mesh_parent_class)->dispose (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_device_olpc_mesh = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_OLPC_MESH,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",     "s", NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Companion",     "o", NM_DEVICE_OLPC_MESH_COMPANION),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveChannel", "u", NM_DEVICE_OLPC_MESH_ACTIVE_CHANNEL),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_OLPC_MESH_SETTING_NAME, NM_LINK_TYPE_OLPC_MESH)
@@ -512,6 +523,8 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_olpc_mesh);
+
 	parent_class->check_connection_compatible = check_connection_compatible;
 	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
 	parent_class->complete_connection = complete_connection;
@@ -534,9 +547,5 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	                        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_OLPC_MESH_SKELETON,
-	                                        NULL);
 }
 
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index bf021095..90f94557 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -28,9 +28,11 @@
 #include <unistd.h>
 #include <errno.h>
 
+#include "nm-wifi-ap.h"
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
+#include "nm-dbus-manager.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-act-request.h"
@@ -48,11 +50,11 @@
 #include "nm-auth-utils.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
+#include "nm-wifi-utils.h"
+#include "nm-wifi-common.h"
 #include "nm-core-internal.h"
 #include "nm-config.h"
 
-#include "introspection/org.freedesktop.NetworkManager.Device.Wireless.h"
-
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWifi);
 
@@ -75,8 +77,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 );
 
 enum {
-	ACCESS_POINT_ADDED,
-	ACCESS_POINT_REMOVED,
 	SCANNING_PROHIBITED,
 
 	LAST_SIGNAL
@@ -87,7 +87,8 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	gint8             invalid_strength_counter;
 
-	GHashTable *      aps;
+	CList             aps_lst_head;
+
 	NMWifiAP *        current_ap;
 	guint32           rate;
 	bool              enabled:1; /* rfkilled or not */
@@ -187,7 +188,7 @@ static void request_wireless_scan (NMDeviceWifi *self,
                                    const GPtrArray *ssids);
 
 static void ap_add_remove (NMDeviceWifi *self,
-                           guint signum,
+                           gboolean is_adding,
                            NMWifiAP *ap,
                            gboolean recheck_available_connections);
 
@@ -345,30 +346,6 @@ supplicant_interface_release (NMDeviceWifi *self)
 	_notify_scanning (self);
 }
 
-static NMWifiAP *
-get_ap_by_path (NMDeviceWifi *self, const char *path)
-{
-	g_return_val_if_fail (path != NULL, NULL);
-	return g_hash_table_lookup (NM_DEVICE_WIFI_GET_PRIVATE (self)->aps, path);
-
-}
-
-static NMWifiAP *
-get_ap_by_supplicant_path (NMDeviceWifi *self, const char *path)
-{
-	GHashTableIter iter;
-	NMWifiAP *ap;
-
-	g_return_val_if_fail (path != NULL, NULL);
-
-	g_hash_table_iter_init (&iter, NM_DEVICE_WIFI_GET_PRIVATE (self)->aps);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-		if (g_strcmp0 (path, nm_wifi_ap_get_supplicant_path (ap)) == 0)
-			return ap;
-	}
-	return NULL;
-}
-
 static void
 update_seen_bssids_cache (NMDeviceWifi *self, NMWifiAP *ap)
 {
@@ -415,7 +392,7 @@ set_current_ap (NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available
 
 		/* Remove any AP from the internal list if it was created by NM or isn't known to the supplicant */
 		if (mode == NM_802_11_MODE_ADHOC || mode == NM_802_11_MODE_AP || nm_wifi_ap_get_fake (old_ap))
-			ap_add_remove (self, ACCESS_POINT_REMOVED, old_ap, recheck_available_connections);
+			ap_add_remove (self, FALSE, old_ap, recheck_available_connections);
 		g_object_unref (old_ap);
 	}
 
@@ -482,32 +459,32 @@ periodic_update_cb (gpointer user_data)
 
 static void
 ap_add_remove (NMDeviceWifi *self,
-               guint signum,
+               gboolean is_adding, /* or else removing */
                NMWifiAP *ap,
                gboolean recheck_available_connections)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	nm_assert (NM_IN_SET (signum, ACCESS_POINT_ADDED, ACCESS_POINT_REMOVED));
-
-	if (signum == ACCESS_POINT_ADDED) {
-		g_hash_table_insert (priv->aps,
-		                     (gpointer) nm_exported_object_export ((NMExportedObject *) ap),
-		                     g_object_ref (ap));
+	if (is_adding) {
+		g_object_ref (ap);
+		ap->wifi_device = NM_DEVICE (self);
+		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
 		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
-	} else
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
+	} else {
+		ap->wifi_device = NULL;
+		c_list_unlink (&ap->aps_lst);
 		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
-
-	g_signal_emit (self, signals[signum], 0, ap);
-
-	if (signum == ACCESS_POINT_REMOVED) {
-		g_hash_table_remove (priv->aps, nm_exported_object_get_path ((NMExportedObject *) ap));
-		nm_exported_object_unexport ((NMExportedObject *) ap);
-		g_object_unref (ap);
 	}
 
 	_notify (self, PROP_ACCESS_POINTS);
 
+	if (!is_adding) {
+		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, FALSE);
+		nm_dbus_object_clear_and_unexport (&ap);
+	}
+
 	nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
 	if (recheck_available_connections)
 		nm_device_recheck_available_connections (NM_DEVICE (self));
@@ -517,20 +494,15 @@ static void
 remove_all_aps (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	GHashTableIter iter;
 	NMWifiAP *ap;
 
-	if (!g_hash_table_size (priv->aps))
+	if (c_list_is_empty (&priv->aps_lst_head))
 		return;
 
 	set_current_ap (self, NULL, FALSE);
 
-again:
-	g_hash_table_iter_init (&iter, priv->aps);
-	if (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-		ap_add_remove (self, ACCESS_POINT_REMOVED, ap, FALSE);
-		goto again;
-	}
+	while ((ap = c_list_first_entry (&priv->aps_lst_head, NMWifiAP, aps_lst)))
+		ap_add_remove (self, FALSE, ap, FALSE);
 
 	nm_device_recheck_available_connections (NM_DEVICE (self));
 }
@@ -680,35 +652,14 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	return TRUE;
 }
 
-static NMWifiAP *
-find_first_compatible_ap (NMDeviceWifi *self,
-                          NMConnection *connection,
-                          gboolean allow_unstable_order)
-{
-	GHashTableIter iter;
-	NMWifiAP *ap;
-	NMWifiAP *cand_ap = NULL;
-
-	g_return_val_if_fail (connection != NULL, NULL);
-
-	g_hash_table_iter_init (&iter, NM_DEVICE_WIFI_GET_PRIVATE (self)->aps);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-		if (!nm_wifi_ap_check_compatible (ap, connection))
-			continue;
-		if (allow_unstable_order)
-			return ap;
-		if (!cand_ap || (nm_wifi_ap_get_id (cand_ap) < nm_wifi_ap_get_id (ap)))
-			cand_ap = ap;
-	}
-	return cand_ap;
-}
-
 static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
                             const char *specific_object)
 {
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *mode;
 
@@ -721,7 +672,7 @@ check_connection_available (NMDevice *device,
 	if (specific_object) {
 		NMWifiAP *ap;
 
-		ap = get_ap_by_path (NM_DEVICE_WIFI (device), specific_object);
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
 		return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
 	}
 
@@ -745,50 +696,18 @@ check_connection_available (NMDevice *device,
 		return TRUE;
 
 	/* check at least one AP is compatible with this connection */
-	return !!find_first_compatible_ap (NM_DEVICE_WIFI (device), connection, TRUE);
-}
-
-static gboolean
-is_manf_default_ssid (const GByteArray *ssid)
-{
-	int 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;
-		}
-	}
-	return FALSE;
+	return !!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 }
 
 static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     const GSList *existing_connections,
+                     NMConnection *const*existing_connections,
                      GError **error)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
 	char *str_ssid = NULL;
@@ -825,7 +744,7 @@ complete_connection (NMDevice *device,
 
 		if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
 			/* Find a compatible AP in the scan list */
-			ap = find_first_compatible_ap (self, connection, FALSE);
+			ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 
 			/* If we still don't have an AP, then the WiFI settings needs to be
 			 * fully specified by the client.  Might not be able to find an AP
@@ -847,7 +766,7 @@ complete_connection (NMDevice *device,
 			return FALSE;
 		ap = NULL;
 	} else {
-		ap = get_ap_by_path (self, specific_object);
+		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
 		if (!ap) {
 			g_set_error (error,
 			             NM_DEVICE_ERROR,
@@ -900,7 +819,7 @@ complete_connection (NMDevice *device,
 		 */
 		if (!nm_wifi_ap_complete_connection (ap,
 		                                     connection,
-		                                     is_manf_default_ssid (ssid),
+		                                     nm_wifi_utils_is_manf_default_ssid (ssid),
 		                                     error)) {
 			if (tmp_ssid)
 				g_byte_array_unref (tmp_ssid);
@@ -1007,6 +926,7 @@ can_auto_connect (NMDevice *device,
                   char **specific_object)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	NMWifiAP *ap;
 	const char *method, *mode;
@@ -1038,102 +958,20 @@ can_auto_connect (NMDevice *device,
 			return FALSE;
 	}
 
-	ap = find_first_compatible_ap (self, connection, FALSE);
+	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	if (ap) {
 		/* All good; connection is usable */
-		NM_SET_OUT (specific_object, g_strdup (nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap))));
+		NM_SET_OUT (specific_object, g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (ap))));
 		return TRUE;
 	}
 
 	return FALSE;
 }
 
-static int
-ap_id_compare (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
+const CList *
+_nm_device_wifi_get_aps (NMDeviceWifi *self)
 {
-	guint64 a_id = nm_wifi_ap_get_id (*((NMWifiAP **) p_a));
-	guint64 b_id = nm_wifi_ap_get_id (*((NMWifiAP **) p_b));
-
-	return a_id < b_id ? -1 : (a_id == b_id ? 0 : 1);
-}
-
-static NMWifiAP **
-ap_list_get_sorted (NMDeviceWifi *self, gboolean include_without_ssid)
-{
-	NMDeviceWifiPrivate *priv;
-	NMWifiAP **list;
-	GHashTableIter iter;
-	NMWifiAP *ap;
-	gsize i, n;
-
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
-	n = g_hash_table_size (priv->aps);
-	list = g_new (NMWifiAP *, n + 1);
-
-	i = 0;
-	if (n > 0) {
-		g_hash_table_iter_init (&iter, priv->aps);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
-			nm_assert (i < n);
-			if (   include_without_ssid
-			    || nm_wifi_ap_get_ssid (ap))
-				list[i++] = ap;
-		}
-		nm_assert (i <= n);
-		nm_assert (!include_without_ssid || i == n);
-
-		g_qsort_with_data (list,
-		                   i,
-		                   sizeof (gpointer),
-		                   ap_id_compare,
-		                   NULL);
-	}
-	list[i] = NULL;
-	return list;
-}
-
-static const char **
-ap_list_get_sorted_paths (NMDeviceWifi *self, gboolean include_without_ssid)
-{
-	gpointer *list;
-	gsize i, j;
-
-	list = (gpointer *) ap_list_get_sorted (self, include_without_ssid);
-	for (i = 0, j = 0; list[i]; i++) {
-		NMWifiAP *ap = list[i];
-		const char *path;
-
-		/* update @list inplace to hold instead the export-path. */
-		path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap));
-		nm_assert (path);
-		list[j++] = (gpointer) path;
-	}
-	return (const char **) list;
-}
-
-static void
-impl_device_wifi_get_access_points (NMDeviceWifi *self,
-                                    GDBusMethodInvocation *context)
-{
-	gs_free const char **list = NULL;
-	GVariant *v;
-
-	list = ap_list_get_sorted_paths (self, FALSE);
-	v = g_variant_new_objv (list, -1);
-	g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
-}
-
-static void
-impl_device_wifi_get_all_access_points (NMDeviceWifi *self,
-                                        GDBusMethodInvocation *context)
-{
-	gs_free const char **list = NULL;
-	GVariant *v;
-
-	list = ap_list_get_sorted_paths (self, TRUE);
-	v = g_variant_new_objv (list, -1);
-	g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
+	return &NM_DEVICE_WIFI_GET_PRIVATE (self)->aps_lst_head;
 }
 
 static void
@@ -1153,7 +991,7 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	randomize = nm_config_data_get_device_config_boolean (NM_CONFIG_GET_DATA,
-	                                                      "wifi.scan-rand-mac-address",
+	                                                      NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
 	                                                      device,
 	                                                      TRUE, TRUE);
 
@@ -1239,7 +1077,6 @@ dbus_request_scan_cb (NMDevice *device,
                       gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
-	NMDeviceWifiPrivate *priv;
 	gs_unref_variant GVariant *scan_options = user_data;
 	gs_unref_ptrarray GPtrArray *ssids = NULL;
 
@@ -1256,8 +1093,6 @@ dbus_request_scan_cb (NMDevice *device,
 		return;
 	}
 
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
 	if (scan_options) {
 		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
 
@@ -1284,10 +1119,10 @@ dbus_request_scan_cb (NMDevice *device,
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
-static void
-impl_device_wifi_request_scan (NMDeviceWifi *self,
-                               GDBusMethodInvocation *context,
-                               GVariant *options)
+void
+_nm_device_wifi_request_scan (NMDeviceWifi *self,
+                              GVariant *options,
+                              GDBusMethodInvocation *invocation)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
@@ -1297,7 +1132,7 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	    || !priv->sup_iface
 	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
 	    || nm_device_is_activating (device)) {
-		g_dbus_method_invocation_return_error_literal (context,
+		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
 		                                               "Scanning not allowed while unavailable or activating");
@@ -1305,7 +1140,7 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	}
 
 	if (nm_supplicant_interface_get_scanning (priv->sup_iface)) {
-		g_dbus_method_invocation_return_error_literal (context,
+		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
 		                                               "Scanning not allowed while already scanning");
@@ -1314,17 +1149,16 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 
 	last_scan = nm_supplicant_interface_get_last_scan_time (priv->sup_iface);
 	if (last_scan && (nm_utils_get_monotonic_timestamp_s () - last_scan) < 10) {
-		g_dbus_method_invocation_return_error_literal (context,
+		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
 		                                               "Scanning not allowed immediately following previous scan");
 		return;
 	}
 
-	/* Ask the manager to authenticate this request for us */
 	g_signal_emit_by_name (device,
 	                       NM_DEVICE_AUTH_REQUEST,
-	                       context,
+	                       invocation,
 	                       NULL,
 	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	                       TRUE,
@@ -1608,17 +1442,15 @@ ap_list_dump (gpointer user_data)
 	priv->ap_dump_id = 0;
 
 	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
-		gs_free NMWifiAP **list = NULL;
-		gsize i;
+		NMWifiAP *ap;
 		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
 
 		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%u next:%u]",
 		       now_s,
 		       priv->last_scan,
 		       priv->scheduled_scan_time);
-		list = ap_list_get_sorted (self, TRUE);
-		for (i = 0; list[i]; i++)
-			_ap_dump (self, LOGL_DEBUG, list[i], "dump", now_s);
+		c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
+			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_s);
 	}
 	return G_SOURCE_REMOVE;
 }
@@ -1689,7 +1521,7 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP)
 		return;
 
-	found_ap = get_ap_by_supplicant_path (self, object_path);
+	found_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
 	if (found_ap) {
 		if (!nm_wifi_ap_update_from_properties (found_ap, object_path, properties))
 			return;
@@ -1721,7 +1553,7 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 			}
 		}
 
-		ap_add_remove (self, ACCESS_POINT_ADDED, ap, TRUE);
+		ap_add_remove (self, TRUE, ap, TRUE);
 	}
 
 	/* Update the current AP if the supplicant notified a current BSS change
@@ -1745,7 +1577,7 @@ supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
 	g_return_if_fail (object_path != NULL);
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	ap = get_ap_by_supplicant_path (self, object_path);
+	ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
 	if (!ap)
 		return;
 
@@ -1758,7 +1590,7 @@ supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
 		if (nm_wifi_ap_set_fake (ap, TRUE))
 			_ap_dump (self, LOGL_DEBUG, ap, "updated", 0);
 	} else {
-		ap_add_remove (self, ACCESS_POINT_REMOVED, ap, TRUE);
+		ap_add_remove (self, FALSE, ap, TRUE);
 		schedule_ap_list_dump (self);
 	}
 }
@@ -2289,7 +2121,7 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 
 	current_bss = nm_supplicant_interface_get_current_bss (iface);
 	if (current_bss)
-		new_ap = get_ap_by_supplicant_path (self, current_bss);
+		new_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, current_bss);
 
 	if (new_ap != priv->current_ap) {
 		const char *new_bssid = NULL;
@@ -2315,9 +2147,9 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 		}
 
 		_LOGD (LOGD_WIFI, "roamed from BSSID %s (%s) to %s (%s)",
-		       old_bssid ? old_bssid : "(none)",
+		       old_bssid ?: "(none)",
 		       old_ssid ? nm_utils_escape_ssid (old_ssid->data, old_ssid->len) : "(none)",
-		       new_bssid ? new_bssid : "(none)",
+		       new_bssid ?: "(none)",
 		       new_ssid ? nm_utils_escape_ssid (new_ssid->data, new_ssid->len) : "(none)");
 
 		set_current_ap (self, new_ap, TRUE);
@@ -2497,6 +2329,7 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
 	NMSettingWirelessSecurityPmf pmf;
+	NMSettingWirelessSecurityFils fils;
 	gs_free char *value = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
@@ -2504,7 +2337,9 @@ build_supplicant_config (NMDeviceWifi *self,
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless != NULL, NULL);
 
-	config = nm_supplicant_config_new ();
+	config = nm_supplicant_config_new (
+		nm_supplicant_interface_get_pmf_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
+		nm_supplicant_interface_get_fils_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES);
 
 	/* Warn if AP mode may not be supported */
 	if (   g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0
@@ -2546,24 +2381,16 @@ build_supplicant_config (NMDeviceWifi *self,
 			                                    NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
 		}
 
-		/* Don't try to enable PMF on non-WPA networks */
-		if (!NM_IN_STRSET (nm_setting_wireless_security_get_key_mgmt (s_wireless_sec),
-		                   "wpa-eap",
-		                   "wpa-psk"))
-			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
-
-		/* Check if we actually support PMF */
-		if (nm_supplicant_interface_get_pmf_support (priv->sup_iface) != NM_SUPPLICANT_FEATURE_YES) {
-			if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
-				g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
-				                     "Supplicant does not support PMF");
-				goto error;
-			} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
-				/* To be on the safe side, assume no support if we can't determine
-				 * capabilities.
-				 */
-				pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
-			}
+		/* Configure FILS (802.11ai) */
+		fils = nm_setting_wireless_security_get_fils (s_wireless_sec);
+		if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) {
+			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+			                                               "wifi-sec.fils",
+			                                               NM_DEVICE (self));
+			fils = _nm_utils_ascii_str_to_int64 (value, 10,
+			                                     NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE,
+			                                     NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
+			                                     NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL);
 		}
 
 		s_8021x = nm_connection_get_setting_802_1x (connection);
@@ -2573,6 +2400,7 @@ build_supplicant_config (NMDeviceWifi *self,
 		                                                         con_uuid,
 		                                                         mtu,
 		                                                         pmf,
+		                                                         fils,
 		                                                         error)) {
 			g_prefix_error (error, "802-11-wireless-security: ");
 			goto error;
@@ -2654,16 +2482,16 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	/* AP mode never uses a specific object or existing scanned AP */
 	if (priv->mode != NM_802_11_MODE_AP) {
 		ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
-		ap = ap_path ? get_ap_by_path (self, ap_path) : NULL;
+		ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL;
 		if (ap)
 			goto done;
 
-		ap = find_first_compatible_ap (self, connection, FALSE);
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	}
 
 	if (ap) {
 		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-		                                          nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap)));
+		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
 		goto done;
 	}
 
@@ -2680,11 +2508,11 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		nm_wifi_ap_set_address (ap, nm_device_get_hw_address (device));
 
 	g_object_freeze_notify (G_OBJECT (self));
-	ap_add_remove (self, ACCESS_POINT_ADDED, ap, TRUE);
+	ap_add_remove (self, TRUE, ap, TRUE);
 	g_object_thaw_notify (G_OBJECT (self));
 	set_current_ap (self, ap, FALSE);
 	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-	                                          nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap)));
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 
 done:
@@ -3005,7 +2833,6 @@ handle_ip_config_timeout (NMDeviceWifi *self,
 	return ret;
 }
 
-
 static NMActStageReturn
 act_stage4_ip4_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -3055,13 +2882,10 @@ activation_success_handler (NMDevice *device)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ifindex (device);
 	NMActRequest *req;
-	NMConnection *applied_connection;
 
 	req = nm_device_get_act_request (device);
 	g_assert (req);
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-
 	/* Clear any critical protocol notification in the wifi stack */
 	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
 
@@ -3099,7 +2923,7 @@ activation_success_handler (NMDevice *device)
 		}
 
 		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
-		                                          nm_exported_object_get_path (NM_EXPORTED_OBJECT (priv->current_ap)));
+		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (priv->current_ap)));
 	}
 
 	periodic_update (self);
@@ -3277,8 +3101,7 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gsize i;
-	char **list;
+	const char **list;
 
 	switch (prop_id) {
 	case PROP_MODE:
@@ -3291,13 +3114,11 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->capabilities);
 		break;
 	case PROP_ACCESS_POINTS:
-		list = (char **) ap_list_get_sorted_paths (self, TRUE);
-		for (i = 0; list[i]; i++)
-			list[i] = g_strdup (list[i]);
-		g_value_take_boxed (value, list);
+		list = nm_wifi_aps_get_paths (&priv->aps_lst_head, TRUE);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
 		break;
 	case PROP_ACTIVE_ACCESS_POINT:
-		nm_utils_g_value_set_object_path (value, priv->current_ap);
+		nm_dbus_utils_g_value_set_object_path (value, priv->current_ap);
 		break;
 	case PROP_SCANNING:
 		g_value_set_boolean (value, priv->is_scanning);
@@ -3333,8 +3154,9 @@ nm_device_wifi_init (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
+	c_list_init (&priv->aps_lst_head);
+
 	priv->mode = NM_802_11_MODE_INFRA;
-	priv->aps = g_hash_table_new (nm_str_hash, g_str_equal);
 }
 
 static void
@@ -3392,9 +3214,7 @@ finalize (GObject *object)
 	NMDeviceWifi *self = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	nm_assert (g_hash_table_size (priv->aps) == 0);
-
-	g_hash_table_unref (priv->aps);
+	nm_assert (c_list_is_empty (&priv->aps_lst_head));
 
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->finalize (object);
 }
@@ -3403,6 +3223,7 @@ static void
 nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
@@ -3413,6 +3234,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
+
 	parent_class->can_auto_connect = can_auto_connect;
 	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
 	parent_class->is_available = is_available;
@@ -3479,24 +3302,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[ACCESS_POINT_ADDED] =
-	    g_signal_new (NM_DEVICE_WIFI_ACCESS_POINT_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1,
-	                  NM_TYPE_WIFI_AP);
-
-	signals[ACCESS_POINT_REMOVED] =
-	    g_signal_new (NM_DEVICE_WIFI_ACCESS_POINT_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1,
-	                  NM_TYPE_WIFI_AP);
-
 	signals[SCANNING_PROHIBITED] =
 	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -3504,13 +3309,4 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
 	                  NULL, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
-
-	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
-	                                        NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
-	                                        "GetAccessPoints", impl_device_wifi_get_access_points,
-	                                        "GetAllAccessPoints", impl_device_wifi_get_all_access_points,
-	                                        "RequestScan", impl_device_wifi_request_scan,
-	                                        NULL);
 }
-
-
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 09707d4f..c13b00de 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -23,7 +23,6 @@
 #define __NETWORKMANAGER_DEVICE_WIFI_H__
 
 #include "devices/nm-device.h"
-#include "nm-wifi-ap.h"
 
 #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))
@@ -39,11 +38,6 @@
 #define NM_DEVICE_WIFI_CAPABILITIES        "wireless-capabilities"
 #define NM_DEVICE_WIFI_SCANNING            "scanning"
 
-/* signals */
-#define NM_DEVICE_WIFI_ACCESS_POINT_ADDED  "access-point-added"
-#define NM_DEVICE_WIFI_ACCESS_POINT_REMOVED "access-point-removed"
-
-/* internal signals */
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
@@ -53,4 +47,10 @@ GType nm_device_wifi_get_type (void);
 
 NMDevice * nm_device_wifi_new (const char *iface, NMDeviceWifiCapabilities capabilities);
 
+const CList *_nm_device_wifi_get_aps (NMDeviceWifi *self);
+
+void _nm_device_wifi_request_scan (NMDeviceWifi *self,
+                                   GVariant *options,
+                                   GDBusMethodInvocation *invocation);
+
 #endif /* __NETWORKMANAGER_DEVICE_WIFI_H__ */
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
new file mode 100644
index 00000000..39db3a04
--- /dev/null
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -0,0 +1,693 @@
+/* -*- 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) 2017 Intel Corporation
+ */
+
+#include "nm-default.h"
+
+#include "nm-iwd-manager.h"
+
+#include <string.h>
+#include <net/if.h>
+
+#include "nm-logging.h"
+#include "nm-core-internal.h"
+#include "nm-manager.h"
+#include "nm-device-iwd.h"
+#include "nm-utils/nm-random-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	gchar *name;
+	NMIwdNetworkSecurity security;
+} KnownNetworkData;
+
+typedef struct {
+	NMManager *manager;
+	GCancellable *cancellable;
+	gboolean running;
+	GDBusObjectManager *object_manager;
+	guint agent_id;
+	gchar *agent_path;
+	GSList *known_networks;
+} NMIwdManagerPrivate;
+
+struct _NMIwdManager {
+	GObject parent;
+	NMIwdManagerPrivate _priv;
+};
+
+struct _NMIwdManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT)
+
+#define NM_IWD_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMIwdManager, NM_IS_IWD_MANAGER)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "iwd-manager"
+#define _NMLOG_DOMAIN                     LOGD_WIFI
+
+#define _NMLOG(level, ...) \
+	G_STMT_START { \
+		if (nm_logging_enabled (level, _NMLOG_DOMAIN)) { \
+			char __prefix[32]; \
+			\
+			if (self) \
+				g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", ""_NMLOG_PREFIX_NAME"", (self)); \
+			else \
+				g_strlcpy (__prefix, _NMLOG_PREFIX_NAME, sizeof (__prefix)); \
+			_nm_log ((level), (_NMLOG_DOMAIN), 0, NULL, NULL, \
+			          "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+			          __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+		} \
+	} G_STMT_END
+
+/*****************************************************************************/
+
+static void
+psk_agent_dbus_method_cb (GDBusConnection *connection,
+                          const gchar *sender, const gchar *object_path,
+                          const gchar *interface_name, const gchar *method_name,
+                          GVariant *parameters,
+                          GDBusMethodInvocation *invocation,
+                          gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
+	const gchar *network_path, *device_path, *ifname;
+	gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL;
+	gs_unref_variant GVariant *value = NULL;
+	gint ifindex;
+	NMDevice *device;
+
+	/* Be paranoid and check the sender address */
+	if (!nm_streq0 (g_dbus_object_manager_client_get_name_owner (omc), sender))
+		goto return_error;
+
+	g_variant_get (parameters, "(&o)", &network_path);
+
+	network = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                               network_path,
+	                                               NM_IWD_NETWORK_INTERFACE);
+	value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (network), "Device");
+	device_path = g_variant_get_string (value, NULL);
+
+	if (!device_path) {
+		_LOGE ("Device not cached for network %s in IWD Agent request",
+		       network_path);
+		goto return_error;
+	}
+
+	device_obj = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                                  device_path,
+	                                                  NM_IWD_DEVICE_INTERFACE);
+	g_variant_unref (value);
+	value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (device_obj), "Name");
+	ifname = g_variant_get_string (value, NULL);
+
+	if (!ifname) {
+		_LOGE ("Name not cached for device %s in IWD Agent request",
+		       device_path);
+		goto return_error;
+	}
+
+	ifindex = if_nametoindex (ifname);
+	if (!ifindex) {
+		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
+		       ifname, device_path, errno);
+		goto return_error;
+	}
+
+	device = nm_manager_get_device_by_ifindex (priv->manager, ifindex);
+	if (!NM_IS_DEVICE_IWD (device)) {
+		_LOGE ("IWD device named %s is not a Wifi device in IWD Agent request",
+                       ifname);
+		goto return_error;
+	}
+
+	if (nm_device_iwd_agent_psk_query (NM_DEVICE_IWD (device), invocation))
+		return;
+
+	_LOGE ("Device %s did not handle the IWD Agent request", ifname);
+
+return_error:
+	/* IWD doesn't look at the specific error */
+	g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR,
+	                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
+	                                               "No PSK available for this connection");
+}
+
+static guint
+psk_agent_export (GDBusConnection *connection, gpointer user_data,
+                  gchar **agent_path, GError **error)
+{
+	static const GDBusArgInfo request_passphrase_arg_network = {
+		-1,
+		(gchar *) "network",
+		(gchar *) "o",
+		NULL,
+	};
+	static const GDBusArgInfo *const request_passphrase_in_args[] = {
+		&request_passphrase_arg_network,
+		NULL,
+	};
+	static const GDBusArgInfo request_passphrase_arg_passphrase = {
+		-1,
+		(gchar *) "passphrase",
+		(gchar *) "s",
+		NULL,
+	};
+	static const GDBusArgInfo *const request_passphrase_out_args[] = {
+		&request_passphrase_arg_passphrase,
+		NULL,
+	};
+	static const GDBusMethodInfo request_passphrase_info = {
+		-1,
+		(gchar *) "RequestPassphrase",
+		(GDBusArgInfo **) &request_passphrase_in_args,
+		(GDBusArgInfo **) &request_passphrase_out_args,
+		NULL,
+	};
+	static const GDBusMethodInfo *const method_info[] = {
+		&request_passphrase_info,
+		NULL,
+	};
+	static GDBusInterfaceInfo interface_info = {
+		-1,
+		(gchar *) "net.connman.iwd.Agent",
+		(GDBusMethodInfo **) &method_info,
+		NULL,
+		NULL,
+		NULL,
+	};
+	static GDBusInterfaceVTable vtable = {
+		psk_agent_dbus_method_cb,
+		NULL,
+		NULL,
+	};
+
+	gchar path[50];
+	unsigned int rnd;
+	guint id;
+
+	if  (!nm_utils_random_bytes (&rnd, sizeof (rnd))) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_FAILED,
+		                     "Can't read urandom.");
+		return 0;
+	}
+
+	nm_sprintf_buf (path, "/agent/%u", rnd);
+
+	id = g_dbus_connection_register_object (connection, path,
+	                                        &interface_info, &vtable,
+	                                        user_data, NULL, error);
+
+	if (id)
+		*agent_path = g_strdup (path);
+	return id;
+}
+
+static void
+register_agent (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusInterface *agent_manager;
+
+	agent_manager = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                                     "/",
+	                                                     NM_IWD_AGENT_MANAGER_INTERFACE);
+
+	/* Register our agent */
+	g_dbus_proxy_call (G_DBUS_PROXY (agent_manager),
+	                   "RegisterAgent",
+	                   g_variant_new ("(o)", priv->agent_path),
+	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	                   NULL, NULL, NULL);
+
+	g_object_unref (agent_manager);
+}
+
+/*****************************************************************************/
+
+static void
+set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface,
+                        GDBusObject *object)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusProxy *proxy;
+	GVariant *value;
+	const char *ifname;
+	gint ifindex;
+	NMDevice *device;
+
+	if (!priv->running)
+		return;
+
+	g_return_if_fail (G_IS_DBUS_PROXY (interface));
+
+	proxy = G_DBUS_PROXY (interface);
+
+	if (strcmp (g_dbus_proxy_get_interface_name (proxy),
+	            NM_IWD_DEVICE_INTERFACE))
+		return;
+
+	value = g_dbus_proxy_get_cached_property (proxy, "Name");
+	if (!value) {
+		_LOGE ("Name not cached for Device at %s",
+		       g_dbus_proxy_get_object_path (proxy));
+		return;
+	}
+
+	ifname = g_variant_get_string (value, NULL);
+	ifindex = if_nametoindex (ifname);
+	g_variant_unref (value);
+
+	if (!ifindex) {
+		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
+		       ifname, g_dbus_proxy_get_object_path (proxy), errno);
+		return;
+	}
+
+	device = nm_manager_get_device_by_ifindex (priv->manager, ifindex);
+	if (!NM_IS_DEVICE_IWD (device)) {
+		_LOGE ("IWD device named %s is not a Wifi device", ifname);
+		return;
+	}
+
+	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
+}
+
+static void
+interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
+                 GDBusInterface *interface, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+
+	set_device_dbus_object (self, interface, object);
+}
+
+static void
+interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
+                   GDBusInterface *interface, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+
+	/*
+	 * TODO: we may need to save the GDBusInterface or GDBusObject
+	 * pointer in the hash table because we may be no longer able to
+	 * access the Name property or map the name to ifindex with
+	 * if_nametoindex at this point.
+	 */
+
+	set_device_dbus_object (self, interface, NULL);
+}
+
+static gboolean
+_om_has_name_owner (GDBusObjectManager *object_manager)
+{
+	gs_free char *name_owner = NULL;
+
+	nm_assert (G_IS_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+
+	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+	return !!name_owner;
+}
+
+static void
+object_added (NMIwdManager *self, GDBusObject *object)
+{
+	GList *interfaces, *iter;
+
+	interfaces = g_dbus_object_get_interfaces (object);
+	for (iter = interfaces; iter; iter = iter->next) {
+		GDBusInterface *interface = G_DBUS_INTERFACE (iter->data);
+
+		set_device_dbus_object (self, interface, object);
+	}
+
+	g_list_free_full (interfaces, g_object_unref);
+}
+
+static void
+known_network_free (KnownNetworkData *network)
+{
+	g_free (network->name);
+	g_free (network);
+}
+
+static void
+list_known_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *variant = NULL;
+	GVariantIter *networks, *props;
+
+	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
+	                                      G_VARIANT_TYPE ("(aa{sv})"),
+	                                      &error);
+	if (!variant) {
+		_LOGE ("ListKnownNetworks() failed: %s", error->message);
+		return;
+	}
+
+	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
+	priv->known_networks = NULL;
+
+	g_variant_get (variant, "(aa{sv})", &networks);
+
+	while (g_variant_iter_next (networks, "a{sv}", &props)) {
+		const gchar *key;
+		const gchar *name = NULL;
+		const gchar *type = NULL;
+		GVariant *val;
+		KnownNetworkData *network_data;
+
+		while (g_variant_iter_next (props, "{&sv}", &key, &val)) {
+			if (!strcmp (key, "Name"))
+				name = g_variant_get_string (val, NULL);
+
+			if (!strcmp (key, "Type"))
+				type = g_variant_get_string (val, NULL);
+
+			g_variant_unref (val);
+		}
+
+		if (!name || !type)
+			goto next;
+
+		network_data = g_new (KnownNetworkData, 1);
+		network_data->name = g_strdup (name);
+		if (!strcmp (type, "open"))
+			network_data->security = NM_IWD_NETWORK_SECURITY_NONE;
+		else if (!strcmp (type, "psk"))
+			network_data->security = NM_IWD_NETWORK_SECURITY_PSK;
+		else if (!strcmp (type, "8021x"))
+			network_data->security = NM_IWD_NETWORK_SECURITY_8021X;
+
+		priv->known_networks = g_slist_append (priv->known_networks,
+		                                       network_data);
+
+next:
+		g_variant_iter_free (props);
+	}
+
+	g_variant_iter_free (networks);
+
+	/* For completness we may want to call nm_device_emit_recheck_auto_activate
+	 * and nm_device_recheck_available_connections for all affected devices
+	 * now but the ListKnownNetworks call should have been really fast,
+	 * faster than any scan on any newly created devices could have happened.
+	 */
+}
+
+static void
+update_known_networks (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusInterface *known_networks_if;
+
+	known_networks_if = g_dbus_object_manager_get_interface (priv->object_manager,
+	                                                         "/",
+	                                                         NM_IWD_KNOWN_NETWORKS_INTERFACE);
+
+	g_dbus_proxy_call (G_DBUS_PROXY (known_networks_if),
+	                   "ListKnownNetworks",
+	                   g_variant_new ("()"),
+	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	                   priv->cancellable, list_known_networks_cb, self);
+
+	g_object_unref (known_networks_if);
+}
+
+static void prepare_object_manager (NMIwdManager *self);
+
+static void
+name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusObjectManager *object_manager = G_DBUS_OBJECT_MANAGER (object);
+
+	nm_assert (object_manager == priv->object_manager);
+
+	if (_om_has_name_owner (object_manager)) {
+		g_signal_handlers_disconnect_by_data (object_manager, self);
+		g_clear_object (&priv->object_manager);
+		prepare_object_manager (self);
+	} else {
+		const CList *tmp_lst;
+		NMDevice *device;
+
+		if (!priv->running)
+			return;
+
+		priv->running = false;
+
+		nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+			if (NM_IS_DEVICE_IWD (device)) {
+				nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device),
+				                               NULL);
+			}
+		}
+	}
+}
+
+static void
+device_added (NMManager *manager, NMDevice *device, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GList *objects, *iter;
+
+	if (!NM_IS_DEVICE_IWD (device))
+		return;
+
+	if (!priv->running)
+		return;
+
+	objects = g_dbus_object_manager_get_objects (priv->object_manager);
+	for (iter = objects; iter; iter = iter->next) {
+		GDBusObject *object = G_DBUS_OBJECT (iter->data);
+		GDBusInterface *interface;
+		GDBusProxy *proxy;
+		GVariant *value;
+		const char *obj_ifname;
+
+		interface = g_dbus_object_get_interface (object,
+		                                         NM_IWD_DEVICE_INTERFACE);
+		if (!interface)
+			continue;
+
+		proxy = G_DBUS_PROXY (interface);
+		value = g_dbus_proxy_get_cached_property (proxy, "Name");
+		if (!value) {
+			g_object_unref (interface);
+			continue;
+		}
+
+		obj_ifname = g_variant_get_string (value, NULL);
+		g_variant_unref (value);
+		g_object_unref (interface);
+
+		if (strcmp (nm_device_get_iface (device), obj_ifname))
+			continue;
+
+		nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
+		break;
+	}
+
+	g_list_free_full (objects, g_object_unref);
+}
+
+static void
+got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GError *error = NULL;
+	GDBusObjectManager *object_manager;
+	GDBusConnection *connection;
+
+	object_manager = g_dbus_object_manager_client_new_for_bus_finish (result, &error);
+	if (object_manager == NULL) {
+		_LOGE ("failed to acquire IWD Object Manager: Wi-Fi will not be available (%s)",
+		       NM_G_ERROR_MSG (error));
+		g_clear_error (&error);
+		return;
+	}
+
+	priv->object_manager = object_manager;
+
+	g_signal_connect (priv->object_manager, "notify::name-owner",
+	                  G_CALLBACK (name_owner_changed), self);
+
+	nm_assert (G_IS_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+
+	connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
+
+	priv->agent_id = psk_agent_export (connection, self,
+	                                   &priv->agent_path, &error);
+	if (!priv->agent_id) {
+		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks will not work: %s",
+		       NM_G_ERROR_MSG (error));
+		g_clear_error (&error);
+	}
+
+	if (_om_has_name_owner (object_manager)) {
+		GList *objects, *iter;
+
+		priv->running = true;
+
+		g_signal_connect (priv->object_manager, "interface-added",
+		                  G_CALLBACK (interface_added), self);
+		g_signal_connect (priv->object_manager, "interface-removed",
+		                  G_CALLBACK (interface_removed), self);
+
+		objects = g_dbus_object_manager_get_objects (object_manager);
+		for (iter = objects; iter; iter = iter->next)
+			object_added (self, G_DBUS_OBJECT (iter->data));
+
+		g_list_free_full (objects, g_object_unref);
+
+		if (priv->agent_id)
+			register_agent (self);
+
+		update_known_networks (self);
+	}
+}
+
+static void
+prepare_object_manager (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+
+	g_dbus_object_manager_client_new_for_bus (NM_IWD_BUS_TYPE,
+	                                          G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+	                                          NM_IWD_SERVICE, "/",
+	                                          NULL, NULL, NULL,
+	                                          priv->cancellable,
+	                                          got_object_manager, self);
+}
+
+gboolean
+nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+                                 NMIwdNetworkSecurity security)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	const GSList *iter;
+
+	for (iter = priv->known_networks; iter; iter = g_slist_next (iter)) {
+		const KnownNetworkData *network = iter->data;
+
+		if (!strcmp (network->name, name) && network->security == security)
+			return true;
+	}
+
+	return false;
+}
+
+void
+nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name,
+                                  NMIwdNetworkSecurity security)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	KnownNetworkData *network_data;
+
+	if (nm_iwd_manager_is_known_network (self, name, security))
+		return;
+
+	network_data = g_new (KnownNetworkData, 1);
+	network_data->name = g_strdup (name);
+	network_data->security = security;
+	priv->known_networks = g_slist_append (priv->known_networks, network_data);
+}
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMIwdManager, nm_iwd_manager_get,
+                            NM_TYPE_IWD_MANAGER);
+
+static void
+nm_iwd_manager_init (NMIwdManager *self)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+
+	priv->manager = g_object_ref (nm_manager_get ());
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
+	                  G_CALLBACK (device_added), self);
+
+	priv->cancellable = g_cancellable_new ();
+
+	prepare_object_manager (self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMIwdManager *self = (NMIwdManager *) object;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+
+	if (priv->object_manager) {
+		if (priv->agent_id) {
+			GDBusConnection *connection;
+			GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
+
+			/* No need to unregister the agent as IWD will detect
+			 * our DBus connection being closed.
+			 */
+
+			connection = g_dbus_object_manager_client_get_connection (omc);
+
+			g_dbus_connection_unregister_object (connection, priv->agent_id);
+			priv->agent_id = 0;
+		}
+
+		g_clear_object (&priv->object_manager);
+	}
+
+	nm_clear_g_free (&priv->agent_path);
+
+	nm_clear_g_cancellable (&priv->cancellable);
+
+	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
+	priv->known_networks = NULL;
+
+	if (priv->manager) {
+		g_signal_handlers_disconnect_by_data (priv->manager, self);
+		g_clear_object (&priv->manager);
+	}
+
+	G_OBJECT_CLASS (nm_iwd_manager_parent_class)->dispose (object);
+}
+
+static void
+nm_iwd_manager_class_init (NMIwdManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+}
diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/devices/wifi/nm-iwd-manager.h
new file mode 100644
index 00000000..8e6b66ff
--- /dev/null
+++ b/src/devices/wifi/nm-iwd-manager.h
@@ -0,0 +1,65 @@
+/* -*- 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) 2017 Intel Corporation
+ */
+
+#ifndef __NETWORKMANAGER_IWD_MANAGER_H__
+#define __NETWORKMANAGER_IWD_MANAGER_H__
+
+#include "devices/nm-device.h"
+
+#define NM_IWD_BUS_TYPE                 G_BUS_TYPE_SYSTEM
+#define NM_IWD_SERVICE                  "net.connman.iwd"
+
+#define NM_IWD_AGENT_MANAGER_INTERFACE  "net.connman.iwd.AgentManager"
+#define NM_IWD_WIPHY_INTERFACE          "net.connman.iwd.Adapter"
+#define NM_IWD_DEVICE_INTERFACE         "net.connman.iwd.Device"
+#define NM_IWD_NETWORK_INTERFACE        "net.connman.iwd.Network"
+#define NM_IWD_AGENT_INTERFACE          "net.connman.iwd.Agent"
+#define NM_IWD_WSC_INTERFACE            \
+	"net.connman.iwd.WiFiSimpleConfiguration"
+#define NM_IWD_KNOWN_NETWORKS_INTERFACE "net.connman.iwd.KnownNetworks"
+#define NM_IWD_SIGNAL_AGENT_INTERFACE   "net.connman.iwd.SignalLevelAgent"
+
+typedef enum {
+	NM_IWD_NETWORK_SECURITY_NONE,
+	NM_IWD_NETWORK_SECURITY_WEP,
+	NM_IWD_NETWORK_SECURITY_PSK,
+	NM_IWD_NETWORK_SECURITY_8021X,
+} NMIwdNetworkSecurity;
+
+#define NM_TYPE_IWD_MANAGER              (nm_iwd_manager_get_type ())
+#define NM_IWD_MANAGER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IWD_MANAGER, NMIwdManager))
+#define NM_IWD_MANAGER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_IWD_MANAGER, NMIwdManagerClass))
+#define NM_IS_IWD_MANAGER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IWD_MANAGER))
+#define NM_IS_IWD_MANAGER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_IWD_MANAGER))
+#define NM_IWD_MANAGER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_IWD_MANAGER, NMIwdManagerClass))
+
+typedef struct _NMIwdManager NMIwdManager;
+typedef struct _NMIwdManagerClass NMIwdManagerClass;
+
+GType nm_iwd_manager_get_type (void);
+
+NMIwdManager *nm_iwd_manager_get (void);
+
+gboolean nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+                                          NMIwdNetworkSecurity security);
+void nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name,
+                                       NMIwdNetworkSecurity security);
+
+#endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index bc823af0..dd6d1deb 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -21,19 +21,20 @@
 
 #include "nm-default.h"
 
+#include "nm-wifi-ap.h"
+
 #include <string.h>
 #include <stdlib.h>
 
-#include "nm-wifi-ap.h"
+#include "nm-setting-wireless.h"
+
 #include "nm-wifi-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
-
-#include "nm-setting-wireless.h"
-
-#include "introspection/org.freedesktop.NetworkManager.AccessPoint.h"
+#include "devices/nm-device.h"
+#include "nm-dbus-manager.h"
 
 #define PROTO_WPA "wpa"
 #define PROTO_RSN "rsn"
@@ -53,7 +54,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMWifiAP,
 	PROP_LAST_SEEN,
 );
 
-typedef struct {
+struct _NMWifiAPPrivate {
 	char *supplicant_path;   /* D-Bus object path of this AP from wpa_supplicant */
 
 	/* Scanned or cached values */
@@ -72,20 +73,17 @@ typedef struct {
 	bool                fake:1;       /* Whether or not the AP is from a scan */
 	bool                hotspot:1;    /* Whether the AP is a local device's hotspot network */
 	gint32              last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
-} NMWifiAPPrivate;
-
-struct _NMWifiAP {
-	NMExportedObject parent;
-	NMWifiAPPrivate _priv;
 };
 
+typedef struct _NMWifiAPPrivate NMWifiAPPrivate;
+
 struct _NMWifiAPClass {
-	NMExportedObjectClass parent;
+	NMDBusObjectClass parent;
 };
 
-G_DEFINE_TYPE (NMWifiAP, nm_wifi_ap, NM_TYPE_EXPORTED_OBJECT)
+G_DEFINE_TYPE (NMWifiAP, nm_wifi_ap, NM_TYPE_DBUS_OBJECT)
 
-#define NM_WIFI_AP_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMWifiAP, NM_IS_WIFI_AP)
+#define NM_WIFI_AP_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiAP, NM_IS_WIFI_AP)
 
 /*****************************************************************************/
 
@@ -97,30 +95,24 @@ nm_wifi_ap_get_supplicant_path (NMWifiAP *ap)
 	return NM_WIFI_AP_GET_PRIVATE (ap)->supplicant_path;
 }
 
-guint64
-nm_wifi_ap_get_id (NMWifiAP *ap)
+const GByteArray *
+nm_wifi_ap_get_ssid (const NMWifiAP *ap)
 {
-	const char *path;
-	guint64 i;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);
-
-	path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap));
-	g_return_val_if_fail (path, 0);
-
-	nm_assert (g_str_has_prefix (path, NM_DBUS_PATH_ACCESS_POINT"/"));
-
-	i = _nm_utils_ascii_str_to_int64 (&path[NM_STRLEN (NM_DBUS_PATH_ACCESS_POINT"/")], 10, 1, G_MAXINT64, 0);
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
 
-	nm_assert (i);
-	return i;
+	return NM_WIFI_AP_GET_PRIVATE (ap)->ssid;
 }
 
-const GByteArray * nm_wifi_ap_get_ssid (const NMWifiAP *ap)
+static GVariant *
+nm_wifi_ap_get_ssid_as_variant (const NMWifiAP *self)
 {
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
+	const NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
 
-	return NM_WIFI_AP_GET_PRIVATE (ap)->ssid;
+	if (priv->ssid) {
+		return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                  priv->ssid->data, priv->ssid->len, 1);
+	} else
+		return g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0);
 }
 
 gboolean
@@ -330,7 +322,7 @@ guint32
 nm_wifi_ap_get_max_bitrate (NMWifiAP *ap)
 {
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), 0);
-	g_return_val_if_fail (nm_exported_object_is_exported (NM_EXPORTED_OBJECT (ap)), 0);
+	g_return_val_if_fail (nm_dbus_object_is_exported (NM_DBUS_OBJECT (ap)), 0);
 
 	return NM_WIFI_AP_GET_PRIVATE (ap)->max_bitrate;
 }
@@ -415,7 +407,9 @@ security_from_vardict (GVariant *security)
 	    && array) {
 		if (g_strv_contains (array, "wpa-psk"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
-		if (g_strv_contains (array, "wpa-eap"))
+		if (g_strv_contains (array, "wpa-eap") ||
+		    g_strv_contains (array, "wpa-fils-sha256") ||
+		    g_strv_contains (array, "wpa-fils-sha384"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
 		g_free (array);
 	}
@@ -636,7 +630,7 @@ get_max_rate_vht_160_ss3 (int mcs)
 static gboolean
 get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
 {
-	guint32 mcs, i;
+	guint32 i;
 	guint8 ht_cap_info;
 	const guint8 *supported_mcs_set;
 	guint32 rate;
@@ -653,7 +647,6 @@ get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
 	*out_maxrate = 0;
 
 	/* Find the maximum supported mcs rate */
-	mcs = -1;
 	for (i = 0; i <= 76; i++) {
 		unsigned int mcs_octet = i / 8;
 		unsigned int MCS_RATE_BIT = 1 << i % 8;
@@ -977,7 +970,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	if (priv->supplicant_path)
 		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
 
-	export_path = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
+	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 	if (export_path)
 		export_path = strrchr (export_path, '/') ?: export_path;
 	else
@@ -1119,8 +1112,8 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE ((NMWifiAP *) object);
-	GVariant *ssid;
+	NMWifiAP *self = NM_WIFI_AP (object);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_FLAGS:
@@ -1133,12 +1126,7 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->rsn_flags);
 		break;
 	case PROP_SSID:
-		if (priv->ssid) {
-			ssid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-			                                  priv->ssid->data, priv->ssid->len, 1);
-		} else
-			ssid = g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0);
-		g_value_take_variant (value, ssid);
+		g_value_take_variant (value, nm_wifi_ap_get_ssid_as_variant (self));
 		break;
 	case PROP_FREQUENCY:
 		g_value_set_uint (value, priv->freq);
@@ -1170,9 +1158,15 @@ get_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-nm_wifi_ap_init (NMWifiAP *ap)
+nm_wifi_ap_init (NMWifiAP *self)
 {
-	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_AP, NMWifiAPPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->aps_lst);
 
 	priv->mode = NM_802_11_MODE_INFRA;
 	priv->flags = NM_802_11_AP_FLAGS_NONE;
@@ -1332,7 +1326,11 @@ error:
 static void
 finalize (GObject *object)
 {
-	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE ((NMWifiAP *) object);
+	NMWifiAP *self = NM_WIFI_AP (object);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
+
+	nm_assert (!self->wifi_device);
+	nm_assert (c_list_is_empty (&self->aps_lst));
 
 	g_free (priv->supplicant_path);
 	if (priv->ssid)
@@ -1342,6 +1340,28 @@ finalize (GObject *object)
 	G_OBJECT_CLASS (nm_wifi_ap_parent_class)->finalize (object);
 }
 
+static const NMDBusInterfaceInfoExtended interface_info_access_point = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_ACCESS_POINT,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Flags",      "u",  NM_WIFI_AP_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WpaFlags",   "u",  NM_WIFI_AP_WPA_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RsnFlags",   "u",  NM_WIFI_AP_RSN_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ssid",       "ay", NM_WIFI_AP_SSID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Frequency",  "u",  NM_WIFI_AP_FREQUENCY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",  "s",  NM_WIFI_AP_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",       "u",  NM_WIFI_AP_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("MaxBitrate", "u",  NM_WIFI_AP_MAX_BITRATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Strength",   "y",  NM_WIFI_AP_STRENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("LastSeen",   "i",  NM_WIFI_AP_LAST_SEEN),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
 static void
 nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 {
@@ -1359,9 +1379,12 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_KEY_MGMT_802_1X )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
-	NMExportedObjectClass *exported_object_class = NM_EXPORTED_OBJECT_CLASS (ap_class);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
+
+	g_type_class_add_private (object_class, sizeof (NMWifiAPPrivate));
 
-	exported_object_class->export_path = NM_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_ACCESS_POINT);
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_ACCESS_POINT);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_access_point);
 
 	object_class->get_property = get_property;
 	object_class->finalize = finalize;
@@ -1424,9 +1447,85 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	                       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,
-	                                        NULL);
+const char **
+nm_wifi_aps_get_paths (const CList *aps_lst_head, gboolean include_without_ssid)
+{
+	NMWifiAP *ap;
+	gsize i, n;
+	const char **list;
+	const char *path;
+
+	n = c_list_length (aps_lst_head);
+	list = g_new (const char *, n + 1);
+
+	i = 0;
+	if (n > 0) {
+		c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
+			nm_assert (i < n);
+			if (   !include_without_ssid
+			    && !nm_wifi_ap_get_ssid (ap))
+				continue;
+
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (ap));
+			nm_assert (path);
+
+			list[i++] = path;
+		}
+		nm_assert (i <= n);
+		nm_assert (!include_without_ssid || i == n);
+	}
+	list[i] = NULL;
+	return list;
 }
 
+NMWifiAP *
+nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
+                                   NMConnection *connection)
+{
+	NMWifiAP *ap;
+
+	g_return_val_if_fail (connection, NULL);
+
+	c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
+		if (nm_wifi_ap_check_compatible (ap, connection))
+			return ap;
+	}
+	return NULL;
+}
+
+NMWifiAP *
+nm_wifi_aps_find_by_supplicant_path (const CList *aps_lst_head, const char *path)
+{
+	NMWifiAP *ap;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
+		if (nm_streq0 (path, nm_wifi_ap_get_supplicant_path (ap)))
+			return ap;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+NMWifiAP *
+nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path)
+{
+	NMWifiAP *ap;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+	ap = (NMWifiAP *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                                 exported_path);
+	if (   !ap
+	    || !NM_IS_WIFI_AP (ap)
+	    || ap->wifi_device != device)
+		return NULL;
+
+	return ap;
+}
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index dd5a4ad1..4fdeee93 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -22,7 +22,7 @@
 #ifndef __NM_WIFI_AP_H__
 #define __NM_WIFI_AP_H__
 
-#include "nm-exported-object.h"
+#include "nm-dbus-object.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 
@@ -44,7 +44,13 @@
 #define NM_WIFI_AP_STRENGTH             "strength"
 #define NM_WIFI_AP_LAST_SEEN            "last-seen"
 
-typedef struct _NMWifiAP NMWifiAP;
+typedef struct {
+	NMDBusObject parent;
+	NMDevice *wifi_device;
+	CList aps_lst;
+	struct _NMWifiAPPrivate *_priv;
+} NMWifiAP;
+
 typedef struct _NMWifiAPClass NMWifiAPClass;
 
 GType nm_wifi_ap_get_type (void);
@@ -66,7 +72,6 @@ gboolean          nm_wifi_ap_complete_connection      (NMWifiAP *self,
                                                        GError **error);
 
 const char *      nm_wifi_ap_get_supplicant_path      (NMWifiAP *ap);
-guint64           nm_wifi_ap_get_id                   (NMWifiAP *ap);
 const GByteArray *nm_wifi_ap_get_ssid                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_ssid                 (NMWifiAP *ap,
                                                        const guint8 *ssid,
@@ -95,4 +100,14 @@ const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        gulong buf_len,
                                                        gint32 now_s);
 
+const char      **nm_wifi_aps_get_paths        (const CList *aps_lst_head,
+                                                gboolean include_without_ssid);
+
+NMWifiAP         *nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
+                                                     NMConnection *connection);
+
+NMWifiAP         *nm_wifi_aps_find_by_supplicant_path (const CList *aps_lst_head, const char *path);
+
+NMWifiAP         *nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path);
+
 #endif /* __NM_WIFI_AP_H__ */
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
new file mode 100644
index 00000000..e5e16f03
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -0,0 +1,206 @@
+/*-*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-common.h"
+
+#include "devices/nm-device.h"
+#include "nm-wifi-ap.h"
+#include "nm-device-wifi.h"
+#include "nm-dbus-manager.h"
+
+#if WITH_IWD
+#include "nm-device-iwd.h"
+#endif
+
+/*****************************************************************************/
+
+void
+nm_device_wifi_emit_signal_access_point (NMDevice *device,
+                                         NMWifiAP *ap,
+                                         gboolean is_added /* or else is_removed */)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (device),
+	                            &nm_interface_info_device_wireless,
+	                            is_added
+	                              ? &nm_signal_info_wireless_access_point_added
+	                              : &nm_signal_info_wireless_access_point_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+}
+
+/*****************************************************************************/
+
+static const CList *
+_dispatch_get_aps (NMDevice *device)
+{
+#if WITH_IWD
+	if (NM_IS_DEVICE_IWD (device))
+		return _nm_device_iwd_get_aps (NM_DEVICE_IWD (device));
+#endif
+	return _nm_device_wifi_get_aps (NM_DEVICE_WIFI (device));
+}
+
+static void
+_dispatch_request_scan (NMDevice *device,
+                        GVariant *options,
+                        GDBusMethodInvocation *invocation)
+{
+#if WITH_IWD
+	if (NM_IS_DEVICE_IWD (device)) {
+		_nm_device_iwd_request_scan (NM_DEVICE_IWD (device),
+		                             options,
+		                             invocation);
+		return;
+	}
+#endif
+	_nm_device_wifi_request_scan (NM_DEVICE_WIFI (device),
+	                              options,
+	                              invocation);
+}
+
+static void
+impl_device_wifi_get_access_points (NMDBusObject *obj,
+                                    const NMDBusInterfaceInfoExtended *interface_info,
+                                    const NMDBusMethodInfoExtended *method_info,
+                                    GDBusConnection *connection,
+                                    const char *sender,
+                                    GDBusMethodInvocation *invocation,
+                                    GVariant *parameters)
+{
+	gs_free const char **list = NULL;
+	GVariant *v;
+	const CList *all_aps;
+
+	/* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
+
+	all_aps = _dispatch_get_aps (NM_DEVICE (obj));
+	list = nm_wifi_aps_get_paths (all_aps, FALSE);
+	v = g_variant_new_objv (list, -1);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new_tuple (&v, 1));
+}
+
+static void
+impl_device_wifi_get_all_access_points (NMDBusObject *obj,
+                                        const NMDBusInterfaceInfoExtended *interface_info,
+                                        const NMDBusMethodInfoExtended *method_info,
+                                        GDBusConnection *connection,
+                                        const char *sender,
+                                        GDBusMethodInvocation *invocation,
+                                        GVariant *parameters)
+{
+	gs_free const char **list = NULL;
+	GVariant *v;
+	const CList *all_aps;
+
+	/* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
+
+	all_aps = _dispatch_get_aps (NM_DEVICE (obj));
+	list = nm_wifi_aps_get_paths (all_aps, TRUE);
+	v = g_variant_new_objv (list, -1);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new_tuple (&v, 1));
+}
+
+static void
+impl_device_wifi_request_scan (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
+{
+	gs_unref_variant GVariant *options = NULL;
+
+	/* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	_dispatch_request_scan (NM_DEVICE (obj),
+	                        options,
+	                        invocation);
+}
+
+const GDBusSignalInfo nm_signal_info_wireless_access_point_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"AccessPointAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("access_point", "o"),
+	),
+);
+
+const GDBusSignalInfo nm_signal_info_wireless_access_point_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"AccessPointRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("access_point", "o"),
+	),
+);
+
+const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIRELESS,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAccessPoints",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("access_points", "ao"),
+					),
+				),
+				.handle = impl_device_wifi_get_access_points,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"GetAllAccessPoints",
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("access_points", "ao"),
+					),
+				),
+				.handle = impl_device_wifi_get_all_access_points,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"RequestScan",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"),
+					),
+				),
+				.handle = impl_device_wifi_request_scan,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+			&nm_signal_info_wireless_access_point_added,
+			&nm_signal_info_wireless_access_point_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",            "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PermHwAddress",        "s",  NM_DEVICE_PERM_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",                 "u",  NM_DEVICE_WIFI_MODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("BitRate",              "u",  NM_DEVICE_WIFI_BITRATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AccessPoints",         "ao", NM_DEVICE_WIFI_ACCESS_POINTS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveAccessPoint",    "o",  NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WirelessCapabilities", "u",  NM_DEVICE_WIFI_CAPABILITIES),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
diff --git a/src/devices/wifi/nm-wifi-common.h b/src/devices/wifi/nm-wifi-common.h
new file mode 100644
index 00000000..91cbeb55
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-common.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_WIFI_COMMON_H__
+#define __NM_WIFI_COMMON_H__
+
+#include "nm-dbus-utils.h"
+#include "nm-wifi-ap.h"
+
+/*****************************************************************************/
+
+void nm_device_wifi_emit_signal_access_point (NMDevice *device,
+                                              NMWifiAP *ap,
+                                              gboolean is_added /* or else is_removed */);
+
+extern const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless;
+extern const GDBusSignalInfo nm_signal_info_wireless_access_point_added;
+extern const GDBusSignalInfo nm_signal_info_wireless_access_point_removed;
+
+#endif  /* __NM_WIFI_COMMON_H__ */
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index a1752634..6b8e5fb8 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -27,8 +27,10 @@
 #include "nm-setting-olpc-mesh.h"
 #include "nm-device-wifi.h"
 #include "nm-device-olpc-mesh.h"
+#include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
 #include "platform/nm-platform.h"
+#include "nm-config.h"
 
 /*****************************************************************************/
 
@@ -75,6 +77,7 @@ create_device (NMDeviceFactory *factory,
 {
 	NMDeviceWifiCapabilities capabilities;
 	NM80211Mode mode;
+	gs_free char *backend = NULL;
 
 	g_return_val_if_fail (iface != NULL, NULL);
 	g_return_val_if_fail (plink != NULL, NULL);
@@ -98,10 +101,30 @@ create_device (NMDeviceFactory *factory,
 		return NULL;
 	}
 
-	if (plink->type == NM_LINK_TYPE_WIFI)
-		return nm_device_wifi_new (iface, capabilities);
-	else
+	if (plink->type != NM_LINK_TYPE_WIFI)
 		return nm_device_olpc_mesh_new (iface);
+
+	backend = nm_config_data_get_device_config_by_pllink (NM_CONFIG_GET_DATA,
+	                                                      NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
+	                                                      plink,
+	                                                      "wifi",
+	                                                      NULL);
+	nm_strstrip (backend);
+
+	nm_log_dbg (LOGD_PLATFORM | LOGD_WIFI,
+	            "(%s) config: backend is %s%s%s%s",
+	            iface,
+	            NM_PRINT_FMT_QUOTE_STRING (backend),
+	            WITH_IWD ? " (iwd support enabled)" : "");
+	if (!backend || !strcasecmp (backend, "wpa_supplicant"))
+		return nm_device_wifi_new (iface, capabilities);
+#if WITH_IWD
+	else if (!strcasecmp (backend, "iwd"))
+		return nm_device_iwd_new (iface, capabilities);
+#endif
+
+	nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) config: unknown or unsupported wifi-backend %s", iface, backend);
+	return NULL;
 }
 
 /*****************************************************************************/
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 3ff82004..044bd392 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -782,3 +782,35 @@ nm_wifi_utils_level_to_quality (gint val)
 	return CLAMP (val, 0, 100);
 }
 
+gboolean
+nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid)
+{
+	int 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;
+		}
+	}
+	return FALSE;
+}
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h
index 1b6c2f4b..def64dd6 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/devices/wifi/nm-wifi-utils.h
@@ -39,4 +39,6 @@ gboolean nm_wifi_utils_complete_connection (const GByteArray *ssid,
 
 guint32 nm_wifi_utils_level_to_quality (gint val);
 
+gboolean nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid);
+
 #endif  /* __NM_WIFI_UTILS_H__ */
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
new file mode 100644
index 00000000..bb8f7c27
--- /dev/null
+++ b/src/devices/wifi/tests/meson.build
@@ -0,0 +1,13 @@
+test_unit = 'test-general'
+
+exe = executable(
+  'wifi-' + test_unit,
+  [test_unit + '.c'] + common_sources,
+  dependencies: test_nm_dep
+)
+
+test(
+  'devices/wifi/' + test_unit,
+  test_script,
+  args: test_args + [exe.full_path()]
+)