summary refs log tree commit diff
path: root/src/devices/wifi/nm-iwd-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wifi/nm-iwd-manager.c')
-rw-r--r--src/devices/wifi/nm-iwd-manager.c540
1 files changed, 197 insertions, 343 deletions
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 1f28d263..d6511296 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -29,21 +29,13 @@
 #include "nm-core-internal.h"
 #include "nm-manager.h"
 #include "nm-device-iwd.h"
-#include "nm-wifi-utils.h"
 #include "nm-utils/nm-random-utils.h"
-#include "settings/nm-settings.h"
 
 /*****************************************************************************/
 
 typedef struct {
-	const char *name;
+	gchar *name;
 	NMIwdNetworkSecurity security;
-	char buf[0];
-} KnownNetworkId;
-
-typedef struct {
-	GDBusProxy *known_network;
-	NMSettingsConnection *mirror_connection;
 } KnownNetworkData;
 
 typedef struct {
@@ -52,8 +44,8 @@ typedef struct {
 	gboolean running;
 	GDBusObjectManager *object_manager;
 	guint agent_id;
-	char *agent_path;
-	GHashTable *known_networks;
+	gchar *agent_path;
+	GSList *known_networks;
 } NMIwdManagerPrivate;
 
 struct _NMIwdManager {
@@ -91,49 +83,20 @@ G_DEFINE_TYPE (NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-static void mirror_8021x_connection_take_and_delete (NMSettingsConnection *sett_conn);
-
-/*****************************************************************************/
-
-static const char *
-get_variant_string_or_null (GVariant *v)
-{
-	if (!v)
-		return NULL;
-
-	if (   !g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)
-	    && !g_variant_is_of_type (v, G_VARIANT_TYPE_OBJECT_PATH))
-		return NULL;
-
-	return g_variant_get_string (v, NULL);
-}
-
-static const char *
-get_property_string_or_null (GDBusProxy *proxy, const char *property)
-{
-	gs_unref_variant GVariant *value = NULL;
-
-	if (!proxy || !property)
-		return NULL;
-
-	value = g_dbus_proxy_get_cached_property (proxy, property);
-
-	return get_variant_string_or_null (value);
-}
-
 static void
 agent_dbus_method_cb (GDBusConnection *connection,
-                      const char *sender, const char *object_path,
-                      const char *interface_name, const char *method_name,
+                      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);
-	const char *network_path, *device_path, *ifname;
+	const gchar *network_path, *device_path, *ifname;
 	gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL;
-	int ifindex;
+	gs_unref_variant GVariant *value = NULL;
+	gint ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
 
@@ -150,8 +113,9 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	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);
 
-	device_path = get_property_string_or_null (G_DBUS_PROXY (network), "Device");
 	if (!device_path) {
 		_LOGD ("agent-request: device not cached for network %s in IWD Agent request",
 		       network_path);
@@ -161,8 +125,10 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	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);
 
-	ifname = get_property_string_or_null (G_DBUS_PROXY (device_obj), "Name");
 	if (!ifname) {
 		_LOGD ("agent-request: name not cached for device %s in IWD Agent request",
 		       device_path);
@@ -241,12 +207,12 @@ static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE
 
 static guint
 iwd_agent_export (GDBusConnection *connection, gpointer user_data,
-                  char **agent_path, GError **error)
+                  gchar **agent_path, GError **error)
 {
 	static const GDBusInterfaceVTable vtable = {
 		.method_call = agent_dbus_method_cb,
 	};
-	char path[50];
+	gchar path[50];
 	unsigned int rnd;
 	guint id;
 
@@ -285,68 +251,38 @@ register_agent (NMIwdManager *self)
 
 /*****************************************************************************/
 
-static KnownNetworkId *
-known_network_id_new (const char *name, NMIwdNetworkSecurity security)
-{
-	KnownNetworkId *id;
-	gsize strsize = strlen (name) + 1;
-
-	id = g_malloc (sizeof (KnownNetworkId) + strsize);
-	id->name = id->buf;
-	id->security = security;
-	memcpy (id->buf, name, strsize);
-
-	return id;
-}
-
-static guint
-known_network_id_hash (KnownNetworkId *id)
-{
-	NMHashState h;
-
-	nm_hash_init (&h, 1947951703u);
-	nm_hash_update_val (&h, id->security);
-	nm_hash_update_str (&h, id->name);
-	return nm_hash_complete (&h);
-}
-
-static gboolean
-known_network_id_equal (KnownNetworkId *a, KnownNetworkId *b)
-{
-	return    a->security == b->security
-	       && nm_streq (a->name, b->name);
-}
-
-static void
-known_network_data_free (KnownNetworkData *network)
-{
-	if (!network)
-		return;
-
-	g_object_unref (network->known_network);
-	mirror_8021x_connection_take_and_delete (network->mirror_connection);
-	g_slice_free (KnownNetworkData, network);
-}
-
-/*****************************************************************************/
-
 static void
-set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
+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;
-	int ifindex;
+	gint ifindex;
 	NMDevice *device;
 
-	ifname = get_property_string_or_null (proxy, "Name");
-	if (!ifname) {
+	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",
@@ -363,183 +299,13 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
 }
 
-/* Create an in-memory NMConnection for a WPA2-Enterprise network that
- * has been preprovisioned with an IWD config file so that NM autoconnect
- * mechanism and the clients know this networks needs no additional EAP
- * configuration from the user.  Only do this if no existing connection
- * SSID and security type match that network yet.
- */
-static NMSettingsConnection *
-mirror_8021x_connection (NMIwdManager *self,
-                         const char *name)
-{
-	NMSettings *settings = NM_SETTINGS_GET;
-	NMSettingsConnection *const*iter;
-	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *settings_connection;
-	char uuid[37];
-	NMSetting *setting;
-	GError *error = NULL;
-	gs_unref_bytes GBytes *new_ssid = NULL;
-
-	for (iter = nm_settings_get_connections (settings, NULL); *iter; iter++) {
-		NMSettingsConnection *sett_conn = *iter;
-		NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
-		NMIwdNetworkSecurity security;
-		gs_free char *ssid_name = NULL;
-		NMSettingWireless *s_wifi;
-
-		security = nm_wifi_connection_get_iwd_security (conn, NULL);
-		if (security != NM_IWD_NETWORK_SECURITY_8021X)
-			continue;
-
-		s_wifi = nm_connection_get_setting_wireless (conn);
-		if (!s_wifi)
-			continue;
-
-		ssid_name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wifi));
-
-		/* We already have an NMSettingsConnection matching this
-		 * KnownNetwork, whether it's saved or an in-memory connection
-		 * potentially created by ourselves.  Nothing to do here.
-		 */
-		if (nm_streq (ssid_name, name))
-			return NULL;
-	}
-
-	connection = nm_simple_connection_new ();
-
-	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_CONNECTION,
-	                                    NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
-	                                    NM_SETTING_CONNECTION_ID, name,
-	                                    NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_buf (uuid),
-	                                    NM_SETTING_CONNECTION_READ_ONLY, TRUE,
-	                                    NULL));
-	nm_connection_add_setting (connection, setting);
-
-	new_ssid = g_bytes_new (name, strlen (name));
-	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_WIRELESS,
-	                                    NM_SETTING_WIRELESS_SSID, new_ssid,
-	                                    NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA,
-	                                    NULL));
-	nm_connection_add_setting (connection, setting);
-
-	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_WIRELESS_SECURITY,
-	                                    NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
-	                                    NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
-	                                    NULL));
-	nm_connection_add_setting (connection, setting);
-
-	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_802_1X, NULL));
-	nm_setting_802_1x_add_eap_method (NM_SETTING_802_1X (setting), "external");
-	nm_connection_add_setting (connection, setting);
-
-	if (!nm_connection_normalize (connection, NULL, NULL, NULL))
-		return NULL;
-
-	settings_connection = nm_settings_add_connection (settings, connection,
-	                                                  FALSE, &error);
-	if (!settings_connection) {
-		_LOGW ("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
-		       name, error->message);
-		g_error_free (error);
-		return NULL;
-	}
-
-	nm_settings_connection_set_flags (settings_connection,
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED |
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,
-	                                  TRUE);
-	return settings_connection;
-}
-
-static void
-mirror_8021x_connection_take_and_delete (NMSettingsConnection *sett_conn)
-{
-	NMSettingsConnectionIntFlags flags;
-
-	if (!sett_conn)
-		return;
-
-	flags = nm_settings_connection_get_flags (sett_conn);
-
-	/* If connection has not been saved since we created it
-	 * in interface_added it too can be removed now. */
-	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
-		nm_settings_connection_delete (sett_conn, NULL);
-
-	g_object_unref (sett_conn);
-}
-
 static void
 interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
                  GDBusInterface *interface, gpointer user_data)
 {
 	NMIwdManager *self = user_data;
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	GDBusProxy *proxy;
-	const char *iface_name;
-
-	if (!priv->running)
-		return;
-
-	g_return_if_fail (G_IS_DBUS_PROXY (interface));
 
-	proxy = G_DBUS_PROXY (interface);
-	iface_name = g_dbus_proxy_get_interface_name (proxy);
-
-	if (nm_streq (iface_name, NM_IWD_DEVICE_INTERFACE)) {
-		set_device_dbus_object (self, proxy, object);
-		return;
-	}
-
-	if (nm_streq (iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
-		KnownNetworkId *id;
-		KnownNetworkData *data;
-		NMIwdNetworkSecurity security;
-		const char *type_str, *name;
-		NMSettingsConnection *sett_conn = NULL;
-
-		type_str = get_property_string_or_null (proxy, "Type");
-		name = get_property_string_or_null (proxy, "Name");
-		if (!type_str || !name)
-			return;
-
-		if (nm_streq (type_str, "open"))
-			security = NM_IWD_NETWORK_SECURITY_NONE;
-		else if (nm_streq (type_str, "psk"))
-			security = NM_IWD_NETWORK_SECURITY_PSK;
-		else if (nm_streq (type_str, "8021x"))
-			security = NM_IWD_NETWORK_SECURITY_8021X;
-		else
-			return;
-
-		id = known_network_id_new (name, security);
-
-		data = g_hash_table_lookup (priv->known_networks, id);
-		if (data)
-			g_free (id);
-		else {
-			data = g_slice_new0 (KnownNetworkData);
-			data->known_network = g_object_ref (proxy);
-			g_hash_table_insert (priv->known_networks, id, data);
-		}
-
-		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
-			sett_conn = mirror_8021x_connection (self, name);
-
-			if (   sett_conn
-			    && sett_conn != data->mirror_connection) {
-				NMSettingsConnection *sett_conn_old = data->mirror_connection;
-
-				data->mirror_connection = nm_g_object_ref (sett_conn);
-				mirror_8021x_connection_take_and_delete (sett_conn_old);
-			}
-		} else
-			mirror_8021x_connection_take_and_delete (g_steal_pointer (&data->mirror_connection));
-
-		return;
-	}
+	set_device_dbus_object (self, interface, object);
 }
 
 static void
