AP: Redesign of bandsteering.

hostapd now takes two arguemnts:

1) -L (request_logging_path):  If set, per-MAC probe and associate requests will
   be logged here, with the same timestamp format as before.

2) -S (steering_timestamp_path):  If set, per-MAC probe requests here will
   enable bandsteering.  The duration for which bandsteering is active begins
   when the first associate is rejected.

The intended use is that the -S argument of one hostapd instance will be the -L
argument of another.  For example, if you wanted to bandsteer to 5GHz, you might
run the 2.4GHz hostapd instance with:

  -L /tmp/wifi/steering/2.4 -S /tmp/wifi/steering/5

...and run the 5GHz instance with:

  -L /tmp/wifi/steering/5

This design takes some bandsteering logic out of hostapd, giving users more
control. It also addresses the problem that some devices probe even when they
have WiFi turned off (meaning that the duration between probe and associate
requests may be arbitrary, rather than just a few seconds (as previously
assumed)).

There are also two bugfixes in this change:

1) The bandsteering code in handle_assoc() was previously executing
   goto fail before all necessary arguments to the send_assoc_resp()
   call directly after fail: were initialized.  This has been fixed by
   moving the bandsteering code down a few lines.

2) Bandsteering timestamp files were being created for the wrong
   interface.  This is now obsolete.

Change-Id: I52111598aab13cb99c59855b4c7e58c5615131e5
diff --git a/hostapd/main.c b/hostapd/main.c
index 68366b9..5ba3b9d 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -38,6 +38,7 @@
 char *alivemon_path = NULL;
 char *fingerprint_path = NULL;
 char *steering_timestamp_path = NULL;
+char *request_logging_path = NULL;
 
 
 #ifndef CONFIG_NO_HOSTAPD_LOGGER
@@ -460,8 +461,10 @@
 		"        (records all messages regardless of debug verbosity)\n"
 #endif /* CONFIG_DEBUG_LINUX_TRACING */
 		"   -t   include timestamps in some debug messages\n"
-		"   -S   enable 5GHz bandsteering, storing delay timestamps in the\n"
-		"        specified directory\n"
+		"   -L   log probe and assoc requests in this directory, with\n"
+		"        a timestamp in each file of the write time plus\n"
+		"        BANDSTEERING_DELAY_SECONDS\n"
+		"   -S   bandsteer based on logged probes in this directory\n"
 		"   -v   show hostapd version\n");
 
 	exit(1);
