summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-04-29 12:23:37 +0200
committerThomas Haller <thaller@redhat.com>2020-04-29 12:23:37 +0200
commit20c17978ff48b59ebc85d9b125ed0719dd427bd5 (patch)
tree26de40a8b727f90ea93e2c60f432ef81297ac324
parentd0d91ed6accf7bdbc88fa75cd78021bce3beba19 (diff)
parentc9ae23af5e1fdf85e3fc34b29440b4de1d67a2ab (diff)
downloadNetworkManager-20c17978ff48b59ebc85d9b125ed0719dd427bd5.tar.gz
wifi: merge branch 'th/wifi-scan' (part 2)
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/479
-rw-r--r--src/devices/wifi/nm-device-wifi.c880
-rw-r--r--src/supplicant/nm-supplicant-interface.c62
2 files changed, 595 insertions, 347 deletions
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index f14e2b25e7..b9e71d94f1 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -48,8 +48,17 @@ _LOG_DECLARE_SELF(NMDeviceWifi);
#define SCAN_INTERVAL_SEC_STEP 20
#define SCAN_INTERVAL_SEC_MAX 120
+#define SCAN_RATE_LIMIT_SEC 8
+
+#define SCAN_EXTRA_DELAY_MSEC 500
+
#define SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC (5*60)
+#define SCAN_REQUEST_SSIDS_MAX_NUM 32u
+#define SCAN_REQUEST_SSIDS_MAX_AGE_MSEC (3 * 60 * NM_UTILS_MSEC_PER_SEC)
+
+#define _LOGT_scan(...) _LOGT (LOGD_WIFI_SCAN, "wifi-scan: " __VA_ARGS__)
+
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
@@ -76,44 +85,58 @@ typedef struct {
CList scanning_prohibited_lst_head;
+ GCancellable *scan_request_cancellable;
+
+ GSource *scan_request_delay_source;
+
NMWifiAP * current_ap;
- guint32 rate;
- bool enabled:1; /* rfkilled or not */
- bool requested_scan:1;
- bool ssid_found:1;
- bool is_scanning:1;
- bool hidden_probe_scan_warn:1;
- gint64 last_scan_msec;
+ GHashTable *scan_request_ssids_hash;
+ CList scan_request_ssids_lst_head;
- gint32 scheduled_scan_time; /* seconds */
- guint8 scan_interval_sec;
- guint pending_scan_id;
- guint ap_dump_id;
+ NMActRequestGetSecretsCallId *wifi_secrets_id;
- NMSupplicantManager *sup_mgr;
+ NMSupplicantManager *sup_mgr;
NMSupplMgrCreateIfaceHandle *sup_create_handle;
NMSupplicantInterface *sup_iface;
- guint sup_timeout_id; /* supplicant association timeout */
- NM80211Mode mode;
+ gint64 scan_last_complete_msec;
+ gint64 scan_periodic_next_msec;
- NMActRequestGetSecretsCallId *wifi_secrets_id;
+ gint64 scan_ratelimited_until_msec;
+
+ guint scan_kickoff_timeout_id;
+
+ guint ap_dump_id;
guint periodic_update_id;
+
guint link_timeout_id;
- guint32 failed_iface_count;
guint reacquire_iface_id;
+ guint wps_timeout_id;
+ guint sup_timeout_id; /* supplicant association timeout */
NMDeviceWifiCapabilities capabilities;
+ NMSettingWirelessWakeOnWLan wowlan_restore;
- gint32 hw_addr_scan_expire;
+ NMDeviceWifiP2P *p2p_device;
+ NM80211Mode mode;
- guint wps_timeout_id;
+ guint32 failed_iface_count;
+ gint32 hw_addr_scan_expire;
- NMSettingWirelessWakeOnWLan wowlan_restore;
+ guint32 rate;
+
+ guint8 scan_periodic_interval_sec;
+
+ bool enabled:1; /* rfkilled or not */
+ bool scan_is_scanning:1;
+ bool scan_periodic_allowed:1;
+ bool scan_explicit_allowed:1;
+ bool scan_explicit_requested:1;
+ bool ssid_found:1;
+ bool hidden_probe_scan_warn:1;
- NMDeviceWifiP2P *p2p_device;
} NMDeviceWifiPrivate;
struct _NMDeviceWifi
@@ -135,12 +158,8 @@ G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
/*****************************************************************************/
-static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic);
-
static void supplicant_iface_state_down (NMDeviceWifi *self);
-static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
-
static void cleanup_association_attempt (NMDeviceWifi * self,
gboolean disconnect);
@@ -173,15 +192,8 @@ static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
GParamSpec *pspec,
NMDeviceWifi *self);
-static void _requested_scan_set (NMDeviceWifi *self, gboolean value);
-
static void periodic_update (NMDeviceWifi *self);
-static void request_wireless_scan (NMDeviceWifi *self,
- gboolean periodic,
- gboolean force_if_scanning,
- const GPtrArray *ssids);
-
static void ap_add_remove (NMDeviceWifi *self,
gboolean is_adding,
NMWifiAP *ap,
@@ -191,6 +203,147 @@ static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
static void recheck_p2p_availability (NMDeviceWifi *self);
+static void _scan_kickoff (NMDeviceWifi *self);
+
+static gboolean _scan_notify_allowed (NMDeviceWifi *self, NMTernary do_kickoff);
+
+/*****************************************************************************/
+
+typedef struct {
+ GBytes *ssid;
+ CList lst;
+ gint64 timestamp_msec;
+} ScanRequestSsidData;
+
+static void
+_scan_request_ssids_remove (NMDeviceWifiPrivate *priv,
+ ScanRequestSsidData *srs_data,
+ GBytes **out_ssid)
+{
+ nm_assert (priv->scan_request_ssids_hash);
+ nm_assert (g_hash_table_lookup (priv->scan_request_ssids_hash, srs_data) == srs_data);
+
+ if (!g_hash_table_remove (priv->scan_request_ssids_hash, srs_data))
+ nm_assert_not_reached ();
+ c_list_unlink_stale (&srs_data->lst);
+ if (out_ssid)
+ *out_ssid = srs_data->ssid;
+ else
+ g_bytes_unref (srs_data->ssid);
+ nm_g_slice_free (srs_data);
+}
+
+static void
+_scan_request_ssids_remove_all (NMDeviceWifiPrivate *priv,
+ gint64 cutoff_with_now_msec,
+ guint cutoff_at_len)
+{
+ ScanRequestSsidData *srs_data;
+
+ if (cutoff_with_now_msec != 0) {
+ gint64 cutoff_time_msec;
+
+ /* remove all entries that are older than a max-age. */
+ nm_assert (cutoff_with_now_msec > 0);
+ cutoff_time_msec = cutoff_with_now_msec - SCAN_REQUEST_SSIDS_MAX_AGE_MSEC;
+ while ((srs_data = c_list_last_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) {
+ if (srs_data->timestamp_msec > cutoff_time_msec)
+ return;
+ _scan_request_ssids_remove (priv, srs_data, NULL);
+ }
+ }
+
+ if (cutoff_at_len != G_MAXUINT) {
+ guint i;
+
+ /* trim the list to cutoff_at_len elements. */
+ i = nm_g_hash_table_size (priv->scan_request_ssids_hash);
+ for (; i > cutoff_at_len; i--) {
+ ScanRequestSsidData *d;
+
+ d = c_list_last_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst);
+ _scan_request_ssids_remove (priv, d, NULL);
+ }
+ nm_assert (i == nm_g_hash_table_size (priv->scan_request_ssids_hash));
+ nm_assert (i == c_list_length (&priv->scan_request_ssids_lst_head));
+ nm_assert (i <= cutoff_at_len);
+ }
+}
+
+static GPtrArray *
+_scan_request_ssids_fetch (NMDeviceWifiPrivate *priv)
+{
+ ScanRequestSsidData *srs_data;
+ GPtrArray *ssids;
+ guint len;
+
+ _scan_request_ssids_remove_all (priv, nm_utils_get_monotonic_timestamp_msec (), SCAN_REQUEST_SSIDS_MAX_NUM);
+
+ len = nm_g_hash_table_size (priv->scan_request_ssids_hash);
+ nm_assert (len == c_list_length (&priv->scan_request_ssids_lst_head));
+
+ if (len == 0)
+ return NULL;
+
+ ssids = g_ptr_array_new_full (len, (GDestroyNotify) g_bytes_unref);
+ while ((srs_data = c_list_first_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) {
+ GBytes *ssid;
+
+ _scan_request_ssids_remove (priv, srs_data, &ssid);
+ g_ptr_array_add (ssids, ssid);
+ }
+ return ssids;
+}
+
+static void
+_scan_request_ssids_track (NMDeviceWifiPrivate *priv,
+ const GPtrArray *ssids)
+{
+ CList old_lst_head;
+ gint64 now_msec;
+ guint i;
+
+ if ( !ssids
+ || ssids->len == 0)
+ return;
+
+ now_msec = nm_utils_get_monotonic_timestamp_msec ();
+
+ if (!priv->scan_request_ssids_hash)
+ priv->scan_request_ssids_hash = g_hash_table_new (nm_pgbytes_hash, nm_pgbytes_equal);
+
+ /* Do a little dance. New elements shall keep their order as in @ssids, but all
+ * new elements should be sorted in the list preexisting elements of the list.
+ * First move the old elements away, and splice them back afterwards. */
+ c_list_init (&old_lst_head);
+ c_list_splice (&old_lst_head, &priv->scan_request_ssids_lst_head);
+
+ for (i = 0; i < ssids->len; i++) {
+ GBytes *ssid = ssids->pdata[i];
+ ScanRequestSsidData *d;
+
+ G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ScanRequestSsidData, ssid) == 0);
+ d = g_hash_table_lookup (priv->scan_request_ssids_hash, &ssid);
+ if (!d) {
+ d = g_slice_new (ScanRequestSsidData);
+ *d = (ScanRequestSsidData) {
+ .lst = C_LIST_INIT (d->lst),
+ .timestamp_msec = now_msec,
+ .ssid = g_bytes_ref (ssid),
+ };
+ g_hash_table_add (priv->scan_request_ssids_hash, d);
+ } else
+ d->timestamp_msec = now_msec;
+ c_list_link_tail (&priv->scan_request_ssids_lst_head, &d->lst);
+ }
+
+ c_list_splice (&priv->scan_request_ssids_lst_head, &old_lst_head);
+
+ /* Trim the excess. After our splice with old_lst_head, the list contains the new
+ * elements (from @ssids) at the front (in there original order), followed by older elements. */
+ _scan_request_ssids_remove_all (priv, now_msec, SCAN_REQUEST_SSIDS_MAX_NUM);
+}
+
/*****************************************************************************/
void
@@ -218,16 +371,15 @@ nm_device_wifi_scanning_prohibited_track (NMDeviceWifi *self,
if (!temporarily_prohibited) {
if (!elem)
return;
-
nm_c_list_elem_free (elem);
- return;
+ } else {
+ if (elem)
+ return;
+ c_list_link_tail (&priv->scanning_prohibited_lst_head,
+ &nm_c_list_elem_new_stale (tag)->lst);
}
- if (elem)
- return;
-
- c_list_link_tail (&priv->scanning_prohibited_lst_head,
- &nm_c_list_elem_new_stale (tag)->lst);
+ _scan_notify_allowed (self, NM_TERNARY_DEFAULT);
}
/*****************************************************************************/
@@ -252,28 +404,36 @@ nm_device_wifi_get_scanning (NMDeviceWifi *self)
{
g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE);
- return NM_DEVICE_WIFI_GET_PRIVATE (self)->is_scanning;
+ return NM_DEVICE_WIFI_GET_PRIVATE (self)->scan_is_scanning;
}
-static void
-_notify_scanning (NMDeviceWifi *self)
+static gboolean
+_scan_is_scanning_eval (NMDeviceWifiPrivate *priv)
+{
+ return priv->scan_request_cancellable
+ || priv->scan_request_delay_source
+ || ( priv->sup_iface
+ && nm_supplicant_interface_get_scanning (priv->sup_iface));
+}
+
+static gboolean
+_scan_notify_is_scanning (NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
- gboolean scanning;
gboolean last_scan_changed = FALSE;
+ NMDeviceState state;
+ gboolean scanning;
- scanning = priv->sup_iface
- && nm_supplicant_interface_get_scanning (priv->sup_iface);
-
- if (scanning == priv->is_scanning)
- return;
+ scanning = _scan_is_scanning_eval (priv);
+ if (scanning == priv->scan_is_scanning)
+ return FALSE;
- priv->is_scanning = scanning;
+ priv->scan_is_scanning = scanning;
if ( !scanning
- || priv->last_scan_msec == 0) {
+ || priv->scan_last_complete_msec == 0) {
last_scan_changed = TRUE;
- priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec ();
+ priv->scan_last_complete_msec = nm_utils_get_monotonic_timestamp_msec ();
}
_LOGD (LOGD_WIFI,
@@ -281,7 +441,16 @@ _notify_scanning (NMDeviceWifi *self)
scanning ? "scanning" : "idle",
last_scan_changed ? " (notify last-scan)" : "");
- schedule_scan (self, TRUE);
+ state = nm_device_get_state (NM_DEVICE (self));
+
+ if (scanning) {
+ /* while the device is activating/activated, we don't need the pending
+ * action. The pending action exists to delay startup complete, while
+ * activating that is already achieved via other means. */
+ if ( state <= NM_DEVICE_STATE_DISCONNECTED
+ || state > NM_DEVICE_STATE_ACTIVATED)
+ nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
+ }
nm_gobject_notify_together (self,
PROP_SCANNING,
@@ -289,13 +458,73 @@ _notify_scanning (NMDeviceWifi *self)
? PROP_LAST_SCAN
: PROP_0);
- if (!priv->is_scanning) {
- _requested_scan_set (self, FALSE);
- if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED) {
- /* Run a quick update of current AP when coming out of a scan */
- periodic_update (self);
- }
+ _scan_kickoff (self);
+
+ if (!_scan_is_scanning_eval (priv)) {
+ if ( state <= NM_DEVICE_STATE_DISCONNECTED
+ || state > NM_DEVICE_STATE_ACTIVATED)
+ nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+ nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
}
+
+ return TRUE;
+}
+
+static gboolean
+_scan_notify_allowed (NMDeviceWifi *self, NMTernary do_kickoff)
+{
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+ gboolean explicit_allowed;
+ gboolean periodic_allowed;
+ NMDeviceState state;
+ gboolean changed = FALSE;
+
+ state = nm_device_get_state (NM_DEVICE (self));
+
+ explicit_allowed = FALSE;
+ periodic_allowed = FALSE;
+
+ if (!c_list_is_empty (&priv->scanning_prohibited_lst_head)) {
+ /* something prohibits scanning. */
+ } else if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC,
+ NM_802_11_MODE_AP)) {
+ /* Don't scan when a an AP or Ad-Hoc connection is active as it will
+ * disrupt connected clients or peers. */
+ } else if (NM_IN_SET (state, NM_DEVICE_STATE_DISCONNECTED,
+ NM_DEVICE_STATE_FAILED)) {
+ /* Can always scan when disconnected */
+ explicit_allowed = TRUE;
+ periodic_allowed = TRUE;
+ } else if (NM_IN_SET (state, NM_DEVICE_STATE_ACTIVATED)) {
+ /* Prohibit periodic scans when connected; we ask the supplicant to
+ * background scan for us, unless the connection is locked to a specific
+ * BSSID (in which case scanning is effectively disabled). */
+ periodic_allowed = FALSE;
+
+ /* Prohibit scans if the supplicant is busy */
+ explicit_allowed = !NM_IN_SET (nm_supplicant_interface_get_state (priv->sup_iface),
+ NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,
+ NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,
+ NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
+ NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE);
+ }
+
+ if ( explicit_allowed != priv->scan_explicit_allowed
+ || periodic_allowed != priv->scan_periodic_allowed) {
+ priv->scan_periodic_allowed = periodic_allowed;
+ priv->scan_explicit_allowed = explicit_allowed;
+ _LOGT_scan ("scan-periodic-allowed=%d, scan-explicit-allowed=%d",
+ periodic_allowed,
+ explicit_allowed);
+ changed = TRUE;
+ }
+
+ if ( do_kickoff == NM_TERNARY_TRUE
+ || ( do_kickoff == NM_TERNARY_DEFAULT
+ && changed))
+ _scan_kickoff (self);
+
+ return changed;
}
static void
@@ -303,7 +532,7 @@ supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
GParamSpec *pspec,
NMDeviceWifi *self)
{
- _notify_scanning (self);
+ _scan_notify_is_scanning (self);
}
static gboolean
@@ -370,7 +599,7 @@ supplicant_interface_acquire_cb (NMSupplicantManager *supplicant_manager,
G_CALLBACK (supplicant_iface_notify_p2p_available),
self);
- _notify_scanning (self);
+ _scan_notify_is_scanning (self);
if (nm_supplicant_interface_get_state (priv->sup_iface) != NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
/* fake an initial state change. */
@@ -399,26 +628,6 @@ supplicant_interface_acquire (NMDeviceWifi *self)
}
static void
-_requested_scan_set (NMDeviceWifi *self, gboolean value)
-{
- NMDeviceWifiPrivate *priv;
-
- value = !!value;
-
- priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
- if (priv->requested_scan == value)
- return;
-
- priv->requested_scan = value;
- if (value)
- nm_device_add_pending_action ((NMDevice *) self, NM_PENDING_ACTION_WIFI_SCAN, TRUE);
- else {
- nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
- nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, TRUE);
- }
-}
-
-static void
supplicant_interface_release (NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
@@ -426,14 +635,14 @@ supplicant_interface_release (NMDeviceWifi *self)
if (nm_clear_pointer (&priv->sup_create_handle, nm_supplicant_manager_create_interface_cancel))
nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
- _requested_scan_set (self, FALSE);
+ nm_clear_g_source (&priv->scan_kickoff_timeout_id);
+ nm_clear_g_source_inst (&priv->scan_request_delay_source);
+ nm_clear_g_cancellable (&priv->scan_request_cancellable);
- nm_clear_g_source (&priv->pending_scan_id);
+ _scan_request_ssids_remove_all (priv, 0, 0);
- /* Reset the scan interval to be pretty frequent when disconnected */
- priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP;
- _LOGD (LOGD_WIFI, "wifi-scan: reset interval to %u seconds",
- (unsigned) priv->scan_interval_sec);
+ priv->scan_periodic_interval_sec = 0;
+ priv->scan_periodic_next_msec = 0;
nm_clear_g_source (&priv->ap_dump_id);
@@ -452,7 +661,7 @@ supplicant_interface_release (NMDeviceWifi *self)
nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL);
}
- _notify_scanning (self);
+ _scan_notify_is_scanning (self);
}
static void
@@ -702,7 +911,6 @@ deactivate (NMDevice *device)
NMDeviceWifi *self = NM_DEVICE_WIFI (device);
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
int ifindex = nm_device_get_ifindex (device);
- NM80211Mode old_mode = priv->mode;
nm_clear_g_source (&priv->periodic_update_id);
@@ -732,9 +940,7 @@ deactivate (NMDevice *device)
_notify (self, PROP_MODE);
}
- /* Ensure we trigger a scan after deactivating a Hotspot */
- if (old_mode == NM_802_11_MODE_AP)
- request_wireless_scan (self, FALSE, FALSE, NULL);
+ _scan_notify_allowed (self, NM_TERNARY_TRUE);
}
static void
@@ -1057,10 +1263,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
static gboolean
get_autoconnect_allowed (NMDevice *device)
{
- NMDeviceWifiPrivate *priv;
-
- priv = NM_DEVICE_WIFI_GET_PRIVATE (NM_DEVICE_WIFI (device));
- return !priv->requested_scan;
+ return !NM_DEVICE_WIFI_GET_PRIVATE (NM_DEVICE_WIFI (device))->scan_is_scanning;
}
static gboolean
@@ -1238,6 +1441,7 @@ dbus_request_scan_cb (NMDevice *device,
gpointer user_data)
{
NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+ NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
gs_unref_ptrarray GPtrArray *ssids = user_data;
if (error) {
@@ -1245,15 +1449,9 @@ dbus_request_scan_cb (NMDevice *device,
return;
}
- if (check_scanning_prohibited (self, FALSE)) {
- g_dbus_method_invocation_return_error_literal (context,
- NM_DEVICE_ERROR,
- NM_DEVICE_ERROR_NOT_ALLOWED,
- "Scanning not allowed at this time");
- return;
- }
-
- request_wireless_scan (self, FALSE, FALSE, ssids);
+ _scan_request_ssids_track (priv, ssids);
+ priv->scan_explicit_requested = TRUE;
+ _scan_kickoff (self);
g_dbus_method_invocation_return_value (context, NULL);
}
@@ -1264,7 +1462,6 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
NMDevice *device = NM_DEVICE (self);
- gint64 last_scan;
gs_unref_ptrarray GPtrArray *ssids = NULL;
if (options) {
@@ -1291,35 +1488,11 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
if ( !priv->enabled
|| !priv->sup_iface
- || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
- || nm_device_is_activating (device)) {
- g_dbus_method_invocation_return_error_literal (invocation,
- NM_DEVICE_ERROR,
- NM_DEVICE_ERROR_NOT_ALLOWED,
- "Scanning not allowed while unavailable or activating");
- return;
- }
-
- if (nm_supplicant_interface_get_scanning (priv->sup_iface)) {
- g_dbus_method_invocation_return_error_literal (invocation,
- NM_DEVICE_ERROR,
- NM_DEVICE_ERROR_NOT_ALLOWED,
- "Scanning not allowed while already scanning");
- return;
- }
-
- last_scan = nm_supplicant_interface_get_last_scan (priv->sup_iface);
- if ( last_scan > 0
- && nm_utils_get_monotonic_timestamp_msec () < last_scan + (10 * NM_UTILS_MSEC_PER_SEC)) {
- /* FIXME: we really should not outright reject a scan request in this case. We should
- * ensure to start a scan request soon, possibly with rate limiting. And there is no
- * need to tell the caller that we aren't going to scan...
- *
- * Same above, if we are currently scanning... */
+ || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED) {
g_dbus_method_invocation_return_error_literal (invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ALLOWED,
- "Scanning not allowed immediately following previous scan");
+ "Scanning not allowed while unavailable");
return;
}
@@ -1334,64 +1507,6 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
}
static gboolean
-check_scanning_prohibited (NMDeviceWifi *self,
- gboolean periodic)
-{
- NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
- nm_assert (NM_IS_SUPPLICANT_INTERFACE (priv->sup_iface));
-
- if (!c_list_is_empty (&priv->scanning_prohibited_lst_head))
- return TRUE;
-
- /* Don't scan when a an AP or Ad-Hoc connection is active as it will
- * disrupt connected clients or peers.
- */
- if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC,
- NM_802_11_MODE_AP))
- return TRUE;
-
- switch (nm_device_get_state (NM_DEVICE (self))) {
- case NM_DEVICE_STATE_UNKNOWN:
- case NM_DEVICE_STATE_UNMANAGED:
- case NM_DEVICE_STATE_UNAVAILABLE:
- case NM_DEVICE_STATE_PREPARE:
- case NM_DEVICE_STATE_CONFIG:
- case NM_DEVICE_STATE_NEED_AUTH:
- case NM_DEVICE_STATE_IP_CONFIG:
- case NM_DEVICE_STATE_IP_CHECK:
- case NM_DEVICE_STATE_SECONDARIES:
- case NM_DEVICE_STATE_DEACTIVATING:
- /* Prohibit scans when unusable or activating */
- return TRUE;
- case NM_DEVICE_STATE_DISCONNECTED:
- case NM_DEVICE_STATE_FAILED:
- /* Can always scan when disconnected */
- return FALSE;
- case NM_DEVICE_STATE_ACTIVATED:
- /* Prohibit periodic scans when connected; we ask the supplicant to
- * background scan for us, unless the connection is locked to a specific
- * BSSID.
- */
- if (periodic)
- return TRUE;
- break;
- }
-
- /* Prohibit scans if the supplicant is busy */
- if ( NM_IN_SET (nm_supplicant_interface_get_state (priv->sup_iface),
- NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,
- NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,
- NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
- NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE)
- || nm_supplicant_interface_get_scanning (priv->sup_iface))
- return TRUE;
-
- /* Allow the scan */
- return FALSE;
-}
-
-static gboolean
hidden_filter_func (NMSettings *settings,
NMSettingsConnection *set_con,
gpointer user_data)
@@ -1410,177 +1525,287 @@ hidden_filter_func (NMSettings *settings,
}
static GPtrArray *
-build_hidden_probe_list (NMDeviceWifi *self)
+_scan_request_ssids_build_hidden (NMDeviceWifi *self,
+ gboolean *out_has_hidden_profiles)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids (priv->sup_iface);
gs_free NMSettingsConnection **connections = NULL;
- guint i, len;
- GPtrArray *ssids = NULL;
- static GBytes *nullssid = NULL;
+ gs_unref_ptrarray GPtrArray *ssids = NULL;
+ gs_unref_hashtable GHashTable *unique_ssids = NULL;
+ guint connections_len;
+ guint n_hidden;
+ guint i;
+
+ NM_SET_OUT (out_has_hidden_profiles, FALSE);
- /* Need at least two: wildcard SSID and one or more hidden SSIDs */
- if (max_scan_ssids < 2)
+ /* collect all pending explicit SSIDs. */
+ ssids = _scan_request_ssids_fetch (priv);
+
+ if (max_scan_ssids == 0) {
+ /* no space. @ssids will be ignored. */
return NULL;
+ }
+
+ if (ssids) {
+ if (ssids->len < max_scan_ssids) {
+ /* Add wildcard SSID using a static wildcard SSID used for every scan */
+ g_ptr_array_insert (ssids, 0, g_bytes_ref (nm_gbytes_get_empty ()));
+ }
+ if (ssids->len >= max_scan_ssids) {
+ /* there is no more space. Use what we have. */
+ g_ptr_array_set_size (ssids, max_scan_ssids);
+ return g_steal_pointer (&ssids);
+ }
+ }
connections = nm_settings_get_connections_clone (nm_device_get_settings ((NMDevice *) self),
- &len,
- hidden_filter_func, NULL,
- NULL, NULL);
+ &connections_len,
+ hidden_filter_func,
+ NULL,
+ NULL,
+ NULL);
if (!connections[0])
- return NULL;
+ return g_steal_pointer (&ssids);
- g_qsort_with_data (connections, len, sizeof (NMSettingsConnection *), nm_settings_connection_cmp_timestamp_p_with_data, NULL);
+ if (!ssids) {
+ ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref);
+ /* Add wildcard SSID using a static wildcard SSID used for every scan */
+ g_ptr_array_insert (ssids, 0, g_bytes_ref (nm_gbytes_get_empty ()));
+ }
- ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref);
+ unique_ssids = g_hash_table_new (nm_gbytes_hash, nm_gbytes_equal);
+ for (i = 1; i < ssids->len; i++) {
+ if (!g_hash_table_add (unique_ssids, ssids->pdata[i]))
+ nm_assert_not_reached ();
+ }
- /* Add wildcard SSID using a static wildcard SSID used for every scan */
- if (G_UNLIKELY (nullssid == NULL))
- nullssid = g_bytes_new_static ("", 0);
- g_ptr_array_add (ssids, g_bytes_ref (nullssid));
+ g_qsort_with_data (connections,
+ connections_len,
+ sizeof (NMSettingsConnection *),
+ nm_settings_connection_cmp_timestamp_p_with_data,
+ NULL);
- for (i = 0; connections[i]; i++) {
+ n_hidden = 0;
+ for (i = 0; i < connections_len; i++) {
NMSettingWireless *s_wifi;
GBytes *ssid;
- if (i >= max_scan_ssids - 1)
+ if (ssids->len >= max_scan_ssids)
break;
- s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i]));
+ if (n_hidden > 4) {
+ /* we allow at most 4 hidden profiles to be actively scanned. The
+ * reason is speed and to not disclose too many SSIDs. */
+ break;
+ }
+
+ s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i]));
ssid = nm_setting_wireless_get_ssid (s_wifi);
+
+ if (!g_hash_table_add (unique_ssids, ssid))
+ continue;
+
g_ptr_array_add (ssids, g_bytes_ref (ssid));
+ n_hidden++;
}
- return ssids;
+ NM_SET_OUT (out_has_hidden_profiles, n_hidden > 0);
+ return g_steal_pointer (&ssids);
}
-static void
-request_wireless_scan (NMDeviceWifi *self,
- gboolean periodic,
- gboolean force_if_scanning,
- const GPtrArray *ssids)
+static gboolean
+_scan_request_delay_cb (gpointer user_data)
{
+ NMDeviceWifi *self = user_data;
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
- gboolean request_started = FALSE;
- nm_clear_g_source (&priv->pending_scan_id);
+ nm_clear_g_source_inst (&priv->scan_request_delay_source);
- if (!force_if_scanning && priv->requested_scan) {
- /* There's already a scan in progress */
- return;
- }
-
- if (!check_scanning_prohibited (self, periodic)) {
- gs_unref_ptrarray GPtrArray *hidden_ssids = NULL;
+ _LOGT_scan ("scan request completed (after extra delay)");
- _LOGD (LOGD_WIFI, "wifi-scan: scanning requested");
-
- if (!ssids) {
- hidden_ssids = build_hidden_probe_list (self);
- if (hidden_ssids) {
- if (priv->hidden_probe_scan_warn) {
- priv->hidden_probe_scan_warn = FALSE;
- _LOGW (LOGD_WIFI, "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. This makes you trackable");
- }
- ssids = hidden_ssids;
- } else
- priv->hidden_probe_scan_warn = TRUE;
- }
-
- if (_LOGD_ENABLED (LOGD_WIFI)) {
- if (ssids) {
- guint i;
-
- for (i = 0; i < ssids->len; i++) {
- gs_free char *ssid_str = NULL;
- GBytes *ssid = ssids->pdata[i];
+ _scan_notify_is_scanning (self);
+ return G_SOURCE_REMOVE;
+}
- ssid_str = g_bytes_get_size (ssid) > 0
- ? _nm_utils_ssid_to_string (ssid)
- : NULL;
- _LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s",
- i, ssid_str ?: "*any*");
- }
- } else
- _LOGD (LOGD_WIFI, "wifi-scan: no SSIDs to probe scan");
- }
+static void
+_scan_supplicant_request_scan_cb (NMSupplicantInterface *supp_iface,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ NMDeviceWifi *self;
+ NMDeviceWifiPrivate *priv;
- _hw_addr_set_scanning (self, FALSE);
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
- nm_supplicant_interface_request_scan (priv->sup_iface,
- ssids ? (GBytes *const*) ssids->pdata : NULL,
- ssids ? ssids->len : 0u,
- NULL,
- NULL,
- NULL);
+ self = user_data;
+ priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
- request_started = TRUE;
- } else
- _LOGD (LOGD_WIFI, "wifi-scan: scanning requested but not allowed at this time");
+ _LOGT_scan ("scan request completed (D-Bus request)");
- _requested_scan_set (self, request_started);
+ /* we just completed a scan request, but possibly the supplicant's state is not yet toggled
+ * to "scanning". That means, our internal scanning state "priv->scan_is_scanning" would already
+ * flip to idle, while in a moment the supplicant would toggle the state again.
+ *
+ * Artificially keep the scanning state on, for another SCAN_EXTRA_DELAY_MSEC msec. */
+ nm_clear_g_source_inst (&priv->scan_request_delay_source);
+ priv->scan_request_delay_source = nm_g_source_attach (nm_g_timeout_source_new (SCAN_EXTRA_DELAY_MSEC,
+ G_PRIORITY_DEFAULT,
+ _scan_request_delay_cb,
+ self,
+ NULL),
+ NULL);
- schedule_scan (self, request_started);
+ g_clear_object (&priv->scan_request_cancellable);
+ _scan_notify_is_scanning (self);
}
static gboolean
-request_wireless_scan_periodic (gpointer user_data)
+_scan_kickoff_timeout_cb (gpointer user_data)
{
NMDeviceWifi *self = user_data;
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
- priv->pending_scan_id = 0;
- request_wireless_scan (self, TRUE, FALSE, NULL);
+ priv->scan_kickoff_timeout_id = 0;
+ _scan_kickoff (self);
return G_SOURCE_REMOVE;
}
-/*
- * schedule_scan
- *
- * Schedule a wireless scan.
- *
- */
static void
-schedule_scan (NMDeviceWifi *self, gboolean backoff)
+_scan_kickoff (NMDeviceWifi *self)
{
NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
- gint32 now = nm_utils_get_monotonic_timestamp_sec ();
-
- /* Cancel the pending scan if it would happen later than (now + the scan_interval_sec) */
- if (priv->pending_scan_id) {
- if (now + priv->scan_interval_sec < priv->scheduled_scan_time)
- nm_clear_g_source (&priv->pending_scan_id);
- }
-
- if (!priv->pending_scan_id) {
- guint factor = 2;
- guint next_scan = priv->scan_interval_sec;
-
- 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_periodic,
- self);
-
- priv->scheduled_scan_time = now + priv->scan_interval_sec;
- if (backoff && (priv->scan_interval_sec < (SCAN_INTERVAL_SEC_MAX / factor))) {
- priv->scan_interval_sec += (SCAN_INTERVAL_SEC_STEP / factor);
- /* Ensure the scan interval will never be less than 20s... */
- priv->scan_interval_sec = MAX(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP);
- /* ... or more than 120s */
- priv->scan_interval_sec = MIN(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MAX);
- } else if (!backoff && (priv->scan_interval_sec == 0)) {
- /* Invalid combination; would cause continual rescheduling of
- * the scan and hog CPU. Reset to something minimally sane.
- */
- priv->scan_interval_sec = 5;
+ gs_unref_ptrarray GPtrArray *ssids = NULL;
+ gboolean is_explict = FALSE;
+ gboolean has_hidden_profiles;
+ gint64 now_msec;
+
+ if (priv->scan_request_cancellable) {
+ _LOGT_scan ("kickoff: don't scan (has scan_request_cancellable)");
+ /* We are currently waiting for a scan request to complete. Wait longer. */
+ return;
+ }
+
+ now_msec = nm_utils_get_monotonic_timestamp_msec ();
+
+ _scan_request_ssids_remove_all (priv, now_msec, SCAN_REQUEST_SSIDS_MAX_NUM);
+
+ if (now_msec < priv->scan_ratelimited_until_msec) {
+ _LOGT_scan ("kickoff: don't scan (rate limited for another %d.%03d msec%s)",
+ (int) ((priv->scan_ratelimited_until_msec - now_msec) / 1000),
+ (int) ((priv->scan_ratelimited_until_msec - now_msec) % 1000),
+ !priv->scan_kickoff_timeout_id ? ", schedule timeout" : "");
+ if ( !priv->scan_kickoff_timeout_id
+ && ( priv->scan_explicit_allowed
+ || priv->scan_periodic_allowed)) {
+ priv->scan_kickoff_timeout_id = g_timeout_add_seconds ((priv->scan_ratelimited_until_msec - now_msec + 999) / 1000,
+ _scan_kickoff_timeout_cb,
+ self);
+ }
+ return;
+ }
+
+ if (priv->scan_explicit_requested) {
+ if (!priv->scan_explicit_allowed) {
+ _LOGT_scan ("kickoff: don't scan (explicit scan requested but not allowed)");
+ return;
}
+ priv->scan_explicit_requested = FALSE;
+ is_explict = TRUE;
+ } else {
- _LOGD (LOGD_WIFI, "wifi-scan: scheduled in %d seconds (interval now %d seconds)",
- next_scan, priv->scan_interval_sec);
+ if (!priv->scan_periodic_allowed) {
+ _LOGT_scan ("kickoff: don't scan (periodic scan currently not allowed)");
+ priv->scan_periodic_next_msec = 0;
+ priv->scan_periodic_interval_sec = 0;
+ nm_clear_g_source (&priv->scan_kickoff_timeout_id);
+ return;
+ }
+
+ nm_assert (priv->scan_explicit_allowed);
+
+ if (now_msec < priv->scan_periodic_next_msec) {
+ _LOGT_scan ("kickoff: don't scan (periodic scan waiting for another %d.%03d msec%s)",
+ (int) ((priv->scan_periodic_next_msec - now_msec) / 1000),
+ (int) ((priv->scan_periodic_next_msec - now_msec) % 1000),
+ !priv->scan_kickoff_timeout_id ? ", schedule timeout" : "");
+ if (!priv->scan_kickoff_timeout_id) {
+ priv->scan_kickoff_timeout_id = g_timeout_add_seconds ((priv->scan_periodic_next_msec - now_msec + 999) / 1000,
+ _scan_kickoff_timeout_cb,
+ self);
+ }
+ return;
+ }
+
+ priv->scan_periodic_interval_sec = NM_CLAMP (((int) priv->scan_periodic_interval_sec) * 3 / 2,
+ SCAN_INTERVAL_SEC_MIN,
+ SCAN_INTERVAL_SEC_MAX);
+ priv->scan_periodic_next_msec = now_msec + 1000 * priv->scan_periodic_interval_sec;
}
+
+ ssids = _scan_request_ssids_build_hidden (self, &has_hidden_profiles);
+ if (has_hidden_profiles) {
+ if (priv->hidden_probe_scan_warn) {
+ priv->hidden_probe_scan_warn = FALSE;
+ _LOGW (LOGD_WIFI, "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. This makes you trackable");
+ }
+ } else if (!is_explict)
+ priv->hidden_probe_scan_warn = TRUE;
+
+ if (_LOGD_ENABLED (LOGD_WIFI)) {
+ gs_free char *ssids_str = NULL;
+ guint ssids_len = 0;
+
+ if (ssids) {
+ gs_strfreev char **strv = NULL;
+ guint i;
+
+ strv = g_new (char *, ssids->len + 1u);
+ for (i = 0; i < ssids->len; i++)
+ strv[i] = _nm_utils_ssid_to_string (ssids->pdata[i]);
+ strv[i] = NULL;
+
+ nm_assert (ssids->len > 0);
+ nm_assert (ssids->len == NM_PTRARRAY_LEN (strv));
+
+ ssids_str = g_strjoinv (", ", strv);
+ ssids_len = ssids->len;
+ }
+ _LOGD (LOGD_WIFI, "wifi-scan: start %s scan (%u SSIDs to probe scan%s%s%s)",
+ is_explict ? "explicit" : "periodic",
+ ssids_len,
+ NM_PRINT_FMT_QUOTED (ssids_str, " [", ssids_str, "]", ""));
+ }
+
+ priv->scan_ratelimited_until_msec = now_msec + (1000 * SCAN_RATE_LIMIT_SEC);
+
+ if (is_explict)
+ _LOGT_scan ("kickoff: explicit scan (ratelimited for %d.%03d msec)",
+ (int) ((priv->scan_ratelimited_until_msec - now_msec) / 1000),
+ (int) ((priv->scan_ratelimited_until_msec - now_msec) % 1000));
+ else {
+ _LOGT_scan ("kickoff: periodic scan (next scan is scheduled in %d.%03d msec, ratelimited for %d.%03d msec)",
+ (int) ((priv->scan_periodic_next_msec - now_msec) / 1000),
+ (int) ((priv->scan_periodic_next_msec - now_msec) % 1000),
+ (int) ((priv->scan_ratelimited_until_msec - now_msec) / 1000),
+ (int) ((priv->scan_ratelimited_until_msec - now_msec) % 1000));
+ }
+
+ _hw_addr_set_scanning (self, FALSE);
+
+ priv->scan_request_cancellable = g_cancellable_new ();
+ nm_supplicant_interface_request_scan (priv->sup_iface,
+ ssids ? (GBytes *const*) ssids->pdata : NULL,
+ ssids ? ssids->len : 0u,
+ priv->scan_request_cancellable,
+ _scan_supplicant_request_scan_cb,
+ self);
+
+ /* It's OK to call _scan_notify_is_scanning() again. They mutually call each other,
+ * but _scan_kickoff() sets "priv->scan_request_cancellable" which will stop
+ * them from recursing indefinitely. */
+ _scan_notify_is_scanning (self);
}
/****************************************************************************
@@ -1601,16 +1826,15 @@ ap_list_dump (gpointer user_data)
gint64 now_msec = nm_utils_get_monotonic_timestamp_msec ();
char str_buf[100];
- _LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s, next:%u]",
+ _LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s]",
(guint) (now_msec / NM_UTILS_MSEC_PER_SEC),
(guint) (now_msec % NM_UTILS_MSEC_PER_SEC),
- priv->last_scan_msec > 0
+ priv->scan_last_complete_msec > 0
? nm_sprintf_buf (str_buf,
"%u.%03u",
- (guint) (priv->last_scan_msec / NM_UTILS_MSEC_PER_SEC),
- (guint) (priv->last_scan_msec % NM_UTILS_MSEC_PER_SEC))
- : "-1",
- priv->scheduled_scan_time);
+ (guint) (priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC),
+ (guint) (priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC))
+ : "-1");
c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
_ap_dump (self, LOGL_DEBUG, ap, "dump", now_msec);
}
@@ -2142,6 +2366,7 @@ supplicant_iface_state (NMDeviceWifi *self,
NMDevice *device = NM_DEVICE (self);
NMDeviceState devstate;
gboolean scanning;
+ gboolean scan_changed;
_LOGI (LOGD_DEVICE | LOGD_WIFI,
"supplicant interface state: %s -> %s%s",
@@ -2162,7 +2387,8 @@ supplicant_iface_state (NMDeviceWifi *self,
nm_device_queue_recheck_available (NM_DEVICE (device),
NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
- priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
+ priv->scan_periodic_interval_sec = 0;
+ priv->scan_periodic_next_msec = 0;
}
/* In these states we know the supplicant is actually talking to something */
@@ -2229,17 +2455,19 @@ supplicant_iface_state (NMDeviceWifi *self,
}
break;
case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
- /* we would clear _requested_scan_set() and trigger a new scan.
+ /* we would clear _scan_has_pending_action_set() and trigger a new scan.
* However, we don't want to cancel the current pending action, so force
* a new scan request. */
- request_wireless_scan (self, FALSE, TRUE, NULL);
break;
default:
break;
}
out:
- _notify_scanning (self);
+ scan_changed = _scan_notify_allowed (self, NM_TERNARY_FALSE);
+ scan_changed |= _scan_notify_is_scanning (self);
+ if (scan_changed)
+ _scan_kickoff (self);
if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
@@ -2768,6 +2996,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
ap = ap_fake;
}
+ _scan_notify_allowed (self, NM_TERNARY_DEFAULT);
+
set_current_ap (self, ap, FALSE);
nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
@@ -3107,8 +3337,8 @@ activation_success_handler (NMDevice *device)
update_seen_bssids_cache (self, priv->current_ap);
- /* Reset scan interval to something reasonable */
- priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + (SCAN_INTERVAL_SEC_STEP * 2);
+ priv->scan_periodic_interval_sec = 0;
+ priv->scan_periodic_next_msec = 0;
}
static void
@@ -3168,9 +3398,6 @@ device_state_changed (NMDevice *device,
nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
break;
case NM_DEVICE_STATE_DISCONNECTED:
- /* Kick off a scan to get latest results */
- priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
- request_wireless_scan (self, FALSE, FALSE, NULL);
break;
default:
break;
@@ -3178,6 +3405,8 @@ device_state_changed (NMDevice *device,
if (clear_aps)
remove_all_aps (self);
+
+ _scan_notify_allowed (self, NM_TERNARY_DEFAULT);
}
static gboolean
@@ -3328,8 +3557,8 @@ get_property (GObject *object, guint prop_id,
break;
case PROP_LAST_SCAN:
g_value_set_int64 (value,
- priv->last_scan_msec > 0
- ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan_msec, NM_UTILS_NSEC_PER_MSEC)
+ priv->scan_last_complete_msec > 0
+ ? nm_utils_monotonic_timestamp_as_boottime (priv->scan_last_complete_msec, NM_UTILS_NSEC_PER_MSEC)
: (gint64) -1);
break;
default:
@@ -3365,6 +3594,7 @@ nm_device_wifi_init (NMDeviceWifi *self)
c_list_init (&priv->aps_lst_head);
c_list_init (&priv->scanning_prohibited_lst_head);
+ c_list_init (&priv->scan_request_ssids_lst_head);
priv->aps_idx_by_supplicant_path = g_hash_table_new (nm_direct_hash, NULL);
priv->hidden_probe_scan_warn = TRUE;
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 700563624a..1c7b9a4213 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -154,6 +154,9 @@ typedef struct _NMSupplicantInterfacePrivate {
bool is_ready_main:1;
bool is_ready_p2p_device:1;
+ bool prop_scan_active:1;
+ bool prop_scan_ssid:1;
+
} NMSupplicantInterfacePrivate;
struct _NMSupplicantInterfaceClass {
@@ -1167,10 +1170,11 @@ static void
parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
{
NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
- gboolean have_active = FALSE;
- gboolean have_ssid = FALSE;
+ const gboolean old_prop_scan_active = priv->prop_scan_active;
+ const gboolean old_prop_scan_ssid = priv->prop_scan_ssid;
+ const guint32 old_max_scan_ssids = priv->max_scan_ssids;
gboolean have_ft = FALSE;
- gint32 max_scan_ssids = -1;
+ gint32 max_scan_ssids;
const char **array;
nm_assert (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
@@ -1195,28 +1199,37 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
}
if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) {
- if (g_strv_contains (array, "active"))
- have_active = TRUE;
- if (g_strv_contains (array, "ssid"))
- have_ssid = TRUE;
+ const char **a;
+
+ priv->prop_scan_active = FALSE;
+ priv->prop_scan_ssid = FALSE;
+ for (a = array; *a; a++) {
+ if (nm_streq (*a, "active"))
+ priv->prop_scan_active = TRUE;
+ else if (nm_streq (*a, "ssid"))
+ priv->prop_scan_ssid = TRUE;
+ }
g_free (array);
}
if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) {
- /* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */
- if ( max_scan_ssids > 0
- && have_active
- && have_ssid) {
- /* wpa_supplicant's NM_WPAS_MAX_SCAN_SSIDS value is 16, but for speed
- * and to ensure we don't disclose too many SSIDs from the hidden
- * list, we'll limit to 5.
- */
- max_scan_ssids = CLAMP (max_scan_ssids, 0, 5);
- if (max_scan_ssids != priv->max_scan_ssids) {
- priv->max_scan_ssids = max_scan_ssids;
- _LOGD ("supports %d scan SSIDs", priv->max_scan_ssids);
- }
- }
+ const gint32 WPAS_MAX_SCAN_SSIDS = 16;
+
+ /* Even if supplicant claims that 20 SSIDs are supported, the Scan request
+ * still only accepts WPAS_MAX_SCAN_SSIDS SSIDs. Otherwise the D-Bus
+ * request will be rejected with "fi.w1.wpa_supplicant1.InvalidArgs"
+ * Body: ('Did not receive correct message arguments.', 'Too many ssids specified. Specify at most four')
+ * */
+ priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, WPAS_MAX_SCAN_SSIDS);
+ }
+
+ if ( old_max_scan_ssids != priv->max_scan_ssids
+ || old_prop_scan_active != priv->prop_scan_active
+ || old_prop_scan_ssid != priv->prop_scan_ssid) {
+ _LOGD ("supports %u scan SSIDs (scan: %cactive %cssid)",
+ (guint32) priv->max_scan_ssids,
+ priv->prop_scan_active ? '+' : '-',
+ priv->prop_scan_ssid ? '+' : '-');
}
}
@@ -2514,9 +2527,14 @@ nm_supplicant_interface_get_ifname (NMSupplicantInterface *self)
guint
nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self)
{
+ NMSupplicantInterfacePrivate *priv;
+
g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), 0);
- return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->max_scan_ssids;
+ priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+ return priv->prop_scan_active && priv->prop_scan_ssid
+ ? priv->max_scan_ssids
+ : 0u;
}
/*****************************************************************************/