@@ -547,41 +313,15 @@ interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
                    GDBusInterface *interface, gpointer user_data)
 {
 	NMIwdManager *self = user_data;
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	GDBusProxy *proxy;
-	const char *iface_name;
 
-	g_return_if_fail (G_IS_DBUS_PROXY (interface));
-
-	proxy = G_DBUS_PROXY (interface);
-	iface_name = g_dbus_proxy_get_interface_name (proxy);
-
-	if (nm_streq (iface_name, NM_IWD_DEVICE_INTERFACE)) {
-		set_device_dbus_object (self, proxy, NULL);
-		return;
-	}
-
-	if (nm_streq (iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
-		KnownNetworkId id;
-		const char *type_str;
-
-		type_str = get_property_string_or_null (proxy, "Type");
-		id.name = get_property_string_or_null (proxy, "Name");
-		if (!type_str || !id.name)
-			return;
-
-		if (nm_streq (type_str, "open"))
-			id.security = NM_IWD_NETWORK_SECURITY_NONE;
-		else if (nm_streq (type_str, "psk"))
-			id.security = NM_IWD_NETWORK_SECURITY_PSK;
-		else if (nm_streq (type_str, "8021x"))
-			id.security = NM_IWD_NETWORK_SECURITY_8021X;
-		else
-			return;
+	/*
+	 * 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.
+	 */
 
-		g_hash_table_remove (priv->known_networks, &id);
-		return;
-	}
+	set_device_dbus_object (self, interface, NULL);
 }
 
 static gboolean
@@ -601,42 +341,106 @@ 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);
 
