Merge hostapd upstream into master.

Current as of:
http://w1.fi/cgit/hostap/commit/?id=ca68a8b561c48393c8ba25055ce294caaa3ac008

Note that this resolves the merge conflicts, but does not compile.
A second commit on top of this one is needed to get a working hostapd.

Change-Id: Ib1f533ef8f3399aaec7d4bc6708f7ea126a2c39c
diff --git a/README.google b/README.google
new file mode 100644
index 0000000..ab37ab8
--- /dev/null
+++ b/README.google
@@ -0,0 +1,33 @@
+Instructions to sync with the upstream hostap.
+
+Do this once:
+  git remote add upstream git://w1.fi/hostap.git
+
+You can use "git remote -v" to check if the upstream
+remote already exists and points to the right place.
+
+To merge with upstream:
+  git fetch -t upstream
+  git checkout upstream/master
+  git push gfiber-internal HEAD:refs/heads/upstream
+
+At this point, upstream/master is a pristine copy of the
+hostapd upstream. So now we want to merge the changes into
+our local, modified tree.
+
+  repo start merge_from_upstream .
+  git merge upstream/master
+
+Resolve any conflicts, and commit.
+
+  repo upload .
+
+This will almost certainly warn that there are an exceptionally
+large number of commits and make you type 'yes'. The vast, vast
+majority of those commits will already exist in Gerrit, from
+when we pushed HEAD:refs/heads/upstream. These will be detected
+as duplicates and not appear in your code review list. So type
+'yes' to upload, and expect just the merge commit (where any
+conflicts were resolved) to appear in your code review list.
+
+You send the merge commit for review.
diff --git a/hostapd/Makefile b/hostapd/Makefile
index 3c7bd6f..b95cbcd 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -829,6 +829,7 @@
 OBJS += ../src/ap/ieee802_11.o
 OBJS += ../src/ap/hw_features.o
 OBJS += ../src/ap/dfs.o
+OBJS += ../src/ap/rm.o
 CFLAGS += -DNEED_AP_MLME
 endif
 ifdef CONFIG_IEEE80211N
@@ -897,6 +898,13 @@
 LIBS_h += -lsqlite3
 endif
 
+ifdef CONFIG_CLIENT_TAXONOMY
+CFLAGS += -DCONFIG_CLIENT_TAXONOMY
+OBJS += ../src/ap/taxonomy.o
+endif
+
+OBJS += ../src/ap/steering.o
+
 ALL=hostapd hostapd_cli
 
 all: verify_config $(ALL)
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 1186644..6846b99 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -3248,9 +3248,12 @@
 		pos++;
 		WPA_PUT_LE16(&bss->bss_load_test[3], atoi(pos));
 		bss->bss_load_test_set = 1;
+#endif /* CONFIG_TESTING_OPTIONS */
 	} else if (os_strcmp(buf, "radio_measurements") == 0) {
 		bss->radio_measurements = atoi(pos);
-#endif /* CONFIG_TESTING_OPTIONS */
+	} else if (os_strcmp(buf, "neighbor_ap_list_file") == 0) {
+		os_free(bss->neighbor_ap_list_file);
+		bss->neighbor_ap_list_file = os_strdup(pos);
 	} else if (os_strcmp(buf, "vendor_elements") == 0) {
 		struct wpabuf *elems;
 		size_t len = os_strlen(pos);
diff --git a/hostapd/defconfig b/hostapd/defconfig
index 4cde2b5..88c2dbc 100644
--- a/hostapd/defconfig
+++ b/hostapd/defconfig
@@ -314,3 +314,8 @@
 # http://wireless.kernel.org/en/users/Documentation/acs
 #
 #CONFIG_ACS=y
+#
+# Client Taxonomy
+# Keeps track of signatures derived from Assoc and Probe frames per-STA.
+# These can be used to identfy the Wifi chipset/driver.
+#CONFIG_CLIENT_TAXONOMY=y
diff --git a/hostapd/main.c b/hostapd/main.c
index dd389a8..0c8686a 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -35,6 +35,10 @@
 };
 
 static struct hapd_global global;
+char *alivemon_path = NULL;
+char *taxonomy_path = NULL;
+char *steering_timestamp_path = NULL;
+char *request_logging_path = NULL;
 
 
 #ifndef CONFIG_NO_HOSTAPD_LOGGER
@@ -442,6 +446,7 @@
 		"options:\n"
 		"   -h   show this usage\n"
 		"   -d   show more debug messages (-dd for even more)\n"
+		"   -A   alivemonitor file\n"
 		"   -B   run daemon in the background\n"
 		"   -e   entropy file\n"
 		"   -g   global control interface path\n"
@@ -456,6 +461,10 @@
 		"        (records all messages regardless of debug verbosity)\n"
 #endif /* CONFIG_DEBUG_LINUX_TRACING */
 		"   -t   include timestamps in some debug messages\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);
@@ -563,7 +572,7 @@
 	interfaces.global_ctrl_sock = -1;
 
 	for (;;) {
-		c = getopt(argc, argv, "b:Bde:f:hKP:Ttu:vg:G:");
+		c = getopt(argc, argv, "b:A:Bde:f:F:hKP:Ttu:vg:G:S:L:");
 		if (c < 0)
 			break;
 		switch (c) {
@@ -575,6 +584,9 @@
 			if (wpa_debug_level > 0)
 				wpa_debug_level--;
 			break;
+		case 'A':
+			alivemon_path = optarg;
+			break;
 		case 'B':
 			daemonize++;
 			break;
@@ -584,6 +596,11 @@
 		case 'f':
 			log_file = optarg;
 			break;
+#ifdef CONFIG_CLIENT_TAXONOMY
+		case 'F':
+			taxonomy_path = optarg;
+			break;
+#endif /* CONFIG_CLIENT_TAXONOMY */
 		case 'K':
 			wpa_debug_show_keys++;
 			break;
@@ -624,6 +641,12 @@
 		case 'u':
 			return gen_uuid(optarg);
 #endif /* CONFIG_WPS */
+		case 'S':
+			steering_timestamp_path = optarg;
+			break;
+		case 'L':
+			request_logging_path = optarg;
+			break;
 		default:
 			usage();
 			break;
@@ -634,7 +657,8 @@
 	    num_bss_configs == 0)
 		usage();
 
-	wpa_msg_register_ifname_cb(hostapd_msg_ifname_cb);
+	// Not needed on our system since we add a log prefix elsewhere
+	//wpa_msg_register_ifname_cb(hostapd_msg_ifname_cb);
 
 	if (log_file)
 		wpa_debug_open_file(log_file);
@@ -734,7 +758,7 @@
 		goto out;
 	}
 
-	ret = 0;
+	ret = interfaces.terminate_on_error;
 
  out:
 	hostapd_global_ctrl_iface_deinit(&interfaces);
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 7b4a7ea..00848ba 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -553,6 +553,8 @@
 	int radio_measurements;
 
 	int vendor_vht;
+
+	char *neighbor_ap_list_file;
 };
 
 
