wifi: follow supplicant's scan list instead of managing AP lifetime internally

Instead of tricky logic to merge APs and age them, just tell the
supplicant what our aging parameters are, and rely on it to handle
removal from the list.  Notable behavioral changes are:

* APs will now be removed when they haven't been seen for two
consecutive scans in which they would have been included.  This
means that when the scan interval is short, out-of-range APs will
be removed much more quickly than the previous 360 seconds.

* APs now live at most 250 seconds (twice our longest scan interval)
instead of the previous 360 seconds.

* The problem with wpa_supplicant < 2.3 not notifying that a BSS has
been seen in the scan if none of its properties actually changed is
now avoided, because an AP is only removed when the supplicant removes it

In general these changes should make the scan list more responsive, at
the cost of slightly more instability in the list due to the unreliability
of WiFi scanning.  But it also removes a layer of complexity and
abstraction from NetworkManager, pushing the scan results list closer
to that which the hardware reports.
This commit is contained in:
Dan Williams 2015-04-16 12:05:05 -05:00
parent be370859ef
commit 910c62d8c7
4 changed files with 99 additions and 249 deletions

View file

@ -131,7 +131,7 @@ struct _NMDeviceWifiPrivate {
gint32 scheduled_scan_time;
guint8 scan_interval; /* seconds */
guint pending_scan_id;
guint scanlist_cull_id;
guint ap_dump_id;
gboolean requested_scan;
NMSupplicantManager *sup_mgr;
@ -189,8 +189,6 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
GParamSpec *pspec,
NMDeviceWifi *self);
static void schedule_scanlist_cull (NMDeviceWifi *self);
static gboolean request_wireless_scan (gpointer user_data);
static void emit_ap_added_removed (NMDeviceWifi *self,
@ -305,10 +303,7 @@ supplicant_interface_release (NMDeviceWifi *self)
_LOGD (LOGD_WIFI_SCAN, "reset scanning interval to %d seconds",
priv->scan_interval);
if (priv->scanlist_cull_id) {
g_source_remove (priv->scanlist_cull_id);
priv->scanlist_cull_id = 0;
}
nm_clear_g_source (&priv->ap_dump_id);
if (priv->sup_iface) {
remove_supplicant_interface_error_handler (self);
@ -1420,11 +1415,6 @@ supplicant_iface_scan_done_cb (NMSupplicantInterface *iface,
priv->last_scan = nm_utils_get_monotonic_timestamp_s ();
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);
@ -1436,15 +1426,14 @@ supplicant_iface_scan_done_cb (NMSupplicantInterface *iface,
*
*/
static void
ap_list_dump (NMDeviceWifi *self)
static gboolean
ap_list_dump (gpointer user_data)
{
NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
GSList *sorted, *iter;
if (!nm_logging_enabled (LOGL_DEBUG, LOGD_WIFI_SCAN))
return;
priv->ap_dump_id = 0;
_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%u next:%u]",
nm_utils_get_monotonic_timestamp_s (),
priv->last_scan,
@ -1453,9 +1442,19 @@ ap_list_dump (NMDeviceWifi *self)
for (iter = sorted; iter; iter = iter->next)
nm_ap_dump (NM_AP (iter->data), " ", nm_device_get_iface (NM_DEVICE (self)));
g_slist_free (sorted);
return G_SOURCE_REMOVE;
}
#define WPAS_REMOVED_TAG "supplicant-removed"
static void
schedule_ap_list_dump (NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
if (!nm_logging_enabled (LOGL_DEBUG, LOGD_WIFI_SCAN))
return;
nm_clear_g_source (&priv->ap_dump_id);
priv->ap_dump_id = g_timeout_add_seconds (1, ap_list_dump, self);
}
static void
try_fill_ssid_for_hidden_ap (NMAccessPoint *ap)
@ -1489,137 +1488,6 @@ try_fill_ssid_for_hidden_ap (NMAccessPoint *ap)
}
}
static void
merge_scanned_ap (NMDeviceWifi *self,
NMAccessPoint *merge_ap,
const char *supplicant_path,
GVariant *properties)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
NMAccessPoint *found_ap = NULL;
const GByteArray *ssid;
const char *bssid;
/* 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 */
_LOGD (LOGD_WIFI_SCAN, "matched hidden AP %s => '%s'",
str_if_set (bssid, "(none)"), nm_utils_escape_ssid (ssid->data, ssid->len));
} else {
/* Didn't have an entry for this AP in the database */
_LOGD (LOGD_WIFI_SCAN, "failed to match hidden AP %s",
str_if_set (bssid, "(none)"));
}
}
found_ap = get_ap_by_supplicant_path (self, supplicant_path);
if (!found_ap)
found_ap = nm_ap_match_in_hash (merge_ap, priv->aps);
if (found_ap) {
_LOGD (LOGD_WIFI_SCAN, "merging AP '%s' %s (%p) with existing (%p)",
ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)",
str_if_set (bssid, "(none)"),
merge_ap,
found_ap);
nm_ap_update_from_properties (found_ap, supplicant_path, properties);
nm_ap_set_fake (found_ap, FALSE);
g_object_set_data (G_OBJECT (found_ap), WPAS_REMOVED_TAG, NULL);
} else {
/* New entry in the list */
_LOGD (LOGD_WIFI_SCAN, "adding new AP '%s' %s (%p)",
ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)",
str_if_set (bssid, "(none)"), merge_ap);
g_object_ref (merge_ap);
nm_ap_export_to_dbus (merge_ap);
g_hash_table_insert (priv->aps, (gpointer) nm_ap_get_dbus_path (merge_ap), merge_ap);
emit_ap_added_removed (self, ACCESS_POINT_ADDED, merge_ap, TRUE);
}
}
static gboolean
cull_scan_list (NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
gint32 now = nm_utils_get_monotonic_timestamp_s ();
guint32 removed = 0, total = 0;
GHashTableIter iter;
NMAccessPoint *ap;
priv->scanlist_cull_id = 0;
_LOGD (LOGD_WIFI_SCAN, "checking scan list for outdated APs");
/* Walk the access point list and remove any access points older than
* three times the inactive scan interval.
*/
g_hash_table_iter_init (&iter, priv->aps);
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &ap)) {
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) {
const GByteArray *ssid = nm_ap_get_ssid (ap);
_LOGD (LOGD_WIFI_SCAN,
" removing %s (%s%s%s)",
str_if_set (nm_ap_get_address (ap), "(none)"),
ssid ? "'" : "",
ssid ? nm_utils_escape_ssid (ssid->data, ssid->len) : "(none)",
ssid ? "'" : "");
emit_ap_added_removed (self, ACCESS_POINT_REMOVED, ap, FALSE);
g_hash_table_iter_remove (&iter);
removed++;
}
}
_LOGD (LOGD_WIFI_SCAN, "removed %d APs (of %d)",
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,
@ -1629,6 +1497,9 @@ supplicant_iface_new_bss_cb (NMSupplicantInterface *iface,
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
NMDeviceState state;
NMAccessPoint *ap;
NMAccessPoint *found_ap = NULL;
const GByteArray *ssid;
const char *bssid;
g_return_if_fail (self != NULL);
g_return_if_fail (properties != NULL);
@ -1642,23 +1513,52 @@ supplicant_iface_new_bss_cb (NMSupplicantInterface *iface,
return;
ap = nm_ap_new_from_properties (object_path, properties);
if (ap) {
nm_ap_dump (ap, "New AP: ", nm_device_get_iface (NM_DEVICE (self)));
/* Add the AP to the device's AP list */
merge_scanned_ap (self, ap, object_path, properties);
g_object_unref (ap);
/* Update the current AP if the supplicant notified a current BSS change
* before it sent the current BSS's scan result.
*/
if (g_strcmp0 (nm_supplicant_interface_get_current_bss (iface), object_path) == 0)
supplicant_iface_notify_current_bss (priv->sup_iface, NULL, self);
} else
if (!ap) {
_LOGW (LOGD_WIFI_SCAN, "invalid AP properties received");
return;
}
/* Remove outdated access points */
schedule_scanlist_cull (self);
/* Let the manager try to fill in the SSID from seen-bssids lists */
bssid = nm_ap_get_address (ap);
ssid = nm_ap_get_ssid (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 (ap);
ssid = nm_ap_get_ssid (ap);
if (ssid && (nm_utils_is_empty_ssid (ssid->data, ssid->len) == FALSE)) {
/* Yay, matched it, no longer treat as hidden */
_LOGD (LOGD_WIFI_SCAN, "matched hidden AP %s => '%s'",
str_if_set (bssid, "(none)"), nm_utils_escape_ssid (ssid->data, ssid->len));
} else {
/* Didn't have an entry for this AP in the database */
_LOGD (LOGD_WIFI_SCAN, "failed to match hidden AP %s",
str_if_set (bssid, "(none)"));
}
}
found_ap = get_ap_by_supplicant_path (self, object_path);
if (found_ap) {
nm_ap_dump (ap, "updated ", nm_device_get_iface (NM_DEVICE (self)));
nm_ap_update_from_properties (found_ap, object_path, properties);
} else {
nm_ap_dump (ap, "added ", nm_device_get_iface (NM_DEVICE (self)));
nm_ap_export_to_dbus (ap);
g_hash_table_insert (priv->aps,
(gpointer) nm_ap_get_dbus_path (ap),
g_object_ref (ap));
emit_ap_added_removed (self, ACCESS_POINT_ADDED, ap, TRUE);
}
g_object_unref (ap);
/* Update the current AP if the supplicant notified a current BSS change
* before it sent the current BSS's scan result.
*/
if (g_strcmp0 (nm_supplicant_interface_get_current_bss (iface), object_path) == 0)
supplicant_iface_notify_current_bss (priv->sup_iface, NULL, self);
schedule_ap_list_dump (self);
}
static void
@ -1679,13 +1579,12 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
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)
if (ap) {
nm_ap_dump (ap, "updated ", nm_device_get_iface (NM_DEVICE (self)));
nm_ap_update_from_properties (ap, object_path, properties);
/* Remove outdated access points */
schedule_scanlist_cull (self);
schedule_ap_list_dump (self);
}
}
static void
@ -1693,27 +1592,19 @@ supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
const char *object_path,
NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv;
NMAccessPoint *ap;
g_return_if_fail (self != NULL);
g_return_if_fail (object_path != NULL);
priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
ap = get_ap_by_supplicant_path (self, object_path);
if (ap) {
gint32 now = nm_utils_get_monotonic_timestamp_s ();
gint32 last_seen = nm_ap_get_last_seen (ap);
/* We don't know when the supplicant last saw the AP's beacons,
* it could be two minutes or it could be 2 seconds. Because the
* supplicant doesn't send property change notifications if the
* AP's other properties don't change, our last-seen time may be
* much older the supplicant's, and the AP would be immediately
* removed from the list on the next cleanup. So update the
* last-seen time to ensure the AP sticks around for at least
* one more periodic scan.
*/
nm_ap_set_last_seen (ap, MAX (last_seen, now - SCAN_INTERVAL_MAX));
g_object_set_data (G_OBJECT (ap), WPAS_REMOVED_TAG, GUINT_TO_POINTER (TRUE));
if (ap && ap != priv->current_ap) {
nm_ap_dump (ap, "removed ", nm_device_get_iface (NM_DEVICE (self)));
emit_ap_added_removed (self, ACCESS_POINT_REMOVED, ap, TRUE);
g_hash_table_remove (priv->aps, nm_ap_get_dbus_path (ap));
schedule_ap_list_dump (self);
}
}