@@ -569,7 +572,7 @@
 	interfaces.global_ctrl_sock = -1;
 
 	for (;;) {
-		c = getopt(argc, argv, "b:A:Bde:f:F:hKP:Ttu:vg:G:S:");
+		c = getopt(argc, argv, "b:A:Bde:f:F:hKP:Ttu:vg:G:S:L:");
 		if (c < 0)
 			break;
 		switch (c) {
@@ -639,6 +642,9 @@
 		case 'S':
 			steering_timestamp_path = optarg;
 			break;
+		case 'L':
+			request_logging_path = optarg;
+			break;
 		default:
 			usage();
 			break;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index c9d8362..bb137ea 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -544,42 +544,9 @@
 	size_t i, resp_len;
 	int noack;
 	enum ssid_match_result res;
-	struct os_reltime now, prev_bandsteer_until, bandsteer_until;
 
-
-	/* If we are seeing a 5GHz (A) probe request, block 2.4GHz (G) for a short
-	 * duration (e.g. a few seconds), unless we have done so recently (e.g. a few
-	 * minutes).  The idea is to steer clients towards the 5GHz interface, but if
-	 * they are re-probing within a few minutes of our last steering attempt then
-	 * they may be having some kind of connection problem or otherwise know
-	 * something we don't, so at that point we do the conservative thing and let
-	 * them connect on 2.4GHz if they want.
-	 */
-	if (steering_timestamp_path) {
-		os_get_reltime(&now);
-		if (garbage_collect_timestamp_files() == -1) {
-			wpa_printf(MSG_ERROR,
-			           "Garbage collecting steering timestamp files failed: %s",
-			           strerror(errno));
-		}
-		/* TODO: Consider bandsteering more agressively (e.g. a longer delay or
-		 * shorter expiration) for devices which have previously successfully
-		 * associated on 5GHz.
-		 */
-		else if (hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211A) {
-			if (!read_timestamp_file(mgmt, hapd, &prev_bandsteer_until) ||
-			    os_reltime_expired(&now, &prev_bandsteer_until,
-			                       BANDSTEERING_EXPIRATION_SECONDS)) {
-				bandsteer_until.sec = now.sec + BANDSTEERING_DELAY_SECONDS;
-				bandsteer_until.usec = now.usec;
-				if (!write_timestamp_file(mgmt, hapd, bandsteer_until)) {
-					wpa_printf(MSG_ERROR, "Failed to write bandsteering timestamp file.");
-				} else {
-					wpa_printf(MSG_INFO, "Set bandsteering delay for " MACSTR,
-					           MAC2STR(mgmt->sa));
-				}
-			}
-		}
+	if (request_logging_path) {
+		maybe_write_timestamp_file(mgmt, hapd, LOG_PROBE);
 	}
 
 	ie = mgmt->u.probe_req.variable;
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 0e62016..74a887c 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1573,7 +1573,7 @@
 	const u8 *pos;
 	int left, i;
 	struct sta_info *sta;
-	struct os_reltime now, bandsteer_until;
+	struct os_reltime now, probe_time, bandsteer_until;
 
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) :
 				      sizeof(mgmt->u.assoc_req))) {
@@ -1582,6 +1582,11 @@
 		return;
 	}
 