diff --git a/src/ap/ap_list.c b/src/ap/ap_list.c
index 78a1f7c..2f007c5 100644
--- a/src/ap/ap_list.c
+++ b/src/ap/ap_list.c
@@ -222,7 +222,7 @@
 	if (!iface->olbc &&
 	    ap_list_beacon_olbc(iface, ap)) {
 		iface->olbc = 1;
-		wpa_printf(MSG_DEBUG, "OLBC AP detected: " MACSTR
+		wpa_printf(MSG_INFO, "OLBC AP detected: " MACSTR
 			   " (channel %d) - enable protection",
 			   MAC2STR(ap->addr), ap->channel);
 		set_beacon++;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 7009855..1a46d4d 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -29,6 +29,10 @@
 #include "beacon.h"
 #include "hs20.h"
 #include "dfs.h"
+#include "ap/steering.h"
+#ifdef CONFIG_CLIENT_TAXONOMY
+#include "taxonomy.h"
+#endif /* CONFIG_CLIENT_TAXONOMY */
 
 
 #ifdef NEED_AP_MLME
@@ -551,6 +555,10 @@
 	int noack;
 	enum ssid_match_result res;
 
+	if (request_logging_path) {
+		maybe_write_timestamp_file(mgmt->sa, hapd, LOG_PROBE);
+	}
+
 	ie = mgmt->u.probe_req.variable;
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.probe_req))
 		return;
@@ -643,6 +651,13 @@
 	}
 #endif /* CONFIG_P2P */
 
+#ifdef CONFIG_CLIENT_TAXONOMY
+	if (sta) {
+		hostapd_taxonomy_probe_req(sta, ie, ie_len);
+		hostapd_write_sta_taxonomy(sta);
+	}
+#endif /* CONFIG_CLIENT_TAXONOMY */
+
 	res = ssid_match(hapd, elems.ssid, elems.ssid_len,
 			 elems.ssid_list, elems.ssid_list_len);
 	if (res == NO_SSID_MATCH) {
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 80e4c2e..55f429a 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -5,7 +5,7 @@
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
  */
-
+#define _GNU_SOURCE
 #include "utils/includes.h"
 
 #include "utils/common.h"
@@ -32,7 +32,9 @@
 #include "hw_features.h"
 #include "dfs.h"
 #include "beacon.h"
-
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
 			const u8 *req_ies, size_t req_ies_len, int reassoc)
@@ -637,7 +639,7 @@
 		return;
 
 	hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211,
-		       HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)");
+		       HOSTAPD_LEVEL_INFO, "authentication OK (FT)");
 	sta->flags |= WLAN_STA_AUTH;
 
 	hostapd_sta_auth(hapd, dst, auth_transaction, status, ies, ies_len);
@@ -1076,10 +1078,16 @@
 void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 			  union wpa_event_data *data)
 {
+	static struct {
+		int all, beacon, probe_req, tx_status, tx_status_mgmt;
+	} counts;
+	static struct os_reltime last_count_print_time;
+	struct os_reltime now;
 	struct hostapd_data *hapd = ctx;
 #ifndef CONFIG_NO_STDOUT_DEBUG
-	int level = MSG_DEBUG;
+	int level = MSG_INFO;
 
+	counts.all++;
 	if (event == EVENT_RX_MGMT && data->rx_mgmt.frame &&
 	    data->rx_mgmt.frame_len >= 24) {
 		const struct ieee80211_hdr *hdr;
@@ -1087,11 +1095,39 @@
 		hdr = (const struct ieee80211_hdr *) data->rx_mgmt.frame;
 		fc = le_to_host16(hdr->frame_control);
 		if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT &&
-		    WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_BEACON)
+		    WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_BEACON) {
 			level = MSG_EXCESSIVE;
+			counts.beacon++;
+		}
 		if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT &&
-		    WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_PROBE_REQ)
+		    WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_PROBE_REQ) {
 			level = MSG_EXCESSIVE;
+			counts.probe_req++;
+		}
+	}
+	if (event == EVENT_TX_STATUS) {
+		if (data->tx_status.type == WLAN_FC_TYPE_MGMT) {
+			level = MSG_EXCESSIVE;
+			counts.tx_status_mgmt++;
+		} else {
+			counts.tx_status++;
+		}
+	}
+
+	os_get_reltime(&now);
+	if (os_reltime_expired(&now, &last_count_print_time, 5)) {
+		wpa_dbg(hapd->msg_ctx, MSG_INFO,
+			"Events: %d beacon=%d probe=%d tx=%d txm=%d",
+			counts.all, counts.beacon, counts.probe_req,
+			counts.tx_status, counts.tx_status_mgmt);
+		if (counts.all && alivemon_path) {
+			int fd = open(alivemon_path,
+				O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW,
+				0666);
+			if (fd >= 0) close(fd);
+		}
+		last_count_print_time = now;
+		memset(&counts, 0, sizeof(counts));
 	}
 
 	wpa_dbg(hapd->msg_ctx, level, "Event %s (%d) received",
@@ -1245,6 +1281,13 @@
 		hostapd_channel_list_updated(
 			hapd->iface, data->channel_list_changed.initiator);
 		break;
+	case EVENT_INTERFACE_STATUS:
+		if (data->interface_status.ievent == EVENT_INTERFACE_REMOVED) {
+			wpa_printf(MSG_ERROR, "Interface removed by someone; exiting.\n");
+			hapd->iface->interfaces->terminate_on_error++;
+			eloop_terminate();
+		}
+		break;
 	case EVENT_DFS_CAC_STARTED:
 		if (!data)
 			break;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 75cc24e..788b118 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -14,6 +14,9 @@
 #include "ap_config.h"
 #include "drivers/driver.h"
 
+extern char *alivemon_path;
+extern char *taxonomy_path;
+
 struct wpa_ctrl_dst;
 struct radius_server_data;
 struct upnp_wps_device_sm;
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 96744c4..6d84ee4 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -221,6 +221,15 @@
 }
 
 
+static int experiment(const char *name)
+{
+	char filename[128];
+	snprintf(filename, sizeof(filename), "/config/experiments/%s.active",
+		 name);
+	return access(filename, F_OK) == 0;
+}
+
+
 #ifdef CONFIG_IEEE80211N
 static int ieee80211n_allowed_ht40_channel_pair(struct hostapd_iface *iface)
 {
@@ -305,18 +314,22 @@
 
 	iface->secondary_ch = iface->conf->secondary_channel;
 	if (!oper40) {
-		wpa_printf(MSG_INFO, "20/40 MHz operation not permitted on "
-			   "channel pri=%d sec=%d based on overlapping BSSes",
-			   iface->conf->channel,
-			   iface->conf->channel +
-			   iface->conf->secondary_channel * 4);
-		iface->conf->secondary_channel = 0;
-		if (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX) {
-			/*
-			 * TODO: Could consider scheduling another scan to check
-			 * if channel width can be changed if no coex reports
-			 * are received from associating stations.
-			 */
+		if (!experiment("NoAutoNarrowWifiChannel")) {
+			wpa_printf(MSG_INFO, "20/40 MHz operation not permitted on "
+				   "channel pri=%d sec=%d based on overlapping BSSes",
+				   iface->conf->channel,
+				   iface->conf->channel +
+				   iface->conf->secondary_channel * 4);
+			iface->conf->secondary_channel = 0;
+			if (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX) {
+				/*
+				 * TODO: Could consider scheduling another scan to check
+				 * if channel width can be changed if no coex reports
+				 * are received from associating stations.
+				 */
+			}
+		} else {
+			wpa_printf(MSG_INFO, "NoAutoNarrowWifiChannel: would have reduced to 20 MHz.");
 		}
 	}
 
diff --git a/src/ap/iapp.c b/src/ap/iapp.c
index 99aa04d..b203b5e 100644
--- a/src/ap/iapp.c
+++ b/src/ap/iapp.c
@@ -292,7 +292,7 @@
 	 * this is not really a reliable verification. */
 
 	hostapd_logger(iapp->hapd, add->mac_addr, HOSTAPD_MODULE_IAPP,
-		       HOSTAPD_LEVEL_DEBUG,
+		       HOSTAPD_LEVEL_INFO,
 		       "Removing STA due to IAPP ADD-notify");
 	ap_sta_disconnect(iapp->hapd, sta, NULL, 0);
 }
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 5b26558..62b40d2 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -40,6 +40,11 @@
 #include "wnm_ap.h"
 #include "ieee802_11.h"
 #include "dfs.h"
+#include "rm.h"
+#include "ap/steering.h"
+#ifdef CONFIG_CLIENT_TAXONOMY
+#include "taxonomy.h"
+#endif /* CONFIG_CLIENT_TAXONOMY */
 
 
 u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid)