-		interface_added (NULL, object, interface, self);
+		set_device_dbus_object (self, interface, object);
 	}
 
 	g_list_free_full (interfaces, g_object_unref);
 }
 
 static void
-release_object_manager (NMIwdManager *self)
+known_network_free (KnownNetworkData *network)
 {
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	g_free (network->name);
+	g_free (network);
+}
 
-	if (!priv->object_manager)
+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_signal_handlers_disconnect_by_data (priv->object_manager, self);
+	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
+	priv->known_networks = NULL;
 
-	if (priv->agent_id) {
-		GDBusConnection *agent_connection;
-		GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
+	g_variant_get (variant, "(aa{sv})", &networks);
 
-		agent_connection = g_dbus_object_manager_client_get_connection (omc);
+	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;
 
-		/* We're is called when we're shutting down (i.e. our DBus connection
-		 * is being closed, and IWD will detect this) or IWD was stopped so
-		 * in either case calling UnregisterAgent will not do anything.
-		 */
-		g_dbus_connection_unregister_object (agent_connection, priv->agent_id);
-		priv->agent_id = 0;
-		nm_clear_g_free (&priv->agent_path);
+		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_clear_object (&priv->object_manager);
+	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);
@@ -651,7 +455,8 @@ name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
 	nm_assert (object_manager == priv->object_manager);
 
 	if (_om_has_name_owner (object_manager)) {
-		release_object_manager (self);
+		g_signal_handlers_disconnect_by_data (object_manager, self);
+		g_clear_object (&priv->object_manager);
 		prepare_object_manager (self);
 	} else {
 		const CList *tmp_lst;
@@ -687,14 +492,28 @@ device_added (NMManager *manager, NMDevice *device, gpointer user_data)
 	objects = g_dbus_object_manager_get_objects (priv->object_manager);
 	for (iter = objects; iter; iter = iter->next) {
 		GDBusObject *object = G_DBUS_OBJECT (iter->data);
-		gs_unref_object GDBusInterface *interface = NULL;
+		GDBusInterface *interface;
+		GDBusProxy *proxy;
+		GVariant *value;
 		const char *obj_ifname;
 
 		interface = g_dbus_object_get_interface (object,
 		                                         NM_IWD_DEVICE_INTERFACE);
-		obj_ifname = get_property_string_or_null ((GDBusProxy *) interface, "Name");
+		if (!interface)
+			continue;
+
+		proxy = G_DBUS_PROXY (interface);
+		value = g_dbus_proxy_get_cached_property (proxy, "Name");
+		if (!value) {
+			g_object_unref (interface);
+			continue;
+		}
 
-		if (!obj_ifname || strcmp (nm_device_get_iface (device), obj_ifname))
+		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);
@@ -716,7 +535,7 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 	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)",
-		       error->message);
+		       NM_G_ERROR_MSG (error));
 		g_clear_error (&error);
 		return;
 	}