+	if (request_logging_path) {
+		maybe_write_timestamp_file(mgmt, hapd, LOG_ASSOC);
+	}
+
+
 #ifdef CONFIG_TESTING_OPTIONS
 	if (reassoc) {
 		if (hapd->iconf->ignore_reassoc_probability > 0.0 &&
@@ -1602,19 +1607,6 @@
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
 
-	if (steering_timestamp_path) {
-		if (hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211G) {
-			os_get_reltime(&now);
-			if (read_timestamp_file(mgmt, hapd, &bandsteer_until) &&
-			    os_reltime_before(&now, &bandsteer_until)) {
-				wpa_printf(MSG_INFO, "Rejecting " MACSTR " until %d sec %d usec",
-				           MAC2STR(mgmt->sa), bandsteer_until.sec, bandsteer_until.usec);
-				resp = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-				goto fail;
-			}
-		}
-	}
-
 	fc = le_to_host16(mgmt->frame_control);
 	seq_ctrl = le_to_host16(mgmt->seq_ctrl);
 
@@ -1644,6 +1636,23 @@
 	}
 
 	sta = ap_get_sta(hapd, mgmt->sa);
+
+	if (steering_timestamp_path) {
+		os_get_reltime(&now);
+		if (read_timestamp_file(mgmt, LOG_PROBE, STEERING_PATH, &probe_time)) {
+			if (!read_timestamp_file(mgmt, LOG_ASSOC, LOGGING_PATH,
+			                         &bandsteer_until) ||
+			    os_reltime_before(&now, &bandsteer_until)) {
+				wpa_printf(MSG_INFO, "Rejecting " MACSTR " until %d sec %d usec",
+				           MAC2STR(mgmt->sa), bandsteer_until.sec,
+				           bandsteer_until.usec);
+				resp = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
+				goto fail;
+			}
+		}
+	}
+
+
 #ifdef CONFIG_IEEE80211R
 	if (sta && sta->auth_alg == WLAN_AUTH_FT &&
 	    (sta->flags & WLAN_STA_AUTH) == 0) {
diff --git a/src/ap/steering.c b/src/ap/steering.c
index 93ca4fd..3f933cf 100644
--- a/src/ap/steering.c
+++ b/src/ap/steering.c
@@ -18,15 +18,18 @@
 #include "steering.h"
 
 static int get_timestamp_filename(const struct ieee80211_mgmt *mgmt,
-                                  const struct hostapd_data *hapd, char *buf,
+                                  logged_request_type type,
+                                  steering_path_type path_type, char *buf,
                                   size_t len) {
-	if (steering_timestamp_path == NULL) {
+  char *path = (path_type == STEERING_PATH ?
+                steering_timestamp_path : request_logging_path);
+
+	if (path == NULL) {
 		return 0;
 	}
 
-	if (os_snprintf(buf, len, "%s/" COMPACT_MACSTR ".%d",
-	                steering_timestamp_path, MAC2STR(mgmt->sa),
-	                hapd->iconf->hw_mode) < 0) {
+	if (os_snprintf(buf, len, "%s/" COMPACT_MACSTR ".%d", path,
+	                MAC2STR(mgmt->sa), type) < 0) {
 		wpa_printf(MSG_ERROR, "os_snprintf couldn't format filename: %s",
 		           strerror(errno));
 		return 0;
@@ -37,17 +40,20 @@
 
 int write_timestamp_file(const struct ieee80211_mgmt *mgmt,
                          const struct hostapd_data *hapd,
+                         logged_request_type type,
                          const struct os_reltime timestamp) {
 	FILE *f;
 	char filename[1024], tmp_filename[1024];
 	int success = 0;
 
-	if (!get_timestamp_filename(mgmt, hapd, filename, sizeof(filename))) {
+	if (!get_timestamp_filename(mgmt, type, LOGGING_PATH, filename,
+	                            sizeof(filename))) {
 		return 0;
 	}
 
 	/* Create a temporary filename to prevent multiple interfaces on the same band
 	 * from touching each others' writes.
+         * Code review:  Still necessary?
 	 */
 	if (os_snprintf(tmp_filename, sizeof(tmp_filename), "%s%s", filename,
 	                os_strrchr(hapd->iface->config_fname, '.')) < 0) {
@@ -81,15 +87,49 @@
 	return success;
 }
 
+int maybe_write_timestamp_file(const struct ieee80211_mgmt *mgmt,
+                               const struct hostapd_data *hapd,
+                               logged_request_type type) {
+	struct os_reltime now, prev_logged_timestamp, new_timestamp;
+	if (!request_logging_path) {
+		return 0;
+	}
+
+	os_get_reltime(&now);
+	if (garbage_collect_timestamp_files() == -1) {
+		wpa_printf(MSG_ERROR,
+		           "Garbage collecting steering timestamp files failed: %s",
+		           strerror(errno));
+		return 0;
+	}
+	if (!read_timestamp_file(mgmt, type, LOGGING_PATH, &prev_logged_timestamp) ||
+	    os_reltime_expired(&now, &prev_logged_timestamp,
+	                       BANDSTEERING_EXPIRATION_SECONDS)) {
+		new_timestamp.sec = now.sec + BANDSTEERING_DELAY_SECONDS;
+		new_timestamp.usec = now.usec;
+		if (!write_timestamp_file(mgmt, hapd, type, new_timestamp)) {
+			wpa_printf(MSG_ERROR, "Failed to write timestamp file.");
+			return 0;
+		} else {
+			wpa_printf(MSG_INFO, "Set timestamp for " MACSTR " (type=%d)",
+			           MAC2STR(mgmt->sa), type);
+			return 1;
+		}
+	}
+}
+
 int read_timestamp_file(const struct ieee80211_mgmt *mgmt,
-                        const struct hostapd_data *hapd,
+                        logged_request_type type,
+                        steering_path_type path_type,
                         struct os_reltime *timestamp) {
 	FILE *f;
 	char filename[1024];
 	int success = 0;
 	struct stat st;
+	os_time_t sec = 0, usec = 0;
 
-	if (!get_timestamp_filename(mgmt, hapd, filename, sizeof(filename))) {
+	if (!get_timestamp_filename(mgmt, type, path_type, filename,
+	                            sizeof(filename))) {
 		return 0;
 	}
 
@@ -103,7 +143,7 @@
 		return 0;
 	}
 
-	if (fscanf(f, "%d %d", &timestamp->sec, &timestamp->usec) < 0) {
+	if (fscanf(f, "%d %d", &timestamp->sec, &timestamp->usec) != 2) {
 		wpa_printf(MSG_ERROR, "fscanf from %s: %s", filename, strerror(errno));
 	} else {
 		success = 1;
@@ -128,7 +168,7 @@
 	return astat.st_ctime - bstat.st_ctime;
 }
 
-int garbage_collect_timestamp_files() {
+int garbage_collect_timestamp_files(const char *path) {
 	int num_timestamp_files = 0, num_timestamp_files_deleted = 0, i = 0;
 	struct dirent **namelist;
 	char original_cwd[1024];
@@ -140,13 +180,13 @@
 		return -1;
 	}
 
-	if (chdir(steering_timestamp_path) == -1) {
+	if (chdir(request_logging_path) == -1) {
 		wpa_printf(MSG_ERROR, "chdir(%s): %s",
-		           steering_timestamp_path, strerror(errno));
+		           request_logging_path, strerror(errno));
 		return -1;
 	}
 
-	num_timestamp_files = scandir(steering_timestamp_path, &namelist, NULL,
+	num_timestamp_files = scandir(request_logging_path, &namelist, NULL,
 	                              file_ctime_lt);
 	for (i = 0; i < num_timestamp_files; ++i) {
 		if (MAX_STEERING_TIMESTAMP_FILES <
diff --git a/src/ap/steering.h b/src/ap/steering.h
index 36b4e09..bc9913e 100644
--- a/src/ap/steering.h
+++ b/src/ap/steering.h
@@ -17,32 +17,53 @@
 #define BANDSTEERING_EXPIRATION_SECONDS 120
 
 extern char *steering_timestamp_path;
+extern char *request_logging_path;
 
 struct hostapd_data;
+enum hostapd_hw_mode;
 struct ieee80211_mgmt;
 struct os_reltime;
-struct dirent;
+
+typedef enum {
+  LOG_PROBE,
+  LOG_ASSOC,
+  NUM_LOGGED_REQUEST_TYPES } logged_request_type;
+typedef enum {
+  LOGGING_PATH,
+  STEERING_PATH,
+  NUM_STEERING_PATH_TYPES } steering_path_type;
 
 /**
- * Writes timestamp for the interface specified by hw_mode and the source
- * address in mgmt.  Returns 1 iff the write succeeded.
+ * Writes timestamp for the source address in mgmt to request_logging_path.
+ * Returns 1 if the write succeeded, 0 otherwise.
  */
 int write_timestamp_file(const struct ieee80211_mgmt *mgmt,
                          const struct hostapd_data *hapd,
+                         logged_request_type type,
                          const struct os_reltime timestamp);
 
 /**
- * Reads a timestamp for the interface specified by hw_mode and the source
- * address in mgmt, putting the result in timestamp.  Returns 1 iff the read
- * succeeded.
+ * Calls write_timestamp_file unless there is an existing file younger than
+ * BANDSTEERING_EXPIRATION_SECONDS. Also garbage collects before writing.
+ * Returns 0 on write or garbage collection failure, 1 otherwise.
+ */
+int maybe_write_timestamp_file(const struct ieee80211_mgmt *mgmt,
+                               const struct hostapd_data *hapd,
+                               logged_request_type type);
+
+/**
+ * Reads a timestamp from either request_logging_path or steering_timestamp_path
+ * (based on path) for the source address in mgmt, putting the result in
+ * timestamp.  Returns 1 if the read succeeded, 0 otherwise.
  */
 int read_timestamp_file(const struct ieee80211_mgmt *mgmt,
-                        const struct hostapd_data *hapd,
+                        logged_request_type type,
+                        steering_path_type path_type,
                         struct os_reltime *timestamp);
 
 /**
  * Delete all but the most recent MAX_TIMESTAMP_FILES files in
- * steering_timestamp_path.  Returns the number of files deleted.
+ * request_logging_path.  Returns the number of files deleted.
  */
 int garbage_collect_timestamp_files();