@@ -235,7 +240,7 @@
 	}
 
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-		       HOSTAPD_LEVEL_DEBUG,
+		       HOSTAPD_LEVEL_INFO,
 		       "authentication OK (shared key)");
 	sta->flags |= WLAN_STA_AUTH;
 	wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
@@ -304,7 +309,7 @@
 		return;
 
 	hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211,
-		       HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)");
+		       HOSTAPD_LEVEL_INFO, "authentication OK (FT)");
 	sta->flags |= WLAN_STA_AUTH;
 	mlme_authenticate_indication(hapd, sta);
 }
@@ -1056,10 +1061,16 @@
 	else
 		ap_sta_no_session_timeout(hapd, sta);
 
+#ifdef CONFIG_FINGERPRINT
+	sta->duration_auth[sta->duration_auth_idx++] =
+	    FINGERPRINT_DURATION_PRESENT | le_to_host16(mgmt->duration);
+	sta->duration_auth_idx %= FINGERPRINT_NSAMPLES;
+#endif /* CONFIG_FINGERPRINT */
+
 	switch (auth_alg) {
 	case WLAN_AUTH_OPEN:
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_DEBUG,
+			       HOSTAPD_LEVEL_INFO,
 			       "authentication OK (open system)");
 		sta->flags |= WLAN_STA_AUTH;
 		wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
@@ -1684,6 +1695,7 @@
 	const u8 *pos;
 	int left, i;
 	struct sta_info *sta;
+	struct os_reltime now, probe_time, bandsteer_until;
 
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) :
 				      sizeof(mgmt->u.assoc_req))) {
@@ -1692,6 +1704,11 @@
 		return;
 	}
 
+	if (request_logging_path) {
+		maybe_write_timestamp_file(mgmt->sa, hapd, LOG_ASSOC);
+	}
+
+
 #ifdef CONFIG_TESTING_OPTIONS
 	if (reassoc) {
 		if (hapd->iconf->ignore_reassoc_probability > 0.0 &&
@@ -1741,6 +1758,34 @@
 	}
 
 	sta = ap_get_sta(hapd, mgmt->sa);
+
+	if (steering_timestamp_path) {
+		/* If the STA has successfully associated on the bandsteering target
+		 * interface, reset the bandsteering state so that we try to bandsteer it
+		 * again.
+		 */
+		if (read_timestamp_file(mgmt->sa, LOG_ASSOC_SUCCESSFUL, STEERING_PATH, NULL)) {
+			delete_timestamp_file(mgmt->sa, LOG_ASSOC_SUCCESSFUL, STEERING_PATH);
+			delete_timestamp_file(mgmt->sa, LOG_ASSOC, LOGGING_PATH);
+		}
+
+		os_get_reltime(&now);
+		if (read_timestamp_file(mgmt->sa, LOG_PROBE, STEERING_PATH, &probe_time)) {
+			if (!read_timestamp_file(mgmt->sa, 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;
+			} else {
+				write_timestamp_file(mgmt->sa, hapd, LOG_BANDSTEERING_FAILED, &now);
+			}
+		}
+	}
+
+
 #ifdef CONFIG_IEEE80211R
 	if (sta && sta->auth_alg == WLAN_AUTH_FT &&
 	    (sta->flags & WLAN_STA_AUTH) == 0) {
@@ -1873,6 +1918,11 @@
 	 * remove the STA immediately. */
 	sta->timeout_next = STA_NULLFUNC;
 
+#ifdef CONFIG_CLIENT_TAXONOMY
+	hostapd_taxonomy_assoc_req(sta, pos, left);
+	hostapd_write_sta_taxonomy(sta);
+#endif /* CONFIG_CLIENT_TAXONOMY */
+
  fail:
 	send_assoc_resp(hapd, sta, resp, reassoc, pos, left);
 }
@@ -1889,7 +1939,7 @@
 		return;
 	}
 
-	wpa_printf(MSG_DEBUG, "disassocation: STA=" MACSTR " reason_code=%d",
+	wpa_printf(MSG_INFO, "disassocation: STA=" MACSTR " reason_code=%d",
 		   MAC2STR(mgmt->sa),
 		   le_to_host16(mgmt->u.disassoc.reason_code));
 
@@ -1941,7 +1991,7 @@
 		return;
 	}
 
-	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "deauthentication: STA=" MACSTR
+	wpa_msg(hapd->msg_ctx, MSG_INFO, "deauthentication: STA=" MACSTR
 		" reason_code=%d",
 		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.deauth.reason_code));
 
@@ -2119,6 +2169,9 @@
 		if (hapd->public_action_cb || hapd->public_action_cb2)
 			return 1;
 		break;