View file

@ -1219,66 +1219,3 @@ nm_ap_complete_connection (NMAccessPoint *self,
error);
}
NMAccessPoint *
nm_ap_match_in_hash (NMAccessPoint *find_ap, GHashTable *hash)
{
GHashTableIter iter;
NMAccessPoint *list_ap, *band_match = NULL;
g_return_val_if_fail (find_ap != NULL, NULL);
g_hash_table_iter_init (&iter, hash);
while (g_hash_table_iter_next (&iter, NULL, (gpointer) &list_ap)) {
const GByteArray * list_ssid = nm_ap_get_ssid (list_ap);
const char * list_addr = nm_ap_get_address (list_ap);
const guint32 list_freq = nm_ap_get_freq (list_ap);
const GByteArray * find_ssid = nm_ap_get_ssid (find_ap);
const char * find_addr = nm_ap_get_address (find_ap);
const guint32 find_freq = nm_ap_get_freq (find_ap);
/* SSID match; if both APs are hiding their SSIDs,
* let matching continue on BSSID and other properties
*/
if ( (!list_ssid && find_ssid)
|| (list_ssid && !find_ssid))
continue;
if ( list_ssid
&& find_ssid
&& !nm_utils_same_ssid (list_ssid->data, list_ssid->len,
find_ssid->data, find_ssid->len,
TRUE))
continue;
/* BSSID match */
if ( nm_ethernet_address_is_valid (list_addr, -1)
&& !nm_utils_hwaddr_matches (list_addr, -1, find_addr, -1))
continue;
/* mode match */
if (nm_ap_get_mode (list_ap) != nm_ap_get_mode (find_ap))
continue;
/* AP flags */
if (nm_ap_get_flags (list_ap) != nm_ap_get_flags (find_ap))
continue;
if (nm_ap_get_wpa_flags (list_ap) != nm_ap_get_wpa_flags (find_ap))
continue;
if (nm_ap_get_rsn_flags (list_ap) != nm_ap_get_rsn_flags (find_ap))
continue;
if (list_freq != find_freq) {
/* Must be last check to ensure all other properties match */
if (freq_to_band (list_freq) == freq_to_band (find_freq))
band_match = list_ap;
continue;
}
return list_ap;
}
return band_match;
}

View file

@ -116,8 +116,6 @@ gboolean nm_ap_complete_connection (NMAccessPoint *self,
gboolean lock_bssid,
GError **error);
NMAccessPoint * nm_ap_match_in_hash (NMAccessPoint *find_ap, GHashTable *hash);
void nm_ap_dump (NMAccessPoint *self,
const char *prefix,
const char *ifname);

View file

@ -645,6 +645,30 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
_nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"),
G_CALLBACK (wpas_iface_network_request), self);
/* Scan result aging parameters */
g_dbus_proxy_call (priv->iface_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
WPAS_DBUS_IFACE_INTERFACE,
"BSSExpireAge",
g_variant_new_uint32 (250)),
G_DBUS_CALL_FLAGS_NONE,
-1,
priv->init_cancellable,
NULL,
NULL);
g_dbus_proxy_call (priv->iface_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
WPAS_DBUS_IFACE_INTERFACE,
"BSSExpireCount",
g_variant_new_uint32 (2)),
G_DBUS_CALL_FLAGS_NONE,
-1,
priv->init_cancellable,
NULL,
NULL);
/* Check whether NetworkReply and AP mode are supported */
priv->ready_count = 1;
g_dbus_proxy_call (priv->iface_proxy,