nl80211: Recover from auth req ENOENT with a scan
authorJouni Malinen <j@w1.fi>
Sun, 4 Dec 2011 19:53:56 +0000 (21:53 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 4 Dec 2011 19:53:56 +0000 (21:53 +0200)
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>

src/drivers/driver_nl80211.c

index 55d3fd1..085ee20 100644 (file)
@@ -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;