+	case WLAN_ACTION_RADIO_MEASUREMENT:
+		hostapd_rm_action(hapd, mgmt, len);
+		return 1;
 	case WLAN_ACTION_VENDOR_SPECIFIC:
 		if (hapd->vendor_action_cb) {
 			if (hapd->vendor_action_cb(hapd->vendor_action_cb_ctx,
@@ -2399,6 +2452,13 @@
 		ap_sta_set_authorized(hapd, sta, 1);
 	}
 
+
+	if (request_logging_path && !steering_timestamp_path) {
+		/* Log the successful associate, so that the interface steering to this one
+		 * knows it can keep bandsteering if it sees another assoc request. */
+		write_timestamp_file(mgmt->da, hapd, LOG_ASSOC_SUCCESSFUL, NULL);
+	}
+
 	if (reassoc)
 		mlme_reassociate_indication(hapd, sta);
 	else
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 863a539..5a66291 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -1774,7 +1774,7 @@
 		 * request and we cannot continue EAP processing (EAP-Failure
 		 * could only be sent if the EAP peer actually replied).
 		 */
-		wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "EAP Timeout, STA " MACSTR,
+		wpa_dbg(hapd->msg_ctx, MSG_INFO, "EAP Timeout, STA " MACSTR,
 			MAC2STR(sta->addr));
 
 		sm->eap_if->portEnabled = FALSE;
@@ -2590,7 +2590,7 @@
 		 * EAP-FAST with anonymous provisioning, may require another
 		 * EAPOL authentication to be started to complete connection.
 		 */
-		wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "IEEE 802.1X: Force "
+		wpa_dbg(hapd->msg_ctx, MSG_INFO, "IEEE 802.1X: Force "
 			"disconnection after EAP-Failure");
 		/* Add a small sleep to increase likelihood of previously
 		 * requested EAP-Failure TX getting out before this should the
diff --git a/src/ap/rm.c b/src/ap/rm.c
new file mode 100644
index 0000000..4c9279e
--- /dev/null
+++ b/src/ap/rm.c
@@ -0,0 +1,172 @@
+/*
+ * hostapd / RM (Radio Management)
+ * Copyright (c) 2015, Google, Inc
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
+#include "hostapd.h"
+#include "ieee802_11.h"
+#include "sta_info.h"
+#include "ap_config.h"
+#include "ap_drv_ops.h"
+
+/* Common clients only look at the first 6 APs in the list anyway. */
+#define NR_MAX_APS 6
+
+/*
+ * bssinfo is defined in 802.11-2012 Figure 8-216,
+ * Table 8-114, and Figure 8-217.
+ */
+static u32 cap_to_bssinfo(u32 cap)
+{
+	u32 ap_reach = 2 << 0;  // Reachability unknown
+	u32 security = 1 << 2;
+	u32 key_scope = 0 << 3;
+	u32 capabilities = 0;
+
+	if (cap & (1 << 8)) capabilities |= (1 << 4);  // Spectrum Management
+	capabilities |= (0 << 5);  // QoS
+	if (cap & (1 << 11)) capabilities |= (1 << 6);  // APSD
+	if (cap & (1 << 12)) capabilities |= (1 << 7);  // Radio Measurement
+	if (cap & (1 << 14)) capabilities |= (1 << 8);  // Delayed Block Ack
+	if (cap & (1 << 15)) capabilities |= (1 << 9);  // Immed Block Ack
+
+	return ap_reach | security | key_scope | capabilities;
+}
+
+static void rm_send_neighbor_report(struct hostapd_data *hapd, const u8 *addr,
+				    u8 dialog_token)
+{
+	#define ELEMSIZ sizeof(struct rrm_neighbor_report_element)
+	u8 buf[sizeof(struct ieee80211_mgmt) + NR_MAX_APS * ELEMSIZ];
+	struct ieee80211_mgmt *m = (struct ieee80211_mgmt *)buf;
+	u8 *v = m->u.action.u.rm_action.variable;
+	struct rrm_neighbor_report_element *elements =
+		(struct rrm_neighbor_report_element *) v;
+	int num_aps = 0;
+	size_t len;
+
+	/* TODO: if the neighbor request included a specific SSID,
+	 * we need to compare with hapd->conf->ssid. If the SSID
+	 * does not match, we should send back an action frame with
+	 * some kind of refused or mismatch error code. */
+
+	os_memset(&buf, 0, sizeof(buf));
+	m->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+					WLAN_FC_STYPE_ACTION);
+	os_memcpy(m->da, addr, ETH_ALEN);
+	os_memcpy(m->sa, hapd->own_addr, ETH_ALEN);
+	os_memcpy(m->bssid, hapd->own_addr, ETH_ALEN);
+	m->u.action.category = WLAN_ACTION_RADIO_MEASUREMENT;
+	m->u.action.u.rm_action.action = WLAN_RRM_NEIGHBOR_REPORT_RESPONSE;
+	m->u.action.u.rm_action.dialog_token = dialog_token;
+
+	if (hapd->conf->neighbor_ap_list_file) {
+		FILE *f = fopen(hapd->conf->neighbor_ap_list_file, "r");
+		char line[128];
+
+		while (f && fgets(line, sizeof(line), f)) {
+			char *start = line;
+			char *tok, *save = NULL;
+			const char *bssid = NULL;
+			u32 cap = 0, bssinfo = 0;
+			u8 channel = 0, phy_type = 0;
+			struct rrm_neighbor_report_element *elem;
+
+			if (num_aps >= NR_MAX_APS) {
+				break;
+			}
+
+			while ((tok = strtok_r(start, "|", &save)) != NULL) {
+				start = NULL;
+				if (strncmp(tok, "bssid:", 6) == 0) {
+					bssid = tok + 6;
+				}
+				if (strncmp(tok, "cap:", 4) == 0) {
+					cap = strtol(tok + 4, NULL, 0);
+				}
+				if (strncmp(tok, "freq:", 5) == 0) {
+					u32 freq = strtol(tok + 5, NULL, 0);
+					ieee80211_freq_to_chan(freq, &channel);
+				}
+				if (strncmp(tok, "phy:", 4) == 0) {
+					phy_type = (u8)strtol(tok + 4, NULL, 0);
+				}
+			}
+
+			if (bssid == NULL || *bssid == '\0') {
+				continue;
+			}
+
+			elem = &elements[num_aps];
+
+			if (hwaddr_aton(bssid, elem->bssid) != 0) {
+				continue;
+			}
+			elem->eid = WLAN_EID_NEIGHBOR_REPORT;
+			elem->len = sizeof(*elem) - 2;
+			bssinfo = cap_to_bssinfo(cap);
+			WPA_PUT_LE32((u8 *)&elem->bssinfo, bssinfo);
+			elem->op_class = 0;  // TODO: regulatory class.
+			elem->channel = channel;
+			elem->phy_type = phy_type;
+			num_aps++;
+		}
+		if (f) {
+			fclose(f);
+		} else {
+			hostapd_logger(hapd, m->da, HOSTAPD_MODULE_IEEE80211,
+				HOSTAPD_LEVEL_NOTICE, "unable to open %s",
+				hapd->conf->neighbor_ap_list_file);
+		}
+	}
+
+	len = (u8 *)&elements[num_aps] - buf;
+	hostapd_logger(hapd, m->da, HOSTAPD_MODULE_IEEE80211,
+		HOSTAPD_LEVEL_NOTICE, "sending Neighbor List with %d APs",
+		num_aps);
+	if (hostapd_drv_send_mlme(hapd, m, len, 0) < 0)
+		wpa_printf(MSG_WARNING, "rm_send_neighbor_report: send failed");
+}
+
+
+/*
+ * Process a WLAN_ACTION_RADIO_MEASUREMENT frame.
+ */
+void hostapd_rm_action(struct hostapd_data *hapd,
+		       const struct ieee80211_mgmt *mgmt, size_t len)
+{
+	int action_code;
+	struct sta_info *sta = ap_get_sta(hapd, mgmt->sa);
+	u8 dialog_token;
+
+	/* Check that the request comes from a valid station. */
+	if (!sta || !(sta->flags & WLAN_STA_ASSOC)) {
+		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_NOTICE,
+			       "rm action received is not from an associated "
+			       "station");
+		/* TODO: respond with action frame refused status code */
+		return;
+	}
+
+	action_code = mgmt->u.action.u.rm_action.action;
+	dialog_token = mgmt->u.action.u.wmm_action.dialog_token;
+	switch (action_code) {
+	case WLAN_RRM_NEIGHBOR_REPORT_REQUEST:
+		rm_send_neighbor_report(hapd, mgmt->sa, dialog_token);
+		return;
+	}
+
+	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
+		       HOSTAPD_LEVEL_INFO,
+		       "hostapd_rm_action - unknown action code %d",
+		       action_code);
+}
diff --git a/src/ap/rm.h b/src/ap/rm.h
new file mode 100644
index 0000000..81fb89e
--- /dev/null
+++ b/src/ap/rm.h
@@ -0,0 +1,15 @@
+/*
+ * hostapd / RM (Radio Measurements)
+ * Copyright 2015, Google, Inc
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef RM_H
+#define RM_H
+
+void hostapd_rm_action(struct hostapd_data *hapd,
+		       const struct ieee80211_mgmt *mgmt, size_t len);
+
+#endif /* RM_H */
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 20847d5..17ecad5 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -335,6 +335,48 @@
 }
 
 
