diff options
| author | Michael Biebl <biebl@debian.org> | 2010-11-29 00:47:11 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2010-11-29 00:47:11 +0100 |
| commit | 9fae01fa351805b2903e535736e06971bc0b925e (patch) | |
| tree | c19bccf84c9f894e50fae678ba6ee2b2044e2d30 /src/nm-device-wifi.c | |
Imported Upstream version 0.8.1 upstream/0.8.1
Diffstat (limited to 'src/nm-device-wifi.c')
| -rw-r--r-- | src/nm-device-wifi.c | 3936 |
1 files changed, 3936 insertions, 0 deletions
diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c new file mode 100644 index 00000000..36be9e3e --- /dev/null +++ b/src/nm-device-wifi.c @@ -0,0 +1,3936 @@ +/* -*- 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) 2005 - 2010 Red Hat, Inc. + * Copyright (C) 2006 - 2008 Novell, Inc. + */ + +#include <glib.h> +#include <glib/gi18n.h> +#include <dbus/dbus.h> +#include <netinet/in.h> +#include <string.h> +#include <net/ethernet.h> +#include <iwlib.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> + +#include "nm-glib-compat.h" +#include "nm-device.h" +#include "nm-device-wifi.h" +#include "nm-device-interface.h" +#include "nm-device-private.h" +#include "nm-utils.h" +#include "nm-logging.h" +#include "nm-marshal.h" +#include "NetworkManagerUtils.h" +#include "nm-activation-request.h" +#include "nm-supplicant-manager.h" +#include "nm-supplicant-interface.h" +#include "nm-supplicant-config.h" +#include "nm-properties-changed-signal.h" +#include "nm-setting-connection.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-8021x.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-system.h" + +static gboolean impl_device_get_access_points (NMDeviceWifi *device, + GPtrArray **aps, + GError **err); + +#include "nm-device-wifi-glue.h" + + +/* All of these are in seconds */ +#define SCAN_INTERVAL_MIN 0 +#define SCAN_INTERVAL_STEP 20 +#define SCAN_INTERVAL_MAX 120 + +#define WIRELESS_SECRETS_TRIES "wireless-secrets-tries" + +static void device_interface_init (NMDeviceInterface *iface_class); + +G_DEFINE_TYPE_EXTENDED (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE, 0, + G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_INTERFACE, device_interface_init)) + +#define NM_DEVICE_WIFI_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_WIFI, NMDeviceWifiPrivate)) + + +enum { + PROP_0, + PROP_HW_ADDRESS, + PROP_MODE, + PROP_BITRATE, + PROP_ACTIVE_ACCESS_POINT, + PROP_CAPABILITIES, + PROP_SCANNING, + PROP_IPW_RFKILL_STATE, + + LAST_PROP +}; + +enum { + ACCESS_POINT_ADDED, + ACCESS_POINT_REMOVED, + HIDDEN_AP_FOUND, + PROPERTIES_CHANGED, + SCANNING_ALLOWED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +typedef enum { + NM_WIFI_ERROR_CONNECTION_NOT_WIRELESS = 0, + NM_WIFI_ERROR_CONNECTION_INVALID, + NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, +} NMWifiError; + +#define NM_WIFI_ERROR (nm_wifi_error_quark ()) +#define NM_TYPE_WIFI_ERROR (nm_wifi_error_get_type ()) + +typedef struct SupplicantStateTask { + NMDeviceWifi *self; + guint32 new_state; + guint32 old_state; + gboolean mgr_task; + guint source_id; +} SupplicantStateTask; + +typedef struct Supplicant { + NMSupplicantManager *mgr; + NMSupplicantInterface *iface; + + /* signal handler ids */ + guint mgr_state_id; + guint iface_error_id; + guint iface_state_id; + guint iface_scanned_ap_id; + guint iface_scan_request_result_id; + guint iface_scan_results_id; + guint iface_con_state_id; + guint iface_notify_scanning_id; + + /* Timeouts and idles */ + guint iface_con_error_cb_id; + guint con_timeout_id; + + GSList *mgr_tasks; + GSList *iface_tasks; +} Supplicant; + +struct _NMDeviceWifiPrivate { + gboolean disposed; + + struct ether_addr hw_addr; + + /* Legacy rfkill for ipw2x00; will be fixed with 2.6.33 kernel */ + char * ipw_rfkill_path; + guint ipw_rfkill_id; + RfKillState ipw_rfkill_state; + + GByteArray * ssid; + gint8 invalid_strength_counter; + iwqual max_qual; + + gint8 num_freqs; + guint32 freqs[IW_MAX_FREQUENCIES]; + + GSList * ap_list; + NMAccessPoint * current_ap; + guint32 rate; + gboolean enabled; /* rfkilled or not */ + + glong scheduled_scan_time; + guint8 scan_interval; /* seconds */ + guint pending_scan_id; + + Supplicant supplicant; + + guint32 failed_link_count; + guint periodic_source_id; + guint link_timeout_id; + + /* Static options from driver */ + guint8 we_version; + guint32 capabilities; + gboolean has_scan_capa_ssid; +}; + +static guint32 nm_device_wifi_get_frequency (NMDeviceWifi *self); + +static gboolean request_wireless_scan (gpointer user_data); + +static void schedule_scan (NMDeviceWifi *self, gboolean backoff); + +static void cancel_pending_scan (NMDeviceWifi *self); + +static int wireless_qual_to_percent (const struct iw_quality *qual, + const struct iw_quality *max_qual); + +static void cleanup_association_attempt (NMDeviceWifi * self, + gboolean disconnect); + +static void remove_supplicant_timeouts (NMDeviceWifi *self); + +static void supplicant_iface_state_cb (NMSupplicantInterface * iface, + guint32 new_state, + guint32 old_state, + NMDeviceWifi *self); + +static void supplicant_iface_connection_state_cb (NMSupplicantInterface * iface, + guint32 new_state, + guint32 old_state, + NMDeviceWifi *self); + +static void supplicant_iface_scanned_ap_cb (NMSupplicantInterface * iface, + GHashTable *properties, + NMDeviceWifi * self); + +static void supplicant_iface_scan_request_result_cb (NMSupplicantInterface * iface, + gboolean success, + NMDeviceWifi * self); + +static void supplicant_iface_scan_results_cb (NMSupplicantInterface * iface, + guint32 num_bssids, + NMDeviceWifi * self); + +static void supplicant_mgr_state_cb (NMSupplicantInterface * iface, + guint32 new_state, + guint32 old_state, + NMDeviceWifi *self); + +static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface, + GParamSpec * pspec, + NMDeviceWifi * self); + +static guint32 nm_device_wifi_get_bitrate (NMDeviceWifi *self); + +static void cull_scan_list (NMDeviceWifi *self); + +static GQuark +nm_wifi_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("nm-wifi-error"); + return quark; +} + +/* This should really be standard. */ +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +static GType +nm_wifi_error_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = { + /* Connection was not a wireless connection. */ + ENUM_ENTRY (NM_WIFI_ERROR_CONNECTION_NOT_WIRELESS, "ConnectionNotWireless"), + /* Connection was not a valid wireless connection. */ + ENUM_ENTRY (NM_WIFI_ERROR_CONNECTION_INVALID, "ConnectionInvalid"), + /* Connection does not apply to this device. */ + ENUM_ENTRY (NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, "ConnectionIncompatible"), + { 0, 0, 0 } + }; + etype = g_enum_register_static ("NMWifiError", values); + } + return etype; +} + +/* IPW rfkill handling (until 2.6.33) */ +RfKillState +nm_device_wifi_get_ipw_rfkill_state (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + char *contents = NULL; + RfKillState state = RFKILL_UNBLOCKED; + const char *str_state = NULL; + + if ( priv->ipw_rfkill_path + && g_file_get_contents (priv->ipw_rfkill_path, &contents, NULL, NULL)) { + contents = g_strstrip (contents); + + /* 0 - RF kill not enabled + * 1 - SW based RF kill active (sysfs) + * 2 - HW based RF kill active + * 3 - Both HW and SW baed RF kill active + */ + switch (contents[0]) { + case '1': + state = RFKILL_SOFT_BLOCKED; + str_state = "soft-blocked"; + break; + case '2': + case '3': + state = RFKILL_HARD_BLOCKED; + str_state = "hard-blocked"; + break; + case '0': + str_state = "unblocked"; + default: + break; + } + g_free (contents); + + nm_log_dbg (LOGD_RFKILL, "(%s): ipw rfkill state '%s'", + nm_device_get_iface (NM_DEVICE (self)), + str_state ? str_state : "(unknown)"); + } + + return state; +} + +static gboolean +ipw_rfkill_state_work (gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + RfKillState old_state; + + old_state = priv->ipw_rfkill_state; + priv->ipw_rfkill_state = nm_device_wifi_get_ipw_rfkill_state (self); + if (priv->ipw_rfkill_state != old_state) + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_IPW_RFKILL_STATE); + + return TRUE; +} + +/* + * nm_device_wifi_update_signal_strength + * + * Update the device's idea of the strength of its connection to the + * current access point. + * + */ +static void +nm_device_wifi_update_signal_strength (NMDeviceWifi *self, + NMAccessPoint *ap) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (NM_DEVICE (self)); + int fd, percent = -1; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd >= 0) { + struct iwreq wrq; + struct iw_statistics stats; + + memset (&stats, 0, sizeof (stats)); + + wrq.u.data.pointer = &stats; + wrq.u.data.length = sizeof (stats); + wrq.u.data.flags = 1; /* Clear updated flag */ + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + + if (ioctl (fd, SIOCGIWSTATS, &wrq) == 0) + percent = wireless_qual_to_percent (&stats.qual, &priv->max_qual); + close (fd); + } + + /* Try to smooth out the strength. Atmel cards, for example, will give no strength + * one second and normal strength the next. + */ + if (percent >= 0 || ++priv->invalid_strength_counter > 3) { + nm_ap_set_strength (ap, (gint8) percent); + priv->invalid_strength_counter = 0; + } +} + + +static gboolean +wireless_get_range (NMDeviceWifi *self, + struct iw_range *range, + guint32 *response_len) +{ + int fd, i = 26; + gboolean success = FALSE; + const char *iface; + struct iwreq wrq; + + g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE); + g_return_val_if_fail (range != NULL, FALSE); + + iface = nm_device_get_iface (NM_DEVICE (self)); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_HW, "(%s): couldn't open control socket.", iface); + return FALSE; + } + + memset (&wrq, 0, sizeof (struct iwreq)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + wrq.u.data.pointer = (caddr_t) range; + wrq.u.data.length = sizeof (struct iw_range); + + /* Need to give some drivers time to recover after suspend/resume + * (ex ipw3945 takes a few seconds to talk to its regulatory daemon; + * see rh bz#362421) + */ + while (i-- > 0) { + if (ioctl (fd, SIOCGIWRANGE, &wrq) == 0) { + if (response_len) + *response_len = wrq.u.data.length; + success = TRUE; + break; + } else if (errno != EAGAIN) { + nm_log_err (LOGD_HW | LOGD_WIFI, + "(%s): couldn't get driver range information (%d).", + iface, errno); + break; + } + + g_usleep (G_USEC_PER_SEC / 4); + } + + if (i <= 0) { + nm_log_warn (LOGD_HW | LOGD_WIFI, + "(%s): driver took too long to respond to IWRANGE query.", + iface); + } + + close (fd); + return success; +} + +static guint32 +real_get_generic_capabilities (NMDevice *dev) +{ + int fd, err; + guint32 caps = NM_DEVICE_CAP_NONE, response_len = 0; + struct iwreq wrq; + struct iw_range range; + const char *iface = nm_device_get_iface (dev); + gboolean success; + + memset (&range, 0, sizeof (struct iw_range)); + success = wireless_get_range (NM_DEVICE_WIFI (dev), &range, &response_len); + if (!success) + return NM_DEVICE_CAP_NONE; + + /* Check for Wireless Extensions support >= 16 for wireless devices */ + if ((response_len < 300) || (range.we_version_compiled < 16)) { + nm_log_err (LOGD_HW | LOGD_WIFI, + "(%s): driver's Wireless Extensions version (%d) is too old.", + iface, range.we_version_compiled); + return NM_DEVICE_CAP_NONE; + } + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_HW, "(%s): couldn't open control socket.", iface); + goto out; + } + + /* Cards that don't scan aren't supported */ + memset (&wrq, 0, sizeof (struct iwreq)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + err = ioctl (fd, SIOCSIWSCAN, &wrq); + close (fd); + if ((err < 0) && (errno == EOPNOTSUPP)) + caps = NM_DEVICE_CAP_NONE; + else + caps |= NM_DEVICE_CAP_NM_SUPPORTED; + +out: + return caps; +} + +#define WPA_CAPS (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | \ + NM_WIFI_DEVICE_CAP_CIPHER_CCMP | \ + NM_WIFI_DEVICE_CAP_WPA | \ + NM_WIFI_DEVICE_CAP_RSN) + +static guint32 +get_wireless_capabilities (NMDeviceWifi *self, + iwrange * range, + guint32 data_len) +{ + guint32 minlen; + guint32 caps = NM_WIFI_DEVICE_CAP_NONE; + const char * iface; + + g_return_val_if_fail (self != NULL, NM_WIFI_DEVICE_CAP_NONE); + g_return_val_if_fail (range != NULL, NM_WIFI_DEVICE_CAP_NONE); + + iface = nm_device_get_iface (NM_DEVICE (self)); + + minlen = ((char *) &range->enc_capa) - (char *) range + sizeof (range->enc_capa); + + /* All drivers should support WEP by default */ + caps |= NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104; + + if ((data_len >= minlen) && range->we_version_compiled >= 18) { + if (range->enc_capa & IW_ENC_CAPA_CIPHER_TKIP) + caps |= NM_WIFI_DEVICE_CAP_CIPHER_TKIP; + + if (range->enc_capa & IW_ENC_CAPA_CIPHER_CCMP) + caps |= NM_WIFI_DEVICE_CAP_CIPHER_CCMP; + + if (range->enc_capa & IW_ENC_CAPA_WPA) + caps |= NM_WIFI_DEVICE_CAP_WPA; + + if (range->enc_capa & IW_ENC_CAPA_WPA2) + caps |= NM_WIFI_DEVICE_CAP_RSN; + + /* Check for cipher support but not WPA support */ + if ( (caps & (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + && !(caps & (NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN))) { + nm_log_warn (LOGD_WIFI, "%s: device supports WPA ciphers but not WPA protocol; " + "WPA unavailable.", iface); + caps &= ~WPA_CAPS; + } + + /* Check for WPA support but not cipher support */ + if ( (caps & (NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN)) + && !(caps & (NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP))) { + nm_log_warn (LOGD_WIFI, "%s: device supports WPA protocol but not WPA ciphers; " + "WPA unavailable.", iface); + caps &= ~WPA_CAPS; + } + } + + return caps; +} + + +static guint32 iw_freq_to_uint32 (struct iw_freq *freq) +{ + if (freq->e == 0) { + /* Some drivers report channel not frequency. Convert to a + * frequency; but this assumes that the device is in b/g mode. + */ + if ((freq->m >= 1) && (freq->m <= 13)) + return 2407 + (5 * freq->m); + else if (freq->m == 14) + return 2484; + } + + return (guint32) (((double) freq->m) * pow (10, freq->e) / 1000000); +} + + +/* Until a new wireless-tools comes out that has the defs and the structure, + * need to copy them here. + */ +/* Scan capability flags - in (struct iw_range *)->scan_capa */ +#define NM_IW_SCAN_CAPA_NONE 0x00 +#define NM_IW_SCAN_CAPA_ESSID 0x01 + +struct iw_range_with_scan_capa +{ + guint32 throughput; + guint32 min_nwid; + guint32 max_nwid; + guint16 old_num_channels; + guint8 old_num_frequency; + + guint8 scan_capa; +/* don't need the rest... */ +}; + + +static GObject* +constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GObject *object; + GObjectClass *klass; + NMDeviceWifi *self; + NMDeviceWifiPrivate *priv; + struct iw_range range; + struct iw_range_with_scan_capa *scan_capa_range; + guint32 response_len = 0; + gboolean success; + int i; + + klass = G_OBJECT_CLASS (nm_device_wifi_parent_class); + object = klass->constructor (type, n_construct_params, construct_params); + if (!object) + return NULL; + + self = NM_DEVICE_WIFI (object); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + nm_log_dbg (LOGD_HW | LOGD_WIFI, "(%s): kernel ifindex %d", + nm_device_get_iface (NM_DEVICE (self)), + nm_device_get_ifindex (NM_DEVICE (self))); + + memset (&range, 0, sizeof (struct iw_range)); + success = wireless_get_range (NM_DEVICE_WIFI (object), &range, &response_len); + if (!success) + goto error; + + priv->max_qual.qual = range.max_qual.qual; + priv->max_qual.level = range.max_qual.level; + priv->max_qual.noise = range.max_qual.noise; + priv->max_qual.updated = range.max_qual.updated; + + priv->num_freqs = MIN (range.num_frequency, IW_MAX_FREQUENCIES); + for (i = 0; i < priv->num_freqs; i++) + priv->freqs[i] = iw_freq_to_uint32 (&range.freq[i]); + + priv->we_version = range.we_version_compiled; + + /* Check for the ability to scan specific SSIDs. Until the scan_capa + * field gets added to wireless-tools, need to work around that by casting + * to the custom structure. + */ + scan_capa_range = (struct iw_range_with_scan_capa *) ⦥ + if (scan_capa_range->scan_capa & NM_IW_SCAN_CAPA_ESSID) { + priv->has_scan_capa_ssid = TRUE; + nm_log_info (LOGD_HW | LOGD_WIFI, + "(%s): driver supports SSID scans (scan_capa 0x%02X).", + nm_device_get_iface (NM_DEVICE (self)), + scan_capa_range->scan_capa); + } else { + nm_log_info (LOGD_HW | LOGD_WIFI, + "(%s): driver does not support SSID scans (scan_capa 0x%02X).", + nm_device_get_iface (NM_DEVICE (self)), + scan_capa_range->scan_capa); + } + + /* 802.11 wireless-specific capabilities */ + priv->capabilities = get_wireless_capabilities (self, &range, response_len); + + /* Connect to the supplicant manager */ + priv->supplicant.mgr = nm_supplicant_manager_get (); + priv->supplicant.mgr_state_id = g_signal_connect (priv->supplicant.mgr, + "state", + G_CALLBACK (supplicant_mgr_state_cb), + self); + + /* The ipw2x00 drivers don't integrate with the kernel rfkill subsystem until + * 2.6.33. Thus all our nice libgudev magic is useless. So we get to poll. + * + * FIXME: when 2.6.33 comes lands, we can do some sysfs parkour to figure out + * if we need to poll or not by matching /sys/class/net/ethX/device to one + * of the /sys/class/rfkill/rfkillX/device links. If there's a match, we + * don't have to poll. + */ + priv->ipw_rfkill_path = g_strdup_printf ("/sys/class/net/%s/device/rf_kill", + nm_device_get_iface (NM_DEVICE (self))); + if (!g_file_test (priv->ipw_rfkill_path, G_FILE_TEST_IS_REGULAR)) { + g_free (priv->ipw_rfkill_path); + priv->ipw_rfkill_path = NULL; + } + priv->ipw_rfkill_state = nm_device_wifi_get_ipw_rfkill_state (self); + + return object; + +error: + g_object_unref (object); + return NULL; +} + +static gboolean +supplicant_interface_acquire (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + guint id, mgr_state; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (priv->supplicant.mgr != NULL, FALSE); + /* interface already acquired? */ + g_return_val_if_fail (priv->supplicant.iface == NULL, TRUE); + + mgr_state = nm_supplicant_manager_get_state (priv->supplicant.mgr); + g_return_val_if_fail (mgr_state == NM_SUPPLICANT_MANAGER_STATE_IDLE, FALSE); + + priv->supplicant.iface = nm_supplicant_manager_get_iface (priv->supplicant.mgr, + nm_device_get_iface (NM_DEVICE (self)), + TRUE); + if (priv->supplicant.iface == NULL) { + nm_log_err (LOGD_WIFI, "Couldn't initialize supplicant interface for %s.", + nm_device_get_iface (NM_DEVICE (self))); + return FALSE; + } + + id = g_signal_connect (priv->supplicant.iface, + "state", + G_CALLBACK (supplicant_iface_state_cb), + self); + priv->supplicant.iface_state_id = id; + + id = g_signal_connect (priv->supplicant.iface, + "scanned-ap", + G_CALLBACK (supplicant_iface_scanned_ap_cb), + self); + priv->supplicant.iface_scanned_ap_id = id; + + id = g_signal_connect (priv->supplicant.iface, + "scan-req-result", + G_CALLBACK (supplicant_iface_scan_request_result_cb), + self); + priv->supplicant.iface_scan_request_result_id = id; + + id = g_signal_connect (priv->supplicant.iface, + "scan-results", + G_CALLBACK (supplicant_iface_scan_results_cb), + self); + priv->supplicant.iface_scan_results_id = id; + + id = g_signal_connect (priv->supplicant.iface, + "connection-state", + G_CALLBACK (supplicant_iface_connection_state_cb), + self); + priv->supplicant.iface_con_state_id = id; + + id = g_signal_connect (priv->supplicant.iface, + "notify::scanning", + G_CALLBACK (supplicant_iface_notify_scanning_cb), + self); + priv->supplicant.iface_notify_scanning_id = id; + + return TRUE; +} + +static void +finish_supplicant_task (SupplicantStateTask *task, gboolean remove_source) +{ + NMDeviceWifi *self = task->self; + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + /* idle/timeout handlers should pass FALSE for remove_source, since they + * will tell glib to remove their source from the mainloop by returning + * FALSE when they exit. When called from this NMDevice's dispose handler, + * remove_source should be TRUE to cancel all outstanding idle/timeout + * handlers asynchronously. + */ + if (task->source_id && remove_source) + g_source_remove (task->source_id); + + if (task->mgr_task) + priv->supplicant.mgr_tasks = g_slist_remove (priv->supplicant.mgr_tasks, task); + else + priv->supplicant.iface_tasks = g_slist_remove (priv->supplicant.iface_tasks, task); + + memset (task, 0, sizeof (SupplicantStateTask)); + g_slice_free (SupplicantStateTask, task); +} + +static void +remove_supplicant_interface_error_handler (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (!priv->supplicant.iface) + return; + + if (priv->supplicant.iface_error_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_error_id); + priv->supplicant.iface_error_id = 0; + } + + if (priv->supplicant.iface_con_error_cb_id > 0) { + g_source_remove (priv->supplicant.iface_con_error_cb_id); + priv->supplicant.iface_con_error_cb_id = 0; + } +} + +static void +supplicant_interface_release (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + + g_return_if_fail (self != NULL); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + cancel_pending_scan (self); + + /* Reset the scan interval to be pretty frequent when disconnected */ + priv->scan_interval = SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP; + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): reset scanning interval to %d seconds", + nm_device_get_iface (NM_DEVICE (self))); + + remove_supplicant_interface_error_handler (self); + + /* Clean up all pending supplicant interface state idle tasks */ + while (priv->supplicant.iface_tasks) + finish_supplicant_task ((SupplicantStateTask *) priv->supplicant.iface_tasks->data, TRUE); + + if (priv->supplicant.iface_state_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_state_id); + priv->supplicant.iface_state_id = 0; + } + + if (priv->supplicant.iface_scanned_ap_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_scanned_ap_id); + priv->supplicant.iface_scanned_ap_id = 0; + } + + if (priv->supplicant.iface_scan_request_result_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_scan_request_result_id); + priv->supplicant.iface_scan_request_result_id = 0; + } + + if (priv->supplicant.iface_scan_results_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_scan_results_id); + priv->supplicant.iface_scan_results_id = 0; + } + + if (priv->supplicant.iface_con_state_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_con_state_id); + priv->supplicant.iface_con_state_id = 0; + } + + if (priv->supplicant.iface_notify_scanning_id > 0) { + g_signal_handler_disconnect (priv->supplicant.iface, priv->supplicant.iface_notify_scanning_id); + priv->supplicant.iface_notify_scanning_id = 0; + } + + if (priv->supplicant.iface) { + /* Tell the supplicant to disconnect from the current AP */ + nm_supplicant_interface_disconnect (priv->supplicant.iface); + + nm_supplicant_manager_release_iface (priv->supplicant.mgr, priv->supplicant.iface); + priv->supplicant.iface = NULL; + } +} + +static NMAccessPoint * +get_active_ap (NMDeviceWifi *self, + NMAccessPoint *ignore_ap, + gboolean match_hidden) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + const char *iface = nm_device_get_iface (NM_DEVICE (self)); + struct ether_addr bssid; + const GByteArray *ssid; + GSList *iter; + int i = 0; + NMAccessPoint *match_nofreq = NULL; + gboolean found_a_band = FALSE; + gboolean found_bg_band = FALSE; + NM80211Mode devmode; + guint32 devfreq; + + nm_device_wifi_get_bssid (self, &bssid); + nm_log_dbg (LOGD_WIFI, "(%s): active BSSID: %02x:%02x:%02x:%02x:%02x:%02x", + iface, + bssid.ether_addr_octet[0], bssid.ether_addr_octet[1], + bssid.ether_addr_octet[2], bssid.ether_addr_octet[3], + bssid.ether_addr_octet[4], bssid.ether_addr_octet[5]); + + if (!nm_ethernet_address_is_valid (&bssid)) + return NULL; + + ssid = nm_device_wifi_get_ssid (self); + nm_log_dbg (LOGD_WIFI, "(%s): active SSID: %s%s%s", + iface, + ssid ? "'" : "", + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", + ssid ? "'" : ""); + + devmode = nm_device_wifi_get_mode (self); + devfreq = nm_device_wifi_get_frequency (self); + + /* When matching hidden APs, do a second pass that ignores the SSID check, + * because NM might not yet know the SSID of the hidden AP in the scan list + * and therefore it won't get matched the first time around. + */ + while (i++ < (match_hidden ? 2 : 1)) { + nm_log_dbg (LOGD_WIFI, " Pass #%d %s", i, i > 1 ? "(ignoring SSID)" : ""); + + /* Find this SSID + BSSID in the device's AP list */ + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + NMAccessPoint *ap = NM_AP (iter->data); + const struct ether_addr *ap_bssid = nm_ap_get_address (ap); + const GByteArray *ap_ssid = nm_ap_get_ssid (ap); + NM80211Mode apmode; + guint32 apfreq; + + nm_log_dbg (LOGD_WIFI, " AP: %s%s%s %02x:%02x:%02x:%02x:%02x:%02x", + ap_ssid ? "'" : "", + ap_ssid ? nm_utils_escape_ssid (ap_ssid->data, ap_ssid->len) : "(none)", + ap_ssid ? "'" : "", + ap_bssid->ether_addr_octet[0], ap_bssid->ether_addr_octet[1], + ap_bssid->ether_addr_octet[2], ap_bssid->ether_addr_octet[3], + ap_bssid->ether_addr_octet[4], ap_bssid->ether_addr_octet[5]); + + if (ignore_ap && (ap == ignore_ap)) { + nm_log_dbg (LOGD_WIFI, " ignored"); + continue; + } + + if (memcmp (bssid.ether_addr_octet, ap_bssid->ether_addr_octet, ETH_ALEN)) { + nm_log_dbg (LOGD_WIFI, " BSSID mismatch"); + continue; + } + + if ((i == 0) && !nm_utils_same_ssid (ssid, ap_ssid, TRUE)) { + nm_log_dbg (LOGD_WIFI, " SSID mismatch"); + continue; + } + + apmode = nm_ap_get_mode (ap); + if (devmode != apmode) { + nm_log_dbg (LOGD_WIFI, " mode mismatch (device %d, ap %d)", + devmode, apmode); + continue; + } + + apfreq = nm_ap_get_freq (ap); + if (devfreq != apfreq) { + nm_log_dbg (LOGD_WIFI, " frequency mismatch (device %u, ap %u)", + devfreq, apfreq); + + if (match_nofreq == NULL) + match_nofreq = ap; + + if (apfreq > 4000) + found_a_band = TRUE; + else if (apfreq > 2000) + found_bg_band = TRUE; + continue; + } + + // FIXME: handle security settings here too + nm_log_dbg (LOGD_WIFI, " matched"); + return ap; + } + } + + /* Some proprietary drivers (wl.o) report tuned frequency (like when + * scanning) instead of the associated AP's frequency. This is a great + * example of how WEXT is underspecified. We use frequency to find the + * active AP in the scan list because some configurations use the same + * SSID/BSSID on the 2GHz and 5GHz bands simultaneously, and we need to + * make sure we get the right AP in the right band. This configuration + * is uncommon though, and the frequency check penalizes closed drivers we + * can't fix. Because we're not total dicks, ignore the frequency condition + * if the associated BSSID/SSID exists only in one band since that's most + * likely the AP we want. + */ + if (match_nofreq && (found_a_band != found_bg_band)) { + const struct ether_addr *ap_bssid = nm_ap_get_address (match_nofreq); + const GByteArray *ap_ssid = nm_ap_get_ssid (match_nofreq); + + nm_log_dbg (LOGD_WIFI, " matched %s%s%s %02x:%02x:%02x:%02x:%02x:%02x", + ap_ssid ? "'" : "", + ap_ssid ? nm_utils_escape_ssid (ap_ssid->data, ap_ssid->len) : "(none)", + ap_ssid ? "'" : "", + ap_bssid->ether_addr_octet[0], ap_bssid->ether_addr_octet[1], + ap_bssid->ether_addr_octet[2], ap_bssid->ether_addr_octet[3], + ap_bssid->ether_addr_octet[4], ap_bssid->ether_addr_octet[5]); + + return match_nofreq; + } + + nm_log_dbg (LOGD_WIFI, " No matching AP found."); + return NULL; +} + +static void +set_current_ap (NMDeviceWifi *self, NMAccessPoint *new_ap) +{ + NMDeviceWifiPrivate *priv; + char *old_path = NULL; + NMAccessPoint *old_ap; + + g_return_if_fail (NM_IS_DEVICE_WIFI (self)); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + old_ap = priv->current_ap; + + if (old_ap) { + old_path = g_strdup (nm_ap_get_dbus_path (old_ap)); + priv->current_ap = NULL; + } + + if (new_ap) { + priv->current_ap = g_object_ref (new_ap); + + /* Move the current AP to the front of the scan list. Since we + * do a lot of searches looking for the current AP, it saves + * time to have it in front. + */ + priv->ap_list = g_slist_remove (priv->ap_list, new_ap); + priv->ap_list = g_slist_prepend (priv->ap_list, new_ap); + } + + /* Unref old AP here to ensure object lives if new_ap == old_ap */ + if (old_ap) + g_object_unref (old_ap); + + /* Only notify if it's really changed */ + if ( (!old_path && new_ap) + || (old_path && !new_ap) + || (old_path && new_ap && strcmp (old_path, nm_ap_get_dbus_path (new_ap)))) + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT); + + g_free (old_path); +} + +static void +periodic_update (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *new_ap; + guint32 new_rate; + + /* In IBSS mode, most newer firmware/drivers do "BSS coalescing" where + * multiple IBSS stations using the same SSID will eventually switch to + * using the same BSSID to avoid network segmentation. When this happens, + * the card's reported BSSID will change, but the the new BSS may not + * be in the scan list, since scanning isn't done in ad-hoc mode for + * various reasons. So pull the BSSID from the card and update the + * current AP with it, if the current AP is adhoc. + */ + if (priv->current_ap && (nm_ap_get_mode (priv->current_ap) == NM_802_11_MODE_ADHOC)) { + struct ether_addr bssid = { {0x0, 0x0, 0x0, 0x0, 0x0, 0x0} }; + + nm_device_wifi_get_bssid (self, &bssid); + /* 0x02 means "locally administered" and should be OR-ed into + * the first byte of IBSS BSSIDs. + */ + if ( (bssid.ether_addr_octet[0] & 0x02) + && nm_ethernet_address_is_valid (&bssid)) + nm_ap_set_address (priv->current_ap, &bssid); + } + + new_ap = get_active_ap (self, NULL, FALSE); + if (new_ap) + nm_device_wifi_update_signal_strength (self, new_ap); + + if ((new_ap || priv->current_ap) && (new_ap != priv->current_ap)) { + const struct ether_addr *new_bssid = NULL; + const GByteArray *new_ssid = NULL; + const struct ether_addr *old_bssid = NULL; + const GByteArray *old_ssid = NULL; + char *old_addr = NULL, *new_addr = NULL; + + if (new_ap) { + new_bssid = nm_ap_get_address (new_ap); + new_addr = nm_ether_ntop (new_bssid); + new_ssid = nm_ap_get_ssid (new_ap); + } + + if (priv->current_ap) { + old_bssid = nm_ap_get_address (priv->current_ap); + old_addr = nm_ether_ntop (old_bssid); + old_ssid = nm_ap_get_ssid (priv->current_ap); + } + + nm_log_info (LOGD_WIFI, "(%s): roamed from BSSID %s (%s) to %s (%s)", + nm_device_get_iface (NM_DEVICE (self)), + old_addr ? old_addr : "(none)", + old_ssid ? nm_utils_escape_ssid (old_ssid->data, old_ssid->len) : "(none)", + new_addr ? new_addr : "(none)", + new_ssid ? nm_utils_escape_ssid (new_ssid->data, new_ssid->len) : "(none)"); + g_free (old_addr); + g_free (new_addr); + + set_current_ap (self, new_ap); + } + + new_rate = nm_device_wifi_get_bitrate (self); + if (new_rate != priv->rate) { + priv->rate = new_rate; + g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_BITRATE); + } +} + +/* + * nm_device_wifi_periodic_update + * + * Periodically update device statistics. + * + */ +static gboolean +nm_device_wifi_periodic_update (gpointer data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMDeviceState state; + + /* BSSID and signal strength have meaningful values only if the device + is activated and not scanning */ + state = nm_device_get_state (NM_DEVICE (self)); + if (state != NM_DEVICE_STATE_ACTIVATED) + goto out; + + if (nm_supplicant_interface_get_scanning (priv->supplicant.iface)) + goto out; + + periodic_update (self); + +out: + return TRUE; +} + +static gboolean +real_hw_is_up (NMDevice *device) +{ + return nm_system_device_is_up (device); +} + +static gboolean +real_hw_bring_up (NMDevice *device, gboolean *no_firmware) +{ + if (!NM_DEVICE_WIFI_GET_PRIVATE (device)->enabled) + return FALSE; + + return nm_system_device_set_up_down (device, TRUE, no_firmware); +} + +static void +real_hw_take_down (NMDevice *dev) +{ + nm_system_device_set_up_down (dev, FALSE, NULL); +} + +static gboolean +real_is_up (NMDevice *device) +{ + if (!NM_DEVICE_WIFI_GET_PRIVATE (device)->periodic_source_id) + return FALSE; + + return TRUE; +} + +static gboolean +real_bring_up (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + priv->periodic_source_id = g_timeout_add_seconds (6, nm_device_wifi_periodic_update, self); + return TRUE; +} + +static void +access_point_removed (NMDeviceWifi *device, NMAccessPoint *ap) +{ + g_signal_emit (device, signals[ACCESS_POINT_REMOVED], 0, ap); +} + +static void +remove_all_aps (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + /* Remove outdated APs */ + while (g_slist_length (priv->ap_list)) { + NMAccessPoint *ap = NM_AP (priv->ap_list->data); + + access_point_removed (self, ap); + priv->ap_list = g_slist_remove (priv->ap_list, ap); + g_object_unref (ap); + } + g_slist_free (priv->ap_list); + priv->ap_list = NULL; +} + +static void +real_take_down (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->periodic_source_id) { + g_source_remove (priv->periodic_source_id); + priv->periodic_source_id = 0; + } + + cleanup_association_attempt (self, TRUE); + set_current_ap (self, NULL); + remove_all_aps (self); +} + +static void +real_deactivate_quickly (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *orig_ap = nm_device_wifi_get_activation_ap (self); + + cleanup_association_attempt (self, TRUE); + + set_current_ap (self, NULL); + priv->rate = 0; + + /* If the AP is 'fake', i.e. it wasn't actually found from + * a scan but the user tried to connect to it manually (maybe it + * was non-broadcasting or something) get rid of it, because 'fake' + * APs should only live for as long as we're connected to them. Fixes + * a bug where user-created Ad-Hoc APs are never removed from the scan + * list, because scanning is disabled while in Ad-Hoc mode (for stability), + * and thus the AP culling never happens. (bgo #569241) + */ + if (orig_ap && nm_ap_get_fake (orig_ap)) { + access_point_removed (self, orig_ap); + priv->ap_list = g_slist_remove (priv->ap_list, orig_ap); + g_object_unref (orig_ap); + } +} + +static void +real_deactivate (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + + nm_device_wifi_set_mode (self, NM_802_11_MODE_INFRA); + /* FIXME: Should we reset the scan interval here? */ +/* nm_device_wifi_set_scan_interval (app_data, self, NM_WIRELESS_SCAN_INTERVAL_ACTIVE); */ +} + +static gboolean +real_check_connection_compatible (NMDevice *device, + NMConnection *connection, + GError **error) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSettingConnection *s_con; + NMSettingWireless *s_wireless; + const GByteArray *mac; + + s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION)); + g_assert (s_con); + + if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME)) { + g_set_error (error, + NM_WIFI_ERROR, NM_WIFI_ERROR_CONNECTION_NOT_WIRELESS, + "The connection was not a WiFi connection."); + return FALSE; + } + + s_wireless = NM_SETTING_WIRELESS (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS)); + if (!s_wireless) { + g_set_error (error, + NM_WIFI_ERROR, NM_WIFI_ERROR_CONNECTION_INVALID, + "The connection was not a valid WiFi connection."); + return FALSE; + } + + mac = nm_setting_wireless_get_mac_address (s_wireless); + if (mac && memcmp (mac->data, &(priv->hw_addr.ether_addr_octet), ETH_ALEN)) { + g_set_error (error, + NM_WIFI_ERROR, NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, + "The connection's MAC address did not match this device."); + return FALSE; + } + + // FIXME: check channel/freq/band against bands the hardware supports + // FIXME: check encryption against device capabilities + // FIXME: check bitrate against device capabilities + + return TRUE; +} + +static gboolean +real_is_available (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSupplicantInterface *sup_iface; + guint32 state; + + if (!priv->enabled) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because not enabled", + nm_device_get_iface (dev)); + return FALSE; + } + + sup_iface = priv->supplicant.iface; + if (!sup_iface) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because supplicant not running", + nm_device_get_iface (dev)); + return FALSE; + } + + state = nm_supplicant_interface_get_state (sup_iface); + if (state != NM_SUPPLICANT_INTERFACE_STATE_READY) { + nm_log_dbg (LOGD_WIFI, "(%s): not available because supplicant interface not ready", + nm_device_get_iface (dev)); + return FALSE; + } + + return TRUE; +} + +static NMConnection * +real_get_best_auto_connection (NMDevice *dev, + GSList *connections, + char **specific_object) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *iter, *ap_iter; + + for (iter = connections; iter; iter = g_slist_next (iter)) { + NMConnection *connection = NM_CONNECTION (iter->data); + NMSettingConnection *s_con; + NMSettingWireless *s_wireless; + const GByteArray *mac; + NMSettingIP4Config *s_ip4; + const char *method = NULL; + + s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION); + if (s_con == NULL) + continue; + if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME)) + continue; + if (!nm_setting_connection_get_autoconnect (s_con)) + continue; + + s_wireless = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + if (!s_wireless) + continue; + + mac = nm_setting_wireless_get_mac_address (s_wireless); + if (mac && memcmp (mac->data, priv->hw_addr.ether_addr_octet, ETH_ALEN)) + continue; + + /* Use the connection if it's a shared connection */ + s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (s_ip4) + method = nm_setting_ip4_config_get_method (s_ip4); + + if (s_ip4 && !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + return connection; + + for (ap_iter = priv->ap_list; ap_iter; ap_iter = g_slist_next (ap_iter)) { + NMAccessPoint *ap = NM_AP (ap_iter->data); + + if (nm_ap_check_compatible (ap, connection)) { + /* All good; connection is usable */ + *specific_object = (char *) nm_ap_get_dbus_path (ap); + return connection; + } + } + } + return NULL; +} + +/* + * nm_device_wifi_get_address + * + * Get a device's hardware address + * + */ +void +nm_device_wifi_get_address (NMDeviceWifi *self, + struct ether_addr *addr) +{ + NMDeviceWifiPrivate *priv; + + g_return_if_fail (self != NULL); + g_return_if_fail (addr != NULL); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + memcpy (addr, &(priv->hw_addr), sizeof (struct ether_addr)); +} + +static void +nm_device_wifi_ap_list_print (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList * elt; + int i = 0; + + g_return_if_fail (NM_IS_DEVICE_WIFI (self)); + + nm_log_dbg (LOGD_WIFI_SCAN, "Current AP list:"); + for (elt = priv->ap_list; elt; elt = g_slist_next (elt), i++) { + NMAccessPoint * ap = NM_AP (elt->data); + nm_ap_print_self (ap, "AP: "); + } + nm_log_dbg (LOGD_WIFI_SCAN, "Current AP list: done"); +} + +static gboolean +impl_device_get_access_points (NMDeviceWifi *self, + GPtrArray **aps, + GError **err) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GSList *elt; + + *aps = g_ptr_array_new (); + + for (elt = priv->ap_list; elt; elt = g_slist_next (elt)) { + NMAccessPoint * ap = NM_AP (elt->data); + + if (nm_ap_get_ssid (ap)) + g_ptr_array_add (*aps, g_strdup (nm_ap_get_dbus_path (ap))); + } + return TRUE; +} + +/* + * nm_device_get_mode + * + * Get managed/infrastructure/adhoc mode on a device + * + */ +NM80211Mode +nm_device_wifi_get_mode (NMDeviceWifi *self) +{ + int fd; + NM80211Mode mode = NM_802_11_MODE_UNKNOWN; + const char *iface; + struct iwreq wrq; + + g_return_val_if_fail (self != NULL, NM_802_11_MODE_UNKNOWN); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + goto out; + + memset (&wrq, 0, sizeof (struct iwreq)); + iface = nm_device_get_iface (NM_DEVICE (self)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + + if (ioctl (fd, SIOCGIWMODE, &wrq) == 0) { + switch (wrq.u.mode) { + case IW_MODE_ADHOC: + mode = NM_802_11_MODE_ADHOC; + break; + case IW_MODE_INFRA: + mode = NM_802_11_MODE_INFRA; + break; + default: + break; + } + } else { + if (errno != ENODEV) { + nm_log_warn (LOGD_HW | LOGD_WIFI, + "(%s): error %d getting card mode", + iface, strerror (errno)); + } + } + close (fd); + +out: + return mode; +} + + +/* + * nm_device_wifi_set_mode + * + * Set managed/infrastructure/adhoc mode on a device + * + */ +gboolean +nm_device_wifi_set_mode (NMDeviceWifi *self, const NM80211Mode mode) +{ + int fd; + const char *iface; + gboolean success = FALSE; + struct iwreq wrq; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail ((mode == NM_802_11_MODE_INFRA) || (mode == NM_802_11_MODE_ADHOC), FALSE); + + if (nm_device_wifi_get_mode (self) == mode) + return TRUE; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + goto out; + + memset (&wrq, 0, sizeof (struct iwreq)); + switch (mode) { + case NM_802_11_MODE_ADHOC: + wrq.u.mode = IW_MODE_ADHOC; + break; + case NM_802_11_MODE_INFRA: + wrq.u.mode = IW_MODE_INFRA; + break; + default: + goto out; + } + + iface = nm_device_get_iface (NM_DEVICE (self)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + + if (ioctl (fd, SIOCSIWMODE, &wrq) < 0) { + if (errno != ENODEV) { + nm_log_err (LOGD_HW | LOGD_WIFI, + "(%s): error setting mode %d", + iface, mode, strerror (errno)); + } + } else + success = TRUE; + close (fd); + +out: + return success; +} + + +/* + * nm_device_wifi_get_frequency + * + * Get current frequency + * + */ +static guint32 +nm_device_wifi_get_frequency (NMDeviceWifi *self) +{ + int fd; + guint32 freq = 0; + const char *iface; + struct iwreq wrq; + + g_return_val_if_fail (self != NULL, 0); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return 0; + + memset (&wrq, 0, sizeof (struct iwreq)); + iface = nm_device_get_iface (NM_DEVICE (self)); + strncpy (wrq.ifr_name, iface, IFNAMSIZ); + + if (ioctl (fd, SIOCGIWFREQ, &wrq) < 0) { + nm_log_warn (LOGD_HW | LOGD_WIFI, + "(%s): error getting frequency: %s", + iface, strerror (errno)); + } else + freq = iw_freq_to_uint32 (&wrq.u.freq); + + close (fd); + return freq; +} + +/* + * wireless_stats_to_percent + * + * Convert an iw_stats structure from a scan or the card into + * a magical signal strength percentage. + * + */ +static int +wireless_qual_to_percent (const struct iw_quality *qual, + const struct iw_quality *max_qual) +{ + int percent = -1; + int level_percent = -1; + + g_return_val_if_fail (qual != NULL, -1); + g_return_val_if_fail (max_qual != NULL, -1); + + nm_log_dbg (LOGD_WIFI, + "QL: qual %d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X ** MAX: qual %d/%u/0x%X, level %d/%u/0x%X, noise %d/%u/0x%X, updated: 0x%X", + (__s8) qual->qual, qual->qual, qual->qual, + (__s8) qual->level, qual->level, qual->level, + (__s8) qual->noise, qual->noise, qual->noise, + qual->updated, + (__s8) max_qual->qual, max_qual->qual, max_qual->qual, + (__s8) max_qual->level, max_qual->level, max_qual->level, + (__s8) max_qual->noise, max_qual->noise, max_qual->noise, + max_qual->updated); + + /* Try using the card's idea of the signal quality first as long as it tells us what the max quality is. + * Drivers that fill in quality values MUST treat them as percentages, ie the "Link Quality" MUST be + * bounded by 0 and max_qual->qual, and MUST change in a linear fashion. Within those bounds, drivers + * are free to use whatever they want to calculate "Link Quality". + */ + if ((max_qual->qual != 0) && !(max_qual->updated & IW_QUAL_QUAL_INVALID) && !(qual->updated & IW_QUAL_QUAL_INVALID)) + percent = (int)(100 * ((double)qual->qual / (double)max_qual->qual)); + + /* If the driver doesn't specify a complete and valid quality, we have two options: + * + * 1) dBm: driver must specify max_qual->level = 0, and have valid values for + * qual->level and (qual->noise OR max_qual->noise) + * 2) raw RSSI: driver must specify max_qual->level > 0, and have valid values for + * qual->level and max_qual->level + * + * This is the WEXT spec. If this interpretation is wrong, I'll fix it. Otherwise, + * If drivers don't conform to it, they are wrong and need to be fixed. + */ + + if ( (max_qual->level == 0) && !(max_qual->updated & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level == 0 */ + && !(qual->updated & IW_QUAL_LEVEL_INVALID) /* Must have valid qual->level */ + && ( ((max_qual->noise > 0) && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) /* Must have valid max_qual->noise */ + || ((qual->noise > 0) && !(qual->updated & IW_QUAL_NOISE_INVALID))) /* OR valid qual->noise */ + ) { + /* Absolute power values (dBm) */ + + /* Reasonable fallbacks for dumb drivers that don't specify either level. */ + #define FALLBACK_NOISE_FLOOR_DBM -90 + #define FALLBACK_SIGNAL_MAX_DBM -20 + int max_level = FALLBACK_SIGNAL_MAX_DBM; + int noise = FALLBACK_NOISE_FLOOR_DBM; + int level = qual->level - 0x100; + + level = CLAMP (level, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM); + + if ((qual->noise > 0) && !(qual->updated & IW_QUAL_NOISE_INVALID)) + noise = qual->noise - 0x100; + else if ((max_qual->noise > 0) && !(max_qual->updated & IW_QUAL_NOISE_INVALID)) + noise = max_qual->noise - 0x100; + noise = CLAMP (noise, FALLBACK_NOISE_FLOOR_DBM, FALLBACK_SIGNAL_MAX_DBM); + + /* A sort of signal-to-noise ratio calculation */ + level_percent = (int)(100 - 70 *( + ((double)max_level - (double)level) / + ((double)max_level - (double)noise))); + nm_log_dbg (LOGD_WIFI, "QL1: level_percent is %d. max_level %d, level %d, noise_floor %d.", + level_percent, max_level, level, noise); + } else if ( (max_qual->level != 0) + && !(max_qual->updated & IW_QUAL_LEVEL_INVALID) /* Valid max_qual->level as upper bound */ + && !(qual->updated & IW_QUAL_LEVEL_INVALID)) { + /* Relative power values (RSSI) */ + + int level = qual->level; + + /* Signal level is relavtive (0 -> max_qual->level) */ + level = CLAMP (level, 0, max_qual->level); + level_percent = (int)(100 * ((double)level / (double)max_qual->level)); + nm_log_dbg (LOGD_WIFI, "QL2: level_percent is %d. max_level %d, level %d.", + level_percent, max_qual->level, level); + } else if (percent == -1) { + nm_log_dbg (LOGD_WIFI, "QL: Could not get quality %% value from driver. Driver is probably buggy."); + } + + /* If the quality percent was 0 or doesn't exist, then try to use signal levels instead */ + if ((percent < 1) && (level_percent >= 0)) + percent = level_percent; + + nm_log_dbg (LOGD_WIFI, "QL: Final quality percent is %d (%d).", + percent, CLAMP (percent, 0, 100)); + return (CLAMP (percent, 0, 100)); +} + + +/* + * nm_device_wifi_get_ssid + * + * If a device is wireless, return the ssid that it is attempting + * to use. + */ +const GByteArray * +nm_device_wifi_get_ssid (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + int sk; + struct iwreq wrq; + char ssid[IW_ESSID_MAX_SIZE + 2]; + guint32 len; + + g_return_val_if_fail (self != NULL, NULL); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + sk = socket (AF_INET, SOCK_DGRAM, 0); + if (!sk) { + nm_log_err (LOGD_HW, "couldn't create socket: %d.", errno); + return NULL; + } + + memset (ssid, 0, sizeof (ssid)); + wrq.u.essid.pointer = (caddr_t) &ssid; + wrq.u.essid.length = sizeof (ssid); + wrq.u.essid.flags = 0; + strncpy (wrq.ifr_name, nm_device_get_iface (NM_DEVICE (self)), IFNAMSIZ); + + if (ioctl (sk, SIOCGIWESSID, &wrq) < 0) { + nm_log_err (LOGD_HW | LOGD_WIFI, "(%s): couldn't get SSID: %d", + nm_device_get_iface (NM_DEVICE (self)), errno); + goto out; + } + + if (priv->ssid) { + g_byte_array_free (priv->ssid, TRUE); + priv->ssid = NULL; + } + + len = wrq.u.essid.length; + if (!nm_utils_is_empty_ssid ((guint8 *) ssid, len)) { + /* Some drivers include nul termination in the SSID, so let's + * remove it here before further processing. WE-21 changes this + * to explicitly require the length _not_ to include nul + * termination. */ + if (len > 0 && ssid[len - 1] == '\0' && priv->we_version < 21) + len--; + + priv->ssid = g_byte_array_sized_new (len); + g_byte_array_append (priv->ssid, (const guint8 *) ssid, len); + } + +out: + close (sk); + return priv->ssid; +} + + +/* + * nm_device_wifi_get_bitrate + * + * For wireless devices, get the bitrate to broadcast/receive at. + * Returned value is rate in Kb/s. + * + */ +static guint32 +nm_device_wifi_get_bitrate (NMDeviceWifi *self) +{ + int err = -1, fd; + struct iwreq wrq; + + g_return_val_if_fail (self != NULL, 0); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return 0; + + memset (&wrq, 0, sizeof (wrq)); + strncpy (wrq.ifr_name, nm_device_get_iface (NM_DEVICE (self)), IFNAMSIZ); + err = ioctl (fd, SIOCGIWRATE, &wrq); + close (fd); + + return ((err == 0) ? wrq.u.bitrate.value / 1000 : 0); +} + +/* + * nm_device_get_bssid + * + * If a device is wireless, get the access point's ethernet address + * that the card is associated with. + */ +void +nm_device_wifi_get_bssid (NMDeviceWifi *self, + struct ether_addr *bssid) +{ + int fd; + struct iwreq wrq; + + g_return_if_fail (self != NULL); + g_return_if_fail (bssid != NULL); + + memset (bssid, 0, sizeof (struct ether_addr)); + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_WIFI, "failed to open control socket."); + return; + } + + memset (&wrq, 0, sizeof (wrq)); + strncpy (wrq.ifr_name, nm_device_get_iface (NM_DEVICE (self)), IFNAMSIZ); + if (ioctl (fd, SIOCGIWAP, &wrq) == 0) + memcpy (bssid->ether_addr_octet, &(wrq.u.ap_addr.sa_data), ETH_ALEN); + + close (fd); +} + + +static gboolean +scanning_allowed (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + guint32 sup_state; + NMActRequest *req; + + g_return_val_if_fail (priv->supplicant.iface != NULL, FALSE); + + 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: + /* Don't scan when unusable or activating */ + return FALSE; + case NM_DEVICE_STATE_DISCONNECTED: + case NM_DEVICE_STATE_FAILED: + /* Can always scan when disconnected */ + return TRUE; + case NM_DEVICE_STATE_ACTIVATED: + /* Need to do further checks when activated */ + break; + } + + /* Don't scan if the supplicant is busy */ + sup_state = nm_supplicant_interface_get_connection_state (priv->supplicant.iface); + if ( sup_state == NM_SUPPLICANT_INTERFACE_CON_STATE_ASSOCIATING + || sup_state == NM_SUPPLICANT_INTERFACE_CON_STATE_ASSOCIATED + || sup_state == NM_SUPPLICANT_INTERFACE_CON_STATE_4WAY_HANDSHAKE + || sup_state == NM_SUPPLICANT_INTERFACE_CON_STATE_GROUP_HANDSHAKE + || nm_supplicant_interface_get_scanning (priv->supplicant.iface)) + return FALSE; + + req = nm_device_get_act_request (NM_DEVICE (self)); + if (req) { + NMConnection *connection; + NMSettingIP4Config *s_ip4; + NMSettingWireless *s_wifi; + const char *ip4_method = NULL; + const GByteArray *bssid; + + /* Don't scan when a shared connection is active; it makes drivers mad */ + connection = nm_act_request_get_connection (req); + s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (s_ip4) + ip4_method = nm_setting_ip4_config_get_method (s_ip4); + + if (s_ip4 && !strcmp (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) + return FALSE; + + /* Don't scan when the connection is locked to a specifc AP, since + * intra-ESS roaming (which requires periodic scanning) isn't being + * used due to the specific AP lock. (bgo #513820) + */ + s_wifi = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + g_assert (s_wifi); + bssid = nm_setting_wireless_get_bssid (s_wifi); + if (bssid && bssid->len == ETH_ALEN) + return FALSE; + } + + return TRUE; +} + +static gboolean +scanning_allowed_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer data) +{ + if (!g_value_get_boolean (handler_return)) + g_value_set_boolean (return_accu, FALSE); + return TRUE; +} + +static gboolean +check_scanning_allowed (NMDeviceWifi *self) +{ + GValue instance = { 0, }; + GValue retval = { 0, }; + + g_value_init (&instance, G_TYPE_OBJECT); + g_value_take_object (&instance, self); + + g_value_init (&retval, G_TYPE_BOOLEAN); + g_value_set_boolean (&retval, TRUE); + + /* Use g_signal_emitv() rather than g_signal_emit() to avoid the return + * value being changed if no handlers are connected */ + g_signal_emitv (&instance, signals[SCANNING_ALLOWED], 0, &retval); + + return g_value_get_boolean (&retval); +} + +static gboolean +request_wireless_scan (gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (user_data); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gboolean backoff = FALSE; + + if (check_scanning_allowed (self)) { + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scanning requested", + nm_device_get_iface (NM_DEVICE (self))); + + if (nm_supplicant_interface_request_scan (priv->supplicant.iface)) { + /* success */ + backoff = TRUE; + } + } else { + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scan requested but not allowed at this time", + nm_device_get_iface (NM_DEVICE (self))); + } + + priv->pending_scan_id = 0; + schedule_scan (self, backoff); + return FALSE; +} + + +/* + * schedule_scan + * + * Schedule a wireless scan. + * + */ +static void +schedule_scan (NMDeviceWifi *self, gboolean backoff) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + GTimeVal now; + + g_get_current_time (&now); + + /* Cancel the pending scan if it would happen later than (now + the scan_interval) */ + if (priv->pending_scan_id) { + if (now.tv_sec + priv->scan_interval < priv->scheduled_scan_time) + cancel_pending_scan (self); + } + + if (!priv->pending_scan_id) { + guint factor = 2, next_scan = priv->scan_interval; + + if ( nm_device_is_activating (NM_DEVICE (self)) + || (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED)) + factor = 1; + + priv->pending_scan_id = g_timeout_add_seconds (next_scan, + request_wireless_scan, + self); + + priv->scheduled_scan_time = now.tv_sec + priv->scan_interval; + if (backoff && (priv->scan_interval < (SCAN_INTERVAL_MAX / factor))) { + priv->scan_interval += (SCAN_INTERVAL_STEP / factor); + /* Ensure the scan interval will never be less than 20s... */ + priv->scan_interval = MAX(priv->scan_interval, SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP); + /* ... or more than 120s */ + priv->scan_interval = MIN(priv->scan_interval, SCAN_INTERVAL_MAX); + } else if (!backoff && (priv->scan_interval == 0)) { + /* Invalid combination; would cause continual rescheduling of + * the scan and hog CPU. Reset to something minimally sane. + */ + priv->scan_interval = 5; + } + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scheduled scan in %d seconds (interval now %d seconds)", + nm_device_get_iface (NM_DEVICE (self)), + next_scan, + priv->scan_interval); + + } +} + + +static void +cancel_pending_scan (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->pending_scan_id) { + g_source_remove (priv->pending_scan_id); + priv->pending_scan_id = 0; + } +} + + +static void +supplicant_iface_scan_request_result_cb (NMSupplicantInterface *iface, + gboolean success, + NMDeviceWifi *self) +{ + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scan request %s", + nm_device_get_iface (NM_DEVICE (self)), + success ? "successful" : "failed"); + + if (check_scanning_allowed (self)) + schedule_scan (self, TRUE); +} + +static void +supplicant_iface_scan_results_cb (NMSupplicantInterface *iface, + guint32 num_results, + NMDeviceWifi *self) +{ + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scan results available (%d APs found)", + nm_device_get_iface (NM_DEVICE (self)), + num_results); + if (num_results == 0) { + /* ensure that old APs get culled, which otherwise only + * happens when there are actual scan results to process. + */ + cull_scan_list (self); + nm_device_wifi_ap_list_print (self); + } +} + +static gboolean +is_encrypted (guint32 flags, guint32 wpa_flags, guint32 rsn_flags) +{ + if (flags & NM_802_11_AP_FLAGS_PRIVACY) + return TRUE; + if (wpa_flags & (NM_802_11_AP_SEC_KEY_MGMT_PSK | NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return TRUE; + if (rsn_flags & (NM_802_11_AP_SEC_KEY_MGMT_PSK | NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return TRUE; + + return FALSE; +} + +/* + * ap_auth_enforced + * + * Checks whether or not there is an encryption key present for + * this connection, and whether or not the authentication method + * in use will result in an authentication rejection if the key + * is wrong. For example, Ad Hoc mode networks don't have a + * master node and therefore nothing exists to reject the station. + * Similarly, Open System WEP access points don't reject a station + * when the key is wrong. Shared Key WEP access points will. + * + */ +static gboolean +ap_auth_enforced (NMConnection *connection, + NMAccessPoint *ap, + gboolean *encrypted) +{ + guint32 flags, wpa_flags, rsn_flags; + gboolean enforced = FALSE; + + g_return_val_if_fail (NM_IS_AP (ap), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (encrypted != NULL, FALSE); + + flags = nm_ap_get_flags (ap); + wpa_flags = nm_ap_get_wpa_flags (ap); + rsn_flags = nm_ap_get_rsn_flags (ap); + + if (nm_ap_get_mode (ap) == NM_802_11_MODE_ADHOC) + goto out; + + /* Static WEP */ + if ( (flags & NM_802_11_AP_FLAGS_PRIVACY) + && (wpa_flags == NM_802_11_AP_SEC_NONE) + && (rsn_flags == NM_802_11_AP_SEC_NONE)) { + NMSettingWirelessSecurity *s_wireless_sec; + const char *auth_alg; + + /* No way to tell if the key is wrong with Open System + * auth mode in WEP. Auth is not enforced like Shared Key. + */ + s_wireless_sec = (NMSettingWirelessSecurity *) nm_connection_get_setting (connection, + NM_TYPE_SETTING_WIRELESS_SECURITY); + if (s_wireless_sec) { + auth_alg = nm_setting_wireless_security_get_auth_alg (s_wireless_sec); + if (!auth_alg || !strcmp (auth_alg, "open")) + goto out; + } + + enforced = TRUE; + } else if (wpa_flags != NM_802_11_AP_SEC_NONE) { /* WPA */ + enforced = TRUE; + } else if (rsn_flags != NM_802_11_AP_SEC_NONE) { /* WPA2 */ + enforced = TRUE; + } + +out: + *encrypted = is_encrypted (flags, wpa_flags, rsn_flags); + return enforced; +} + + +/**************************************************************************** + * WPA Supplicant control stuff + * + */ + +/* + * merge_scanned_ap + * + * If there is already an entry that matches the BSSID and ESSID of the + * AP to merge, replace that entry with the scanned AP. Otherwise, add + * the scanned AP to the list. + * + * TODO: possibly need to differentiate entries based on security too; i.e. if + * there are two scan results with the same BSSID and SSID but different + * security options? + * + */ +static void +merge_scanned_ap (NMDeviceWifi *self, + NMAccessPoint *merge_ap) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *found_ap = NULL; + const GByteArray *ssid; + gboolean strict_match = TRUE; + NMAccessPoint *current_ap = NULL; + + /* Let the manager try to fill in the SSID from seen-bssids lists + * if it can + */ + ssid = nm_ap_get_ssid (merge_ap); + if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) { + g_signal_emit (self, signals[HIDDEN_AP_FOUND], 0, merge_ap); + nm_ap_set_broadcast (merge_ap, FALSE); + } + + /* If the incoming scan result matches the hidden AP that NM is currently + * connected to but hasn't been seen in the scan list yet, don't use + * strict matching. Because the capabilities of the fake AP have to be + * constructed from the NMConnection of the activation request, they won't + * always be the same as the capabilities of the real AP from the scan. + */ + current_ap = nm_device_wifi_get_activation_ap (self); + if (current_ap && nm_ap_get_fake (current_ap)) + strict_match = FALSE; + + found_ap = nm_ap_match_in_list (merge_ap, priv->ap_list, strict_match); + if (found_ap) { + nm_ap_set_flags (found_ap, nm_ap_get_flags (merge_ap)); + nm_ap_set_wpa_flags (found_ap, nm_ap_get_wpa_flags (merge_ap)); + nm_ap_set_rsn_flags (found_ap, nm_ap_get_rsn_flags (merge_ap)); + nm_ap_set_strength (found_ap, nm_ap_get_strength (merge_ap)); + nm_ap_set_last_seen (found_ap, nm_ap_get_last_seen (merge_ap)); + nm_ap_set_broadcast (found_ap, nm_ap_get_broadcast (merge_ap)); + nm_ap_set_freq (found_ap, nm_ap_get_freq (merge_ap)); + nm_ap_set_max_bitrate (found_ap, nm_ap_get_max_bitrate (merge_ap)); + + /* If the AP is noticed in a scan, it's automatically no longer + * fake, since it clearly exists somewhere. + */ + nm_ap_set_fake (found_ap, FALSE); + } else { + /* New entry in the list */ + // FIXME: figure out if reference counts are correct here for AP objects + g_object_ref (merge_ap); + priv->ap_list = g_slist_prepend (priv->ap_list, merge_ap); + nm_ap_export_to_dbus (merge_ap); + g_signal_emit (self, signals[ACCESS_POINT_ADDED], 0, merge_ap); + } +} + +static void +cull_scan_list (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + GTimeVal cur_time; + GSList *outdated_list = NULL; + GSList *elt; + NMActRequest *req; + const char *cur_ap_path = NULL; + guint32 removed = 0, total = 0; + + g_return_if_fail (self != NULL); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + g_get_current_time (&cur_time); + + req = nm_device_get_act_request (NM_DEVICE (self)); + if (req) + cur_ap_path = nm_act_request_get_specific_object (req); + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): checking scan list for outdated APs", + nm_device_get_iface (NM_DEVICE (self))); + + /* Walk the access point list and remove any access points older than + * three times the inactive scan interval. + */ + for (elt = priv->ap_list; elt; elt = g_slist_next (elt), total++) { + NMAccessPoint * ap = NM_AP (elt->data); + const glong ap_time = nm_ap_get_last_seen (ap); + gboolean keep = FALSE; + const guint prune_interval_s = SCAN_INTERVAL_MAX * 3; + + /* Don't ever prune the AP we're currently associated with */ + if (cur_ap_path && !strcmp (cur_ap_path, nm_ap_get_dbus_path (ap))) + keep = TRUE; + if (nm_ap_get_fake (ap)) + keep = TRUE; + + if (!keep && (ap_time + prune_interval_s < cur_time.tv_sec)) + outdated_list = g_slist_append (outdated_list, ap); + } + + /* Remove outdated APs */ + for (elt = outdated_list; elt; elt = g_slist_next (elt)) { + NMAccessPoint *outdated_ap = NM_AP (elt->data); + const struct ether_addr *bssid; + const GByteArray *ssid; + + bssid = nm_ap_get_address (outdated_ap); + ssid = nm_ap_get_ssid (outdated_ap); + nm_log_dbg (LOGD_WIFI_SCAN, + " removing %02x:%02x:%02x:%02x:%02x:%02x (%s%s%s)", + bssid->ether_addr_octet[0], bssid->ether_addr_octet[1], + bssid->ether_addr_octet[2], bssid->ether_addr_octet[3], + bssid->ether_addr_octet[4], bssid->ether_addr_octet[5], + ssid ? "'" : "", + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)", + ssid ? "'" : ""); + + access_point_removed (self, outdated_ap); + priv->ap_list = g_slist_remove (priv->ap_list, outdated_ap); + g_object_unref (outdated_ap); + removed++; + } + g_slist_free (outdated_list); + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): removed %d APs (of %d)", + nm_device_get_iface (NM_DEVICE (self)), + removed, total); +} + +#define SET_QUALITY_MEMBER(qual_item, lc_member, uc_member) \ + if (lc_member != -1) { \ + qual_item.lc_member = lc_member; \ + qual_item.updated |= IW_QUAL_##uc_member##_UPDATED; \ + } else { \ + qual_item.updated |= IW_QUAL_##uc_member##_INVALID; \ + } + +static void +set_ap_strength_from_properties (NMDeviceWifi *self, + NMAccessPoint *ap, + GHashTable *properties) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + int qual, level, noise; + struct iw_quality quality; + GValue *value; + gint8 strength; + + value = (GValue *) g_hash_table_lookup (properties, "quality"); + qual = value ? g_value_get_int (value) : -1; + + value = (GValue *) g_hash_table_lookup (properties, "level"); + level = value ? g_value_get_int (value) : -1; + + value = (GValue *) g_hash_table_lookup (properties, "noise"); + noise = value ? g_value_get_int (value) : -1; + + /* Calculate and set the AP's signal quality */ + memset (&quality, 0, sizeof (struct iw_quality)); + SET_QUALITY_MEMBER (quality, qual, QUAL); + SET_QUALITY_MEMBER (quality, level, LEVEL); + SET_QUALITY_MEMBER (quality, noise, NOISE); + + strength = wireless_qual_to_percent (&quality, &priv->max_qual); + nm_ap_set_strength (ap, strength); +} + +static void +supplicant_iface_scanned_ap_cb (NMSupplicantInterface *iface, + GHashTable *properties, + NMDeviceWifi *self) +{ + NMDeviceState state; + NMAccessPoint *ap; + + g_return_if_fail (self != NULL); + g_return_if_fail (properties != NULL); + g_return_if_fail (iface != NULL); + + /* Ignore new APs when unavailable or unamnaged */ + state = nm_device_get_state (NM_DEVICE (self)); + if (state <= NM_DEVICE_STATE_UNAVAILABLE) + return; + + ap = nm_ap_new_from_properties (properties); + if (ap) { + set_ap_strength_from_properties (self, ap, properties); + + nm_ap_print_self (ap, "AP: "); + + /* Add the AP to the device's AP list */ + merge_scanned_ap (self, ap); + g_object_unref (ap); + + /* Remove outdated access points */ + cull_scan_list (self); + + nm_device_wifi_ap_list_print (self); + } else { + nm_log_warn (LOGD_WIFI_SCAN, "(%s): invalid AP properties received", + nm_device_get_iface (NM_DEVICE (self))); + } +} + + +static void +cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + remove_supplicant_interface_error_handler (self); + remove_supplicant_timeouts (self); + if (disconnect && priv->supplicant.iface) + nm_supplicant_interface_disconnect (priv->supplicant.iface); +} + + +static void +remove_link_timeout (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + + g_return_if_fail (self != NULL); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->link_timeout_id) { + g_source_remove (priv->link_timeout_id); + priv->link_timeout_id = 0; + } +} + + +/* + * link_timeout_cb + * + * Called when the link to the access point has been down for a specified + * period of time. + */ +static gboolean +link_timeout_cb (gpointer user_data) +{ + NMDevice * dev = NM_DEVICE (user_data); + NMDeviceWifi * self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMActRequest * req = NULL; + NMAccessPoint * ap = NULL; + NMConnection * connection; + const char * setting_name; + gboolean auth_enforced, encrypted = FALSE; + + g_assert (dev); + + priv->link_timeout_id = 0; + + req = nm_device_get_act_request (dev); + ap = nm_device_wifi_get_activation_ap (self); + if (req == NULL || ap == NULL) { + /* shouldn't ever happen */ + nm_log_err (LOGD_WIFI, "couldn't get activation request or activation AP."); + if (nm_device_is_activating (dev)) { + cleanup_association_attempt (self, TRUE); + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NONE); + } + return FALSE; + } + + /* Disconnect event while activated; the supplicant hasn't been able + * to reassociate within the timeout period, so the connection must + * fail. + */ + if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED) { + nm_device_state_changed (dev, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + return FALSE; + } + + /* Disconnect event during initial authentication and credentials + * ARE checked - we are likely to have wrong key. Ask the user for + * another one. + */ + if (nm_device_get_state (dev) != NM_DEVICE_STATE_CONFIG) + goto time_out; + + connection = nm_act_request_get_connection (req); + if (!connection) + goto time_out; + + auth_enforced = ap_auth_enforced (connection, ap, &encrypted); + if (!encrypted || !auth_enforced) + goto time_out; + + /* Drivers are still just too crappy, and emit too many disassociation + * events during connection. So for now, just let the driver and supplicant + * keep trying to associate, and don't ask for new secrets when we get + * disconnected during association. + */ + if (0) { + nm_connection_clear_secrets (connection); + setting_name = nm_connection_need_secrets (connection, NULL); + if (!setting_name) + goto time_out; + + /* Association/authentication failed during association, probably have a + * bad encryption key and the authenticating entity (AP, RADIUS server, etc) + * denied the association due to bad credentials. + */ + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): disconnected during association," + " asking for new key.", nm_device_get_iface (dev)); + cleanup_association_attempt (self, TRUE); + nm_device_state_changed (dev, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + nm_act_request_get_secrets (req, + setting_name, + TRUE, + SECRETS_CALLER_WIFI, + NULL, + NULL); + + return FALSE; + } + +time_out: + nm_log_warn (LOGD_WIFI, "(%s): link timed out.", nm_device_get_iface (dev)); + return FALSE; +} + +static gboolean +schedule_state_handler (NMDeviceWifi *self, + GSourceFunc handler, + guint32 new_state, + guint32 old_state, + gboolean mgr_task) +{ + NMDeviceWifiPrivate *priv; + SupplicantStateTask *task; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (handler != NULL, FALSE); + + if (new_state == old_state) + return TRUE; + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + task = g_slice_new0 (SupplicantStateTask); + if (!task) { + nm_log_err (LOGD_WIFI, "Not enough memory to process supplicant manager state change."); + return FALSE; + } + + task->self = self; + task->new_state = new_state; + task->old_state = old_state; + task->mgr_task = mgr_task; + + task->source_id = g_idle_add (handler, task); + if (mgr_task) + priv->supplicant.mgr_tasks = g_slist_append (priv->supplicant.mgr_tasks, task); + else + priv->supplicant.iface_tasks = g_slist_append (priv->supplicant.iface_tasks, task); + + return TRUE; +} + +static gboolean +supplicant_iface_state_cb_handler (gpointer user_data) +{ + SupplicantStateTask *task = (SupplicantStateTask *) user_data; + NMDeviceWifi *self; + NMDeviceWifiPrivate *priv; + + g_return_val_if_fail (task != NULL, FALSE); + + self = task->self; + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + nm_log_info (LOGD_WIFI, "(%s): supplicant interface state: %s -> %s", + nm_device_get_iface (NM_DEVICE (self)), + nm_supplicant_interface_state_to_string (task->old_state), + nm_supplicant_interface_state_to_string (task->new_state)); + + if (task->new_state == NM_SUPPLICANT_INTERFACE_STATE_READY) { + priv->scan_interval = SCAN_INTERVAL_MIN; + + /* If the interface can now be activated because the supplicant is now + * available, transition to DISCONNECTED. + */ + if ( (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_UNAVAILABLE) + && nm_device_is_available (NM_DEVICE (self))) { + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE); + } + + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): supplicant ready, requesting initial scan", + nm_device_get_iface (NM_DEVICE (self))); + + /* Request a scan to get latest results */ + cancel_pending_scan (self); + request_wireless_scan (self); + } else if (task->new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) { + cleanup_association_attempt (self, FALSE); + supplicant_interface_release (self); + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } + + finish_supplicant_task (task, FALSE); + return FALSE; +} + + +static void +supplicant_iface_state_cb (NMSupplicantInterface * iface, + guint32 new_state, + guint32 old_state, + NMDeviceWifi *self) +{ + g_return_if_fail (self != NULL); + + schedule_state_handler (self, + supplicant_iface_state_cb_handler, + new_state, + old_state, + FALSE); +} + + +static gboolean +supplicant_iface_connection_state_cb_handler (gpointer user_data) +{ + SupplicantStateTask *task = (SupplicantStateTask *) user_data; + NMDeviceWifi *self = task->self; + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMDevice *dev = NM_DEVICE (self); + gboolean scanning; + + if (!nm_device_get_act_request (dev)) { + /* The device is not activating or already activated; do nothing. */ + goto out; + } + + nm_log_info (LOGD_WIFI, "(%s): supplicant connection state: %s -> %s", + nm_device_get_iface (dev), + nm_supplicant_interface_connection_state_to_string (task->old_state), + nm_supplicant_interface_connection_state_to_string (task->new_state)); + + scanning = nm_supplicant_interface_get_scanning (priv->supplicant.iface); + + if (task->new_state == NM_SUPPLICANT_INTERFACE_CON_STATE_COMPLETED) { + remove_supplicant_interface_error_handler (self); + remove_supplicant_timeouts (self); + + /* If this is the initial association during device activation, + * schedule the next activation stage. + */ + if (nm_device_get_state (dev) == NM_DEVICE_STATE_CONFIG) { + NMAccessPoint *ap = nm_device_wifi_get_activation_ap (self); + const GByteArray * ssid = nm_ap_get_ssid (ap); + + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless) Stage 2 of 5 (Device Configure) " + "successful. Connected to wireless network '%s'.", + nm_device_get_iface (dev), + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)"); + nm_device_activate_schedule_stage3_ip_config_start (dev); + } + } else if (task->new_state == NM_SUPPLICANT_INTERFACE_CON_STATE_DISCONNECTED) { + if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED || nm_device_is_activating (dev)) { + /* Start the link timeout so we allow some time for reauthentication, + * use a longer timeout if we are scanning since some cards take a + * while to scan. + */ + if (!priv->link_timeout_id) { + priv->link_timeout_id = g_timeout_add_seconds (scanning ? 30 : 15, + link_timeout_cb, self); + } + } + } + +out: + finish_supplicant_task (task, FALSE); + return FALSE; +} + + +static void +supplicant_iface_connection_state_cb (NMSupplicantInterface * iface, + guint32 new_state, + guint32 old_state, + NMDeviceWifi *self) +{ + g_return_if_fail (self != NULL); + + schedule_state_handler (self, + supplicant_iface_connection_state_cb_handler, + new_state, + old_state, + FALSE); +} + + +static gboolean +supplicant_mgr_state_cb_handler (gpointer user_data) +{ + SupplicantStateTask *task = (SupplicantStateTask *) user_data; + NMDeviceWifi *self; + NMDeviceWifiPrivate *priv; + NMDevice *dev; + NMDeviceState dev_state; + + g_return_val_if_fail (task != NULL, FALSE); + + self = task->self; + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + dev = NM_DEVICE (self); + + nm_log_info (LOGD_WIFI, "(%s): supplicant manager state: %s -> %s", + nm_device_get_iface (NM_DEVICE (self)), + nm_supplicant_manager_state_to_string (task->old_state), + nm_supplicant_manager_state_to_string (task->new_state)); + + /* If the supplicant went away, release the supplicant interface */ + if (task->new_state == NM_SUPPLICANT_MANAGER_STATE_DOWN) { + if (priv->supplicant.iface) { + cleanup_association_attempt (self, FALSE); + supplicant_interface_release (self); + } + + if (nm_device_get_state (dev) > NM_DEVICE_STATE_UNAVAILABLE) { + nm_device_state_changed (dev, NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } + } else if (task->new_state == NM_SUPPLICANT_MANAGER_STATE_IDLE) { + dev_state = nm_device_get_state (dev); + if ( priv->enabled + && !priv->supplicant.iface + && (dev_state >= NM_DEVICE_STATE_UNAVAILABLE)) { + /* request a supplicant interface from the supplicant manager */ + supplicant_interface_acquire (self); + + /* if wireless is enabled and we have a supplicant interface, + * we can transition to the DISCONNECTED state. + */ + if (priv->supplicant.iface) { + nm_device_state_changed (dev, NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_NONE); + } + } + } + + finish_supplicant_task (task, FALSE); + return FALSE; +} + +static void +supplicant_mgr_state_cb (NMSupplicantInterface * iface, + guint32 new_state, + guint32 old_state, + NMDeviceWifi *self) +{ + g_return_if_fail (self != NULL); + + schedule_state_handler (self, + supplicant_mgr_state_cb_handler, + new_state, + old_state, + TRUE); +} + +struct iface_con_error_cb_data { + NMDeviceWifi *self; + char *name; + char *message; +}; + +static gboolean +supplicant_iface_connection_error_cb_handler (gpointer user_data) +{ + NMDeviceWifi *self; + NMDeviceWifiPrivate *priv; + struct iface_con_error_cb_data * cb_data = (struct iface_con_error_cb_data *) user_data; + + g_return_val_if_fail (cb_data != NULL, FALSE); + + self = cb_data->self; + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (!nm_device_is_activating (NM_DEVICE (self))) + goto out; + + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): association request to the supplicant " + "failed: %s - %s", + nm_device_get_iface (NM_DEVICE (self)), + cb_data->name, + cb_data->message); + + cleanup_association_attempt (self, TRUE); + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + +out: + priv->supplicant.iface_con_error_cb_id = 0; + g_free (cb_data->name); + g_free (cb_data->message); + g_slice_free (struct iface_con_error_cb_data, cb_data); + return FALSE; +} + + +static void +supplicant_iface_connection_error_cb (NMSupplicantInterface * iface, + const char * name, + const char * message, + NMDeviceWifi * self) +{ + NMDeviceWifiPrivate *priv; + struct iface_con_error_cb_data *cb_data; + guint id; + + g_return_if_fail (self != NULL); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + cb_data = g_slice_new0 (struct iface_con_error_cb_data); + if (cb_data == NULL) { + nm_log_err (LOGD_WIFI, "Not enough memory to process supplicant connection error."); + return; + } + + cb_data->self = self; + cb_data->name = g_strdup (name); + cb_data->message = g_strdup (message); + + if (priv->supplicant.iface_con_error_cb_id) + g_source_remove (priv->supplicant.iface_con_error_cb_id); + + id = g_idle_add (supplicant_iface_connection_error_cb_handler, cb_data); + priv->supplicant.iface_con_error_cb_id = id; +} + +static void +supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface, + GParamSpec *pspec, + NMDeviceWifi *self) +{ + gboolean scanning; + + scanning = nm_supplicant_interface_get_scanning (iface); + nm_log_dbg (LOGD_WIFI_SCAN, "(%s): now %s", + nm_device_get_iface (NM_DEVICE (self)), + scanning ? "scanning" : "not scanning"); + + g_object_notify (G_OBJECT (self), "scanning"); +} + +static void +remove_supplicant_connection_timeout (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + + g_return_if_fail (self != NULL); + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + /* Remove any pending timeouts on the request */ + if (priv->supplicant.con_timeout_id) { + g_source_remove (priv->supplicant.con_timeout_id); + priv->supplicant.con_timeout_id = 0; + } +} + +static NMActStageReturn +handle_auth_or_fail (NMDeviceWifi *self, + NMActRequest *req, + gboolean new_secrets) +{ + const char *setting_name; + guint32 tries; + NMAccessPoint *ap; + NMConnection *connection; + + g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), NM_ACT_STAGE_RETURN_FAILURE); + + if (!req) { + req = nm_device_get_act_request (NM_DEVICE (self)); + g_assert (req); + } + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + ap = nm_device_wifi_get_activation_ap (self); + g_assert (ap); + + tries = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES)); + if (tries > 3) + return NM_ACT_STAGE_RETURN_FAILURE; + + nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE); + + nm_connection_clear_secrets (connection); + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + gboolean get_new; + + /* If the caller doesn't necessarily want completely new secrets, + * only ask for new secrets after the first failure. + */ + get_new = new_secrets ? TRUE : (tries ? TRUE : FALSE); + nm_act_request_get_secrets (req, + setting_name, + get_new, + SECRETS_CALLER_WIFI, + NULL, + NULL); + + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, GUINT_TO_POINTER (++tries)); + } else { + nm_log_warn (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets."); + } + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/* + * supplicant_connection_timeout_cb + * + * Called when the supplicant has been unable to connect to an access point + * within a specified period of time. + */ +static gboolean +supplicant_connection_timeout_cb (gpointer user_data) +{ + NMDevice * dev = NM_DEVICE (user_data); + NMDeviceWifi * self = NM_DEVICE_WIFI (user_data); + NMAccessPoint * ap; + NMActRequest * req; + gboolean auth_enforced = FALSE, encrypted = FALSE; + NMConnection *connection; + + cleanup_association_attempt (self, TRUE); + + if (!nm_device_is_activating (dev)) + return FALSE; + + /* Timed out waiting for authentication success; if the security in use + * does not require access point side authentication (Open System + * WEP, for example) then we are likely using the wrong authentication + * algorithm or key. Request new one from the user. + */ + + req = nm_device_get_act_request (dev); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + ap = nm_device_wifi_get_activation_ap (self); + g_assert (ap); + + auth_enforced = ap_auth_enforced (connection, ap, &encrypted); + if (!encrypted) { + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): association took too long, " + "failing activation.", + nm_device_get_iface (dev)); + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + } else { + /* Authentication failed, encryption key is probably bad */ + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): association took too long.", + nm_device_get_iface (dev)); + + if (handle_auth_or_fail (self, req, TRUE) == NM_ACT_STAGE_RETURN_POSTPONE) { + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): asking for new secrets", + nm_device_get_iface (dev)); + } else { + nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_NO_SECRETS); + } + } + + return FALSE; +} + + +static gboolean +start_supplicant_connection_timeout (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + guint id; + + g_return_val_if_fail (self != NULL, FALSE); + + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + /* Set up a timeout on the connection attempt to fail it after 25 seconds */ + id = g_timeout_add_seconds (25, supplicant_connection_timeout_cb, self); + if (id == 0) { + nm_log_err (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): couldn't start supplicant " + "timeout timer.", + nm_device_get_iface (NM_DEVICE (self))); + return FALSE; + } + priv->supplicant.con_timeout_id = id; + return TRUE; +} + + +static void +remove_supplicant_timeouts (NMDeviceWifi *self) +{ + g_return_if_fail (self != NULL); + + remove_supplicant_connection_timeout (self); + remove_link_timeout (self); +} + +static guint32 +find_supported_frequency (NMDeviceWifi *self, guint32 *freqs) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + int i; + + for (i = 0; i < priv->num_freqs; i++) { + while (*freqs) { + if (priv->freqs[i] == *freqs) + return *freqs; + freqs++; + } + } + + return 0; +} + +static NMSupplicantConfig * +build_supplicant_config (NMDeviceWifi *self, + NMConnection *connection, + NMAccessPoint *ap) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMSupplicantConfig *config = NULL; + NMSettingWireless *s_wireless; + NMSettingWirelessSecurity *s_wireless_sec; + guint32 adhoc_freq = 0; + + g_return_val_if_fail (self != NULL, NULL); + + s_wireless = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + g_return_val_if_fail (s_wireless != NULL, NULL); + + config = nm_supplicant_config_new (); + if (!config) + return NULL; + + /* Figure out the Ad-Hoc frequency to use if creating an adhoc network; if + * nothing was specified then pick something usable. + */ + if ((nm_ap_get_mode (ap) == NM_802_11_MODE_ADHOC) && nm_ap_get_user_created (ap)) { + const char *band = nm_setting_wireless_get_band (s_wireless); + + adhoc_freq = nm_ap_get_freq (ap); + if (!adhoc_freq) { + if (band && !strcmp (band, "a")) { + guint32 a_freqs[] = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0}; + adhoc_freq = find_supported_frequency (self, a_freqs); + } else { + guint32 bg_freqs[] = {2412, 2437, 2462, 2472, 0}; + adhoc_freq = find_supported_frequency (self, bg_freqs); + } + } + + if (!adhoc_freq) { + if (band && !strcmp (band, "a")) + adhoc_freq = 5180; + else + adhoc_freq = 2462; + } + } + + if (!nm_supplicant_config_add_setting_wireless (config, + s_wireless, + nm_ap_get_broadcast (ap), + adhoc_freq, + priv->has_scan_capa_ssid)) { + nm_log_err (LOGD_WIFI, "Couldn't add 802-11-wireless setting to supplicant config."); + goto error; + } + + s_wireless_sec = (NMSettingWirelessSecurity *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS_SECURITY); + if (s_wireless_sec) { + NMSetting8021x *s_8021x; + const char *con_path = nm_connection_get_path (connection); + + g_assert (con_path); + s_8021x = (NMSetting8021x *) nm_connection_get_setting (connection, NM_TYPE_SETTING_802_1X); + if (!nm_supplicant_config_add_setting_wireless_security (config, + s_wireless_sec, + s_8021x, + con_path)) { + nm_log_err (LOGD_WIFI, "Couldn't add 802-11-wireless-security setting to " + "supplicant config."); + goto error; + } + } else { + if (!nm_supplicant_config_add_no_security (config)) { + nm_log_err (LOGD_WIFI, "Couldn't add unsecured option to supplicant config."); + goto error; + } + } + + return config; + +error: + g_object_unref (config); + return NULL; +} + +/****************************************************************************/ + +static void +real_update_hw_address (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + struct ifreq req; + int fd; + + fd = socket (PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + nm_log_err (LOGD_HW, "could not open control socket."); + return; + } + + memset (&req, 0, sizeof (struct ifreq)); + strncpy (req.ifr_name, nm_device_get_iface (dev), IFNAMSIZ); + if (ioctl (fd, SIOCGIFHWADDR, &req) < 0) { + nm_log_err (LOGD_HW | LOGD_WIFI, "(%s) error getting hardware address: %d", + nm_device_get_iface (dev), errno); + goto out; + } + + if (memcmp (&priv->hw_addr, &req.ifr_hwaddr.sa_data, sizeof (struct ether_addr))) { + memcpy (&priv->hw_addr, &req.ifr_hwaddr.sa_data, sizeof (struct ether_addr)); + g_object_notify (G_OBJECT (dev), NM_DEVICE_WIFI_HW_ADDRESS); + } + +out: + close (fd); +} + + +static NMActStageReturn +real_act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *ap = NULL; + NMActRequest *req; + NMConnection *connection; + GSList *iter; + + /* If the user is trying to connect to an AP that NM doesn't yet know about + * (hidden network or something), create an fake AP from the security + * settings in the connection to use until the AP is recognized from the + * scan list, which should show up when the connection is successful. + */ + ap = nm_device_wifi_get_activation_ap (self); + if (ap) + goto done; + + req = nm_device_get_act_request (NM_DEVICE (self)); + g_return_val_if_fail (req != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + connection = nm_act_request_get_connection (req); + g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + /* Find a compatible AP in the scan list */ + for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) { + NMAccessPoint *candidate = NM_AP (iter->data); + + if (nm_ap_check_compatible (candidate, connection)) { + ap = candidate; + break; + } + } + + /* If no compatible AP was found, create a fake AP (network is likely + * hidden) and try to use that. + */ + if (!ap) { + ap = nm_ap_new_fake_from_connection (connection); + g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + switch (nm_ap_get_mode (ap)) { + case NM_802_11_MODE_ADHOC: + nm_ap_set_user_created (ap, TRUE); + break; + case NM_802_11_MODE_INFRA: + default: + nm_ap_set_broadcast (ap, FALSE); + break; + } + + priv->ap_list = g_slist_prepend (priv->ap_list, ap); + nm_ap_export_to_dbus (ap); + g_signal_emit (self, signals[ACCESS_POINT_ADDED], 0, ap); + } + + nm_act_request_set_specific_object (req, nm_ap_get_dbus_path (ap)); + +done: + set_current_ap (self, ap); + return NM_ACT_STAGE_RETURN_SUCCESS; +} + + +static void +real_connection_secrets_updated (NMDevice *dev, + NMConnection *connection, + GSList *updated_settings, + RequestSecretsCaller caller) +{ + NMActRequest *req; + gboolean valid = FALSE; + GSList *iter; + + g_return_if_fail (caller == SECRETS_CALLER_WIFI); + + if (nm_device_get_state (dev) != NM_DEVICE_STATE_NEED_AUTH) + return; + + for (iter = updated_settings; iter; iter = g_slist_next (iter)) { + const char *setting_name = (const char *) iter->data; + + if ( !strcmp (setting_name, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME) + || !strcmp (setting_name, NM_SETTING_802_1X_SETTING_NAME)) { + valid = TRUE; + } else { + nm_log_warn (LOGD_DEVICE, "Ignoring updated secrets for setting '%s'.", + setting_name); + } + } + + req = nm_device_get_act_request (dev); + g_assert (req); + + g_return_if_fail (nm_act_request_get_connection (req) == connection); + + nm_device_activate_schedule_stage1_device_prepare (dev); +} + +static NMActStageReturn +real_act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + const char *iface = nm_device_get_iface (dev); + NMSupplicantConfig *config = NULL; + gulong id = 0; + NMActRequest *req; + NMAccessPoint *ap; + NMConnection *connection; + NMSettingConnection *s_connection; + const char *setting_name; + NMSettingWireless *s_wireless; + + g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + remove_supplicant_timeouts (self); + + req = nm_device_get_act_request (dev); + g_assert (req); + + ap = nm_device_wifi_get_activation_ap (self); + g_assert (ap); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + s_connection = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION); + g_assert (s_connection); + + s_wireless = (NMSettingWireless *) nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS); + g_assert (s_wireless); + + /* If we need secrets, get them */ + setting_name = nm_connection_need_secrets (connection, NULL); + if (setting_name) { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): access point '%s' has security," + " but secrets are required.", + iface, nm_setting_connection_get_id (s_connection)); + + ret = handle_auth_or_fail (self, req, FALSE); + if (ret == NM_ACT_STAGE_RETURN_FAILURE) + *reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + goto out; + } + + /* have secrets, or no secrets required */ + if (nm_setting_wireless_get_security (s_wireless)) { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): connection '%s' has security" + ", and secrets exist. No new secrets needed.", + iface, nm_setting_connection_get_id (s_connection)); + } else { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): connection '%s' requires no " + "security. No secrets needed.", + iface, nm_setting_connection_get_id (s_connection)); + } + + config = build_supplicant_config (self, connection, ap); + if (config == NULL) { + nm_log_err (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): couldn't build wireless configuration.", + iface); + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED; + goto out; + } + + /* Hook up error signal handler to capture association errors */ + id = g_signal_connect (priv->supplicant.iface, + "connection-error", + G_CALLBACK (supplicant_iface_connection_error_cb), + self); + priv->supplicant.iface_error_id = id; + + if (!nm_supplicant_interface_set_config (priv->supplicant.iface, config)) { + nm_log_err (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): couldn't send wireless " + "configuration to the supplicant.", iface); + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED; + goto out; + } + + if (!start_supplicant_connection_timeout (self)) { + *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED; + goto out; + } + + /* 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, TRUE); + + if (config) { + /* Supplicant interface object refs the config; we no longer care about + * it after this function. + */ + g_object_unref (config); + } + return ret; +} + +static NMActStageReturn +real_act_stage4_get_ip4_config (NMDevice *dev, + NMIP4Config **config, + NMDeviceStateReason *reason) +{ + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + NMDeviceClass *parent_class; + + g_return_val_if_fail (config != NULL, NM_ACT_STAGE_RETURN_FAILURE); + g_return_val_if_fail (*config == NULL, NM_ACT_STAGE_RETURN_FAILURE); + + /* Chain up to parent */ + parent_class = NM_DEVICE_CLASS (nm_device_wifi_parent_class); + ret = parent_class->act_stage4_get_ip4_config (dev, config, reason); + + if ((ret == NM_ACT_STAGE_RETURN_SUCCESS) && *config) { + NMConnection *connection; + NMSettingWireless *s_wireless; + guint32 mtu; + + connection = nm_act_request_get_connection (nm_device_get_act_request (dev)); + g_assert (connection); + s_wireless = NM_SETTING_WIRELESS (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIRELESS)); + g_assert (s_wireless); + + /* MTU override */ + mtu = nm_setting_wireless_get_mtu (s_wireless); + if (mtu) + nm_ip4_config_set_mtu (*config, mtu); + } + + return ret; +} + + +static NMActStageReturn +handle_ip_config_timeout (NMDeviceWifi *self, + NMConnection *connection, + gboolean may_fail, + gboolean *chain_up, + NMDeviceStateReason *reason) +{ + NMAccessPoint *ap; + NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE; + gboolean auth_enforced = FALSE, encrypted = FALSE; + + g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE); + + ap = nm_device_wifi_get_activation_ap (self); + g_assert (ap); + + /* If nothing checks the security authentication information (as in + * Open System WEP for example), and DHCP times out, then + * the encryption key is likely wrong. Ask the user for a new one. + * Otherwise the failure likely happened after a successful authentication. + */ + auth_enforced = ap_auth_enforced (connection, ap, &encrypted); + if (encrypted && !auth_enforced && !may_fail) { + NMSettingConnection *s_con; + + s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION)); + + /* Activation failed, we must have bad encryption key */ + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): could not get IP configuration for " + "connection '%s'.", + nm_device_get_iface (NM_DEVICE (self)), + nm_setting_connection_get_id (s_con)); + + ret = handle_auth_or_fail (self, NULL, TRUE); + if (ret == NM_ACT_STAGE_RETURN_POSTPONE) { + nm_log_info (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s/wireless): asking for new secrets", + nm_device_get_iface (NM_DEVICE (self))); + } else { + *reason = NM_DEVICE_STATE_REASON_NO_SECRETS; + } + } else { + /* Non-encrypted network or authentication is enforced by some + * entity (AP, RADIUS server, etc), but IP configure failed. Let the + * superclass handle it. + */ + *chain_up = TRUE; + } + + return ret; +} + + +static NMActStageReturn +real_act_stage4_ip4_config_timeout (NMDevice *dev, + NMIP4Config **config, + NMDeviceStateReason *reason) +{ + NMActRequest *req; + NMConnection *connection; + NMSettingIP4Config *s_ip4; + gboolean may_fail = FALSE, chain_up = FALSE; + NMActStageReturn ret; + + req = nm_device_get_act_request (dev); + g_assert (req); + connection = nm_act_request_get_connection (req); + g_assert (connection); + + s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); + if (s_ip4) + may_fail = nm_setting_ip4_config_get_may_fail (s_ip4); + + ret = handle_ip_config_timeout (NM_DEVICE_WIFI (dev), connection, may_fail, &chain_up, reason); + if (chain_up) + ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip4_config_timeout (dev, config, reason); + + return ret; +} + +static NMActStageReturn +real_act_stage4_ip6_config_timeout (NMDevice *dev, + NMIP6Config **config, + NMDeviceStateReason *reason) +{ + NMActRequest *req; + NMConnection *connection; + NMSettingIP6Config *s_ip6; + gboolean may_fail = FALSE, chain_up = FALSE; + NMActStageReturn ret; + + req = nm_device_get_act_request (dev); + g_assert (req); + connection = nm_act_request_get_connection (req); + g_assert (connection); + + s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG); + if (s_ip6) + may_fail = nm_setting_ip6_config_get_may_fail (s_ip6); + + ret = handle_ip_config_timeout (NM_DEVICE_WIFI (dev), connection, may_fail, &chain_up, reason); + if (chain_up) + ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip6_config_timeout (dev, config, reason); + + return ret; +} + +static void +activation_success_handler (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *ap; + struct ether_addr bssid = { {0x0, 0x0, 0x0, 0x0, 0x0, 0x0} }; + NMAccessPoint *tmp_ap; + NMActRequest *req; + NMConnection *connection; + + req = nm_device_get_act_request (dev); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + /* Clear wireless secrets tries on success */ + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL); + + ap = nm_device_wifi_get_activation_ap (self); + + /* If the AP isn't fake, it was found in the scan list and all its + * details are known. + */ + if (!nm_ap_get_fake (ap)) + goto done; + + /* If the activate AP was fake, it probably won't have a BSSID at all. + * But if activation was successful, the card will know the BSSID. Grab + * the BSSID off the card and fill in the BSSID of the activation AP. + */ + nm_device_wifi_get_bssid (self, &bssid); + if (!nm_ethernet_address_is_valid (nm_ap_get_address (ap))) + nm_ap_set_address (ap, &bssid); + if (!nm_ap_get_freq (ap)) + nm_ap_set_freq (ap, nm_device_wifi_get_frequency (self)); + if (!nm_ap_get_max_bitrate (ap) && nm_ap_get_user_created (ap)) + nm_ap_set_max_bitrate (ap, nm_device_wifi_get_bitrate (self)); + + tmp_ap = get_active_ap (self, ap, TRUE); + if (tmp_ap) { + const GByteArray *ssid = nm_ap_get_ssid (tmp_ap); + + /* Found a better match in the scan list than the fake AP. Use it + * instead. + */ + + /* If the better match was a hidden AP, update it's SSID */ + if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) + nm_ap_set_ssid (tmp_ap, nm_ap_get_ssid (ap)); + + nm_act_request_set_specific_object (req, nm_ap_get_dbus_path (tmp_ap)); + + priv->ap_list = g_slist_remove (priv->ap_list, ap); + g_object_unref (ap); + } + +done: + periodic_update (self); + + /* Reset scan interval to something reasonable */ + priv->scan_interval = SCAN_INTERVAL_MIN + (SCAN_INTERVAL_STEP * 2); +} + + +static void +activation_failure_handler (NMDevice *dev) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (dev); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMAccessPoint *ap; + const GByteArray * ssid; + NMActRequest *req; + NMConnection *connection; + + req = nm_device_get_act_request (dev); + g_assert (req); + + connection = nm_act_request_get_connection (req); + g_assert (connection); + + /* Clear wireless secrets tries on failure */ + g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL); + + if ((ap = nm_device_wifi_get_activation_ap (self))) { + if (nm_ap_get_fake (ap)) { + /* Fake APs are ones that don't show up in scans, + * but which the user explicitly attempted to connect to. + * However, if we fail on one of these, remove it from the + * list because we don't have any scan or capability info + * for it, and they are pretty much useless. + */ + access_point_removed (self, ap); + priv->ap_list = g_slist_remove (priv->ap_list, ap); + g_object_unref (ap); + } + } + + ssid = nm_ap_get_ssid (ap); + nm_log_warn (LOGD_DEVICE | LOGD_WIFI, + "Activation (%s) failed for access point (%s)", + nm_device_get_iface (dev), + ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)"); +} + +static gboolean +real_can_interrupt_activation (NMDevice *dev) +{ + if (nm_device_get_state (dev) == NM_DEVICE_STATE_NEED_AUTH) + return TRUE; + + return FALSE; +} + + +static guint32 +real_get_type_capabilities (NMDevice *dev) +{ + return NM_DEVICE_WIFI_GET_PRIVATE (dev)->capabilities; +} + + +static gboolean +spec_match_list (NMDevice *device, const GSList *specs) +{ + struct ether_addr ether; + char *hwaddr; + gboolean matched; + + nm_device_wifi_get_address (NM_DEVICE_WIFI (device), ðer); + hwaddr = nm_ether_ntop (ðer); + matched = nm_match_spec_hwaddr (specs, hwaddr); + g_free (hwaddr); + + return matched; +} + +static void +device_state_changed (NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason, + gpointer user_data) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + gboolean clear_aps = FALSE; + + if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) { + /* Clean up the supplicant interface because in these states the + * device cannot be used. + */ + if (priv->supplicant.iface) + supplicant_interface_release (self); + } + + /* Start or stop the rfkill poll worker for ipw cards */ + if (priv->ipw_rfkill_path) { + if (new_state > NM_DEVICE_STATE_UNMANAGED) { + if (!priv->ipw_rfkill_id) + priv->ipw_rfkill_id = g_timeout_add_seconds (3, ipw_rfkill_state_work, self); + } else if (new_state <= NM_DEVICE_STATE_UNMANAGED) { + if (priv->ipw_rfkill_id) { + g_source_remove (priv->ipw_rfkill_id); + priv->ipw_rfkill_id = 0; + } + } + } + + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + clear_aps = TRUE; + break; + case NM_DEVICE_STATE_UNAVAILABLE: + /* If the device is enabled and the supplicant manager is ready, + * acquire a supplicant interface and transition to DISCONNECTED because + * the device is now ready to use. + */ + if (priv->enabled) { + gboolean success; + struct iw_range range; + + /* Wait for some drivers like ipw3945 to come back to life */ + success = wireless_get_range (self, &range, NULL); + + if (!priv->supplicant.iface) + supplicant_interface_acquire (self); + } + clear_aps = TRUE; + 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: + // FIXME: ensure that the activation request is destroyed + break; + default: + break; + } + + if (clear_aps) + remove_all_aps (self); +} + +NMAccessPoint * +nm_device_wifi_get_activation_ap (NMDeviceWifi *self) +{ + NMDeviceWifiPrivate *priv; + NMActRequest *req; + const char *ap_path; + GSList * elt; + + g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), NULL); + + req = nm_device_get_act_request (NM_DEVICE (self)); + if (!req) + return NULL; + + ap_path = nm_act_request_get_specific_object (req); + if (!ap_path) + return NULL; + + /* Find the AP by it's object path */ + priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + for (elt = priv->ap_list; elt; elt = g_slist_next (elt)) { + NMAccessPoint *ap = NM_AP (elt->data); + + if (!strcmp (ap_path, nm_ap_get_dbus_path (ap))) + return ap; + } + return NULL; +} + +static void +real_set_enabled (NMDeviceInterface *device, gboolean enabled) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (device); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + NMDeviceState state; + + if (priv->enabled == enabled) + return; + + priv->enabled = enabled; + + nm_log_dbg (LOGD_WIFI, "(%s): device now %s", + nm_device_get_iface (NM_DEVICE (device)), + enabled ? "enabled" : "disabled"); + + state = nm_device_interface_get_state (NM_DEVICE_INTERFACE (self)); + if (state < NM_DEVICE_STATE_UNAVAILABLE) { + nm_log_dbg (LOGD_WIFI, "(%s): %s blocked by UNMANAGED state", + enabled ? "enable" : "disable", + nm_device_get_iface (NM_DEVICE (device))); + return; + } + + if (enabled) { + gboolean no_firmware = FALSE, success; + struct iw_range range; + + if (state != NM_DEVICE_STATE_UNAVAILABLE) + nm_log_warn (LOGD_CORE, "not in expected unavailable state!"); + + if (!nm_device_hw_bring_up (NM_DEVICE (self), TRUE, &no_firmware)) { + nm_log_dbg (LOGD_WIFI, "(%s): enable blocked by failure to bring device up", + nm_device_get_iface (NM_DEVICE (device))); + + /* The device sucks, or HAL was lying to us about the killswitch state */ + priv->enabled = FALSE; + return; + } + + /* Wait for some drivers like ipw3945 to come back to life */ + success = wireless_get_range (self, &range, NULL); + + /* iface should be NULL here, but handle it anyway if it's not */ + g_warn_if_fail (priv->supplicant.iface == NULL); + if (priv->supplicant.iface) + supplicant_interface_release (self); + + supplicant_interface_acquire (self); + nm_log_dbg (LOGD_WIFI, "(%s): enable waiting on supplicant state", + nm_device_get_iface (NM_DEVICE (device))); + } else { + nm_device_state_changed (NM_DEVICE (self), + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + nm_device_hw_take_down (NM_DEVICE (self), TRUE); + } +} + +/********************************************************************/ + +NMDevice * +nm_device_wifi_new (const char *udi, + const char *iface, + const char *driver) +{ + g_return_val_if_fail (udi != NULL, NULL); + g_return_val_if_fail (iface != NULL, NULL); + g_return_val_if_fail (driver != NULL, NULL); + + return (NMDevice *) g_object_new (NM_TYPE_DEVICE_WIFI, + NM_DEVICE_INTERFACE_UDI, udi, + NM_DEVICE_INTERFACE_IFACE, iface, + NM_DEVICE_INTERFACE_DRIVER, driver, + NM_DEVICE_INTERFACE_TYPE_DESC, "802.11 WiFi", + NM_DEVICE_INTERFACE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI, + NM_DEVICE_INTERFACE_RFKILL_TYPE, RFKILL_TYPE_WLAN, + NULL); +} + +static void +device_interface_init (NMDeviceInterface *iface_class) +{ + iface_class->set_enabled = real_set_enabled; +} + +static void +nm_device_wifi_init (NMDeviceWifi * self) +{ + g_signal_connect (self, "state-changed", G_CALLBACK (device_state_changed), NULL); +} + +static void +dispose (GObject *object) +{ + NMDeviceWifi *self = NM_DEVICE_WIFI (object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); + + if (priv->disposed) { + G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object); + return; + } + + priv->disposed = TRUE; + + if (priv->periodic_source_id) { + g_source_remove (priv->periodic_source_id); + priv->periodic_source_id = 0; + } + + /* Clean up all pending supplicant tasks */ + while (priv->supplicant.iface_tasks) + finish_supplicant_task ((SupplicantStateTask *) priv->supplicant.iface_tasks->data, TRUE); + while (priv->supplicant.mgr_tasks) + finish_supplicant_task ((SupplicantStateTask *) priv->supplicant.mgr_tasks->data, TRUE); + + cleanup_association_attempt (self, TRUE); + supplicant_interface_release (self); + + if (priv->supplicant.mgr_state_id) { + g_signal_handler_disconnect (priv->supplicant.mgr, priv->supplicant.mgr_state_id); + priv->supplicant.mgr_state_id = 0; + } + + if (priv->supplicant.mgr) { + g_object_unref (priv->supplicant.mgr); + priv->supplicant.mgr = NULL; + } + + if (priv->ssid) { + g_byte_array_free (priv->ssid, TRUE); + priv->ssid = NULL; + } + + set_current_ap (self, NULL); + remove_all_aps (self); + + g_free (priv->ipw_rfkill_path); + if (priv->ipw_rfkill_id) { + g_source_remove (priv->ipw_rfkill_id); + priv->ipw_rfkill_id = 0; + } + + G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object); +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + NMDeviceWifi *device = NM_DEVICE_WIFI (object); + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device); + struct ether_addr hw_addr; + + switch (prop_id) { + case PROP_HW_ADDRESS: + nm_device_wifi_get_address (device, &hw_addr); + g_value_take_string (value, nm_ether_ntop (&hw_addr)); + break; + case PROP_MODE: + g_value_set_uint (value, nm_device_wifi_get_mode (device)); + break; + case PROP_BITRATE: + g_value_set_uint (value, priv->rate); + break; + case PROP_CAPABILITIES: + g_value_set_uint (value, priv->capabilities); + break; + case PROP_ACTIVE_ACCESS_POINT: + if (priv->current_ap) + g_value_set_boxed (value, nm_ap_get_dbus_path (priv->current_ap)); + else + g_value_set_boxed (value, "/"); + break; + case PROP_SCANNING: + g_value_set_boolean (value, nm_supplicant_interface_get_scanning (priv->supplicant.iface)); + break; + case PROP_IPW_RFKILL_STATE: + g_value_set_uint (value, nm_device_wifi_get_ipw_rfkill_state (device)); + 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) +{ + NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_IPW_RFKILL_STATE: + /* construct only */ + priv->ipw_rfkill_state = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +nm_device_wifi_class_init (NMDeviceWifiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMDeviceWifiPrivate)); + + object_class->constructor = constructor; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + parent_class->get_type_capabilities = real_get_type_capabilities; + parent_class->get_generic_capabilities = real_get_generic_capabilities; + parent_class->hw_is_up = real_hw_is_up; + parent_class->hw_bring_up = real_hw_bring_up; + parent_class->hw_take_down = real_hw_take_down; + parent_class->is_up = real_is_up; + parent_class->bring_up = real_bring_up; + parent_class->take_down = real_take_down; + parent_class->update_hw_address = real_update_hw_address; + parent_class->get_best_auto_connection = real_get_best_auto_connection; + parent_class->is_available = real_is_available; + parent_class->connection_secrets_updated = real_connection_secrets_updated; + parent_class->check_connection_compatible = real_check_connection_compatible; + + parent_class->act_stage1_prepare = real_act_stage1_prepare; + parent_class->act_stage2_config = real_act_stage2_config; + parent_class->act_stage4_get_ip4_config = real_act_stage4_get_ip4_config; + parent_class->act_stage4_ip4_config_timeout = real_act_stage4_ip4_config_timeout; + parent_class->act_stage4_ip6_config_timeout = real_act_stage4_ip6_config_timeout; + parent_class->deactivate = real_deactivate; + parent_class->deactivate_quickly = real_deactivate_quickly; + parent_class->can_interrupt_activation = real_can_interrupt_activation; + parent_class->spec_match_list = spec_match_list; + + klass->scanning_allowed = scanning_allowed; + + /* Properties */ + g_object_class_install_property (object_class, PROP_HW_ADDRESS, + g_param_spec_string (NM_DEVICE_WIFI_HW_ADDRESS, + "MAC Address", + "Hardware MAC address", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_MODE, + g_param_spec_uint (NM_DEVICE_WIFI_MODE, + "Mode", + "Mode", + NM_802_11_MODE_UNKNOWN, + NM_802_11_MODE_INFRA, + NM_802_11_MODE_INFRA, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_BITRATE, + g_param_spec_uint (NM_DEVICE_WIFI_BITRATE, + "Bitrate", + "Bitrate", + 0, G_MAXUINT32, 0, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_ACTIVE_ACCESS_POINT, + g_param_spec_boxed (NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT, + "Active access point", + "Currently active access point", + DBUS_TYPE_G_OBJECT_PATH, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_CAPABILITIES, + g_param_spec_uint (NM_DEVICE_WIFI_CAPABILITIES, + "Wireless Capabilities", + "Wireless Capabilities", + 0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_SCANNING, + g_param_spec_boolean (NM_DEVICE_WIFI_SCANNING, + "Scanning", + "Scanning", + FALSE, + G_PARAM_READABLE | NM_PROPERTY_PARAM_NO_EXPORT)); + + g_object_class_install_property (object_class, PROP_IPW_RFKILL_STATE, + g_param_spec_uint (NM_DEVICE_WIFI_IPW_RFKILL_STATE, + "IpwRfkillState", + "ipw rf-kill state", + RFKILL_UNBLOCKED, RFKILL_HARD_BLOCKED, RFKILL_UNBLOCKED, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | NM_PROPERTY_PARAM_NO_EXPORT)); + + /* Signals */ + signals[ACCESS_POINT_ADDED] = + g_signal_new ("access-point-added", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWifiClass, access_point_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[ACCESS_POINT_REMOVED] = + g_signal_new ("access-point-removed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWifiClass, access_point_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[HIDDEN_AP_FOUND] = + g_signal_new ("hidden-ap-found", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (NMDeviceWifiClass, hidden_ap_found), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + signals[PROPERTIES_CHANGED] = + nm_properties_changed_signal_new (object_class, + G_STRUCT_OFFSET (NMDeviceWifiClass, properties_changed)); + + signals[SCANNING_ALLOWED] = + g_signal_new ("scanning-allowed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed), + scanning_allowed_accumulator, NULL, + _nm_marshal_BOOLEAN__VOID, + G_TYPE_BOOLEAN, 0); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass), &dbus_glib_nm_device_wifi_object_info); + + dbus_g_error_domain_register (NM_WIFI_ERROR, NULL, NM_TYPE_WIFI_ERROR); +} + + |