summaryrefslogtreecommitdiff
path: root/src/devices/wifi/nm-device-wifi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wifi/nm-device-wifi.c')
-rw-r--r--src/devices/wifi/nm-device-wifi.c3502
1 files changed, 3502 insertions, 0 deletions
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
new file mode 100644
index 000000000..95173cf47
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -0,0 +1,3502 @@
+/* -*- 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 - 2012 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 <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <linux/sockios.h>
+#include <linux/ethtool.h>
+#include <sys/ioctl.h>
+#include <netinet/ether.h>
+#include <errno.h>
+
+#include "nm-glib-compat.h"
+#include "nm-dbus-manager.h"
+#include "nm-device.h"
+#include "nm-device-wifi.h"
+#include "nm-device-private.h"
+#include "nm-utils.h"
+#include "nm-logging.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-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-platform.h"
+#include "nm-manager-auth.h"
+#include "nm-settings-connection.h"
+#include "nm-enum-types.h"
+#include "nm-dbus-glib-types.h"
+#include "nm-wifi-enum-types.h"
+#include "nm-connection-provider.h"
+
+
+static gboolean impl_device_get_access_points (NMDeviceWifi *device,
+ GPtrArray **aps,
+ GError **err);
+
+static gboolean impl_device_get_all_access_points (NMDeviceWifi *device,
+ GPtrArray **aps,
+ GError **err);
+
+static void impl_device_request_scan (NMDeviceWifi *device,
+ GHashTable *options,
+ DBusGMethodInvocation *context);
+
+#include "nm-device-wifi-glue.h"
+
+
+/* All of these are in seconds */
+#define SCAN_INTERVAL_MIN 3
+#define SCAN_INTERVAL_STEP 20
+#define SCAN_INTERVAL_MAX 120
+
+#define WIRELESS_SECRETS_TRIES "wireless-secrets-tries"
+
+G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_WIFI_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE_WIFI, NMDeviceWifiPrivate))
+
+
+enum {
+ PROP_0,
+ PROP_PERM_HW_ADDRESS,
+ PROP_MODE,
+ PROP_BITRATE,
+ PROP_ACCESS_POINTS,
+ PROP_ACTIVE_ACCESS_POINT,
+ PROP_CAPABILITIES,
+ PROP_SCANNING,
+
+ LAST_PROP
+};
+
+enum {
+ ACCESS_POINT_ADDED,
+ ACCESS_POINT_REMOVED,
+ SCANNING_ALLOWED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _NMDeviceWifiPrivate {
+ gboolean disposed;
+
+ guint8 perm_hw_addr[ETH_ALEN]; /* Permanent MAC address */
+ guint8 initial_hw_addr[ETH_ALEN]; /* Initial MAC address (as seen when NM starts) */
+
+ gint8 invalid_strength_counter;
+
+ GSList * ap_list;
+ NMAccessPoint * current_ap;
+ guint32 rate;
+ gboolean enabled; /* rfkilled or not */
+
+ gint32 scheduled_scan_time;
+ guint8 scan_interval; /* seconds */
+ guint pending_scan_id;
+ guint scanlist_cull_id;
+ gboolean requested_scan;
+
+ NMSupplicantManager *sup_mgr;
+ NMSupplicantInterface *sup_iface;
+ guint sup_timeout_id; /* supplicant association timeout */
+
+ gboolean ssid_found;
+ NM80211Mode mode;
+
+ guint32 failed_link_count;
+ guint periodic_source_id;
+ guint link_timeout_id;
+
+ NMDeviceWifiCapabilities capabilities;
+};
+
+static gboolean check_scanning_allowed (NMDeviceWifi *self);
+
+static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
+
+static void cancel_pending_scan (NMDeviceWifi *self);
+
+static void cleanup_association_attempt (NMDeviceWifi * self,
+ gboolean disconnect);
+
+static void supplicant_iface_state_cb (NMSupplicantInterface *iface,
+ guint32 new_state,
+ guint32 old_state,
+ int disconnect_reason,
+ gpointer user_data);
+
+static void supplicant_iface_new_bss_cb (NMSupplicantInterface * iface,
+ const char *object_path,
+ GHashTable *properties,
+ NMDeviceWifi * self);
+
+static void supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
+ const char *object_path,
+ GHashTable *properties,
+ NMDeviceWifi *self);
+
+static void supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
+ const char *object_path,
+ NMDeviceWifi *self);
+
+static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface,
+ gboolean success,
+ NMDeviceWifi * self);
+
+static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface,
+ GParamSpec * pspec,
+ NMDeviceWifi * self);
+
+static void schedule_scanlist_cull (NMDeviceWifi *self);
+
+static gboolean request_wireless_scan (gpointer user_data);
+
+static void remove_access_point (NMDeviceWifi *device, NMAccessPoint *ap);
+
+static void remove_supplicant_interface_error_handler (NMDeviceWifi *self);
+
+/*****************************************************************/
+
+#define NM_WIFI_ERROR (nm_wifi_error_quark ())
+
+static GQuark
+nm_wifi_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("nm-wifi-error");
+ return quark;
+}
+
+/*****************************************************************/
+
+static GObject*
+constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+ GObjectClass *klass;
+ NMDeviceWifi *self;
+ NMDeviceWifiPrivate *priv;
+
+ 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)));
+
+ if (!nm_platform_wifi_get_capabilities (nm_device_get_ifindex (NM_DEVICE (self)),
+ &priv->capabilities)) {
+ nm_log_warn (LOGD_HW | LOGD_WIFI, "(%s): failed to initialize WiFi driver",
+ nm_device_get_iface (NM_DEVICE (self)));
+ g_object_unref (object);
+ return NULL;
+ }
+
+ if (priv->capabilities & NM_WIFI_DEVICE_CAP_AP) {
+ nm_log_info (LOGD_HW | LOGD_WIFI, "(%s): driver supports Access Point (AP) mode",
+ nm_device_get_iface (NM_DEVICE (self)));
+ }
+
+ /* Connect to the supplicant manager */
+ priv->sup_mgr = nm_supplicant_manager_get ();
+ g_assert (priv->sup_mgr);
+
+ return object;
+}
+
+static gboolean
+supplicant_interface_acquire (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ g_return_val_if_fail (self != NULL, FALSE);
+ /* interface already acquired? */
+ g_return_val_if_fail (priv->sup_iface == NULL, TRUE);
+
+ priv->sup_iface = nm_supplicant_manager_iface_get (priv->sup_mgr,
+ nm_device_get_iface (NM_DEVICE (self)),
+ TRUE);
+ if (priv->sup_iface == NULL) {
+ nm_log_err (LOGD_WIFI, "Couldn't initialize supplicant interface for %s.",
+ nm_device_get_iface (NM_DEVICE (self)));
+ return FALSE;
+ }
+
+ if (nm_supplicant_interface_get_state (priv->sup_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+ nm_device_add_pending_action (NM_DEVICE (self), "waiting for supplicant", TRUE);
+
+ g_signal_connect (priv->sup_iface,
+ NM_SUPPLICANT_INTERFACE_STATE,
+ G_CALLBACK (supplicant_iface_state_cb),
+ self);
+ g_signal_connect (priv->sup_iface,
+ NM_SUPPLICANT_INTERFACE_NEW_BSS,
+ G_CALLBACK (supplicant_iface_new_bss_cb),
+ self);
+ g_signal_connect (priv->sup_iface,
+ NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
+ G_CALLBACK (supplicant_iface_bss_updated_cb),
+ self);
+ g_signal_connect (priv->sup_iface,
+ NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
+ G_CALLBACK (supplicant_iface_bss_removed_cb),
+ self);
+ g_signal_connect (priv->sup_iface,
+ NM_SUPPLICANT_INTERFACE_SCAN_DONE,
+ G_CALLBACK (supplicant_iface_scan_done_cb),
+ self);
+ g_signal_connect (priv->sup_iface,
+ "notify::scanning",
+ G_CALLBACK (supplicant_iface_notify_scanning_cb),
+ self);
+
+ return TRUE;
+}
+
+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)),
+ priv->scan_interval);
+
+ if (priv->scanlist_cull_id) {
+ g_source_remove (priv->scanlist_cull_id);
+ priv->scanlist_cull_id = 0;
+ }
+
+ if (priv->sup_iface) {
+ remove_supplicant_interface_error_handler (self);
+
+ /* Clear supplicant interface signal handlers */
+ g_signal_handlers_disconnect_by_data (priv->sup_iface, self);
+
+ /* Tell the supplicant to disconnect from the current AP */
+ nm_supplicant_interface_disconnect (priv->sup_iface);
+
+ nm_supplicant_manager_iface_release (priv->sup_mgr, priv->sup_iface);
+ priv->sup_iface = NULL;
+ }
+}
+
+static NMAccessPoint *
+get_ap_by_path (NMDeviceWifi *self, const char *path)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ GSList *iter;
+
+ if (!path)
+ return NULL;
+
+ for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) {
+ if (g_strcmp0 (path, nm_ap_get_dbus_path (NM_AP (iter->data))) == 0)
+ return NM_AP (iter->data);
+ }
+ return NULL;
+}
+
+static NMAccessPoint *
+get_ap_by_supplicant_path (NMDeviceWifi *self, const char *path)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ GSList *iter;
+
+ if (!path)
+ return NULL;
+
+ for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) {
+ if (g_strcmp0 (path, nm_ap_get_supplicant_path (NM_AP (iter->data))) == 0)
+ return NM_AP (iter->data);
+ }
+ return NULL;
+}
+
+static NMAccessPoint *
+find_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));
+ int ifindex = nm_device_get_ifindex (NM_DEVICE (self));
+ struct ether_addr bssid;
+ GByteArray *ssid;
+ GSList *iter;
+ int i = 0;
+ NMAccessPoint *match_nofreq = NULL, *active_ap = NULL;
+ gboolean found_a_band = FALSE;
+ gboolean found_bg_band = FALSE;
+ NM80211Mode devmode;
+ guint32 devfreq;
+
+ nm_platform_wifi_get_bssid (ifindex, &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_platform_wifi_get_ssid (ifindex);
+ 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_platform_wifi_get_mode (ifindex);
+ devfreq = nm_platform_wifi_get_frequency (ifindex);
+
+ /* 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 (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");
+ active_ap = ap;
+ goto done;
+ }
+ }
+
+ /* 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. Sometimes wl.o returns a frequency of 0, so if
+ * we can't match the AP based on frequency at all, just give up.
+ */
+ if (match_nofreq && ((found_a_band != found_bg_band) || (devfreq == 0))) {
+ 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]);
+
+ active_ap = match_nofreq;
+ goto done;
+ }
+
+ nm_log_dbg (LOGD_WIFI, " No matching AP found.");
+
+done:
+ if (ssid)
+ g_byte_array_free (ssid, TRUE);
+ return active_ap;
+}
+
+static void
+update_seen_bssids_cache (NMDeviceWifi *self, NMAccessPoint *ap)
+{
+ NMConnection *connection;
+
+ g_return_if_fail (NM_IS_DEVICE_WIFI (self));
+
+ if (ap == NULL)
+ return;
+
+ /* Don't cache the BSSID for Ad-Hoc APs */
+ if (nm_ap_get_mode (ap) != NM_802_11_MODE_INFRA)
+ return;
+
+ if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED) {
+ connection = nm_device_get_connection (NM_DEVICE (self));
+ if (connection) {
+ nm_settings_connection_add_seen_bssid (NM_SETTINGS_CONNECTION (connection),
+ nm_ap_get_address (ap));
+ }
+ }
+}
+
+static void
+set_current_ap (NMDeviceWifi *self, NMAccessPoint *new_ap, gboolean recheck_available_connections, gboolean force_remove_old_ap)
+{
+ NMDeviceWifiPrivate *priv;
+ 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 == new_ap)
+ return;
+
+ 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);
+
+ /* Update seen BSSIDs cache */
+ update_seen_bssids_cache (self, priv->current_ap);
+ } else
+ priv->current_ap = NULL;
+
+ if (old_ap) {
+ NM80211Mode mode = nm_ap_get_mode (old_ap);
+
+ if (force_remove_old_ap || mode == NM_802_11_MODE_ADHOC || mode == NM_802_11_MODE_AP || nm_ap_get_fake (old_ap)) {
+ remove_access_point (self, old_ap);
+ if (recheck_available_connections)
+ nm_device_recheck_available_connections (NM_DEVICE (self));
+ }
+ g_object_unref (old_ap);
+ }
+
+ g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT);
+}
+
+static void
+periodic_update (NMDeviceWifi *self, NMAccessPoint *ignore_ap)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ int ifindex = nm_device_get_ifindex (NM_DEVICE (self));
+ NMAccessPoint *new_ap;
+ guint32 new_rate;
+ int percent;
+ NMDeviceState state;
+ guint32 supplicant_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)
+ return;
+
+ /* Only update current AP if we're actually talking to something, otherwise
+ * assume the old one (if any) is still valid until we're told otherwise or
+ * the connection fails.
+ */
+ supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
+ if ( supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING
+ || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED
+ || nm_supplicant_interface_get_scanning (priv->sup_iface))
+ return;
+
+ /* In AP mode we currently have nothing to do. */
+ if (priv->mode == NM_802_11_MODE_AP)
+ return;
+
+ /* 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 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_platform_wifi_get_bssid (ifindex, &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 = find_active_ap (self, ignore_ap, FALSE);
+ if (new_ap) {
+ /* Try to smooth out the strength. Atmel cards, for example, will give no strength
+ * one second and normal strength the next.
+ */
+ percent = nm_platform_wifi_get_quality (ifindex);
+ if (percent >= 0 || ++priv->invalid_strength_counter > 3) {
+ nm_ap_set_strength (new_ap, (gint8) percent);
+ priv->invalid_strength_counter = 0;
+ }
+ }
+
+ if (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_utils_hwaddr_ntoa (new_bssid, ARPHRD_ETHER);
+ new_ssid = nm_ap_get_ssid (new_ap);
+ }
+
+ if (priv->current_ap) {
+ old_bssid = nm_ap_get_address (priv->current_ap);
+ old_addr = nm_utils_hwaddr_ntoa (old_bssid, ARPHRD_ETHER);
+ 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, TRUE, FALSE);
+ }
+
+ new_rate = nm_platform_wifi_get_rate (ifindex);
+ if (new_rate != priv->rate) {
+ priv->rate = new_rate;
+ g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_BITRATE);
+ }
+}
+
+static gboolean
+periodic_update_cb (gpointer user_data)
+{
+ periodic_update (NM_DEVICE_WIFI (user_data), NULL);
+ return TRUE;
+}
+
+static gboolean
+bring_up (NMDevice *device, gboolean *no_firmware)
+{
+ if (!NM_DEVICE_WIFI_GET_PRIVATE (device)->enabled)
+ return FALSE;
+
+ return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->bring_up (device, no_firmware);
+}
+
+static void
+emit_ap_added_removed (NMDeviceWifi *self,
+ guint signum,
+ NMAccessPoint *ap,
+ gboolean recheck_available_connections)
+{
+ g_signal_emit (self, signals[signum], 0, ap);
+ g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_ACCESS_POINTS);
+ nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+ if (recheck_available_connections)
+ nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+static void
+remove_access_point (NMDeviceWifi *device,
+ NMAccessPoint *ap)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ g_return_if_fail (ap);
+ g_return_if_fail (ap != priv->current_ap);
+ g_return_if_fail (g_slist_find (priv->ap_list, ap));
+
+ priv->ap_list = g_slist_remove (priv->ap_list, ap);
+ emit_ap_added_removed (self, ACCESS_POINT_REMOVED, ap, FALSE);
+ g_object_unref (ap);
+}
+
+static void
+remove_all_aps (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ if (priv->ap_list) {
+ set_current_ap (self, NULL, FALSE, FALSE);
+
+ while (priv->ap_list)
+ remove_access_point (self, NM_AP (priv->ap_list->data));
+
+ nm_device_recheck_available_connections (NM_DEVICE (self));
+ }
+}
+
+static void
+deactivate (NMDevice *dev)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ int ifindex = nm_device_get_ifindex (dev);
+ NMConnection *connection;
+ NM80211Mode old_mode = priv->mode;
+
+ connection = nm_device_get_connection (dev);
+ if (connection) {
+ /* Clear wireless secrets tries when deactivating */
+ g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL);
+ }
+
+ if (priv->periodic_source_id) {
+ g_source_remove (priv->periodic_source_id);
+ priv->periodic_source_id = 0;
+ }
+
+ cleanup_association_attempt (self, TRUE);
+
+ 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.
+ **/
+ set_current_ap (self, NULL, TRUE, FALSE);
+
+ /* Clear any critical protocol notification in the Wi-Fi stack */
+ nm_platform_wifi_indicate_addressing_running (ifindex, FALSE);
+
+ /* Reset MAC address back to initial address */
+ nm_device_set_hw_addr (dev, priv->initial_hw_addr, "reset", LOGD_WIFI);
+
+ /* Ensure we're in infrastructure mode after deactivation; some devices
+ * (usually older ones) don't scan well in adhoc mode.
+ */
+ if (nm_platform_wifi_get_mode (ifindex) != NM_802_11_MODE_INFRA) {
+ nm_device_take_down (NM_DEVICE (self), TRUE);
+ nm_platform_wifi_set_mode (ifindex, NM_802_11_MODE_INFRA);
+ nm_device_bring_up (NM_DEVICE (self), TRUE, NULL);
+ }
+
+ if (priv->mode != NM_802_11_MODE_INFRA) {
+ priv->mode = NM_802_11_MODE_INFRA;
+ g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_MODE);
+ }
+
+ /* Ensure we trigger a scan after deactivating a Hotspot */
+ if (old_mode == NM_802_11_MODE_AP) {
+ cancel_pending_scan (self);
+ request_wireless_scan (self);
+ }
+}
+
+static gboolean
+is_adhoc_wpa (NMConnection *connection)
+{
+ NMSettingWireless *s_wifi;
+ NMSettingWirelessSecurity *s_wsec;
+ const char *mode, *key_mgmt;
+
+ /* The kernel doesn't support Ad-Hoc WPA connections well at this time,
+ * and turns them into open networks. It's been this way since at least
+ * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
+ */
+
+ s_wifi = nm_connection_get_setting_wireless (connection);
+ g_return_val_if_fail (s_wifi != NULL, FALSE);
+
+ mode = nm_setting_wireless_get_mode (s_wifi);
+ if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) != 0)
+ return FALSE;
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (!s_wsec)
+ return FALSE;
+
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
+ if (g_strcmp0 (key_mgmt, "wpa-none") != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMSettingConnection *s_con;
+ NMSettingWireless *s_wireless;
+ const GByteArray *mac;
+ const GSList *mac_blacklist, *mac_blacklist_iter;
+ const char *mode;
+
+ if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->check_connection_compatible (device, connection))
+ return FALSE;
+
+ s_con = nm_connection_get_setting_connection (connection);
+ g_assert (s_con);
+
+ if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME))
+ return FALSE;
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ if (!s_wireless)
+ return FALSE;
+
+ mac = nm_setting_wireless_get_mac_address (s_wireless);
+ if (mac && memcmp (mac->data, &priv->perm_hw_addr, ETH_ALEN))
+ return FALSE;
+
+ /* Check for MAC address blacklist */
+ mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
+ for (mac_blacklist_iter = mac_blacklist; mac_blacklist_iter;
+ mac_blacklist_iter = g_slist_next (mac_blacklist_iter)) {
+ struct ether_addr addr;
+
+ if (!ether_aton_r (mac_blacklist_iter->data, &addr)) {
+ g_warn_if_reached ();
+ continue;
+ }
+
+ if (memcmp (&addr, &priv->perm_hw_addr, ETH_ALEN) == 0)
+ return FALSE;
+ }
+
+ if (is_adhoc_wpa (connection))
+ return FALSE;
+
+ /* Early exit if supplicant or device doesn't support requested mode */
+ mode = nm_setting_wireless_get_mode (s_wireless);
+ if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) {
+ if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC))
+ return FALSE;
+ } else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
+ if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP))
+ return FALSE;
+
+ if (priv->sup_iface) {
+ if (nm_supplicant_interface_get_ap_support (priv->sup_iface) == AP_SUPPORT_NO)
+ 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
+_internal_check_connection_available (NMDevice *device,
+ NMConnection *connection,
+ const char *specific_object,
+ gboolean ignore_ap_list)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device);
+ NMSettingWireless *s_wifi;
+ const char *mode;
+ GSList *ap_iter = NULL;
+
+ s_wifi = nm_connection_get_setting_wireless (connection);
+ g_return_val_if_fail (s_wifi, FALSE);
+
+ if (specific_object) {
+ NMAccessPoint *ap;
+
+ ap = get_ap_by_path (NM_DEVICE_WIFI (device), specific_object);
+ return ap ? nm_ap_check_compatible (ap, connection) : FALSE;
+ }
+
+ /* Ad-Hoc and AP connections are always available because they may be
+ * started at any time.
+ */
+ mode = nm_setting_wireless_get_mode (s_wifi);
+ if ( g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0
+ || g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0)
+ return TRUE;
+
+ /* Hidden SSIDs obviously don't always appear in the scan list either */
+ if (nm_setting_wireless_get_hidden (s_wifi) || ignore_ap_list)
+ return TRUE;
+
+ /* check if its visible */
+ for (ap_iter = priv->ap_list; ap_iter; ap_iter = g_slist_next (ap_iter)) {
+ if (nm_ap_check_compatible (NM_AP (ap_iter->data), connection))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+check_connection_available (NMDevice *device,
+ NMConnection *connection,
+ const char *specific_object)
+{
+ return _internal_check_connection_available (device, connection, specific_object, FALSE);
+}
+
+/* FIXME: remove this function when we require the 'hidden' property to be
+ * set before a hidden connection can be activated.
+ */
+static gboolean
+check_connection_available_wifi_hidden (NMDevice *device,
+ NMConnection *connection)
+{
+ return _internal_check_connection_available (device, connection, NULL, TRUE);
+}
+
+/*
+ * List of manufacturer default SSIDs that are often unchanged by users.
+ *
+ * NOTE: this list should *not* contain networks that you would like to
+ * automatically roam to like "Starbucks" or "AT&T" or "T-Mobile HotSpot".
+ */
+static const char *
+manf_defaults[] = {
+ "linksys",
+ "linksys-a",
+ "linksys-g",
+ "default",
+ "belkin54g",
+ "NETGEAR",
+ "o2DSL",
+ "WLAN",
+ "ALICE-WLAN",
+ "Speedport W 501V",
+};
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a[0]))
+
+static gboolean
+is_manf_default_ssid (const GByteArray *ssid)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE (manf_defaults); i++) {
+ if (ssid->len == strlen (manf_defaults[i])) {
+ if (memcmp (manf_defaults[i], ssid->data, ssid->len) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+ NMConnection *connection,
+ const char *specific_object,
+ const GSList *existing_connections,
+ GError **error)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMSettingWireless *s_wifi;
+ NMSettingWirelessSecurity *s_wsec;
+ NMSetting8021x *s_8021x;
+ const GByteArray *setting_mac;
+ char *format, *str_ssid = NULL;
+ NMAccessPoint *ap = NULL;
+ const GByteArray *ssid = NULL;
+ GSList *iter;
+ gboolean hidden = FALSE;
+
+ s_wifi = nm_connection_get_setting_wireless (connection);
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+
+ if (!specific_object) {
+ /* If not given a specific object, we need at minimum an SSID */
+ if (!s_wifi) {
+ g_set_error_literal (error,
+ NM_WIFI_ERROR,
+ NM_WIFI_ERROR_CONNECTION_INVALID,
+ "A 'wireless' setting is required if no AP path was given.");
+ return FALSE;
+ }
+
+ ssid = nm_setting_wireless_get_ssid (s_wifi);
+ if (!ssid || !ssid->len) {
+ g_set_error_literal (error,
+ NM_WIFI_ERROR,
+ NM_WIFI_ERROR_CONNECTION_INVALID,
+ "A 'wireless' setting with a valid SSID is required if no AP path was given.");
+ return FALSE;
+ }
+
+ /* Find a compatible AP in the scan list */
+ for (iter = priv->ap_list; iter; iter = g_slist_next (iter)) {
+ if (nm_ap_check_compatible (NM_AP (iter->data), connection)) {
+ ap = NM_AP (iter->data);
+ break;
+ }
+ }
+
+ /* If we still don't have an AP, then the WiFI settings needs to be
+ * fully specified by the client. Might not be able to find an AP
+ * if the network isn't broadcasting the SSID for example.
+ */
+ if (!ap) {
+ GSList *settings = NULL;
+ gboolean valid;
+
+ settings = g_slist_prepend (settings, s_wifi);
+ if (s_wsec)
+ settings = g_slist_prepend (settings, s_wsec);
+ if (s_8021x)
+ settings = g_slist_prepend (settings, s_8021x);
+ valid = nm_setting_verify (NM_SETTING (s_wifi), settings, error);
+ g_slist_free (settings);
+ if (!valid)
+ return FALSE;
+
+ hidden = TRUE;
+ }
+ } else {
+ ap = get_ap_by_path (self, specific_object);
+ if (!ap) {
+ g_set_error (error,
+ NM_WIFI_ERROR,
+ NM_WIFI_ERROR_ACCESS_POINT_NOT_FOUND,
+ "The access point %s was not in the scan list.",
+ specific_object);
+ return FALSE;
+ }
+ }
+
+ /* Add a wifi setting if one doesn't exist yet */
+ if (!s_wifi) {
+ s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
+ nm_connection_add_setting (connection, NM_SETTING (s_wifi));
+ }
+
+ if (ap) {
+ ssid = nm_ap_get_ssid (ap);
+
+ if (ssid == NULL) {
+ /* The AP must be hidden. Connecting to a WiFi AP requires the SSID
+ * as part of the initial handshake, so check the connection details
+ * for the SSID. The AP object will still be used for encryption
+ * settings and such.
+ */
+ ssid = nm_setting_wireless_get_ssid (s_wifi);
+ }
+
+ if (ssid == NULL) {
+ /* If there's no SSID on the AP itself, and no SSID in the
+ * connection data, then we cannot connect at all. Return an error.
+ */
+ g_set_error_literal (error,
+ NM_WIFI_ERROR,
+ NM_WIFI_ERROR_CONNECTION_INVALID,
+ "A 'wireless' setting with a valid SSID is required for hidden access points.");
+ return FALSE;
+ }
+
+ /* If the SSID is a well-known SSID, lock the connection to the AP's
+ * specific BSSID so NM doesn't autoconnect to some random wifi net.
+ */
+ if (!nm_ap_complete_connection (ap,
+ connection,
+ is_manf_default_ssid (ssid),
+ error))
+ return FALSE;
+ }
+
+ /* The kernel doesn't support Ad-Hoc WPA connections well at this time,
+ * and turns them into open networks. It's been this way since at least
+ * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
+ */
+ if (is_adhoc_wpa (connection)) {
+ g_set_error_literal (error,
+ NM_SETTING_WIRELESS_ERROR,
+ NM_SETTING_WIRELESS_ERROR_INVALID_PROPERTY,
+ "WPA Ad-Hoc disabled due to kernel bugs");
+ return FALSE;
+ }
+
+ g_assert (ssid);
+ str_ssid = nm_utils_ssid_to_utf8 (ssid);
+ format = g_strdup_printf ("%s %%d", str_ssid);
+
+ nm_utils_complete_generic (connection,
+ NM_SETTING_WIRELESS_SETTING_NAME,
+ existing_connections,
+ format,
+ str_ssid,
+ TRUE);
+ g_free (str_ssid);
+ g_free (format);
+
+ if (hidden)
+ g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
+
+ setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
+ if (setting_mac) {
+ /* Make sure the setting MAC (if any) matches the device's permanent MAC */
+ if (memcmp (setting_mac->data, priv->perm_hw_addr, ETH_ALEN)) {
+ g_set_error (error,
+ NM_SETTING_WIRELESS_ERROR,
+ NM_SETTING_WIRELESS_ERROR_INVALID_PROPERTY,
+ NM_SETTING_WIRELESS_MAC_ADDRESS);
+ return FALSE;
+ }
+ } else {
+ GByteArray *mac;
+ const guint8 null_mac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 };
+
+ /* Lock the connection to this device by default if it uses a
+ * permanent MAC address (ie not a 'locally administered' one)
+ */
+ if ( !(priv->perm_hw_addr[0] & 0x02)
+ && memcmp (priv->perm_hw_addr, null_mac, ETH_ALEN)) {
+ mac = g_byte_array_sized_new (ETH_ALEN);
+ g_byte_array_append (mac, priv->perm_hw_addr, ETH_ALEN);
+ g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_MAC_ADDRESS, mac, NULL);
+ g_byte_array_free (mac, TRUE);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+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->sup_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
+ || state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) {
+ nm_log_dbg (LOGD_WIFI, "(%s): not available because supplicant interface not ready",
+ nm_device_get_iface (dev));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+can_auto_connect (NMDevice *dev,
+ NMConnection *connection,
+ char **specific_object)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ GSList *ap_iter;
+ const char *method = NULL;
+ guint64 timestamp = 0;
+
+ if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->can_auto_connect (dev, connection, specific_object))
+ return FALSE;
+
+ /* Don't autoconnect to networks that have been tried at least once
+ * but haven't been successful, since these are often accidental choices
+ * from the menu and the user may not know the password.
+ */
+ if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp)) {
+ if (timestamp == 0)
+ return FALSE;
+ }
+
+ /* Use the connection if it's a shared connection */
+ method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+ if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+ return TRUE;
+
+ 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 TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+ap_list_dump (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_dump (ap, "List 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;
+}
+
+static gboolean
+impl_device_get_all_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))
+ g_ptr_array_add (*aps, g_strdup (nm_ap_get_dbus_path (NM_AP (elt->data))));
+ return TRUE;
+}
+
+static void
+request_scan_cb (NMDevice *device,
+ DBusGMethodInvocation *context,
+ GError *error,
+ gpointer user_data)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+ GError *local = NULL;
+
+ if (error) {
+ dbus_g_method_return_error (context, error);
+ return;
+ }
+
+ if (!check_scanning_allowed (self)) {
+ local = g_error_new_literal (NM_WIFI_ERROR,
+ NM_WIFI_ERROR_SCAN_NOT_ALLOWED,
+ "Scanning not allowed at this time");
+ dbus_g_method_return_error (context, local);
+ g_error_free (local);
+ return;
+ }
+
+ cancel_pending_scan (self);
+ request_wireless_scan (self);
+ dbus_g_method_return (context);
+}
+
+static void
+impl_device_request_scan (NMDeviceWifi *self,
+ GHashTable *options,
+ DBusGMethodInvocation *context)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMDevice *device = NM_DEVICE (self);
+ gint32 last_scan;
+ GError *error;
+
+ if ( !priv->enabled
+ || !priv->sup_iface
+ || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
+ || nm_device_is_activating (device)) {
+ error = g_error_new_literal (NM_WIFI_ERROR,
+ NM_WIFI_ERROR_SCAN_NOT_ALLOWED,
+ "Scanning not allowed while unavailable or activating");
+ goto error;
+ }
+
+ if (nm_supplicant_interface_get_scanning (priv->sup_iface)) {
+ error = g_error_new_literal (NM_WIFI_ERROR,
+ NM_WIFI_ERROR_SCAN_NOT_ALLOWED,
+ "Scanning not allowed while already scanning");
+ goto error;
+ }
+
+ last_scan = nm_supplicant_interface_get_last_scan_time (priv->sup_iface);
+ if (last_scan && (nm_utils_get_monotonic_timestamp_s () - last_scan) < 10) {
+ error = g_error_new_literal (NM_WIFI_ERROR,
+ NM_WIFI_ERROR_SCAN_NOT_ALLOWED,
+ "Scanning not allowed immediately following previous scan");
+ goto error;
+ }
+
+ /* Ask the manager to authenticate this request for us */
+ g_signal_emit_by_name (device,
+ NM_DEVICE_AUTH_REQUEST,
+ context,
+ NULL,
+ NM_AUTH_PERMISSION_NETWORK_CONTROL,
+ TRUE,
+ request_scan_cb,
+ NULL);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static gboolean
+scanning_allowed (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ guint32 sup_state;
+ NMConnection *connection;
+
+ g_return_val_if_fail (priv->sup_iface != NULL, FALSE);
+
+ /* Scanning not done in AP mode */
+ if (priv->mode == NM_802_11_MODE_AP)
+ return 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:
+ case NM_DEVICE_STATE_IP_CHECK:
+ case NM_DEVICE_STATE_SECONDARIES:
+ case NM_DEVICE_STATE_DEACTIVATING:
+ /* 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_state (priv->sup_iface);
+ if ( sup_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
+ || sup_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED
+ || sup_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE
+ || sup_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE
+ || nm_supplicant_interface_get_scanning (priv->sup_iface))
+ return FALSE;
+
+ connection = nm_device_get_connection (NM_DEVICE (self));
+ if (connection) {
+ NMSettingWireless *s_wifi;
+ const char *ip4_method = NULL;
+ const GByteArray *bssid;
+
+ /* Don't scan when a shared connection is active; it makes drivers mad */
+ ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+
+ if (!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 = nm_connection_get_setting_wireless (connection);
+ 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 = G_VALUE_INIT;
+ GValue retval = G_VALUE_INIT;
+
+ 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
+hidden_filter_func (NMConnectionProvider *provider,
+ NMConnection *connection,
+ gpointer user_data)
+{
+ NMSettingWireless *s_wifi;
+
+ s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (connection);
+ return s_wifi ? nm_setting_wireless_get_hidden (s_wifi) : FALSE;
+}
+
+static GPtrArray *
+build_hidden_probe_list (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids (priv->sup_iface);
+ GSList *connections, *iter;
+ GPtrArray *ssids = NULL;
+ static GByteArray *nullssid = NULL;
+
+ /* Need at least two: wildcard SSID and one or more hidden SSIDs */
+ if (max_scan_ssids < 2)
+ return NULL;
+
+ /* Static wildcard SSID used for every scan */
+ if (G_UNLIKELY (nullssid == NULL))
+ nullssid = g_byte_array_new ();
+
+ connections = nm_connection_provider_get_best_connections (nm_connection_provider_get (),
+ max_scan_ssids - 1,
+ NM_SETTING_WIRELESS_SETTING_NAME,
+ NULL,
+ hidden_filter_func,
+ NULL);
+ if (connections && connections->data) {
+ ssids = g_ptr_array_sized_new (max_scan_ssids - 1);
+ g_ptr_array_add (ssids, nullssid); /* Add wildcard SSID */
+ }
+
+ for (iter = connections; iter; iter = g_slist_next (iter)) {
+ NMConnection *connection = iter->data;
+ NMSettingWireless *s_wifi;
+ const GByteArray *ssid;
+
+ s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (connection);
+ g_assert (s_wifi);
+ ssid = nm_setting_wireless_get_ssid (s_wifi);
+ g_assert (ssid);
+ g_ptr_array_add (ssids, (gpointer) ssid);
+ }
+ g_slist_free (connections);
+
+ return ssids;
+}
+
+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;
+ GPtrArray *ssids = NULL;
+
+ if (priv->requested_scan) {
+ /* There's already a scan in progress */
+ return FALSE;
+ }
+
+ if (check_scanning_allowed (self)) {
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scanning requested",
+ nm_device_get_iface (NM_DEVICE (self)));
+
+ ssids = build_hidden_probe_list (self);
+
+ if (nm_logging_enabled (LOGL_DEBUG, LOGD_WIFI_SCAN)) {
+ if (ssids) {
+ guint i;
+ char *foo;
+
+ for (i = 0; i < ssids->len; i++) {
+ foo = nm_utils_ssid_to_utf8 (g_ptr_array_index (ssids, i));
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): (%d) probe scanning SSID '%s'",
+ nm_device_get_iface (NM_DEVICE (self)),
+ i, foo ? foo : "<hidden>");
+ g_free (foo);
+ }
+ } else {
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): no SSIDs to probe scan",
+ nm_device_get_iface (NM_DEVICE (self)));
+ }
+ }
+
+ if (nm_supplicant_interface_request_scan (priv->sup_iface, ssids)) {
+ /* success */
+ backoff = TRUE;
+ priv->requested_scan = TRUE;
+ nm_device_add_pending_action (NM_DEVICE (self), "scan", TRUE);
+ }
+
+ if (ssids) {
+ /* Elements owned by the connections, so we don't free them here */
+ g_ptr_array_free (ssids, 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);
+ gint32 now = nm_utils_get_monotonic_timestamp_s ();
+
+ /* Cancel the pending scan if it would happen later than (now + the scan_interval) */
+ if (priv->pending_scan_id) {
+ if (now + 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 + 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_done_cb (NMSupplicantInterface *iface,
+ gboolean success,
+ NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): scan %s",
+ nm_device_get_iface (NM_DEVICE (self)),
+ success ? "successful" : "failed");
+
+ schedule_scan (self, success);
+
+ /* Ensure that old APs get removed, which otherwise only
+ * happens when there are new BSSes.
+ */
+ schedule_scanlist_cull (self);
+
+ if (priv->requested_scan) {
+ priv->requested_scan = FALSE;
+ nm_device_remove_pending_action (NM_DEVICE (self), "scan", TRUE);
+ }
+}
+
+/****************************************************************************
+ * WPA Supplicant control stuff
+ *
+ */
+
+static void
+try_fill_ssid_for_hidden_ap (NMAccessPoint *ap)
+{
+ const struct ether_addr *bssid;
+ const GSList *connections, *iter;
+
+ g_return_if_fail (nm_ap_get_ssid (ap) == NULL);
+
+ bssid = nm_ap_get_address (ap);
+ g_assert (bssid);
+
+ /* Look for this AP's BSSID in the seen-bssids list of a connection,
+ * and if a match is found, copy over the SSID */
+ connections = nm_connection_provider_get_connections (nm_connection_provider_get ());
+ for (iter = connections; iter; iter = g_slist_next (iter)) {
+ NMConnection *connection = NM_CONNECTION (iter->data);
+ NMSettingWireless *s_wifi;
+
+ s_wifi = nm_connection_get_setting_wireless (connection);
+ if (s_wifi) {
+ if (nm_settings_connection_has_seen_bssid (NM_SETTINGS_CONNECTION (connection), bssid)) {
+ nm_ap_set_ssid (ap, nm_setting_wireless_get_ssid (s_wifi));
+ break;
+ }
+ }
+ }
+}
+
+#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
+#define MAC_ARG(x) ((guint8*)(x))[0],((guint8*)(x))[1],((guint8*)(x))[2],((guint8*)(x))[3],((guint8*)(x))[4],((guint8*)(x))[5]
+
+/*
+ * 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;
+ const struct ether_addr *bssid;
+ gboolean strict_match = TRUE;
+
+ /* Let the manager try to fill in the SSID from seen-bssids lists */
+ bssid = nm_ap_get_address (merge_ap);
+ ssid = nm_ap_get_ssid (merge_ap);
+ if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) {
+ /* Try to fill the SSID from the AP database */
+ try_fill_ssid_for_hidden_ap (merge_ap);
+
+ ssid = nm_ap_get_ssid (merge_ap);
+ if (ssid && (nm_utils_is_empty_ssid (ssid->data, ssid->len) == FALSE)) {
+ /* Yay, matched it, no longer treat as hidden */
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): matched hidden AP " MAC_FMT " => '%s'",
+ nm_device_get_iface (NM_DEVICE (self)),
+ MAC_ARG (bssid->ether_addr_octet),
+ nm_utils_escape_ssid (ssid->data, ssid->len));
+ nm_ap_set_broadcast (merge_ap, FALSE);
+ } else {
+ /* Didn't have an entry for this AP in the database */
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): failed to match hidden AP " MAC_FMT,
+ nm_device_get_iface (NM_DEVICE (self)),
+ MAC_ARG (bssid->ether_addr_octet));
+ }
+ }
+
+ /* 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.
+ */
+ if (priv->current_ap && nm_ap_get_fake (priv->current_ap))
+ strict_match = FALSE;
+
+ found_ap = get_ap_by_supplicant_path (self, nm_ap_get_supplicant_path (merge_ap));
+ if (!found_ap)
+ found_ap = nm_ap_match_in_list (merge_ap, priv->ap_list, strict_match);
+ if (found_ap) {
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): merging AP '%s' " MAC_FMT " (%p) with existing (%p)",
+ nm_device_get_iface (NM_DEVICE (self)),
+ ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)",
+ MAC_ARG (bssid->ether_addr_octet),
+ merge_ap,
+ found_ap);
+
+ nm_ap_set_supplicant_path (found_ap, nm_ap_get_supplicant_path (merge_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 */
+ nm_log_dbg (LOGD_WIFI_SCAN, "(%s): adding new AP '%s' " MAC_FMT " (%p)",
+ nm_device_get_iface (NM_DEVICE (self)),
+ ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)",
+ MAC_ARG (bssid->ether_addr_octet),
+ merge_ap);
+
+ g_object_ref (merge_ap);
+ priv->ap_list = g_slist_prepend (priv->ap_list, merge_ap);
+ nm_ap_export_to_dbus (merge_ap);
+ emit_ap_added_removed (self, ACCESS_POINT_ADDED, merge_ap, TRUE);
+ }
+}
+
+#define WPAS_REMOVED_TAG "supplicant-removed"
+
+static gboolean
+cull_scan_list (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ gint32 now = nm_utils_get_monotonic_timestamp_s ();
+ GSList *outdated_list = NULL;
+ GSList *elt;
+ guint32 removed = 0, total = 0;
+
+ priv->scanlist_cull_id = 0;
+
+ 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 = elt->data;
+ const guint prune_interval_s = SCAN_INTERVAL_MAX * 3;
+ gint32 last_seen;
+
+ /* Don't cull the associated AP or manually created APs */
+ if (ap == priv->current_ap)
+ continue;
+ g_assert (!nm_ap_get_fake (ap)); /* only the current_ap can be fake */
+
+ /* Don't cull APs still known to the supplicant. Since the supplicant
+ * doesn't yet emit property updates for "last seen" we have to rely
+ * on changing signal strength for updating "last seen". But if the
+ * AP's strength doesn't change we won't get any updates for the AP,
+ * and we'll end up here even if the AP was still found by the
+ * supplicant in the last scan.
+ */
+ if ( nm_ap_get_supplicant_path (ap)
+ && g_object_get_data (G_OBJECT (ap), WPAS_REMOVED_TAG) == NULL)
+ continue;
+
+ last_seen = nm_ap_get_last_seen (ap);
+ if (!last_seen || last_seen + prune_interval_s < now)
+ outdated_list = g_slist_prepend (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 ? "'" : "");
+
+ remove_access_point (self, 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);
+
+ ap_list_dump (self);
+
+ if(removed > 0)
+ nm_device_recheck_available_connections (NM_DEVICE (self));
+
+ return FALSE;
+}
+
+static void
+schedule_scanlist_cull (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ /* Cull the scan list after the last request for it has come in */
+ if (priv->scanlist_cull_id)
+ g_source_remove (priv->scanlist_cull_id);
+ priv->scanlist_cull_id = g_timeout_add_seconds (4, (GSourceFunc) cull_scan_list, self);
+}
+
+static void
+supplicant_iface_new_bss_cb (NMSupplicantInterface *iface,
+ const char *object_path,
+ 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, unmanaged, or in AP mode */
+ state = nm_device_get_state (NM_DEVICE (self));
+ if (state <= NM_DEVICE_STATE_UNAVAILABLE)
+ return;
+ if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP)
+ return;
+
+ ap = nm_ap_new_from_properties (object_path, properties);
+ if (ap) {
+ nm_ap_dump (ap, "New AP: ");
+
+ /* Add the AP to the device's AP list */
+ merge_scanned_ap (self, ap);
+ g_object_unref (ap);
+ } else {
+ nm_log_warn (LOGD_WIFI_SCAN, "(%s): invalid AP properties received",
+ nm_device_get_iface (NM_DEVICE (self)));
+ }
+
+ /* Remove outdated access points */
+ schedule_scanlist_cull (self);
+}
+
+static void
+supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
+ const char *object_path,
+ GHashTable *properties,
+ NMDeviceWifi *self)
+{
+ NMDeviceState state;
+ NMAccessPoint *ap;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (object_path != NULL);
+ g_return_if_fail (properties != NULL);
+
+ /* Ignore new APs when unavailable or unamnaged */
+ state = nm_device_get_state (NM_DEVICE (self));
+ if (state <= NM_DEVICE_STATE_UNAVAILABLE)
+ return;
+
+ /* Update the AP's last-seen property */
+ ap = get_ap_by_supplicant_path (self, object_path);
+ if (ap)
+ nm_ap_set_last_seen (ap, nm_utils_get_monotonic_timestamp_s ());
+
+ /* Remove outdated access points */
+ schedule_scanlist_cull (self);
+}
+
+static void
+supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
+ const char *object_path,
+ NMDeviceWifi *self)
+{
+ NMAccessPoint *ap;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (object_path != NULL);
+
+ ap = get_ap_by_supplicant_path (self, object_path);
+ if (ap)
+ g_object_set_data (G_OBJECT (ap), WPAS_REMOVED_TAG, GUINT_TO_POINTER (TRUE));
+}
+
+static void
+remove_supplicant_timeouts (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ if (priv->sup_timeout_id) {
+ g_source_remove (priv->sup_timeout_id);
+ priv->sup_timeout_id = 0;
+ }
+
+ if (priv->link_timeout_id) {
+ g_source_remove (priv->link_timeout_id);
+ priv->link_timeout_id = 0;
+ }
+}
+
+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->sup_iface)
+ nm_supplicant_interface_disconnect (priv->sup_iface);
+}
+
+static void
+wifi_secrets_cb (NMActRequest *req,
+ guint32 call_id,
+ NMConnection *connection,
+ GError *error,
+ gpointer user_data)
+{
+ NMDevice *dev = NM_DEVICE (user_data);
+
+ g_return_if_fail (req == nm_device_get_act_request (dev));
+ g_return_if_fail (nm_device_get_state (dev) == NM_DEVICE_STATE_NEED_AUTH);
+ g_return_if_fail (nm_act_request_get_connection (req) == connection);
+
+ if (error) {
+ nm_log_warn (LOGD_WIFI, "%s", error->message);
+ nm_device_state_changed (dev,
+ NM_DEVICE_STATE_FAILED,
+ NM_DEVICE_STATE_REASON_NO_SECRETS);
+ } else
+ nm_device_activate_schedule_stage1_device_prepare (dev);
+}
+
+/*
+ * 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);
+
+ nm_log_warn (LOGD_WIFI, "(%s): link timed out.", nm_device_get_iface (dev));
+
+ priv->link_timeout_id = 0;
+
+ /* 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)
+ return FALSE;
+
+ /* If the access point failed, and wasn't found by the supplicant when it
+ * attempted to reconnect, then it's probably out of range or turned off.
+ * Remove it from the list and if it's actually still present, it'll be
+ * found in the next scan.
+ */
+ if (priv->ssid_found == FALSE && priv->current_ap)
+ set_current_ap (self, NULL, TRUE, TRUE);
+
+ nm_device_state_changed (dev,
+ NM_DEVICE_STATE_FAILED,
+ priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT :
+ NM_DEVICE_STATE_REASON_SSID_NOT_FOUND);
+ return FALSE;
+}
+
+static gboolean
+need_new_8021x_secrets (NMDeviceWifi *self,
+ guint32 old_state,
+ const char **setting_name)
+{
+ NMSetting8021x *s_8021x;
+ NMSettingWirelessSecurity *s_wsec;
+ NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
+ NMConnection *connection;
+
+ g_assert (setting_name != NULL);
+
+ connection = nm_device_get_connection (NM_DEVICE (self));
+ g_return_val_if_fail (connection != NULL, FALSE);
+
+ /* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's
+ * attempting to authenticate with the AP.
+ */
+ if (old_state != NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED)
+ return FALSE;
+
+ /* If it's an 802.1x or LEAP connection with "always ask"/unsaved secrets
+ * then we need to ask again because it might be an OTP token and the PIN
+ * may have changed.
+ */
+
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (s_8021x) {
+ nm_setting_get_secret_flags (NM_SETTING (s_8021x),
+ NM_SETTING_802_1X_PASSWORD,
+ &secret_flags,
+ NULL);
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ *setting_name = NM_SETTING_802_1X_SETTING_NAME;
+ return *setting_name ? TRUE : FALSE;
+ }
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (s_wsec) {
+ nm_setting_get_secret_flags (NM_SETTING (s_wsec),
+ NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+ &secret_flags,
+ NULL);
+ if (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED)
+ *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+ return *setting_name ? TRUE : FALSE;
+ }
+
+ /* Not a LEAP or 802.1x connection */
+ return FALSE;
+}
+
+static gboolean
+need_new_wpa_psk (NMDeviceWifi *self,
+ guint32 old_state,
+ const char **setting_name)
+{
+ NMSettingWirelessSecurity *s_wsec;
+ NMConnection *connection;
+ const char *key_mgmt = NULL;
+
+ g_assert (setting_name != NULL);
+
+ connection = nm_device_get_connection (NM_DEVICE (self));
+ g_return_val_if_fail (connection != NULL, FALSE);
+
+ /* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */
+ if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE)
+ return FALSE;
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (s_wsec)
+ key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
+
+ if (g_strcmp0 (key_mgmt, "wpa-psk") == 0) {
+ *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+ return TRUE;
+ }
+
+ /* Not a WPA-PSK connection */
+ return FALSE;
+}
+
+static gboolean
+handle_8021x_or_psk_auth_fail (NMDeviceWifi *self,
+ guint32 new_state,
+ guint32 old_state,
+ int disconnect_reason)
+{
+ NMDevice *device = NM_DEVICE (self);
+ NMActRequest *req;
+ NMConnection *connection;
+ const char *setting_name = NULL;
+ gboolean handled = FALSE;
+
+ g_return_val_if_fail (new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE);
+
+ req = nm_device_get_act_request (NM_DEVICE (self));
+ g_return_val_if_fail (req != NULL, FALSE);
+
+ connection = nm_act_request_get_connection (req);
+ g_assert (connection);
+
+ if ( need_new_8021x_secrets (self, old_state, &setting_name)
+ || need_new_wpa_psk (self, old_state, &setting_name)) {
+
+ nm_connection_clear_secrets (connection);
+
+ nm_log_info (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): disconnected during association,"
+ " asking for new key.", nm_device_get_iface (device));
+
+ cleanup_association_attempt (self, TRUE);
+ nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+ nm_act_request_get_secrets (req,
+ setting_name,
+ NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION
+ | NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW,
+ NULL,
+ wifi_secrets_cb,
+ self);
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+ guint32 new_state,
+ guint32 old_state,
+ int disconnect_reason,
+ gpointer user_data)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMDevice *device = NM_DEVICE (self);
+ NMDeviceState devstate;
+ gboolean scanning;
+
+ if (new_state == old_state)
+ return;
+
+ nm_log_info (LOGD_DEVICE | LOGD_WIFI,
+ "(%s): supplicant interface state: %s -> %s",
+ nm_device_get_iface (device),
+ nm_supplicant_interface_state_to_string (old_state),
+ nm_supplicant_interface_state_to_string (new_state));
+
+ devstate = nm_device_get_state (device);
+ scanning = nm_supplicant_interface_get_scanning (iface);
+
+ /* In these states we know the supplicant is actually talking to something */
+ if ( new_state >= NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
+ && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
+ priv->ssid_found = TRUE;
+
+ switch (new_state) {
+ case 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 ((devstate == NM_DEVICE_STATE_UNAVAILABLE) && nm_device_is_available (device)) {
+ nm_device_state_changed (device,
+ 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 (device));
+
+ /* Request a scan to get latest results */
+ cancel_pending_scan (self);
+ request_wireless_scan (self);
+
+ if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+ nm_device_remove_pending_action (device, "waiting for supplicant", TRUE);
+ break;
+ case NM_SUPPLICANT_INTERFACE_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 (devstate == NM_DEVICE_STATE_CONFIG) {
+ NMConnection *connection;
+ NMSettingWireless *s_wifi;
+ const GByteArray *ssid;
+
+ connection = nm_device_get_connection (NM_DEVICE (self));
+ g_return_if_fail (connection);
+
+ s_wifi = nm_connection_get_setting_wireless (connection);
+ g_return_if_fail (s_wifi);
+
+ ssid = nm_setting_wireless_get_ssid (s_wifi);
+ g_return_if_fail (ssid);
+
+ nm_log_info (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless) Stage 2 of 5 (Device Configure) "
+ "successful. %s '%s'.",
+ nm_device_get_iface (device),
+ priv->mode == NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot" :
+ "Connected to wireless network",
+ ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)");
+ nm_device_activate_schedule_stage3_ip_config_start (device);
+ } else if (devstate == NM_DEVICE_STATE_ACTIVATED)
+ periodic_update (self, NULL);
+ break;
+ case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED:
+ if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) {
+ /* Disconnect of an 802.1x/LEAP connection during authentication,
+ * or disconnect of a WPA-PSK connection during the 4-way handshake,
+ * often means secrets are wrong. Not always the case, but until we
+ * have more information from wpa_supplicant about why the
+ * disconnect happened this is the best we can do.
+ */
+ if (handle_8021x_or_psk_auth_fail (self, new_state, old_state, disconnect_reason))
+ break;
+ }
+
+ /* Otherwise it might be a stupid driver or some transient error, so
+ * let the supplicant try to reconnect a few more times. Give it more
+ * time if a scan is in progress since the link might be dropped during
+ * the scan but will be re-established when the scan is done.
+ */
+ if (devstate == NM_DEVICE_STATE_ACTIVATED) {
+ if (priv->link_timeout_id == 0) {
+ priv->link_timeout_id = g_timeout_add_seconds (scanning ? 30 : 15, link_timeout_cb, self);
+ priv->ssid_found = FALSE;
+ }
+ }
+ break;
+ case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+ cleanup_association_attempt (self, FALSE);
+
+ if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+ nm_device_remove_pending_action (device, "waiting for supplicant", TRUE);
+
+ /* If the device is already in UNAVAILABLE state then the state change
+ * is a NOP and the interface won't be re-acquired in the device state
+ * change handler. So ensure we have a new one here so that we're
+ * ready if the supplicant comes back.
+ */
+ supplicant_interface_release (self);
+ supplicant_interface_acquire (self);
+
+ nm_device_state_changed (device,
+ NM_DEVICE_STATE_UNAVAILABLE,
+ NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+ break;
+ default:
+ break;
+ }
+
+ /* Signal scanning state changes */
+ if ( new_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
+ || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
+ g_object_notify (G_OBJECT (self), "scanning");
+}
+
+static void
+supplicant_iface_connection_error_cb (NMSupplicantInterface *iface,
+ const char *name,
+ const char *message,
+ NMDeviceWifi *self)
+{
+ NMDevice *device = NM_DEVICE (self);
+
+ if (nm_device_is_activating (device)) {
+ nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): supplicant association failed: %s - %s",
+ nm_device_get_iface (device), name, message);
+
+ cleanup_association_attempt (self, TRUE);
+ nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+ }
+}
+
+static void
+remove_supplicant_interface_error_handler (NMDeviceWifi *self)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+ if (priv->sup_iface) {
+ g_signal_handlers_disconnect_by_func (priv->sup_iface,
+ supplicant_iface_connection_error_cb,
+ self);
+ }
+}
+
+static void
+supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
+ GParamSpec *pspec,
+ NMDeviceWifi *self)
+{
+ NMDeviceState state;
+ 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" : "idle");
+
+ g_object_notify (G_OBJECT (self), "scanning");
+
+ /* Run a quick update of current AP when coming out of a scan */
+ state = nm_device_get_state (NM_DEVICE (self));
+ if (!scanning && state == NM_DEVICE_STATE_ACTIVATED)
+ periodic_update (self, NULL);
+}
+
+static NMActStageReturn
+handle_auth_or_fail (NMDeviceWifi *self,
+ NMActRequest *req,
+ gboolean new_secrets)
+{
+ const char *setting_name;
+ guint32 tries;
+ NMConnection *connection;
+ NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+
+ 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);
+
+ 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) {
+ NMSettingsGetSecretsFlags flags = NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+
+ if (new_secrets)
+ flags |= NM_SETTINGS_GET_SECRETS_FLAG_REQUEST_NEW;
+ nm_act_request_get_secrets (req, setting_name, flags, NULL, wifi_secrets_cb, self);
+
+ g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, GUINT_TO_POINTER (++tries));
+ ret = NM_ACT_STAGE_RETURN_POSTPONE;
+ } else
+ nm_log_warn (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
+
+ return ret;
+}
+
+/*
+ * 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);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMActRequest *req;
+ NMConnection *connection;
+
+ cleanup_association_attempt (self, TRUE);
+
+ if (!nm_device_is_activating (dev))
+ return FALSE;
+
+ /* Timed out waiting for a successful connection to the AP; if the AP's
+ * security requires network-side authentication (like WPA or 802.1x)
+ * and the connection attempt timed out then it's likely the authentication
+ * information (passwords, pin codes, etc) are wrong.
+ */
+
+ req = nm_device_get_act_request (dev);
+ g_assert (req);
+
+ connection = nm_act_request_get_connection (req);
+ g_assert (connection);
+
+ if ( priv->mode == NM_802_11_MODE_ADHOC
+ || priv->mode == NM_802_11_MODE_AP) {
+ /* In Ad-Hoc and AP modes there's nothing to check the encryption key
+ * (if any), so supplicant timeouts here are almost certainly the wifi
+ * driver being really stupid.
+ */
+ nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): %s network creation took "
+ "too long, failing activation.",
+ nm_device_get_iface (dev),
+ priv->mode == NM_802_11_MODE_ADHOC ? "Ad-Hoc" : "Hotspot");
+ nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED,
+ NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+ return FALSE;
+ }
+
+ g_assert (priv->mode == NM_802_11_MODE_INFRA);
+
+ if (priv->ssid_found && nm_connection_get_setting_wireless_security (connection)) {
+ guint64 timestamp = 0;
+ gboolean new_secrets = TRUE;
+
+ /* Connection failed; either driver problems, the encryption key is
+ * wrong, or the passwords or certificates were wrong.
+ */
+ nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): association took too long.",
+ nm_device_get_iface (dev));
+
+ /* Ask for new secrets only if we've never activated this connection
+ * before. If we've connected before, don't bother the user with
+ * dialogs, just retry or fail, and if we never connect the user can
+ * fix the password somewhere else.
+ */
+ if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp))
+ new_secrets = !timestamp;
+
+ if (handle_auth_or_fail (self, req, new_secrets) == 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);
+ }
+ } else {
+ 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,
+ priv->ssid_found ? NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT :
+ NM_DEVICE_STATE_REASON_SSID_NOT_FOUND);
+ }
+
+ return FALSE;
+}
+
+static NMSupplicantConfig *
+build_supplicant_config (NMDeviceWifi *self,
+ NMConnection *connection,
+ guint32 fixed_freq)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMSupplicantConfig *config = NULL;
+ NMSettingWireless *s_wireless;
+ NMSettingWirelessSecurity *s_wireless_sec;
+
+ g_return_val_if_fail (self != NULL, NULL);
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ g_return_val_if_fail (s_wireless != NULL, NULL);
+
+ config = nm_supplicant_config_new ();
+ if (!config)
+ return NULL;
+
+ /* Warn if AP mode may not be supported */
+ if ( g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0
+ && nm_supplicant_interface_get_ap_support (priv->sup_iface) == AP_SUPPORT_UNKNOWN) {
+ nm_log_warn (LOGD_WIFI, "Supplicant may not support AP mode; connection may time out.");
+ }
+
+ if (!nm_supplicant_config_add_setting_wireless (config,
+ s_wireless,
+ fixed_freq)) {
+ nm_log_err (LOGD_WIFI, "Couldn't add 802-11-wireless setting to supplicant config.");
+ goto error;
+ }
+
+ s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+ if (s_wireless_sec) {
+ NMSetting8021x *s_8021x;
+ const char *con_uuid = nm_connection_get_uuid (connection);
+
+ g_assert (con_uuid);
+ s_8021x = nm_connection_get_setting_802_1x (connection);
+ if (!nm_supplicant_config_add_setting_wireless_security (config,
+ s_wireless_sec,
+ s_8021x,
+ con_uuid)) {
+ 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
+update_permanent_hw_address (NMDevice *dev)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ struct ifreq req;
+ struct ethtool_perm_addr *epaddr = NULL;
+ int fd, ret;
+
+ fd = socket (PF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ nm_log_err (LOGD_HW, "could not open control socket.");
+ return;
+ }
+
+ /* Get permanent MAC address */
+ memset (&req, 0, sizeof (struct ifreq));
+ strncpy (req.ifr_name, nm_device_get_iface (dev), IFNAMSIZ);
+
+ epaddr = g_malloc0 (sizeof (struct ethtool_perm_addr) + ETH_ALEN);
+ epaddr->cmd = ETHTOOL_GPERMADDR;
+ epaddr->size = ETH_ALEN;
+ req.ifr_data = (void *) epaddr;
+
+ errno = 0;
+ ret = ioctl (fd, SIOCETHTOOL, &req);
+ if ((ret < 0) || !nm_ethernet_address_is_valid ((struct ether_addr *) epaddr->data)) {
+ nm_log_dbg (LOGD_HW | LOGD_ETHER, "(%s): unable to read permanent MAC address (error %d)",
+ nm_device_get_iface (dev), errno);
+ /* Fall back to current address */
+ memcpy (epaddr->data, nm_device_get_hw_address (dev, NULL), ETH_ALEN);
+ }
+
+ if (memcmp (&priv->perm_hw_addr, epaddr->data, ETH_ALEN)) {
+ memcpy (&priv->perm_hw_addr, epaddr->data, ETH_ALEN);
+ g_object_notify (G_OBJECT (dev), NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS);
+ }
+
+ g_free (epaddr);
+ close (fd);
+}
+
+static void
+update_initial_hw_address (NMDevice *dev)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ char *mac_str;
+
+ /* This sets initial MAC address from current MAC address. It should only
+ * be called from NMDevice constructor() to really get the initial address.
+ */
+ memcpy (priv->initial_hw_addr, nm_device_get_hw_address (dev, NULL), ETH_ALEN);
+
+ mac_str = nm_utils_hwaddr_ntoa (priv->initial_hw_addr, ARPHRD_ETHER);
+ nm_log_dbg (LOGD_DEVICE | LOGD_ETHER, "(%s): read initial MAC address %s",
+ nm_device_get_iface (dev), mac_str);
+ g_free (mac_str);
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ NMActStageReturn ret;
+ NMAccessPoint *ap = NULL;
+ NMActRequest *req;
+ NMConnection *connection;
+ NMSettingWireless *s_wireless;
+ const GByteArray *cloned_mac;
+ GSList *iter;
+ const char *mode;
+ const char *ap_path;
+
+ ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage1_prepare (dev, reason);
+ if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+ return ret;
+
+ 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);
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ g_assert (s_wireless);
+
+ mode = nm_setting_wireless_get_mode (s_wireless);
+ if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0)
+ priv->mode = NM_802_11_MODE_INFRA;
+ else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0)
+ priv->mode = NM_802_11_MODE_ADHOC;
+ else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
+ priv->mode = NM_802_11_MODE_AP;
+
+ /* Scanning not done in AP mode; clear the scan list */
+ remove_all_aps (self);
+ }
+ g_object_notify (G_OBJECT (self), NM_DEVICE_WIFI_MODE);
+
+ /* The kernel doesn't support Ad-Hoc WPA connections well at this time,
+ * and turns them into open networks. It's been this way since at least
+ * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
+ */
+ if (is_adhoc_wpa (connection)) {
+ nm_log_warn (LOGD_WIFI, "Ad-Hoc WPA disabled due to kernel bugs");
+ *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED;
+ return NM_ACT_STAGE_RETURN_FAILURE;
+ }
+
+ /* Set spoof MAC to the interface */
+ cloned_mac = nm_setting_wireless_get_cloned_mac_address (s_wireless);
+ if (cloned_mac && (cloned_mac->len == ETH_ALEN))
+ nm_device_set_hw_addr (dev, (const guint8 *) cloned_mac->data, "set", LOGD_WIFI);
+
+ /* AP mode never uses a specific object or existing scanned AP */
+ if (priv->mode != NM_802_11_MODE_AP) {
+
+ ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
+ ap = ap_path ? get_ap_by_path (self, ap_path) : NULL;
+ if (ap)
+ goto done;
+
+ /* 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 (ap) {
+ nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_ap_get_dbus_path (ap));
+ goto done;
+ }
+
+ /* If the user is trying to connect to an AP that NM doesn't yet know about
+ * (hidden network or something) or starting a Hotspot, create an fake AP
+ * from the security settings in the connection. This "fake" AP gets used
+ * until the real one is found in the scan list (Ad-Hoc or Hidden), or until
+ * the device is deactivated (Hotspot).
+ */
+ ap = nm_ap_new_fake_from_connection (connection);
+ g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+ if (nm_ap_get_mode (ap) == NM_802_11_MODE_INFRA)
+ nm_ap_set_broadcast (ap, FALSE);
+ else if (nm_ap_is_hotspot (ap))
+ nm_ap_set_address (ap, (const struct ether_addr *) nm_device_get_hw_address (dev, NULL));
+
+ priv->ap_list = g_slist_prepend (priv->ap_list, ap);
+ nm_ap_export_to_dbus (ap);
+ g_object_freeze_notify (G_OBJECT (self));
+ set_current_ap (self, ap, FALSE, FALSE);
+ emit_ap_added_removed (self, ACCESS_POINT_ADDED, ap, TRUE);
+ g_object_thaw_notify (G_OBJECT (self));
+ nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req), nm_ap_get_dbus_path (ap));
+ return NM_ACT_STAGE_RETURN_SUCCESS;
+
+done:
+ set_current_ap (self, ap, TRUE, FALSE);
+ return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+ensure_hotspot_frequency (NMDeviceWifi *self,
+ NMSettingWireless *s_wifi,
+ NMAccessPoint *ap)
+{
+ const char *band = nm_setting_wireless_get_band (s_wifi);
+ const guint32 a_freqs[] = { 5180, 5200, 5220, 5745, 5765, 5785, 5805, 0 };
+ const guint32 bg_freqs[] = { 2412, 2437, 2462, 2472, 0 };
+ guint32 freq = 0;
+
+ g_assert (ap);
+
+ if (nm_ap_get_freq (ap))
+ return;
+
+ if (g_strcmp0 (band, "a") == 0)
+ freq = nm_platform_wifi_find_frequency (nm_device_get_ifindex (NM_DEVICE (self)), a_freqs);
+ else
+ freq = nm_platform_wifi_find_frequency (nm_device_get_ifindex (NM_DEVICE (self)), bg_freqs);
+
+ if (!freq)
+ freq = (g_strcmp0 (band, "a") == 0) ? 5180 : 2462;
+
+ nm_ap_set_freq (ap, freq);
+}
+
+static NMActStageReturn
+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;
+ NMActRequest *req;
+ NMAccessPoint *ap;
+ NMConnection *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 = priv->current_ap;
+ if (!ap) {
+ *reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
+ goto out;
+ }
+
+ connection = nm_act_request_get_connection (req);
+ g_assert (connection);
+
+ s_wireless = nm_connection_get_setting_wireless (connection);
+ g_assert (s_wireless);
+
+ /* If we need secrets, get them */
+ setting_name = nm_connection_need_secrets (connection, NULL);
+ if (setting_name) {
+ nm_log_info (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): access point '%s' has security,"
+ " but secrets are required.",
+ iface, nm_connection_get_id (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_connection_get_setting_wireless_security (connection)) {
+ nm_log_info (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): connection '%s' has security"
+ ", and secrets exist. No new secrets needed.",
+ iface, nm_connection_get_id (connection));
+ } else {
+ nm_log_info (LOGD_DEVICE | LOGD_WIFI,
+ "Activation (%s/wireless): connection '%s' requires no "
+ "security. No secrets needed.",
+ iface, nm_connection_get_id (connection));
+ }
+
+ priv->ssid_found = FALSE;
+
+ /* Supplicant requires an initial frequency for Ad-Hoc and Hotspot; if the user
+ * didn't specify one and we didn't find an AP that matched the connection,
+ * just pick a frequency the device supports.
+ */
+ if ((nm_ap_get_mode (ap) == NM_802_11_MODE_ADHOC) || nm_ap_is_hotspot (ap))
+ ensure_hotspot_frequency (self, s_wireless, ap);
+
+ /* Build up the supplicant configuration */
+ config = build_supplicant_config (self, connection, nm_ap_get_freq (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 */
+ g_signal_connect (priv->sup_iface,
+ NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR,
+ G_CALLBACK (supplicant_iface_connection_error_cb),
+ self);
+
+ if (!nm_supplicant_interface_set_config (priv->sup_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;
+ }
+
+ /* Set up a timeout on the association attempt to fail after 25 seconds */
+ priv->sup_timeout_id = g_timeout_add_seconds (25, supplicant_connection_timeout_cb, self);
+
+ if (!priv->periodic_source_id)
+ priv->periodic_source_id = g_timeout_add_seconds (6, periodic_update_cb, self);
+
+ /* 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
+act_stage3_ip4_config_start (NMDevice *device,
+ NMIP4Config **out_config,
+ NMDeviceStateReason *reason)
+{
+ NMConnection *connection;
+ NMSettingIP4Config *s_ip4;
+ const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
+
+ connection = nm_device_get_connection (device);
+ g_assert (connection);
+ s_ip4 = nm_connection_get_setting_ip4_config (connection);
+ if (s_ip4)
+ method = nm_setting_ip4_config_get_method (s_ip4);
+
+ /* Indicate that a critical protocol is about to start */
+ if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
+ nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (device), TRUE);
+
+ return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip4_config_start (device, out_config, reason);
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+ NMIP6Config **out_config,
+ NMDeviceStateReason *reason)
+{
+ NMConnection *connection;
+ NMSettingIP6Config *s_ip6;
+ const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+
+ connection = nm_device_get_connection (device);
+ g_assert (connection);
+ s_ip6 = nm_connection_get_setting_ip6_config (connection);
+ if (s_ip6)
+ method = nm_setting_ip6_config_get_method (s_ip6);
+
+ /* Indicate that a critical protocol is about to start */
+ if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 ||
+ strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0)
+ nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (device), TRUE);
+
+ return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip6_config_start (device, out_config, reason);
+}
+
+static void
+ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
+{
+ NMConnection *connection;
+ NMSettingWireless *s_wifi;
+ guint32 mtu;
+
+ connection = nm_device_get_connection (device);
+ g_assert (connection);
+ s_wifi = nm_connection_get_setting_wireless (connection);
+ g_assert (s_wifi);
+
+ /* MTU override */
+ mtu = nm_setting_wireless_get_mtu (s_wifi);
+ if (mtu)
+ nm_ip4_config_set_mtu (config, mtu);
+}
+
+static gboolean
+is_static_wep (NMConnection *connection)
+{
+ NMSettingWirelessSecurity *s_wsec;
+ const char *str;
+
+ g_return_val_if_fail (connection != NULL, FALSE);
+
+ s_wsec = nm_connection_get_setting_wireless_security (connection);
+ if (!s_wsec)
+ return FALSE;
+
+ str = nm_setting_wireless_security_get_key_mgmt (s_wsec);
+ if (g_strcmp0 (str, "none") != 0)
+ return FALSE;
+
+ str = nm_setting_wireless_security_get_auth_alg (s_wsec);
+ if (g_strcmp0 (str, "leap") == 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static NMActStageReturn
+handle_ip_config_timeout (NMDeviceWifi *self,
+ NMConnection *connection,
+ gboolean may_fail,
+ gboolean *chain_up,
+ NMDeviceStateReason *reason)
+{
+ NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+
+ g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+ if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) {
+ *chain_up = TRUE;
+ return ret;
+ }
+
+ /* If IP configuration times out and it's a static WEP connection, that
+ * usually means the WEP key is wrong. WEP's Open System auth mode has
+ * no provision for figuring out if the WEP key is wrong, so you just have
+ * to wait for DHCP to fail to figure it out. For all other WiFi security
+ * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
+ * connection would have failed before IP configuration.
+ */
+ if (!may_fail && is_static_wep (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_connection_get_id (connection));
+
+ 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 {
+ /* Not static WEP or failure allowed; let superclass handle it */
+ *chain_up = TRUE;
+ }
+
+ return ret;
+}
+
+
+static NMActStageReturn
+act_stage4_ip4_config_timeout (NMDevice *dev, NMDeviceStateReason *reason)
+{
+ NMConnection *connection;
+ NMSettingIP4Config *s_ip4;
+ gboolean may_fail = FALSE, chain_up = FALSE;
+ NMActStageReturn ret;
+
+ connection = nm_device_get_connection (dev);
+ g_assert (connection);
+
+ s_ip4 = nm_connection_get_setting_ip4_config (connection);
+ 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, reason);
+
+ return ret;
+}
+
+static NMActStageReturn
+act_stage4_ip6_config_timeout (NMDevice *dev, NMDeviceStateReason *reason)
+{
+ NMConnection *connection;
+ NMSettingIP6Config *s_ip6;
+ gboolean may_fail = FALSE, chain_up = FALSE;
+ NMActStageReturn ret;
+
+ connection = nm_device_get_connection (dev);
+ g_assert (connection);
+
+ s_ip6 = nm_connection_get_setting_ip6_config (connection);
+ 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, reason);
+
+ return ret;
+}
+
+static void
+activation_success_handler (NMDevice *dev)
+{
+ NMDeviceWifi *self = NM_DEVICE_WIFI (dev);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ int ifindex = nm_device_get_ifindex (dev);
+ NMAccessPoint *ap;
+ struct ether_addr bssid = { {0x0, 0x0, 0x0, 0x0, 0x0, 0x0} };
+ NMAccessPoint *tmp_ap = NULL;
+ 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 any critical protocol notification in the wifi stack */
+ nm_platform_wifi_indicate_addressing_running (ifindex, FALSE);
+
+ /* Clear wireless secrets tries on success */
+ g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL);
+
+ ap = priv->current_ap;
+
+ /* If the AP isn't fake, it was found in the scan list and all its
+ * details are known.
+ */
+ if (!ap || !nm_ap_get_fake (ap)){
+ ap = NULL;
+ 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_platform_wifi_get_bssid (ifindex, &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_platform_wifi_get_frequency (ifindex));
+ if (!nm_ap_get_max_bitrate (ap))
+ nm_ap_set_max_bitrate (ap, nm_platform_wifi_get_rate (ifindex));
+
+ tmp_ap = find_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_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
+ nm_ap_get_dbus_path (tmp_ap));
+ }
+
+done:
+ periodic_update (self, ap);
+
+ /* ap might be already unrefed, because it was a fake_ap. But we don't touch it... */
+ if (tmp_ap && ap == priv->current_ap) {
+ /* Strange, we would expect periodic_update() to find a better AP
+ * then the fake one and reset it. Reset the fake current_ap to NULL
+ * now, which will remove the fake ap.
+ **/
+ set_current_ap (self, NULL, TRUE, FALSE);
+ }
+
+ /* No need to update seen BSSIDs cache, that is done by set_current_ap() already */
+
+ /* Reset scan interval to something reasonable */
+ priv->scan_interval = SCAN_INTERVAL_MIN + (SCAN_INTERVAL_STEP * 2);
+}
+
+static void
+activation_failure_handler (NMDevice *dev)
+{
+ NMConnection *connection;
+
+ connection = nm_device_get_connection (dev);
+ g_assert (connection);
+
+ /* Clear wireless secrets tries on failure */
+ g_object_set_data (G_OBJECT (connection), WIRELESS_SECRETS_TRIES, NULL);
+
+ /* Clear any critical protocol notification in the wifi stack */
+ nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (dev), FALSE);
+}
+
+static void
+device_state_changed (NMDevice *device,
+ NMDeviceState new_state,
+ NMDeviceState old_state,
+ NMDeviceStateReason reason)
+{
+ 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->sup_iface)
+ supplicant_interface_release (self);
+
+ if (priv->periodic_source_id) {
+ g_source_remove (priv->periodic_source_id);
+ priv->periodic_source_id = 0;
+ }
+
+ cleanup_association_attempt (self, TRUE);
+ remove_all_aps (self);
+ }
+
+ 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 && (nm_device_get_firmware_missing (device) == FALSE)) {
+ if (!priv->sup_iface)
+ supplicant_interface_acquire (self);
+ }
+ clear_aps = TRUE;
+ break;
+ case NM_DEVICE_STATE_NEED_AUTH:
+ if (priv->sup_iface)
+ nm_supplicant_interface_disconnect (priv->sup_iface);
+ break;
+ case NM_DEVICE_STATE_IP_CHECK:
+ /* Clear any critical protocol notification in the wifi stack */
+ nm_platform_wifi_indicate_addressing_running (nm_device_get_ifindex (device), FALSE);
+ 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:
+ /* Kick off a scan to get latest results */
+ priv->scan_interval = SCAN_INTERVAL_MIN;
+ cancel_pending_scan (self);
+ request_wireless_scan (self);
+ break;
+ default:
+ break;
+ }
+
+ if (clear_aps)
+ remove_all_aps (self);
+}
+
+static void
+set_enabled (NMDevice *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_get_state (NM_DEVICE (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;
+
+ if (state != NM_DEVICE_STATE_UNAVAILABLE)
+ nm_log_warn (LOGD_CORE, "not in expected unavailable state!");
+
+ if (!nm_device_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)));
+
+ if (no_firmware)
+ nm_device_set_firmware_missing (NM_DEVICE (device), TRUE);
+ else {
+ /* The device sucks, or the kernel was lying to us about the killswitch state */
+ priv->enabled = FALSE;
+ }
+ return;
+ }
+
+ /* Re-initialize the supplicant interface and wait for it to be ready */
+ if (priv->sup_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_take_down (NM_DEVICE (self), TRUE);
+ }
+}
+
+/********************************************************************/
+
+NMDevice *
+nm_device_wifi_new (NMPlatformLink *platform_device)
+{
+ g_return_val_if_fail (platform_device != NULL, NULL);
+
+ return (NMDevice *) g_object_new (NM_TYPE_DEVICE_WIFI,
+ NM_DEVICE_PLATFORM_DEVICE, platform_device,
+ NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+ NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
+ NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+ NULL);
+}
+
+static void
+nm_device_wifi_init (NMDeviceWifi *self)
+{
+ NM_DEVICE_WIFI_GET_PRIVATE (self)->mode = NM_802_11_MODE_INFRA;
+}
+
+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;
+ }
+
+ cleanup_association_attempt (self, TRUE);
+ supplicant_interface_release (self);
+
+ g_clear_object (&priv->sup_mgr);
+
+ remove_all_aps (self);
+
+ 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);
+ GPtrArray *array;
+ GSList *iter;
+
+ switch (prop_id) {
+ case PROP_PERM_HW_ADDRESS:
+ g_value_take_string (value, nm_utils_hwaddr_ntoa (&priv->perm_hw_addr, ARPHRD_ETHER));
+ break;
+ case PROP_MODE:
+ g_value_set_uint (value, priv->mode);
+ 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_ACCESS_POINTS:
+ array = g_ptr_array_sized_new (4);
+ for (iter = priv->ap_list; iter; iter = g_slist_next (iter))
+ g_ptr_array_add (array, g_strdup (nm_ap_get_dbus_path (NM_AP (iter->data))));
+ g_value_take_boxed (value, array);
+ 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->sup_iface));
+ 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)
+{
+ switch (prop_id) {
+ 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->bring_up = bring_up;
+ parent_class->update_permanent_hw_address = update_permanent_hw_address;
+ parent_class->update_initial_hw_address = update_initial_hw_address;
+ parent_class->can_auto_connect = can_auto_connect;
+ parent_class->is_available = is_available;
+ parent_class->check_connection_compatible = check_connection_compatible;
+ parent_class->check_connection_available = check_connection_available;
+ parent_class->check_connection_available_wifi_hidden = check_connection_available_wifi_hidden;
+ parent_class->complete_connection = complete_connection;
+ parent_class->set_enabled = set_enabled;
+
+ parent_class->act_stage1_prepare = act_stage1_prepare;
+ parent_class->act_stage2_config = act_stage2_config;
+ parent_class->ip4_config_pre_commit = ip4_config_pre_commit;
+ parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+ parent_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+ parent_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
+ parent_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+ parent_class->deactivate = deactivate;
+
+ parent_class->state_changed = device_state_changed;
+
+ klass->scanning_allowed = scanning_allowed;
+
+ /* Properties */
+ g_object_class_install_property (object_class, PROP_PERM_HW_ADDRESS,
+ g_param_spec_string (NM_DEVICE_WIFI_PERMANENT_HW_ADDRESS,
+ "Permanent MAC Address",
+ "Permanent 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_AP,
+ 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_ACCESS_POINTS,
+ g_param_spec_boxed (NM_DEVICE_WIFI_ACCESS_POINTS,
+ "Access points",
+ "Access points",
+ DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
+ 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));
+
+ /* 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, NULL,
+ 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,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_OBJECT);
+
+ 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, NULL,
+ G_TYPE_BOOLEAN, 0);
+
+ nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
+ 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);
+}
+
+