+#ifdef CONFIG_CLIENT_TAXONOMY
+void hostapd_write_sta_taxonomy(struct sta_info *sta)
+{
+	int i;
+	char tmpfile[1024 + 4], filename[1024];
+	FILE *f;
+
+	if (taxonomy_path == NULL) {
+		return;
+	}
+
+	if ((os_strlen(sta->probe_ie_taxonomy) == 0) ||
+	    (os_strlen(sta->assoc_ie_taxonomy) == 0)) {
+		return;
+	}
+
+	snprintf(filename, sizeof(filename), "%s/" MACSTR,
+	         taxonomy_path, MAC2STR(sta->addr));
+	snprintf(tmpfile, sizeof(tmpfile), "%s/" MACSTR ".tmp",
+	         taxonomy_path, MAC2STR(sta->addr));
+	if ((f = fopen(tmpfile, "w")) == NULL) {
+		wpa_printf(MSG_ERROR, "open %s failed", tmpfile);
+		return;
+	}
+	fprintf(f, "wifi|probe:%s|assoc:%s", sta->probe_ie_taxonomy,
+	        sta->assoc_ie_taxonomy);
+	fclose(f);
+	if (rename(tmpfile, filename)) {
+		wpa_printf(MSG_ERROR, "rename %s failed", tmpfile);
+		unlink(tmpfile);
+	}
+}
+static void hostapd_remove_sta_taxonomy(struct sta_info *sta)
+{
+	char filename[1024];
+
+	snprintf(filename, sizeof(filename), "%s/" MACSTR,
+	         taxonomy_path, MAC2STR(sta->addr));
+	unlink(filename);
+}
+#endif /* CONFIG_CLIENT_TAXONOMY */
+
 /**
  * ap_handle_timer - Per STA timer handler
  * @eloop_ctx: struct hostapd_data *
@@ -357,6 +399,9 @@
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "deauthenticated due to "
 			       "local deauth request");
+#ifdef CONFIG_CLIENT_TAXONOMY
+		hostapd_remove_sta_taxonomy(sta);
+#endif /* CONFIG_CLIENT_TAXONOMY */
 		ap_free_sta(hapd, sta);
 		return;
 	}
@@ -393,14 +438,14 @@
 			goto skip_poll;
 		} else if (inactive_sec < hapd->conf->ap_max_inactivity) {
 			/* station activity detected; reset timeout state */
-			wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			wpa_msg(hapd->msg_ctx, MSG_INFO,
 				"Station " MACSTR " has been active %is ago",
 				MAC2STR(sta->addr), inactive_sec);
 			sta->timeout_next = STA_NULLFUNC;
 			next_time = hapd->conf->ap_max_inactivity + fuzz -
 				inactive_sec;
 		} else {
-			wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			wpa_msg(hapd->msg_ctx, MSG_INFO,
 				"Station " MACSTR " has been "
 				"inactive too long: %d sec, max allowed: %d",
 				MAC2STR(sta->addr), inactive_sec,
@@ -423,6 +468,10 @@
 		next_time = hapd->conf->ap_max_inactivity;
 	}
 
+#ifdef CONFIG_CLIENT_TAXONOMY
+	hostapd_write_sta_taxonomy(sta);
+#endif /* CONFIG_CLIENT_TAXONOMY */
+
 skip_poll:
 	if (next_time) {
 		wpa_printf(MSG_DEBUG, "%s: register ap_handle_timer timeout "
@@ -698,8 +747,8 @@
 void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta,
 			 u16 reason)
 {
-	wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR,
-		   hapd->conf->iface, MAC2STR(sta->addr));
+	wpa_printf(MSG_INFO, "%s: disassociate STA " MACSTR " (reason=0x%04x)",
+		   hapd->conf->iface, MAC2STR(sta->addr), reason);
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
 	sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
 	ap_sta_set_authorized(hapd, sta, 0);
@@ -737,8 +786,8 @@
 void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta,
 			   u16 reason)
 {
-	wpa_printf(MSG_DEBUG, "%s: deauthenticate STA " MACSTR,
-		   hapd->conf->iface, MAC2STR(sta->addr));
+	wpa_printf(MSG_INFO, "%s: deauthenticate STA " MACSTR " (reason=0x%04x)",
+		   hapd->conf->iface, MAC2STR(sta->addr), reason);
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
 	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
 	ap_sta_set_authorized(hapd, sta, 0);
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 52a9997..ce8d9fb 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -167,6 +167,12 @@
 	u16 last_seq_ctrl;
 	/* Last Authentication/(Re)Association Request/Action frame subtype */
 	u8 last_subtype;
+
+#ifdef CONFIG_CLIENT_TAXONOMY
+#define TAXONOMY_STRING_LEN 384
+	char probe_ie_taxonomy[TAXONOMY_STRING_LEN];
+	char assoc_ie_taxonomy[TAXONOMY_STRING_LEN];
+#endif /* CONFIG_CLIENT_TAXONOMY */
 };
 
 
@@ -197,6 +203,9 @@
 void ap_sta_hash_add(struct hostapd_data *hapd, struct sta_info *sta);
 void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta);
 void ap_sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta);