@@ -730,13 +549,11 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 
 	connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
 
-	priv->agent_id = iwd_agent_export (connection,
-	                                   self,
-	                                   &priv->agent_path,
-	                                   &error);
+	priv->agent_id = iwd_agent_export (connection, self,
+	                                   &priv->agent_path, &error);
 	if (!priv->agent_id) {
-		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks may not work: %s",
-		       error->message);
+		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks will not work: %s",
+		       NM_G_ERROR_MSG (error));
 		g_clear_error (&error);
 	}
 
@@ -750,8 +567,6 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 		g_signal_connect (priv->object_manager, "interface-removed",
 		                  G_CALLBACK (interface_removed), self);
 
-		g_hash_table_remove_all (priv->known_networks);
-
 		objects = g_dbus_object_manager_get_objects (object_manager);
 		for (iter = objects; iter; iter = iter->next)
 			object_added (self, G_DBUS_OBJECT (iter->data));
@@ -760,6 +575,8 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 
 		if (priv->agent_id)
 			register_agent (self);
+
+		update_known_networks (self);
 	}
 }
 
@@ -777,13 +594,36 @@ prepare_object_manager (NMIwdManager *self)
 }
 
 gboolean
-nm_iwd_manager_is_known_network (NMIwdManager *self, const char *name,
+nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
                                  NMIwdNetworkSecurity security)
 {
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	KnownNetworkId kn_id = { name, security };
+	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 g_hash_table_contains (priv->known_networks, &kn_id);
+	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);
 }
 
 /*****************************************************************************/
@@ -802,11 +642,6 @@ nm_iwd_manager_init (NMIwdManager *self)
 
 	priv->cancellable = g_cancellable_new ();
 
-	priv->known_networks = g_hash_table_new_full ((GHashFunc) known_network_id_hash,
-	                                              (GEqualFunc) known_network_id_equal,
-	                                              g_free,
-	                                              (GDestroyNotify) known_network_data_free);
-
 	prepare_object_manager (self);
 }
 
@@ -816,11 +651,30 @@ dispose (GObject *object)
 	NMIwdManager *self = (NMIwdManager *) object;
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
 
-	release_object_manager (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);
 
-	nm_clear_pointer (&priv->known_networks, g_hash_table_destroy);
+	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);