Browse Source

nl80211: Recover from auth req ENOENT with a scan

cfg80211 rejects NL80211_CMD_AUTHENTICATE with ENOENT if the BSS entry
for the target BSS is not available. This can happen if the cfg80211
entry has expired before wpa_supplicant entry (e.g., during a suspend).
To recover from this quickly, run a single channel scan to get the
cfg80211 entry back and then retry authentication command again. This
is handled within driver_nl80211.c to keep the core wpa_supplicant
implementation cleaner.

Signed-hostap: Jouni Malinen <j@w1.fi>
Jouni Malinen 13 years ago
parent
commit
536fd62dba
1 changed files with 149 additions and 0 deletions
  1. 149 0
      src/drivers/driver_nl80211.c

+ 149 - 0
src/drivers/driver_nl80211.c

@@ -238,6 +238,8 @@ struct wpa_driver_nl80211_data {
 	unsigned int device_ap_sme:1;
 	unsigned int poll_command_supported:1;
 	unsigned int data_tx_status:1;
+	unsigned int scan_for_auth:1;
+	unsigned int retry_auth:1;
 
 	u64 remain_on_chan_cookie;
 	u64 send_action_cookie;
@@ -261,6 +263,20 @@ struct wpa_driver_nl80211_data {
 	int last_freq;
 	int last_freq_ht;
 #endif /* HOSTAPD */
+
+	/* From failed authentication command */
+	int auth_freq;
+	u8 auth_bssid_[ETH_ALEN];
+	u8 auth_ssid[32];
+	size_t auth_ssid_len;
+	int auth_alg;
+	u8 *auth_ie;
+	size_t auth_ie_len;
+	u8 auth_wep_key[4][16];
+	size_t auth_wep_key_len[4];
+	int auth_wep_tx_keyidx;
+	int auth_local_state_change;
+	int auth_p2p;
 };
 
 
@@ -313,6 +329,8 @@ static int nl80211_disable_11b_rates(struct wpa_driver_nl80211_data *drv,
 				     int ifindex, int disabled);
 
 static int nl80211_leave_ibss(struct wpa_driver_nl80211_data *drv);
+static int wpa_driver_nl80211_authenticate_retry(
+	struct wpa_driver_nl80211_data *drv);
 
 
 static int is_ap_interface(enum nl80211_iftype nlmode)
@@ -1271,6 +1289,14 @@ static void send_scan_event(struct wpa_driver_nl80211_data *drv, int aborted,
 	int freqs[MAX_REPORT_FREQS];
 	int num_freqs = 0;
 
+	if (drv->scan_for_auth) {
+		drv->scan_for_auth = 0;
+		wpa_printf(MSG_DEBUG, "nl80211: Scan results for missing "
+			   "cfg80211 BSS entry");
+		wpa_driver_nl80211_authenticate_retry(drv);
+		return;
+	}
+
 	os_memset(&event, 0, sizeof(event));
 	info = &event.scan_info;
 	info->aborted = aborted;
@@ -2775,6 +2801,8 @@ static void wpa_driver_nl80211_deinit(void *priv)
 
 	os_free(drv->filter_ssids);
 
+	os_free(drv->auth_ie);
+
 	if (drv->in_interface_list)
 		dl_list_del(&drv->list);
 
@@ -2818,6 +2846,8 @@ static int wpa_driver_nl80211_scan(void *priv,
 	struct nl_msg *msg, *ssids, *freqs, *rates;
 	size_t i;
 
+	drv->scan_for_auth = 0;
+
 	msg = nlmsg_alloc();
 	ssids = nlmsg_alloc();
 	freqs = nlmsg_alloc();
@@ -3798,6 +3828,52 @@ static int wpa_driver_nl80211_disassociate(void *priv, const u8 *addr,
 }
 
 
+static void nl80211_copy_auth_params(struct wpa_driver_nl80211_data *drv,
+				     struct wpa_driver_auth_params *params)
+{
+	int i;
+
+	drv->auth_freq = params->freq;
+	drv->auth_alg = params->auth_alg;
+	drv->auth_wep_tx_keyidx = params->wep_tx_keyidx;
+	drv->auth_local_state_change = params->local_state_change;
+	drv->auth_p2p = params->p2p;
+
+	if (params->bssid)
+		os_memcpy(drv->auth_bssid_, params->bssid, ETH_ALEN);
+	else
+		os_memset(drv->auth_bssid_, 0, ETH_ALEN);
+
+	if (params->ssid) {
+		os_memcpy(drv->auth_ssid, params->ssid, params->ssid_len);
+		drv->auth_ssid_len = params->ssid_len;
+	} else
+		drv->auth_ssid_len = 0;
+
+
+	os_free(drv->auth_ie);
+	drv->auth_ie = NULL;
+	drv->auth_ie_len = 0;
+	if (params->ie) {
+		drv->auth_ie = os_malloc(params->ie_len);
+		if (drv->auth_ie) {
+			os_memcpy(drv->auth_ie, params->ie, params->ie_len);
+			drv->auth_ie_len = params->ie_len;
+		}
+	}
+
+	for (i = 0; i < 4; i++) {
+		if (params->wep_key[i] && params->wep_key_len[i] &&
+		    params->wep_key_len[i] <= 16) {
+			os_memcpy(drv->auth_wep_key[i], params->wep_key[i],
+				  params->wep_key_len[i]);
+			drv->auth_wep_key_len[i] = params->wep_key_len[i];
+		} else
+			drv->auth_wep_key_len[i] = 0;
+	}
+}
+
+
 static int wpa_driver_nl80211_authenticate(
 	void *priv, struct wpa_driver_auth_params *params)
 {
@@ -3808,6 +3884,10 @@ static int wpa_driver_nl80211_authenticate(
 	enum nl80211_auth_type type;
 	enum nl80211_iftype nlmode;
 	int count = 0;
+	int is_retry;
+
+	is_retry = drv->retry_auth;
+	drv->retry_auth = 0;
 
 	drv->associated = 0;
 	os_memset(drv->auth_bssid, 0, ETH_ALEN);
@@ -3903,6 +3983,36 @@ retry:
 			nlmsg_free(msg);
 			goto retry;
 		}
+
+		if (ret == -ENOENT && params->freq && !is_retry) {
+			/*
+			 * cfg80211 has likely expired the BSS entry even
+			 * though it was previously available in our internal
+			 * BSS table. To recover quickly, start a single
+			 * channel scan on the specified channel.
+			 */
+			struct wpa_driver_scan_params scan;
+			int freqs[2];
+
+			os_memset(&scan, 0, sizeof(scan));
+			scan.num_ssids = 1;
+			if (params->ssid) {
+				scan.ssids[0].ssid = params->ssid;
+				scan.ssids[0].ssid_len = params->ssid_len;
+			}
+			freqs[0] = params->freq;
+			freqs[1] = 0;
+			scan.freqs = freqs;
+			wpa_printf(MSG_DEBUG, "nl80211: Trigger single "
+				   "channel scan to refresh cfg80211 BSS "
+				   "entry");
+			ret = wpa_driver_nl80211_scan(bss, &scan);
+			if (ret == 0) {
+				nl80211_copy_auth_params(drv, params);
+				drv->scan_for_auth = 1;
+			}
+		}
+
 		goto nla_put_failure;
 	}
 	ret = 0;
@@ -3915,6 +4025,45 @@ nla_put_failure:
 }
 
 
+static int wpa_driver_nl80211_authenticate_retry(
+	struct wpa_driver_nl80211_data *drv)
+{
+	struct wpa_driver_auth_params params;
+	struct i802_bss *bss = &drv->first_bss;
+	int i;
+
+	wpa_printf(MSG_DEBUG, "nl80211: Try to authenticate again");
+
+	os_memset(&params, 0, sizeof(params));
+	params.freq = drv->auth_freq;
+	params.auth_alg = drv->auth_alg;
+	params.wep_tx_keyidx = drv->auth_wep_tx_keyidx;
+	params.local_state_change = drv->auth_local_state_change;
+	params.p2p = drv->auth_p2p;
+
+	if (!is_zero_ether_addr(drv->auth_bssid_))
+		params.bssid = drv->auth_bssid_;
+
+	if (drv->auth_ssid_len) {
+		params.ssid = drv->auth_ssid;
+		params.ssid_len = drv->auth_ssid_len;
+	}
+
+	params.ie = drv->auth_ie;
+	params.ie_len = drv->auth_ie_len;
+
+	for (i = 0; i < 4; i++) {
+		if (drv->auth_wep_key_len[i]) {
+			params.wep_key[i] = drv->auth_wep_key[i];
+			params.wep_key_len[i] = drv->auth_wep_key_len[i];
+		}
+	}
+
+	drv->retry_auth = 1;
+	return wpa_driver_nl80211_authenticate(bss, &params);
+}
+
+
 struct phy_info_arg {
 	u16 *num_modes;
 	struct hostapd_hw_modes *modes;