+#ifdef CONFIG_CLIENT_TAXONOMY
+void hostapd_write_sta_taxonomy(struct sta_info *sta);
+#endif /* CONFIG_CLIENT_TAXONOMY */
 void hostapd_free_stas(struct hostapd_data *hapd);
 void ap_handle_timer(void *eloop_ctx, void *timeout_ctx);
 void ap_sta_replenish_timeout(struct hostapd_data *hapd, struct sta_info *sta,
diff --git a/src/ap/steering.c b/src/ap/steering.c
new file mode 100644
index 0000000..2916262
--- /dev/null
+++ b/src/ap/steering.c
@@ -0,0 +1,250 @@
+/*
+ * hostapd / Interface steering
+ * Copyright (c) 2015 Google, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include "common.h"
+#include "common/defs.h"
+#include "common/ieee802_11_defs.h"
+#include "hostapd.h"
+#include "steering.h"
+
+static int get_timestamp_filename(const u8 *mac,
+                                  logged_request_type type,
+                                  steering_path_type path_type, char *buf,
+                                  size_t len) {
+  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", path,
+	                MAC2STR(mac), type) < 0) {
+		wpa_printf(MSG_ERROR, "os_snprintf couldn't format filename: %s",
+		           strerror(errno));
+		return 0;
+	}
+
+	return 1;
+}
+
+int write_timestamp_file(const u8 *mac,
+                         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(mac, 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.
+	 */
+	if (os_snprintf(tmp_filename, sizeof(tmp_filename), "%s%s", filename,
+	                os_strrchr(hapd->iface->config_fname, '.')) < 0) {
+		wpa_printf(MSG_ERROR, "os_snprintf couldn't format temp filename: %s",
+		           strerror(errno));
+		return 0;
+	}
+
+	if ((f = fopen(tmp_filename, "w")) == NULL) {
+		wpa_printf(MSG_ERROR, "fopen(%s) for write: %s", tmp_filename,
+		           strerror(errno));
+		return 0;
+	}
+
+	if (timestamp) {
+		if (fprintf(f, "%d %d", timestamp->sec, timestamp->usec) < 0) {
+			wpa_printf(MSG_ERROR, "fprintf to %s: %s", tmp_filename, strerror(errno));
+		} else {
+			success = 1;
+		}
+	}
+
+	if (fclose(f) == EOF) {
+		wpa_printf(MSG_ERROR, "fclose(%s): %s", tmp_filename, strerror(errno));
+		return 0;
+	}
+
+	if (rename(tmp_filename, filename) != 0) {
+		wpa_printf(MSG_ERROR, "rename(%s, %s): %s", tmp_filename, filename,
+		           strerror(errno));
+		return 0;
+	}
+
+	return success;
+}
+
+int maybe_write_timestamp_file(const u8 *mac,
+                               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(mac, 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(mac, 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(mac), type);
+			return 1;
+		}
+	}
+}
+
+int read_timestamp_file(const u8 *mac,
+                        logged_request_type type,
+                        steering_path_type path_type,
+                        struct os_reltime *timestamp) {
+	FILE *f;
+	char filename[1024];
+	int success = 1;
+	struct stat st;
+	os_time_t sec = 0, usec = 0;
+
+	if (!get_timestamp_filename(mac, type, path_type, filename,
+	                            sizeof(filename))) {
+		return 0;
+	}
+
+	if (stat(filename, &st) == -1) {
+		return 0;
+	}
+
+	f = fopen(filename, "r");
+	if (f == NULL) {
+		wpa_printf(MSG_ERROR, "open(%s) for read: %s", filename, strerror(errno));
+		return 0;
+	}
+
+	if (timestamp) {
+		if (fscanf(f, "%d %d", &timestamp->sec, &timestamp->usec) != 2) {
+			wpa_printf(MSG_ERROR, "fscanf from %s: %s", filename, strerror(errno));
+			success = 0;
+		}
+	}
+
+	if (fclose(f) == EOF) {
+		wpa_printf(MSG_ERROR, "fclose(%s): %s", filename, strerror(errno));
+		return 0;
+	}
+
+	return success;
+}
+
+int delete_timestamp_file(const u8 *mac,
+                          logged_request_type type,
+                          steering_path_type path_type) {
+	char filename[1024];
+	struct stat st;
+
+	if (!get_timestamp_filename(mac, type, path_type, filename,
+	                            sizeof(filename))) {
+		return 0;
+	}
+
+	if (stat(filename, &st) == -1) {
+		return 1;
+	}
+
+	if (unlink(filename) == -1) {
+		wpa_printf(MSG_ERROR, "unlink(%s): %s", filename, strerror(errno));
+		return 0;
+	}
+
+	return 1;
+}
+
+int file_ctime_lt(const struct dirent **a, const struct dirent **b) {
+	struct stat astat, bstat;
+
+	/* If we can't stat both of the files, give up and say they're equivalent. */
+	if (stat((*a)->d_name, &astat) == -1 || stat((*b)->d_name, &bstat) == -1) {
+		return 0;
+	}
+
+	return astat.st_ctime - bstat.st_ctime;
+}
+
+/* Only garbage collect LOG_PROBE files. */
+int should_garbage_collect(const struct dirent *name) {
+	char *extension = os_strrchr(name->d_name, '.');
+	char buf[4];
+	os_snprintf(buf, sizeof(buf), ".%d", LOG_PROBE);
+
+	return os_strncmp(extension, buf, sizeof(buf)) == 0;
+}
+
+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];
+	char *filename;
+	int error = 0;
+
+	if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
+		wpa_printf(MSG_ERROR, "getcwd(): %s", strerror(errno));
+		return -1;
+	}
+
+	if (chdir(request_logging_path) == -1) {
+		wpa_printf(MSG_ERROR, "chdir(%s): %s",
+		           request_logging_path, strerror(errno));
+		return -1;
+	}
+
+	num_timestamp_files = scandir(request_logging_path, &namelist,
+	                              should_garbage_collect, file_ctime_lt);
+	for (i = 0; i < num_timestamp_files; ++i) {
+		if (MAX_STEERING_TIMESTAMP_FILES <
+		    /* The -2 is because scandir includes "." and "..". */
+		    (num_timestamp_files - 2) - num_timestamp_files_deleted) {
+			filename = namelist[i]->d_name;
+			if (filename[0] != '.' && !error) {
+				if (unlink(filename) == -1) {
+					wpa_printf(MSG_ERROR, "unlink(%s): %s", filename, strerror(errno));
+					error = 1;
+				} else {
+					++num_timestamp_files_deleted;
+				}
+			}
+		}
+		os_free(namelist[i]);
+	}
+	os_free(namelist);
+
+	if (chdir(original_cwd) == -1) {
+		wpa_printf(MSG_ERROR, "chdir(%s): %s", original_cwd, strerror(errno));
+		return -1;
+	}
+
+	return error ? -1 : num_timestamp_files_deleted;
+}
diff --git a/src/ap/steering.h b/src/ap/steering.h
new file mode 100644
index 0000000..a17d79c
--- /dev/null
+++ b/src/ap/steering.h
@@ -0,0 +1,81 @@
+/*
+ * hostapd / Interface steering
+ * Copyright (c) 2015 Google, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef STEERING_H
+#define STEERING_H
+
+#define MAX_STEERING_TIMESTAMP_FILES 100
+/* 10 seconds is long enough to scan all channels on both bands at least twice
+ * at 100ms/channel.
+ */
+#define BANDSTEERING_DELAY_SECONDS 10
+#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;
+
+typedef enum {
+  LOG_PROBE,
+  LOG_ASSOC,
+  LOG_BANDSTEERING_FAILED,
+  LOG_ASSOC_SUCCESSFUL,
+  NUM_LOGGED_REQUEST_TYPES } logged_request_type;
+typedef enum {
+  LOGGING_PATH,
+  STEERING_PATH,
+  NUM_STEERING_PATH_TYPES } steering_path_type;
+
+/**
+ * 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 u8 *mac,
+                         const struct hostapd_data *hapd,
+                         logged_request_type type,
+                         const struct os_reltime *timestamp);
+
+/**
+ * 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 u8 *mac,
+                               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 u8 *mac,
+                        logged_request_type type,
+                        steering_path_type path_type,
+                        struct os_reltime *timestamp);
+
+/**
+ * Deletes a timestamp file from either request_logging_path or
+ * steering_timestamp_path (based on path) for the source address in mgmt.
+ * Returns 1 if the delete succeeded or the file does not exist, 0 otherwise.
+ */
+int delete_timestamp_file(const u8 *mac,
+                          logged_request_type type,
+                          steering_path_type path_type);
+
+/**
+ * Delete all but the most recent MAX_TIMESTAMP_FILES files of type LOG_PROBE in
+ * request_logging_path.  Returns the number of files deleted.
+ */
+int garbage_collect_timestamp_files();
+
+#endif /* STEERING_H */
diff --git a/src/ap/taxonomy.c b/src/ap/taxonomy.c
new file mode 100644
index 0000000..9a8812d
--- /dev/null
+++ b/src/ap/taxonomy.c
@@ -0,0 +1,178 @@
+/*
+ * hostapd / Station client taxonomy
+ * Copyright (c) 2015 Google, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+/*
+ * Parse a series of IEs, as in Probe or Association packets,
+ * and render them to a descriptive string. The tag number of
+ * standard options is written to the string, while the vendor
+ * ID and subtag are written for vendor options.
+ *
+ * Example strings:
+ * 0,1,50,45,221(00904c,51)
+ * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2)
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "sta_info.h"
+
+/* Copy a string with no funny schtuff allowed; only alphanumerics. */
+static void no_mischief_strncpy(char *dst, const char *src, size_t n)
+{
+	size_t i;
+	for (i = 0; i < n; i++) {
+		unsigned char s = src[i];
+		int is_lower = (s >= 'a' && s <= 'z');
+		int is_upper = (s >= 'A' && s <= 'Z');
+		int is_digit = (s >= '0' && s <= '9');
+		if (is_lower || is_upper || is_digit) {
+			/* TODO: if any manufacturer uses Unicode within the
+			 * WPS header, it will get mangled here. */
+			dst[i] = s;
+		} else {
+			/* note that even spaces will be transformed to underscores,
+			 * so 'Nexus 7' will turn into 'Nexus_7'. This is deliberate,
+			 * to make the string easier to parse. */
+			dst[i] = '_';
+		}
+	}
+}
+
+static int get_wps_name(char *name, size_t name_len,
+		const u8 *data, size_t data_len)
+{
+	/* Inside the WPS IE are a series of sub-IEs, using two byte IDs
+	 * and two byte lengths. We're looking for the model name, if
+	 * present. */
+	while (data_len >= 4) {
+		u16 id, elen;
+		id = (data[0] << 8) | data[1];
+		elen = (data[2] << 8) | data[3];
+		data += 4;
+		data_len -= 4;
+
+		if (elen > data_len) {
+			return 0;
+		}
+
+		if (id == 0x1023) {
+			/* Model name, like 'Nexus 7' */
+			size_t n = (elen < name_len) ? elen : name_len;
+			no_mischief_strncpy(name, data, n);
+			return n;
+		}
+
+		data += elen;
+		data_len -= elen;
+	}
+
+	return 0;
+}
+
+static void ie_to_string(char *fstr, size_t fstr_len,
+                         const u8 *ie, size_t ie_len)
+{
+	size_t flen = fstr_len - 1;
+	char htcap[7 + 4 + 1];  // ",htcap:" + %04hx + trailing NUL
+	char vhtcap[8 + 8 + 1];  // ",vhtcap:" + %08x + trailing NUL
+	#define WPS_NAME_LEN		32
+	char wps[WPS_NAME_LEN + 5 + 1];  // room to prepend ",wps:" + trailing NUL
+	int num = 0;
+
+	memset(htcap, 0, sizeof(htcap));
+	memset(vhtcap, 0, sizeof(vhtcap));
+	memset(wps, 0, sizeof(wps));
+	fstr[0] = '\0';
+
+	while (ie_len >= 2) {
+		u8 id, elen;
+		char tagbuf[32];
+		char *sep = (num++ == 0) ? "" : ",";
+
+		id = *ie++;
+		elen = *ie++;
+		ie_len -= 2;
+
+		if (elen > ie_len) {
+			break;
+		}
+
+		if ((id == 221) && (elen >= 4)) {
+			/* Vendor specific */
+			int is_MSFT = (ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2);
+			if (is_MSFT && ie[3] == 0x04) {
+				/* WPS */
+				char model_name[WPS_NAME_LEN + 1];
+				const u8 *data = &ie[4];
+				size_t data_len = elen - 4;
+				memset(model_name, 0, sizeof(model_name));
+				if (get_wps_name(model_name, WPS_NAME_LEN, data, data_len)) {
+					snprintf(wps, sizeof(wps), ",wps:%s", model_name);
+				}
+			}
+
+			snprintf(tagbuf, sizeof(tagbuf), "%s%d(%02x%02x%02x,%d)",
+			         sep, id, ie[0], ie[1], ie[2], ie[3]);
+		} else {
+			if ((id == 45) && (elen > 2)) {
+				/* HT Capabilities (802.11n) */
+				u16 cap;
+				memcpy(&cap, ie, sizeof(cap));
+				snprintf(htcap, sizeof(htcap), ",htcap:%04hx",
+				         le_to_host16(cap));
+			}
+			if ((id == 191) && (elen > 4)) {
+				/* VHT Capabilities (802.11ac) */
+				u32 cap;
+				memcpy(&cap, ie, sizeof(cap));
+				snprintf(vhtcap, sizeof(vhtcap), ",vhtcap:%08x",
+				         le_to_host32(cap));
+			}
+			snprintf(tagbuf, sizeof(tagbuf), "%s%d", sep, id);
+		}
+
+		strncat(fstr, tagbuf, flen);
+		flen = fstr_len - strlen(fstr) - 1;
+
+		ie += elen;
+		ie_len -= elen;
+	}
+
+	if (strlen(htcap)) {
+		strncat(fstr, htcap, flen);
+		flen = fstr_len - strlen(fstr) - 1;
+	}
+	if (strlen(vhtcap)) {
+		strncat(fstr, vhtcap, flen);
+		flen = fstr_len - strlen(fstr) - 1;
+	}
+	if (strlen(wps)) {
+		strncat(fstr, wps, flen);
+		flen = fstr_len - strlen(fstr) - 1;
+	}
+
+	fstr[fstr_len - 1] = '\0';
+}
+
+void hostapd_taxonomy_probe_req(struct sta_info *sta,
+	const u8 *ie, size_t ie_len)
+{
+	ie_to_string(sta->probe_ie_taxonomy,
+			     sizeof(sta->probe_ie_taxonomy),
+				 ie, ie_len);
+}
+
+void hostapd_taxonomy_assoc_req(struct sta_info *sta,
+	const u8 *ie, size_t ie_len)
+{
+	ie_to_string(sta->assoc_ie_taxonomy,
+	             sizeof(sta->assoc_ie_taxonomy),
+	             ie, ie_len);
+}
+
+/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab : */
diff --git a/src/ap/taxonomy.h b/src/ap/taxonomy.h
new file mode 100644
index 0000000..e8b37b0
--- /dev/null
+++ b/src/ap/taxonomy.h
@@ -0,0 +1,17 @@
+/*
+ * hostapd / Station client taxonomy
+ * Copyright (c) 2015 Google, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef TAXONOMY_H
+#define TAXONOMY_H
+
+void hostapd_taxonomy_probe_req(struct sta_info *sta,
+	const u8 *ie, size_t ie_len);
+void hostapd_taxonomy_assoc_req(struct sta_info *sta,
+	const u8 *ie, size_t ie_len);
+
+#endif /* TAXONOMY_H */
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index b83b460..af24cf0 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -264,7 +264,7 @@
 	struct wpa_authenticator *wpa_auth = eloop_ctx;
 	struct wpa_group *group;
 
