summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am8
-rw-r--r--configure.ac21
-rw-r--r--src/devices/wifi/nm-device-iwd.c1844
-rw-r--r--src/devices/wifi/nm-device-iwd.h60
-rw-r--r--src/devices/wifi/nm-device-wifi.c147
-rw-r--r--src/devices/wifi/nm-iwd-manager.c552
-rw-r--r--src/devices/wifi/nm-iwd-manager.h53
-rw-r--r--src/devices/wifi/nm-wifi-ap.c85
-rw-r--r--src/devices/wifi/nm-wifi-ap.h8
-rw-r--r--src/devices/wifi/nm-wifi-factory.c23
-rw-r--r--src/devices/wifi/nm-wifi-utils.c32
-rw-r--r--src/devices/wifi/nm-wifi-utils.h2
-rw-r--r--src/nm-config.h1
13 files changed, 2703 insertions, 133 deletions
diff --git a/Makefile.am b/Makefile.am
index e7277ee4cc..4d2657a79a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2703,6 +2703,14 @@ src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES = \
src/devices/wifi/nm-device-olpc-mesh.c \
src/devices/wifi/nm-device-olpc-mesh.h
+if WITH_IWD
+src_devices_wifi_libnm_device_plugin_wifi_la_SOURCES += \
+ src/devices/wifi/nm-device-iwd.c \
+ src/devices/wifi/nm-device-iwd.h \
+ src/devices/wifi/nm-iwd-manager.c \
+ src/devices/wifi/nm-iwd-manager.h
+endif
+
src_devices_wifi_libnm_device_plugin_wifi_la_CPPFLAGS = \
-I$(srcdir)/src \
-I$(builddir)/src \
diff --git a/configure.ac b/configure.ac
index 0f3933eaed..9d8f10c137 100644
--- a/configure.ac
+++ b/configure.ac
@@ -280,6 +280,26 @@ else
fi
dnl
+dnl Default to using wpa_supplicant but allow IWD as wifi backend
+dnl
+AC_ARG_WITH(iwd,
+ AS_HELP_STRING([--with-iwd=yes],
+ [Support IWD as wifi-backend in addition to wpa_supplicant (experimental)]),
+ ac_with_iwd=$withval, ac_with_iwd="no")
+if test "$ac_with_iwd" != 'no'; then
+ ac_with_iwd='yes'
+fi
+if test x"$ac_with_iwd" = x"yes"; then
+ if test "$enable_wifi" != "yes"; then
+ AC_MSG_ERROR(Enabling IWD support and disabling Wi-Fi makes no sense)
+ fi
+ AC_DEFINE(WITH_IWD, 1, [Define to compile with the IWD wifi-backend])
+else
+ AC_DEFINE(WITH_IWD, 0, [Define to compile without the IWD wifi-backend])
+fi
+AM_CONDITIONAL(WITH_IWD, test x"${ac_with_iwd}" = x"yes")
+
+dnl
dnl Check for newer VLAN flags
dnl
AC_MSG_CHECKING([Linux kernel VLAN_FLAG_LOOSE_BINDING enum value])
@@ -1417,6 +1437,7 @@ echo " ovs: $enable_ovs"
echo " libnm-glib: $with_libnm_glib"
echo " nmcli: $build_nmcli"
echo " nmtui: $build_nmtui"
+echo " iwd: $ac_with_iwd"
echo
echo "Configuration plugins (main.plugins=${config_plugins_default})"
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
new file mode 100644
index 0000000000..770af84135
--- /dev/null
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -0,0 +1,1844 @@
+/* -*- 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-core-internal.h"
+#include "nm-config.h"
+#include "nm-iwd-manager.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.Wireless.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceIwd);
+
+static NM_CACHED_QUARK_FCN ("wireless-secrets-tries", wireless_secrets_tries_quark)
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd,
+ PROP_MODE,
+ PROP_BITRATE,
+ PROP_ACCESS_POINTS,
+ PROP_ACTIVE_ACCESS_POINT,
+ PROP_CAPABILITIES,
+ PROP_SCANNING,
+);
+
+enum {
+ ACCESS_POINT_ADDED,
+ ACCESS_POINT_REMOVED,
+ SCANNING_PROHIBITED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+ GDBusObject * dbus_obj;
+ GDBusProxy * dbus_proxy;
+ GHashTable * aps;
+ GHashTable * new_aps;
+ NMWifiAP * current_ap;
+ GCancellable * cancellable;
+ NMDeviceWifiCapabilities capabilities;
+ NMActRequestGetSecretsCallId *wifi_secrets_id;
+ bool enabled:1;
+ bool can_scan:1;
+ bool scanning: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
+_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,
+ guint signum,
+ NMWifiAP *ap,
+ gboolean recheck_available_connections)
+{
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_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));
+ _ap_dump (self, LOGL_DEBUG, ap, "added", 0);
+ } else
+ _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);
+
+ 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, ACCESS_POINT_REMOVED, old_ap, recheck_available_connections);
+ g_object_unref (old_ap);
+ }
+
+ _notify (self, PROP_ACTIVE_ACCESS_POINT);
+ _notify (self, PROP_MODE);
+}
+
+static gboolean
+update_ap_func (gpointer key, gpointer value, gpointer user_data)
+{
+ NMWifiAP *ap = value;
+ NMDeviceIwd *self = user_data;
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+ NMWifiAP *new_ap = NULL;
+
+ if (priv->new_aps)
+ new_ap = g_hash_table_lookup (priv->new_aps,
+ nm_wifi_ap_get_supplicant_path (ap));
+
+ if (new_ap) {
+ g_hash_table_steal (priv->new_aps,
+ nm_wifi_ap_get_supplicant_path (ap));
+
+ if (nm_wifi_ap_set_strength (ap, nm_wifi_ap_get_strength (new_ap)))
+ _ap_dump (self, LOGL_TRACE, ap, "updated", 0);
+
+ g_object_unref (new_ap);
+ return FALSE;
+ }
+
+ 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.
+ */
+ return FALSE;
+
+ _ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
+
+ g_signal_emit (self, signals[ACCESS_POINT_REMOVED], 0, ap);
+
+ nm_exported_object_unexport ((NMExportedObject *) ap);
+ g_object_unref (ap);
+
+ return TRUE;
+}
+
+static void
+remove_all_aps (NMDeviceIwd *self)
+{
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+ if (!g_hash_table_size (priv->aps))
+ return;
+
+ set_current_ap (self, NULL, FALSE);
+
+ g_hash_table_foreach_remove (priv->aps, update_ap_func, self);
+
+ _notify (self, PROP_ACCESS_POINTS);
+ 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;
+ gboolean changed = FALSE;
+ GHashTableIter ap_iter;
+
+ 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;
+ }
+
+ priv->new_aps = g_hash_table_new (g_str_hash, g_str_equal);
+
+ 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);
+ 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 (priv->new_aps,
+ (gpointer) nm_wifi_ap_get_supplicant_path (ap),
+ ap);
+ }
+
+ g_variant_iter_free (networks);
+
+ if (g_hash_table_foreach_remove (priv->aps, update_ap_func, self))
+ changed = TRUE;
+
+ g_hash_table_iter_init (&ap_iter, priv->new_aps);
+ while (g_hash_table_iter_next (&ap_iter, NULL, (gpointer) &ap)) {
+ ap_add_remove (self, ACCESS_POINT_ADDED, ap, FALSE);
+ changed = TRUE;
+ }
+
+ g_hash_table_destroy (priv->new_aps);
+ priv->new_aps = NULL;
+
+ if (changed) {
+ _notify (self, PROP_ACCESS_POINTS);
+ 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
+cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
+{
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (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
+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;
+
+ return TRUE;
+}
+
+static NMWifiAP *
+get_ap_by_path (NMDeviceIwd *self, const char *path)
+{
+ g_return_val_if_fail (path != NULL, NULL);
+ return g_hash_table_lookup (NM_DEVICE_IWD_GET_PRIVATE (self)->aps, path);
+
+}
+
+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);
+
+ /* 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 = get_ap_by_path (self, specific_object);
+ return ap ? nm_wifi_ap_check_compatible (ap, connection) : 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;
+
+ 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, connection, TRUE);
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+ NMConnection *connection,
+ const char *specific_object,
+ const GSList *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, connection, FALSE);
+ 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 = get_ap_by_path (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);
+
+ 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);
+
+ return priv->enabled && priv->dbus_obj;
+}
+
+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;
+ }
+
+ ap = nm_wifi_aps_find_first_compatible (priv->aps, connection, FALSE);
+ if (ap) {
+ /* All good; connection is usable */
+ NM_SET_OUT (specific_object, g_strdup (nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap))));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+impl_device_iwd_get_access_points (NMDeviceIwd *self,
+ GDBusMethodInvocation *context)
+{
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+ gs_free const char **list = NULL;
+ GVariant *v;
+
+ list = nm_wifi_aps_get_sorted_paths (priv->aps, 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_iwd_get_all_access_points (NMDeviceIwd *self,
+ GDBusMethodInvocation *context)
+{
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+ gs_free const char **list = NULL;
+ GVariant *v;
+
+ list = nm_wifi_aps_get_sorted_paths (priv->aps, TRUE);
+ v = g_variant_new_objv (list, -1);
+ g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
+}
+
+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
+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;
+ gs_unref_ptrarray GPtrArray *ssids = NULL;
+
+ 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;
+ }
+ }
+
+ g_dbus_proxy_call (priv->dbus_proxy, "Scan", g_variant_new ("()"),
+ G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+ g_dbus_method_invocation_return_value (context, NULL);
+}
+
+static void
+impl_device_iwd_request_scan (NMDeviceIwd *self,
+ GDBusMethodInvocation *context,
+ GVariant *options)
+{
+ 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 (context,
+ NM_DEVICE_ERROR,
+ NM_DEVICE_ERROR_NOT_ALLOWED,
+ "Scanning not allowed while unavailable");
+ return;
+ }
+
+ /* Ask the manager to authenticate this request for us */
+ g_signal_emit_by_name (device,
+ NM_DEVICE_AUTH_REQUEST,
+ context,
+ 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 *connection,
+ GError *error,
+ gpointer user_data)
+{
+ NMDevice *device = user_data;
+ NMDeviceIwd *self = user_data;
+ NMDeviceIwdPrivate *priv;
+
+ 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 (req == nm_device_get_act_request (device));
+ g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
+ g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
+
+ if (error) {
+ _LOGW (LOGD_WIFI, "%s", error->message);
+
+ nm_device_state_changed (device,
+ NM_DEVICE_STATE_FAILED,
+ NM_DEVICE_STATE_REASON_NO_SECRETS);
+ } else
+ nm_device_activate_schedule_stage1_device_prepare (device);
+}
+
+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);
+}
+
+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);
+ g_return_if_fail (priv->wifi_secrets_id);
+}
+
+static gboolean
+need_new_8021x_secrets (NMDeviceIwd *self,
+ const char **setting_name)
+{
+ NMSetting8021x *s_8021x;
+ NMSettingWirelessSecurity *s_wsec;
+ NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+ NMConnection *connection;
+
+ g_assert (setting_name != NULL);
+
+ connection = nm_device_get_applied_connection (NM_DEVICE (self));
+ g_return_val_if_fail (connection != NULL, FALSE);
+
+ /* If it's an 802.1x or LEAP connection with "always ask"/unsaved secrets
+ * then we need to ask again because it might be an OTP token and the PIN
+ * may have changed.
+ */
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x) {
+ if (!nm_setting_get_secret_flags (NM_SETTING (s_8021x),
+ NM_SETTING_802_1X_PASSWORD,
+ &secret_flags,
+ NULL))
+ g_assert_not_reached ();
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ *setting_name = NM_SETTING_802_1X_SETTING_NAME;
+ return *setting_name ? TRUE : FALSE;
+ }
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (s_wsec) {
+ if (!nm_setting_get_secret_flags (NM_SETTING (s_wsec),
+ NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+ &secret_flags,
+ NULL))
+ g_assert_not_reached ();
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+ return *setting_name ? TRUE : FALSE;
+ }
+
+ /* Not a LEAP or 802.1x connection */
+ return FALSE;
+}
+
+static gboolean
+need_new_wpa_psk (NMDeviceIwd *self,
+ const char **setting_name)
+{
+ NMSettingWirelessSecurity *s_wsec;
+ NMConnection *connection;
+ const char *key_mgmt = NULL;
+
+ g_assert (setting_name != NULL);
+
+ connection = nm_device_get_applied_connection (NM_DEVICE (self));
+ g_return_val_if_fail (connection != NULL, FALSE);
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (s_wsec)
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
+
+ if (g_strcmp0 (key_mgmt, "wpa-psk") == 0) {
+ /* We don't have any data from IWD about the disconnect
+ * reason or association state when the disconnect happened
+ * so just assume it was a bad password.
+ */
+ *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+ return TRUE;
+ }
+
+ /* Not a WPA-PSK connection */
+ return FALSE;
+}
+
+static gboolean
+handle_8021x_or_psk_auth_fail (NMDeviceIwd *self)
+{
+ NMDevice *device = NM_DEVICE (self);
+ NMActRequest *req;
+ const char *setting_name = NULL;
+ gboolean handled = FALSE;
+
+ req = nm_device_get_act_request (device);
+ g_return_val_if_fail (req != NULL, FALSE);
+
+ if ( need_new_8021x_secrets (self, &setting_name)
+ || need_new_wpa_psk (self, &setting_name)) {
+ nm_act_request_clear_secrets (req);
+
+ _LOGI (LOGD_DEVICE | LOGD_WIFI,
+ "Activation: (wifi) disconnected during association, asking for new key");
+
+ cleanup_association_attempt (self, FALSE);
+ nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH,
+ NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+ wifi_secrets_get_secrets (self,
+ setting_name,
+ NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+ | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+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;
+ gs_unref_variant GVariant *variant = NULL;
+ NMConnection *connection;
+ NMSettingWireless *s_wifi;
+ GBytes *ssid;
+
+ 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 || nm_connection_get_setting_wireless_security (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")) {
+ if (handle_8021x_or_psk_auth_fail (self)) {
+ _LOGW (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) asking for new secrets");
+ } else {
+ cleanup_association_attempt (self, FALSE);
+ 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;
+
+ _LOGI (LOGD_DEVICE | LOGD_WIFI,
+ "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Connected to '%s'.",
+ ssid ? nm_utils_escape_ssid (g_bytes_get_data (ssid, NULL),
+ g_bytes_get_size (ssid)) : "(none)");
+ nm_device_activate_schedule_stage3_ip_config_start (device);
+ return;
+
+failed:
+ cleanup_association_attempt (self, FALSE);
+ nm_device_queue_state (device, NM_DEVICE_STATE_FAILED,
+ NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static gboolean
+handle_auth_or_fail (NMDeviceIwd *self,
+ NMActRequest *req,
+ gboolean new_secrets)
+{
+ const char *setting_name;
+ guint32 tries;
+ NMConnection *applied_connection;
+ NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+
+ g_return_val_if_fail (NM_IS_DEVICE_IWD (self), FALSE);
+
+ applied_connection = nm_act_request_get_applied_connection (req);
+
+ tries = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark ()));
+ if (tries > 3)
+ return FALSE;
+
+ nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+
+ nm_act_request_clear_secrets (req);
+ setting_name = nm_connection_need_secrets (applied_connection, NULL);
+ if (!setting_name) {
+ _LOGW (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+ return FALSE;
+ }
+
+ if (new_secrets)
+ get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+ wifi_secrets_get_secrets (self, setting_name, get_secret_flags);
+ g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), GUINT_TO_POINTER (++tries));
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+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 ? get_ap_by_path (self, ap_path) : NULL;
+ if (!ap) {
+ ap = nm_wifi_aps_find_first_compatible (priv->aps, connection, FALSE);
+
+ /* 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_exported_object_get_path (NM_EXPORTED_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;
+ const char *setting_name;
+ NMSettingWireless *s_wireless;
+ 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);
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ g_assert (s_wireless);
+
+ /* If we need secrets, get them */
+ setting_name = nm_connection_need_secrets (connection, NULL);
+ if (setting_name) {
+ _LOGI (LOGD_DEVICE | LOGD_WIFI,
+ "Activation: (wifi) access point '%s' has security, but secrets are required.",
+ nm_connection_get_id (connection));
+
+ if (handle_auth_or_fail (self, req, FALSE))
+ ret = NM_ACT_STAGE_RETURN_POSTPONE;
+ else {
+ NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+ ret = NM_ACT_STAGE_RETURN_FAILURE;
+ }
+ goto out;
+ }
+
+ /* Have secrets or no secrets required */
+ if (nm_connection_get_setting_wireless_security (connection)) {
+ _LOGI (LOGD_DEVICE | LOGD_WIFI,
+ "Activation: (wifi) connection '%s' has security, and secrets exist. No new secrets needed.",
+ nm_connection_get_id (connection));
+ } else {
+ _LOGI (LOGD_DEVICE | LOGD_WIFI,
+ "Activation: (wifi) connection '%s' requires no security. No secrets needed.",
+ nm_connection_get_id (connection));
+ }
+
+ /* 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) {
+ return FALSE;
+
+ _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, -1,
+ 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 void
+activation_success_handler (NMDevice *device)
+{
+ NMDeviceIwd *self = NM_DEVICE_IWD (device);
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+ 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 wireless secrets tries on success */
+ g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
+
+ /* There should always be a current AP */
+ g_warn_if_fail (priv->current_ap);
+}
+
+static void
+activation_failure_handler (NMDevice *device)
+{
+ NMConnection *applied_connection;
+
+ applied_connection = nm_device_get_applied_connection (device);
+ g_assert (applied_connection);
+
+ /* Clear wireless secrets tries on failure */
+ g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
+}
+
+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);
+ else if (old_state <= NM_DEVICE_STATE_UNAVAILABLE)
+ update_aps (self);
+
+ 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:
+ send_disconnect (self);
+ break;
+ case NM_DEVICE_STATE_IP_CHECK:
+ break;
+ case NM_DEVICE_STATE_ACTIVATED:
+ activation_success_handler (device);
+ break;
+ case NM_DEVICE_STATE_FAILED:
+ activation_failure_handler (device);
+ 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 (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);
+ gsize i;
+ 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 = (char **) nm_wifi_aps_get_sorted_paths (priv->aps, TRUE);
+ for (i = 0; list[i]; i++)
+ list[i] = g_strdup (list[i]);
+ g_value_take_boxed (value, list);
+ break;
+ case PROP_ACTIVE_ACCESS_POINT:
+ nm_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);
+
+ _LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
+
+ /* Don't allow scanning while connecting, disconnecting or roaming */
+ priv->can_scan = NM_IN_STRSET (new_state, "connected", "disconnected");
+
+ if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) {
+ /* If we're activating, 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 ( nm_device_is_activating (device)
+ || nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED)
+ 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 ( !nm_device_is_activating (device)
+ && nm_device_get_state (device) != NM_DEVICE_STATE_ACTIVATED)
+ 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);
+
+ 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);
+
+ if (new_scanning == priv->scanning)
+ return;
+
+ priv->scanning = new_scanning;
+
+ _notify (self, PROP_SCANNING);
+
+ if (!priv->scanning)
+ update_aps (self);
+}
+
+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));
+
+ 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;
+
+ 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);
+
+ g_signal_connect (priv->dbus_proxy, "g-properties-changed",
+ G_CALLBACK (properties_changed), self);
+
+ /* Call Disconnect to make sure IWD's autoconnect is disabled. We've
+ * most likely just brought the device UP so it would be in
+ * autoconnect by default.
+ */
+ send_disconnect (self);
+}
+
+const gchar *
+nm_device_iwd_agent_psk_query (NMDeviceIwd *self)
+{
+ NMActRequest *req;
+ NMConnection *connection;
+ NMSettingWireless *s_wireless;
+ NMSettingWirelessSecurity *s_wireless_sec;
+
+ req = nm_device_get_act_request (NM_DEVICE (self));
+ if (!req)
+ return NULL;
+
+ connection = nm_act_request_get_applied_connection (req);
+ if (!connection)
+ return NULL;
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ if (!s_wireless)
+ return NULL;
+
+ s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+ if (!s_wireless_sec)
+ return NULL;
+
+ return nm_setting_wireless_security_get_psk (s_wireless_sec);
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_iwd_init (NMDeviceIwd *self)
+{
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+ priv->aps = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* 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);
+
+ wifi_secrets_cancel (self);
+
+ 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);
+}
+
+static void
+finalize (GObject *object)
+{
+ NMDeviceIwd *self = NM_DEVICE_IWD (object);
+ NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+ nm_assert (g_hash_table_size (priv->aps) == 0);
+
+ g_hash_table_unref (priv->aps);
+
+ G_OBJECT_CLASS (nm_device_iwd_parent_class)->finalize (object);
+}
+
+static void
+nm_device_iwd_class_init (NMDeviceIwdClass *klass)
+{
+ GObjectClass *object_class = G_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;
+ object_class->finalize = finalize;
+
+ parent_class->can_auto_connect = can_auto_connect;
+ parent_class->is_available = is_available;
+ 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->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->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[ACCESS_POINT_ADDED] =
+ g_signal_new (NM_DEVICE_IWD_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_IWD_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_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);
+
+ nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+ NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
+ "GetAccessPoints", impl_device_iwd_get_access_points,
+ "GetAllAccessPoints", impl_device_iwd_get_all_access_points,
+ "RequestScan", impl_device_iwd_request_scan,
+ NULL);
+}
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
new file mode 100644
index 0000000000..332f4282c0
--- /dev/null
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -0,0 +1,60 @@
+/* -*- 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
+
+/* signals */
+#define NM_DEVICE_IWD_ACCESS_POINT_ADDED NM_DEVICE_WIFI_ACCESS_POINT_ADDED
+#define NM_DEVICE_IWD_ACCESS_POINT_REMOVED NM_DEVICE_WIFI_ACCESS_POINT_REMOVED
+
+/* internal signals */
+#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);
+
+const gchar *nm_device_iwd_agent_psk_query (NMDeviceIwd *device);
+
+#endif /* __NETWORKMANAGER_DEVICE_IWD_H__ */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index bf021095a5..68749ee224 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -48,6 +48,7 @@
#include "nm-auth-utils.h"
#include "settings/nm-settings-connection.h"
#include "settings/nm-settings.h"
+#include "nm-wifi-utils.h"
#include "nm-core-internal.h"
#include "nm-config.h"
@@ -680,35 +681,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 +701,7 @@ check_connection_available (NMDevice *device,
if (specific_object) {
NMWifiAP *ap;
- ap = get_ap_by_path (NM_DEVICE_WIFI (device), specific_object);
+ ap = get_ap_by_path (self, specific_object);
return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
}
@@ -745,40 +725,7 @@ 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, connection, TRUE);
}
static gboolean
@@ -789,6 +736,7 @@ complete_connection (NMDevice *device,
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 +773,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, connection, FALSE);
/* 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
@@ -900,7 +848,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 +955,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,7 +987,7 @@ can_auto_connect (NMDevice *device,
return FALSE;
}
- ap = find_first_compatible_ap (self, connection, FALSE);
+ ap = nm_wifi_aps_find_first_compatible (priv->aps, connection, FALSE);
if (ap) {
/* All good; connection is usable */
NM_SET_OUT (specific_object, g_strdup (nm_exported_object_get_path (NM_EXPORTED_OBJECT (ap))));
@@ -1048,78 +997,15 @@ can_auto_connect (NMDevice *device,
return FALSE;
}
-static int
-ap_id_compare (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
-{
- 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)
{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
gs_free const char **list = NULL;
GVariant *v;
- list = ap_list_get_sorted_paths (self, FALSE);
+ list = nm_wifi_aps_get_sorted_paths (priv->aps, FALSE);
v = g_variant_new_objv (list, -1);
g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
}
@@ -1128,10 +1014,11 @@ static void
impl_device_wifi_get_all_access_points (NMDeviceWifi *self,
GDBusMethodInvocation *context)
{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
gs_free const char **list = NULL;
GVariant *v;
- list = ap_list_get_sorted_paths (self, TRUE);
+ list = nm_wifi_aps_get_sorted_paths (priv->aps, TRUE);
v = g_variant_new_objv (list, -1);
g_dbus_method_invocation_return_value (context, g_variant_new_tuple (&v, 1));
}
@@ -1616,7 +1503,7 @@ ap_list_dump (gpointer user_data)
now_s,
priv->last_scan,
priv->scheduled_scan_time);
- list = ap_list_get_sorted (self, TRUE);
+ list = nm_wifi_aps_get_sorted (priv->aps, TRUE);
for (i = 0; list[i]; i++)
_ap_dump (self, LOGL_DEBUG, list[i], "dump", now_s);
}
@@ -2658,7 +2545,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
if (ap)
goto done;
- ap = find_first_compatible_ap (self, connection, FALSE);
+ ap = nm_wifi_aps_find_first_compatible (priv->aps, connection, FALSE);
}
if (ap) {
@@ -3291,7 +3178,7 @@ 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);
+ list = (char **) nm_wifi_aps_get_sorted_paths (priv->aps, TRUE);
for (i = 0; list[i]; i++)
list[i] = g_strdup (list[i]);
g_value_take_boxed (value, list);
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
new file mode 100644
index 0000000000..e5ff930ca5
--- /dev/null
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -0,0 +1,552 @@
+/* -*- 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-manager.h"
+#include "nm-device-iwd.h"
+#include "nm-utils/nm-random-utils.h"
+
+/*****************************************************************************/
+
+typedef struct {
+ GCancellable *cancellable;
+ gboolean running;
+ GDBusObjectManager *object_manager;
+ guint agent_id;
+ gchar *agent_path;
+} 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;
+ NMManager *manager;
+ NMDevice *device;
+ const gchar *psk;
+
+ /* 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;
+ }
+
+ manager = nm_manager_get ();
+
+ device = nm_manager_get_device_by_ifindex (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;
+ }
+
+ psk = nm_device_iwd_agent_psk_query (NM_DEVICE_IWD (device));
+ if (!psk) {
+ _LOGW ("Device %s had no PSK for the IWD Agent", ifname);
+ goto return_error;
+ }
+
+ _LOGI ("Sending the PSK to the IWD Agent for device %s", ifname);
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(s)", psk));
+ return;
+
+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
+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;
+ NMManager *manager;
+
+ 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;
+ }
+
+ manager = nm_manager_get ();
+
+ device = nm_manager_get_device_by_ifindex (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
+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)) {
+ GList *objects, *iter;
+
+ priv->running = true;
+
+ 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) {
+ GDBusInterface *agent_manager;
+
+ agent_manager = g_dbus_object_manager_get_interface (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);
+ }
+ } else {
+ NMManager *manager = nm_manager_get ();
+ const GSList *devices, *iter;
+
+ priv->running = false;
+
+ devices = nm_manager_get_devices (manager);
+ for (iter = devices; iter; iter = iter->next) {
+ NMDevice *device = NM_DEVICE (iter->data);
+
+ if (!NM_IS_DEVICE_IWD (device))
+ continue;
+
+ nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device),
+ NULL);
+ }
+ }
+}
+
+static void
+device_added (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;
+ NMManager *manager = nm_manager_get ();
+
+ g_clear_object (&priv->cancellable);
+
+ 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, "interface-added",
+ G_CALLBACK (interface_added), self);
+ g_signal_connect (priv->object_manager, "interface-removed",
+ G_CALLBACK (interface_removed), self);
+ 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);
+ }
+
+ name_owner_changed (G_OBJECT (object_manager), NULL, self);
+
+ g_signal_connect (manager, "device-added",
+ G_CALLBACK (device_added), 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);
+}
+
+/*****************************************************************************/
+
+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->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_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 0000000000..197020bb86
--- /dev/null
+++ b/src/devices/wifi/nm-iwd-manager.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 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"
+
+#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);
+
+#endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index bc823af0a5..ed6ad28811 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -1430,3 +1430,88 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
NULL);
}
+/*****************************************************************************/
+
+static int
+ap_id_compare (gconstpointer p_a, gconstpointer p_b, gpointer user_data)
+{
+ 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);
+}
+
+NMWifiAP **
+nm_wifi_aps_get_sorted (GHashTable *aps, gboolean include_without_ssid)
+{
+ NMWifiAP **list;
+ GHashTableIter iter;
+ NMWifiAP *ap;
+ gsize i, n;
+
+ n = g_hash_table_size (aps);
+ list = g_new (NMWifiAP *, n + 1);
+
+ i = 0;
+ if (n > 0) {
+ g_hash_table_iter_init (&iter, 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;
+}
+
+const char **
+nm_wifi_aps_get_sorted_paths (GHashTable *aps, gboolean include_without_ssid)
+{
+ gpointer *list;
+ gsize i, j;
+
+ list = (gpointer *) nm_wifi_aps_get_sorted (aps, 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;
+}
+
+NMWifiAP *
+nm_wifi_aps_find_first_compatible (GHashTable *aps,
+ 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, 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;
+}
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index dd5a4ad12b..1748223121 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -95,4 +95,12 @@ const char *nm_wifi_ap_to_string (const NMWifiAP *self,
gulong buf_len,
gint32 now_s);
+NMWifiAP **nm_wifi_aps_get_sorted (GHashTable *aps,
+ gboolean include_without_ssid);
+const char **nm_wifi_aps_get_sorted_paths (GHashTable *aps,
+ gboolean include_without_ssid);
+NMWifiAP *nm_wifi_aps_find_first_compatible (GHashTable *aps,
+ NMConnection *connection,
+ gboolean allow_unstable_order);
+
#endif /* __NM_WIFI_AP_H__ */
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index a1752634bc..1272e94139 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,24 @@ 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_value (NM_CONFIG_GET_DATA,
+ NM_CONFIG_KEYFILE_GROUP_MAIN,
+ NM_CONFIG_KEYFILE_KEY_MAIN_WIFI_BACKEND,
+ NM_CONFIG_GET_VALUE_STRIP);
+
+ nm_log_dbg (LOGD_PLATFORM | LOGD_WIFI, "(%s) config: backend is %s, %i", iface, backend, WITH_IWD);
+ 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 3ff82004d0..044bd392dd 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 1b6c2f4bfb..def64dd6ff 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/nm-config.h b/src/nm-config.h
index 8bdd5002d2..3deec5fa72 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -65,6 +65,7 @@
#define NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG "debug"
#define NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE "hostname-mode"
#define NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER "slaves-order"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_WIFI_BACKEND "wifi-backend"
#define NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND "backend"
#define NM_CONFIG_KEYFILE_KEY_CONFIG_ENABLE "enable"
#define NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS ".was"