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, 343 insertions, 197 deletions
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index d6511296..1f28d263 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -29,13 +29,21 @@
 #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 {
-	gchar *name;
+	const char *name;
 	NMIwdNetworkSecurity security;
+	char buf[0];
+} KnownNetworkId;
+
+typedef struct {
+	GDBusProxy *known_network;
+	NMSettingsConnection *mirror_connection;
 } KnownNetworkData;
 
 typedef struct {
@@ -44,8 +52,8 @@ typedef struct {
 	gboolean running;
 	GDBusObjectManager *object_manager;
 	guint agent_id;
-	gchar *agent_path;
-	GSList *known_networks;
+	char *agent_path;
+	GHashTable *known_networks;
 } NMIwdManagerPrivate;
 
 struct _NMIwdManager {
@@ -83,20 +91,49 @@ 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 gchar *sender, const gchar *object_path,
-                      const gchar *interface_name, const gchar *method_name,
+                      const char *sender, const char *object_path,
+                      const char *interface_name, const char *method_name,
                       GVariant *parameters,
                       GDBusMethodInvocation *invocation,
                       gpointer user_data)
 {
 	NMIwdManager *self = user_data;
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	const gchar *network_path, *device_path, *ifname;
+	const char *network_path, *device_path, *ifname;
 	gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL;
-	gs_unref_variant GVariant *value = NULL;
-	gint ifindex;
+	int ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
 
@@ -113,9 +150,8 @@ 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);
@@ -125,10 +161,8 @@ 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);
@@ -207,12 +241,12 @@ static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE
 
 static guint
 iwd_agent_export (GDBusConnection *connection, gpointer user_data,
-                  gchar **agent_path, GError **error)
+                  char **agent_path, GError **error)
 {
 	static const GDBusInterfaceVTable vtable = {
 		.method_call = agent_dbus_method_cb,
 	};
-	gchar path[50];
+	char path[50];
 	unsigned int rnd;
 	guint id;
 
@@ -251,38 +285,68 @@ register_agent (NMIwdManager *self)
 
 /*****************************************************************************/
 
-static void
-set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface,
-                        GDBusObject *object)
+static KnownNetworkId *
+known_network_id_new (const char *name, NMIwdNetworkSecurity security)
 {
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	GDBusProxy *proxy;
-	GVariant *value;
-	const char *ifname;
-	gint ifindex;
-	NMDevice *device;
+	KnownNetworkId *id;
+	gsize strsize = strlen (name) + 1;
 
-	if (!priv->running)
-		return;
+	id = g_malloc (sizeof (KnownNetworkId) + strsize);
+	id->name = id->buf;
+	id->security = security;
+	memcpy (id->buf, name, strsize);
 
-	g_return_if_fail (G_IS_DBUS_PROXY (interface));
+	return id;
+}
 
-	proxy = G_DBUS_PROXY (interface);
+static guint
+known_network_id_hash (KnownNetworkId *id)
+{
+	NMHashState h;
 
-	if (strcmp (g_dbus_proxy_get_interface_name (proxy),
-	            NM_IWD_DEVICE_INTERFACE))
+	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;
 
-	value = g_dbus_proxy_get_cached_property (proxy, "Name");
-	if (!value) {
+	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,
+                        GDBusObject *object)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	const char *ifname;
+	int ifindex;
+	NMDevice *device;
+
+	ifname = get_property_string_or_null (proxy, "Name");
+	if (!ifname) {
 		_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",
@@ -299,13 +363,183 @@ set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface,
 	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));
 
-	set_device_dbus_object (self, interface, object);
+	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;
+	}
 }
 
 static void
@@ -313,15 +547,41 @@ 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;
 
-	/*
-	 * 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_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;
 
-	set_device_dbus_object (self, interface, NULL);
+		g_hash_table_remove (priv->known_networks, &id);
+		return;
+	}
 }
 
 static gboolean
@@ -341,106 +601,42 @@ 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);
+		interface_added (NULL, object, interface, self);
 	}
 
 	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)
+release_object_manager (NMIwdManager *self)
 {
-	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 (!priv->object_manager)
+		return;
 
-		if (!name || !type)
-			goto next;
+	g_signal_handlers_disconnect_by_data (priv->object_manager, self);
 
-		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;
+	if (priv->agent_id) {
+		GDBusConnection *agent_connection;
+		GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
 
-		priv->known_networks = g_slist_append (priv->known_networks,
-		                                       network_data);
+		agent_connection = g_dbus_object_manager_client_get_connection (omc);
 
-next:
-		g_variant_iter_free (props);
+		/* 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);
 	}
 
-	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);
+	g_clear_object (&priv->object_manager);
 }
 
 static void prepare_object_manager (NMIwdManager *self);
@@ -455,8 +651,7 @@ name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
 	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);
+		release_object_manager (self);
 		prepare_object_manager (self);
 	} else {
 		const CList *tmp_lst;
@@ -492,28 +687,14 @@ 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);
-		GDBusInterface *interface;
-		GDBusProxy *proxy;
-		GVariant *value;
+		gs_unref_object GDBusInterface *interface = NULL;
 		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 = get_property_string_or_null ((GDBusProxy *) interface, "Name");
 
-		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))
+		if (!obj_ifname || strcmp (nm_device_get_iface (device), obj_ifname))
 			continue;
 
 		nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
@@ -535,7 +716,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)",
-		       NM_G_ERROR_MSG (error));
+		       error->message);
 		g_clear_error (&error);
 		return;
 	}
@@ -549,11 +730,13 @@ 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 will not work: %s",
-		       NM_G_ERROR_MSG (error));
+		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks may not work: %s",
+		       error->message);
 		g_clear_error (&error);
 	}
 
@@ -567,6 +750,8 @@ 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));
@@ -575,8 +760,6 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 
 		if (priv->agent_id)
 			register_agent (self);
-
-		update_known_networks (self);
 	}
 }
 
@@ -594,36 +777,13 @@ prepare_object_manager (NMIwdManager *self)
 }
 
 gboolean
-nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+nm_iwd_manager_is_known_network (NMIwdManager *self, const char *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;
-	}
+	KnownNetworkId kn_id = { name, security };
 
-	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);
+	return g_hash_table_contains (priv->known_networks, &kn_id);
 }
 
 /*****************************************************************************/
@@ -642,6 +802,11 @@ 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);
 }
 
@@ -651,30 +816,11 @@ 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);
+	release_object_manager (self);
 
 	nm_clear_g_cancellable (&priv->cancellable);
 
-	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
-	priv->known_networks = NULL;
+	nm_clear_pointer (&priv->known_networks, g_hash_table_destroy);
 
 	if (priv->manager) {
 		g_signal_handlers_disconnect_by_data (priv->manager, self);