-	wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "rekeying GTK");
+	wpa_auth_logger(wpa_auth, NULL, LOGGER_INFO, "rekeying GTK");
 	for (group = wpa_auth->group; group; group = group->next) {
 		group->GTKReKey = TRUE;
 		do {
@@ -285,7 +285,7 @@
 	struct wpa_authenticator *wpa_auth = eloop_ctx;
 	struct wpa_state_machine *sm = timeout_ctx;
 
-	wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, "rekeying PTK");
+	wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, "rekeying PTK");
 	wpa_request_new_ptk(sm);
 	wpa_sm_step(sm);
 }
@@ -661,7 +661,7 @@
 		return;
 
 	if (sm->wpa_auth->conf.wpa_strict_rekey && sm->has_GTK) {
-		wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_DEBUG,
+		wpa_auth_logger(sm->wpa_auth, sm->addr, LOGGER_INFO,
 				"strict rekeying - force GTK rekey since STA "
 				"is leaving");
 		eloop_cancel_timeout(wpa_rekey_gtk, sm->wpa_auth, NULL);
@@ -1095,7 +1095,7 @@
 			 * Counter update and the station will be allowed to
 			 * continue.
 			 */
-			wpa_printf(MSG_DEBUG, "WPA: Reject 4-way handshake to "
+			wpa_printf(MSG_ERROR, "WPA: Reject 4-way handshake to "
 				   "collect more entropy for random number "
 				   "generation");
 			random_mark_pool_ready();
@@ -1139,6 +1139,8 @@
 		}
 #ifdef CONFIG_IEEE80211R
 		if (ft && ft_check_msg_2_of_4(wpa_auth, sm, &kde) < 0) {
+			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
+				"WPA ft_check_msg_2_of_4 failure");
 			wpa_sta_disconnect(wpa_auth, sm->addr);
 			return;
 		}
