CHROMIUMOS: wpa_supplicant: add fast reconnect support

When dropped by an AP for inactivity or because the session has timed
out, try to re-join directly before falling back to a full scan and
the normal work.  This reduces the time to re-join a network from 5-10
seconds to ~300ms + time to renew DHCP.

Note we cannot directly re-authenticate because by the time we issue
a request mac80211 has discarded the BSS state which is required to
process an MLME request.  Instead we scan the channel where we last
saw the AP and then use the normal join mechanism. Since this is not
true for all drivers it might be worthwhile to try a direct Auth first
and only then fall back to scanning.

BUG=chromium-os:10733
TEST=Roaming + manual tests

Review URL: http://codereview.chromium.org/6538036

Change-Id: I7f275ece747dfee26de18aa2b1c2dd370abae9ad
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 85f7558..1499378 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -884,9 +884,13 @@
 	struct wpa_bss *selected;
 	struct wpa_ssid *ssid = NULL;
 	struct wpa_scan_results *scan_res;
+	int was_fast_reconnect;
 
 	wpa_supplicant_notify_scanning(wpa_s, 0);
 
+	was_fast_reconnect = wpa_s->fast_reconnect;
+	wpa_s->fast_reconnect = FALSE;
+
 	scan_res = wpa_supplicant_get_scan_results(wpa_s,
 						   data ? &data->scan_info :
 						   NULL, 1);
@@ -950,6 +954,14 @@
 		if (skip)
 			return;
 		wpa_supplicant_connect(wpa_s, selected, ssid);
+	} else if (was_fast_reconnect) {
+		/*
+		 * Processed deferred disassoc work on a failed
+		 * fast reconnect.
+		 */
+		wpa_printf(MSG_INFO, "Fast reconnect failed");
+		wpa_scan_results_free(scan_res);
+		wpa_supplicant_mark_disassoc(wpa_s);
 	} else {
 		wpa_scan_results_free(scan_res);
 		wpa_printf(MSG_DEBUG, "No suitable network found");
@@ -1245,10 +1257,47 @@
 }
 
 
+/*
+ * Try to return to the same AP immediately.  Directly scan
+ * on the current channel for the current BSSID and try to
+ * reassociate.  If the station is not present we will clock
+ * the state machine to DISCONNECTED in wpa_supplicant_event_scan_results
+ * which will notify external agents.
+ */
+static int wpa_supplicant_fast_reconnect(struct wpa_supplicant *wpa_s,
+					 const u8 *bssid)
+{
+	struct wpa_driver_scan_params params;
+	int freqs[2];
+
+	wpa_s->reassociate = 1;
+	wpa_s->fast_reconnect = 1;
+
+	os_memset(&params, 0, sizeof(params));
+	params.num_ssids = 1;
+	params.ssids[0].ssid = wpa_s->current_ssid->ssid;
+	params.ssids[0].ssid_len = wpa_s->current_ssid->ssid_len;
+	freqs[0] = wpa_s->assoc_freq;
+	freqs[1] = 0;
+	params.freqs = freqs;
+
+	wpa_msg(wpa_s, MSG_INFO, "Fast reconnect bssid=" MACSTR
+		" ssid=%.*s freq=%d", MAC2STR(bssid),
+		params.ssids[0].ssid_len, params.ssids[0].ssid,
+		params.freqs[0]);
+
+	bgscan_deinit(wpa_s);
+	wpa_s->bgscan_ssid = NULL;
+
+	return wpa_supplicant_trigger_scan(wpa_s, &params);
+}
+
+
 static void wpa_supplicant_event_disassoc(struct wpa_supplicant *wpa_s,
 					  u16 reason_code)
 {
 	const u8 *bssid;
+	int fast_reconnect;
 #ifdef CONFIG_SME
 	int authenticating;
 	u8 prev_pending_bssid[ETH_ALEN];
@@ -1273,18 +1322,29 @@
 		wpa_msg(wpa_s, MSG_INFO, "WPA: 4-Way Handshake failed - "
 			"pre-shared key may be incorrect");
 	}
-	if (wpa_s->wpa_state >= WPA_ASSOCIATING)
-		wpa_supplicant_req_scan(wpa_s, 0, 100000);
-	bssid = wpa_s->bssid;
-	if (is_zero_ether_addr(bssid))
-		bssid = wpa_s->pending_bssid;
 	/*
-	 * Don't blacklist the AP on normal/expected disconnects.
+	 * If in a COMPLETED state and we were dropped for a reason
+	 * we can directly recover from directly re-join the AP without
+	 * clocking the state machine and/or notifying external agents.
+	 *
+	 * TODO(sleffler) guard against looping (should be only if AP is busted)
 	 */
-	if (reason_code != WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY &&
-	    reason_code != WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA &&
-	    reason_code != WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA)
+	fast_reconnect =
+	    wpa_s->wpa_state == WPA_COMPLETED &&
+	    (reason_code == WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY ||
+	     reason_code == WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA ||
+	     reason_code == WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA);
+	bssid = wpa_s->bssid;
+	if (is_zero_ether_addr(bssid)) {
+		bssid = wpa_s->pending_bssid;
+		fast_reconnect = FALSE;	/* XXX can this happen? */
+		wpa_printf(MSG_DEBUG, "Do not reconnect; pending bssid switch");
+	}
+	if (!fast_reconnect) {
+		if (wpa_s->wpa_state >= WPA_ASSOCIATING)
+			wpa_supplicant_req_scan(wpa_s, 0, 100000);
 		wpa_blacklist_add(wpa_s, bssid);
+	}
 	wpa_sm_notify_disassoc(wpa_s->wpa);
 	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_DISCONNECTED "bssid=" MACSTR
 		" reason=%d",
@@ -1294,6 +1354,13 @@
 		wpa_s->keys_cleared = 0;
 		wpa_clear_keys(wpa_s, wpa_s->bssid);
 	}
+	if (fast_reconnect) {
+		if (wpa_supplicant_fast_reconnect(wpa_s, bssid) == 0)
+			return;
+		/* NB: fall through to slow path */
+		wpa_printf(MSG_DEBUG, "Fast reconnect: Failed to trigger scan");
+		wpa_supplicant_req_scan(wpa_s, 0, 100000);
+	}
 	wpa_supplicant_mark_disassoc(wpa_s);
 	bgscan_deinit(wpa_s);
 	wpa_s->bgscan_ssid = NULL;
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 047cea6..42a2fde 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -326,6 +326,7 @@
 	int reassociate; /* reassociation requested */
 	int disconnected; /* all connections disabled; i.e., do no reassociate
 			   * before this has been cleared */
+	int fast_reconnect;		/* reassociate or go to disconnected */
 	struct wpa_ssid *current_ssid;
 	struct wpa_bss *current_bss;
 	int ap_ies_from_associnfo;