@@ -1780,6 +1782,7 @@
 {
 	SM_ENTRY_MA(WPA_PTK, DISCONNECT, wpa_ptk);
 	sm->Disconnect = FALSE;
+	wpa_printf(MSG_INFO, "WPA_PTK: disconnect state");
 	wpa_sta_disconnect(sm->wpa_auth, sm->addr);
 }
 
@@ -2322,6 +2325,7 @@
 		int klen = wpa_cipher_key_len(sm->pairwise);
 		if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0,
 				     sm->PTK.tk, klen)) {
+			wpa_printf(MSG_ERROR, "wpa_auth_set_key error");
 			wpa_sta_disconnect(sm->wpa_auth, sm->addr);
 			return;
 		}
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 7cd0b6c..59a56e8 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -131,7 +131,7 @@
 					u16 reason)
 {
 	struct hostapd_data *hapd = ctx;
-	wpa_printf(MSG_DEBUG, "%s: WPA authenticator requests disconnect: "
+	wpa_printf(MSG_INFO, "%s: WPA authenticator requests disconnect: "
 		   "STA " MACSTR " reason %d",
 		   __func__, MAC2STR(addr), reason);
 	ap_sta_disconnect(hapd, NULL, addr, reason);
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 47b15de..feab921 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -615,6 +615,11 @@
 					u8 action; /* 15 */
 					u8 variable[];
 				} STRUCT_PACKED slf_prot_action;
+				struct {
+					u8 action; /* 5 */
+					u8 dialog_token;
+					u8 variable[0];
+				} STRUCT_PACKED rm_action;
 			} u;
 		} STRUCT_PACKED action;
 	} u;
@@ -1356,6 +1361,18 @@
 	u8 variable[0];
 } STRUCT_PACKED;
 
+/* IEEE Std 802.11-2012, 8.5.7.7 - Neighbor Report Element format */
+struct rrm_neighbor_report_element {
+	u8 eid;
+	u8 len;
+	u8 bssid[6];
+	le32 bssinfo;
+	u8 op_class;
+	u8 channel;
+	u8 phy_type;
+	u8 variable[0];
+} STRUCT_PACKED;
+
 #define SSID_MAX_LEN 32
 
 #endif /* IEEE802_11_DEFS_H */
diff --git a/src/crypto/random.c b/src/crypto/random.c
index bc758aa..15c8b03 100644
--- a/src/crypto/random.c
+++ b/src/crypto/random.c
@@ -230,7 +230,7 @@
 	 * so use non-blocking read to avoid blocking the application
 	 * completely.
 	 */
-	fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+	fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK);
 	if (fd < 0) {
 		wpa_printf(MSG_ERROR, "random: Cannot open /dev/random: %s",
 			   strerror(errno));
@@ -411,7 +411,7 @@
 	if (random_fd >= 0)
 		return;
 
-	random_fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
+	random_fd = open("/dev/urandom", O_RDONLY | O_NONBLOCK);
 	if (random_fd < 0) {
 		wpa_printf(MSG_ERROR, "random: Cannot open /dev/random: %s",
 			   strerror(errno));
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index be0e7c5..640e099 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -962,7 +962,7 @@
 			drv->ignore_if_down_event = 0;
 			return;
 		}
-		wpa_printf(MSG_DEBUG, "nl80211: Interface down (%s/%s)",
+		wpa_printf(MSG_WARNING, "nl80211: Interface down (%s/%s)",
 			   namebuf, ifname);
 		if (os_strcmp(drv->first_bss->ifname, ifname) != 0) {
 			wpa_printf(MSG_DEBUG,
@@ -3314,7 +3314,7 @@
 
 	beacon_set = params->reenable ? 0 : bss->beacon_set;
 
-	wpa_printf(MSG_DEBUG, "nl80211: Set beacon (beacon_set=%d)",
+	wpa_printf(MSG_INFO, "nl80211: Set beacon (beacon_set=%d)",
 		   beacon_set);
 	if (beacon_set)
 		cmd = NL80211_CMD_SET_BEACON;
diff --git a/src/drivers/rfkill.c b/src/drivers/rfkill.c
index 45b26c4..f2cd47c 100644
--- a/src/drivers/rfkill.c
+++ b/src/drivers/rfkill.c
@@ -110,7 +110,7 @@
 	rfkill->cfg = cfg;
 	rfkill->fd = open("/dev/rfkill", O_RDONLY);
 	if (rfkill->fd < 0) {
-		wpa_printf(MSG_INFO, "rfkill: Cannot open RFKILL control "
+		wpa_printf(MSG_DEBUG, "rfkill: Cannot open RFKILL control "
 			   "device");
 		goto fail;
 	}
diff --git a/src/utils/eloop.c b/src/utils/eloop.c
index 4a565eb..f929dcd 100644
--- a/src/utils/eloop.c
+++ b/src/utils/eloop.c
@@ -939,7 +939,7 @@
 			_tv.tv_usec = tv.usec;
 #endif /* CONFIG_ELOOP_SELECT */
 		}
-
+		fflush(stdout);
 #ifdef CONFIG_ELOOP_POLL
 		num_poll_fds = eloop_sock_table_set_fds(
 			&eloop.readers, &eloop.writers, &eloop.exceptions,