hostapd: Add survey dump support
This adds survey dump support for all frequencies and for specific desired frequencies. This will later be used by ACS code for spectrum heuristics. Signed-hostap: Michal Kazior <michal.kazior@tieto.com>
This commit is contained in:
		
							parent
							
								
									245e026ec8
								
							
						
					
					
						commit
						0185007c2e
					
				
					 6 changed files with 365 additions and 2 deletions
				
			
		|  | @ -225,4 +225,14 @@ static inline void hostapd_drv_poll_client(struct hostapd_data *hapd, | |||
| 	hapd->driver->poll_client(hapd->drv_priv, own_addr, addr, qos); | ||||
| } | ||||
| 
 | ||||
| static inline int hostapd_drv_get_survey(struct hostapd_data *hapd, | ||||
| 					 unsigned int freq) | ||||
| { | ||||
| 	if (hapd->driver == NULL) | ||||
| 		return -1; | ||||
| 	if (!hapd->driver->get_survey) | ||||
| 		return -1; | ||||
| 	return hapd->driver->get_survey(hapd->drv_priv, freq); | ||||
| } | ||||
| 
 | ||||
| #endif /* AP_DRV_OPS */ | ||||
|  |  | |||
|  | @ -715,6 +715,72 @@ static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src, | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static struct hostapd_channel_data * hostapd_get_mode_channel( | ||||
| 	struct hostapd_iface *iface, unsigned int freq) | ||||
| { | ||||
| 	int i; | ||||
| 	struct hostapd_channel_data *chan; | ||||
| 
 | ||||
| 	for (i = 0; i < iface->current_mode->num_channels; i++) { | ||||
| 		chan = &iface->current_mode->channels[i]; | ||||
| 		if (!chan) | ||||
| 			return NULL; | ||||
| 		if ((unsigned int) chan->freq == freq) | ||||
| 			return chan; | ||||
| 	} | ||||
| 
 | ||||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void hostapd_update_nf(struct hostapd_iface *iface, | ||||
| 			      struct hostapd_channel_data *chan, | ||||
| 			      struct freq_survey *survey) | ||||
| { | ||||
| 	if (!iface->chans_surveyed) { | ||||
| 		chan->min_nf = survey->nf; | ||||
| 		iface->lowest_nf = survey->nf; | ||||
| 	} else { | ||||
| 		if (dl_list_empty(&chan->survey_list)) | ||||
| 			chan->min_nf = survey->nf; | ||||
| 		else if (survey->nf < chan->min_nf) | ||||
| 			chan->min_nf = survey->nf; | ||||
| 		if (survey->nf < iface->lowest_nf) | ||||
| 			iface->lowest_nf = survey->nf; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void hostapd_event_get_survey(struct hostapd_data *hapd, | ||||
| 				     struct survey_results *survey_results) | ||||
| { | ||||
| 	struct hostapd_iface *iface = hapd->iface; | ||||
| 	struct freq_survey *survey, *tmp; | ||||
| 	struct hostapd_channel_data *chan; | ||||
| 
 | ||||
| 	if (dl_list_empty(&survey_results->survey_list)) { | ||||
| 		wpa_printf(MSG_DEBUG, "No survey data received"); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dl_list_for_each_safe(survey, tmp, &survey_results->survey_list, | ||||
| 			      struct freq_survey, list) { | ||||
| 		chan = hostapd_get_mode_channel(iface, survey->freq); | ||||
| 		if (!chan) | ||||
| 			continue; | ||||
| 		if (chan->flag & HOSTAPD_CHAN_DISABLED) | ||||
| 			continue; | ||||
| 
 | ||||
| 		dl_list_del(&survey->list); | ||||
| 		dl_list_add_tail(&chan->survey_list, &survey->list); | ||||
| 
 | ||||
| 		hostapd_update_nf(iface, chan, survey); | ||||
| 
 | ||||
| 		iface->chans_surveyed++; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void wpa_supplicant_event(void *ctx, enum wpa_event_type event, | ||||
| 			  union wpa_event_data *data) | ||||
| { | ||||
|  | @ -856,6 +922,9 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, | |||
| 			hapd, data->connect_failed_reason.addr, | ||||
| 			data->connect_failed_reason.code); | ||||
| 		break; | ||||
| 	case EVENT_SURVEY: | ||||
| 		hostapd_event_get_survey(hapd, &data->survey_results); | ||||
| 		break; | ||||
| 	default: | ||||
| 		wpa_printf(MSG_DEBUG, "Unknown event %d", event); | ||||
| 		break; | ||||
|  |  | |||
|  | @ -301,6 +301,15 @@ struct hostapd_iface { | |||
| 	int olbc_ht; | ||||
| 
 | ||||
| 	u16 ht_op_mode; | ||||
| 
 | ||||
| 	/* surveying helpers */ | ||||
| 
 | ||||
| 	/* number of channels surveyed */ | ||||
| 	unsigned int chans_surveyed; | ||||
| 
 | ||||
| 	/* lowest observed noise floor in dBm */ | ||||
| 	s8 lowest_nf; | ||||
| 
 | ||||
| 	void (*scan_cb)(struct hostapd_iface *iface); | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ | |||
| #define WPA_SUPPLICANT_DRIVER_VERSION 4 | ||||
| 
 | ||||
| #include "common/defs.h" | ||||
| #include "utils/list.h" | ||||
| 
 | ||||
| #define HOSTAPD_CHAN_DISABLED 0x00000001 | ||||
| #define HOSTAPD_CHAN_PASSIVE_SCAN 0x00000002 | ||||
|  | @ -55,9 +56,20 @@ struct hostapd_channel_data { | |||
| 	int flag; | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * max_tx_power - maximum transmit power in dBm | ||||
| 	 * max_tx_power - Maximum transmit power in dBm | ||||
| 	 */ | ||||
| 	u8 max_tx_power; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * survey_list - Linked list of surveys | ||||
| 	 */ | ||||
| 	struct dl_list survey_list; | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * min_nf - Minimum observed noise floor, in dBm, based on all | ||||
| 	 * surveyed channel data | ||||
| 	 */ | ||||
| 	s8 min_nf; | ||||
| }; | ||||
| 
 | ||||
| #define HOSTAPD_MODE_FLAG_HT_INFO_KNOWN BIT(0) | ||||
|  | @ -2728,6 +2740,31 @@ struct wpa_driver_ops { | |||
| 	 * mode. | ||||
| 	 */ | ||||
| 	int (*stop_ap)(void *priv); | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * get_survey - Retrieve survey data | ||||
| 	 * @priv: Private driver interface data | ||||
| 	 * @freq: If set, survey data for the specified frequency is only | ||||
| 	 *	being requested. If not set, all survey data is requested. | ||||
| 	 * Returns: 0 on success, -1 on failure | ||||
| 	 * | ||||
| 	 * Use this to retrieve: | ||||
| 	 * | ||||
| 	 * - the observed channel noise floor | ||||
| 	 * - the amount of time we have spent on the channel | ||||
| 	 * - the amount of time during which we have spent on the channel that | ||||
| 	 *   the radio has determined the medium is busy and we cannot | ||||
| 	 *   transmit | ||||
| 	 * - the amount of time we have spent receiving data | ||||
| 	 * - the amount of time we have spent transmitting data | ||||
| 	 * | ||||
| 	 * This data can be used for spectrum heuristics. One example is | ||||
| 	 * Automatic Channel Selection (ACS). The channel survey data is | ||||
| 	 * kept on a linked list on the channel data, one entry is added | ||||
| 	 * for each survey. The min_nf of the channel is updated for each | ||||
| 	 * survey. | ||||
| 	 */ | ||||
| 	int (*get_survey)(void *priv, unsigned int freq); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -3212,10 +3249,56 @@ enum wpa_event_type { | |||
| 	 * | ||||
| 	 * The channel which was previously unavailable is now available again. | ||||
| 	 */ | ||||
| 	EVENT_DFS_NOP_FINISHED | ||||
| 	EVENT_DFS_NOP_FINISHED, | ||||
| 
 | ||||
| 	/*
 | ||||
| 	* EVENT_SURVEY - Received survey data | ||||
| 	* | ||||
| 	* This event gets triggered when a driver query is issued for survey | ||||
| 	* data and the requested data becomes available. The returned data is | ||||
| 	* stored in struct survey_results. The results provide at most one | ||||
| 	* survey entry for each frequency and at minimum will provide one survey | ||||
| 	* entry for one frequency. The survey data can be os_malloc()'d and | ||||
| 	* then os_free()'d, so the event callback must only copy data. | ||||
| 	*/ | ||||
| 	EVENT_SURVEY | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * struct freq_survey - Channel survey info | ||||
|  * | ||||
|  * @ifidx: Interface index in which this survey was observed | ||||
|  * @freq: Center of frequency of the surveyed channel | ||||
|  * @nf: Channel noise floor in dBm | ||||
|  * @channel_time: Amount of time in ms the radio spent on the channel | ||||
|  * @channel_time_busy: Amount of time in ms the radio detected some signal | ||||
|  *     that indicated to the radio the channel was not clear | ||||
|  * @channel_time_rx: Amount of time the radio spent receiving data | ||||
|  * @channel_time_tx: Amount of time the radio spent transmitting data | ||||
|  * @filled: bitmask indicating which fields have been reported, see | ||||
|  *     SURVEY_HAS_* defines. | ||||
|  * @list: Internal list pointers | ||||
|  */ | ||||
| struct freq_survey { | ||||
| 	u32 ifidx; | ||||
| 	unsigned int freq; | ||||
| 	s8 nf; | ||||
| 	u64 channel_time; | ||||
| 	u64 channel_time_busy; | ||||
| 	u64 channel_time_rx; | ||||
| 	u64 channel_time_tx; | ||||
| 	unsigned int filled; | ||||
| 	struct dl_list list; | ||||
| }; | ||||
| 
 | ||||
| #define SURVEY_HAS_NF BIT(0) | ||||
| #define SURVEY_HAS_CHAN_TIME BIT(1) | ||||
| #define SURVEY_HAS_CHAN_TIME_BUSY BIT(2) | ||||
| #define SURVEY_HAS_CHAN_TIME_RX BIT(3) | ||||
| #define SURVEY_HAS_CHAN_TIME_TX BIT(4) | ||||
| 
 | ||||
| 
 | ||||
| /**
 | ||||
|  * union wpa_event_data - Additional data for wpa_supplicant_event() calls | ||||
|  */ | ||||
|  | @ -3858,6 +3941,17 @@ union wpa_event_data { | |||
| 	struct dfs_event { | ||||
| 		int freq; | ||||
| 	} dfs_event; | ||||
| 
 | ||||
| 	/**
 | ||||
| 	 * survey_results - Survey result data for EVENT_SURVEY | ||||
| 	 * @freq_filter: Requested frequency survey filter, 0 if request | ||||
| 	 *	was for all survey data | ||||
| 	 * @survey_list: Linked list of survey data | ||||
| 	 */ | ||||
| 	struct survey_results { | ||||
| 		unsigned int freq_filter; | ||||
| 		struct dl_list survey_list; /* struct freq_survey */ | ||||
| 	} survey_results; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  |  | |||
|  | @ -84,6 +84,7 @@ const char * event_to_string(enum wpa_event_type event) | |||
| 	E2S(DFS_CAC_FINISHED); | ||||
| 	E2S(DFS_CAC_ABORTED); | ||||
| 	E2S(DFS_NOP_FINISHED); | ||||
| 	E2S(SURVEY); | ||||
| 	} | ||||
| 
 | ||||
| 	return "UNKNOWN"; | ||||
|  |  | |||
|  | @ -9971,6 +9971,185 @@ static int nl80211_flush_pmkid(void *priv) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void clean_survey_results(struct survey_results *survey_results) | ||||
| { | ||||
| 	struct freq_survey *survey, *tmp; | ||||
| 
 | ||||
| 	if (dl_list_empty(&survey_results->survey_list)) | ||||
| 		return; | ||||
| 
 | ||||
| 	dl_list_for_each_safe(survey, tmp, &survey_results->survey_list, | ||||
| 			      struct freq_survey, list) { | ||||
| 		dl_list_del(&survey->list); | ||||
| 		os_free(survey); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void add_survey(struct nlattr **sinfo, u32 ifidx, | ||||
| 		       struct dl_list *survey_list) | ||||
| { | ||||
| 	struct freq_survey *survey; | ||||
| 
 | ||||
| 	survey = os_zalloc(sizeof(struct freq_survey)); | ||||
| 	if  (!survey) | ||||
| 		return; | ||||
| 
 | ||||
| 	survey->ifidx = ifidx; | ||||
| 	survey->freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]); | ||||
| 	survey->filled = 0; | ||||
| 
 | ||||
| 	if (sinfo[NL80211_SURVEY_INFO_NOISE]) { | ||||
| 		survey->nf = (int8_t) | ||||
| 			nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]); | ||||
| 		survey->filled |= SURVEY_HAS_NF; | ||||
| 	} | ||||
| 
 | ||||
| 	if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]) { | ||||
| 		survey->channel_time = | ||||
| 			nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]); | ||||
| 		survey->filled |= SURVEY_HAS_CHAN_TIME; | ||||
| 	} | ||||
| 
 | ||||
| 	if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]) { | ||||
| 		survey->channel_time_busy = | ||||
| 			nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]); | ||||
| 		survey->filled |= SURVEY_HAS_CHAN_TIME_BUSY; | ||||
| 	} | ||||
| 
 | ||||
| 	if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]) { | ||||
| 		survey->channel_time_rx = | ||||
| 			nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]); | ||||
| 		survey->filled |= SURVEY_HAS_CHAN_TIME_RX; | ||||
| 	} | ||||
| 
 | ||||
| 	if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]) { | ||||
| 		survey->channel_time_tx = | ||||
| 			nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]); | ||||
| 		survey->filled |= SURVEY_HAS_CHAN_TIME_TX; | ||||
| 	} | ||||
| 
 | ||||
| 	wpa_printf(MSG_DEBUG, "nl80211: Freq survey dump event (freq=%d MHz noise=%d channel_time=%ld busy_time=%ld tx_time=%ld rx_time=%ld filled=%04x)", | ||||
| 		   survey->freq, | ||||
| 		   survey->nf, | ||||
| 		   (unsigned long int) survey->channel_time, | ||||
| 		   (unsigned long int) survey->channel_time_busy, | ||||
| 		   (unsigned long int) survey->channel_time_tx, | ||||
| 		   (unsigned long int) survey->channel_time_rx, | ||||
| 		   survey->filled); | ||||
| 
 | ||||
| 	dl_list_add_tail(survey_list, &survey->list); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int check_survey_ok(struct nlattr **sinfo, u32 surveyed_freq, | ||||
| 			   unsigned int freq_filter) | ||||
| { | ||||
| 	if (!freq_filter) | ||||
| 		return 1; | ||||
| 
 | ||||
| 	return freq_filter == surveyed_freq; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int survey_handler(struct nl_msg *msg, void *arg) | ||||
| { | ||||
| 	struct nlattr *tb[NL80211_ATTR_MAX + 1]; | ||||
| 	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); | ||||
| 	struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1]; | ||||
| 	struct survey_results *survey_results; | ||||
| 	u32 surveyed_freq = 0; | ||||
| 	u32 ifidx; | ||||
| 
 | ||||
| 	static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1] = { | ||||
| 		[NL80211_SURVEY_INFO_FREQUENCY] = { .type = NLA_U32 }, | ||||
| 		[NL80211_SURVEY_INFO_NOISE] = { .type = NLA_U8 }, | ||||
| 	}; | ||||
| 
 | ||||
| 	survey_results = (struct survey_results *) arg; | ||||
| 
 | ||||
| 	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), | ||||
| 		  genlmsg_attrlen(gnlh, 0), NULL); | ||||
| 
 | ||||
| 	ifidx = nla_get_u32(tb[NL80211_ATTR_IFINDEX]); | ||||
| 
 | ||||
| 	if (!tb[NL80211_ATTR_SURVEY_INFO]) | ||||
| 		return NL_SKIP; | ||||
| 
 | ||||
| 	if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX, | ||||
| 			     tb[NL80211_ATTR_SURVEY_INFO], | ||||
| 			     survey_policy)) | ||||
| 		return NL_SKIP; | ||||
| 
 | ||||
| 	if (!sinfo[NL80211_SURVEY_INFO_FREQUENCY]) { | ||||
| 		wpa_printf(MSG_ERROR, "nl80211: Invalid survey data"); | ||||
| 		return NL_SKIP; | ||||
| 	} | ||||
| 
 | ||||
| 	surveyed_freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]); | ||||
| 
 | ||||
| 	if (!check_survey_ok(sinfo, surveyed_freq, | ||||
| 			     survey_results->freq_filter)) | ||||
| 		return NL_SKIP; | ||||
| 
 | ||||
| 	if (survey_results->freq_filter && | ||||
| 	    survey_results->freq_filter != surveyed_freq) { | ||||
| 		wpa_printf(MSG_EXCESSIVE, "nl80211: Ignoring survey data for freq %d MHz", | ||||
| 			   surveyed_freq); | ||||
| 		return NL_SKIP; | ||||
| 	} | ||||
| 
 | ||||
| 	add_survey(sinfo, ifidx, &survey_results->survey_list); | ||||
| 
 | ||||
| 	return NL_SKIP; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int wpa_driver_nl80211_get_survey(void *priv, unsigned int freq) | ||||
| { | ||||
| 	struct i802_bss *bss = priv; | ||||
| 	struct wpa_driver_nl80211_data *drv = bss->drv; | ||||
| 	struct nl_msg *msg; | ||||
| 	int err = -ENOBUFS; | ||||
| 	union wpa_event_data data; | ||||
| 	struct survey_results *survey_results; | ||||
| 
 | ||||
| 	os_memset(&data, 0, sizeof(data)); | ||||
| 	survey_results = &data.survey_results; | ||||
| 
 | ||||
| 	dl_list_init(&survey_results->survey_list); | ||||
| 
 | ||||
| 	msg = nlmsg_alloc(); | ||||
| 	if (!msg) | ||||
| 		goto nla_put_failure; | ||||
| 
 | ||||
| 	nl80211_cmd(drv, msg, NLM_F_DUMP, NL80211_CMD_GET_SURVEY); | ||||
| 
 | ||||
| 	NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); | ||||
| 
 | ||||
| 	if (freq) | ||||
| 		data.survey_results.freq_filter = freq; | ||||
| 
 | ||||
| 	do { | ||||
| 		wpa_printf(MSG_DEBUG, "nl80211: Fetch survey data"); | ||||
| 		err = send_and_recv_msgs(drv, msg, survey_handler, | ||||
| 					 survey_results); | ||||
| 	} while (err > 0); | ||||
| 
 | ||||
| 	if (err) { | ||||
| 		wpa_printf(MSG_ERROR, "nl80211: Failed to process survey data"); | ||||
| 		goto out_clean; | ||||
| 	} | ||||
| 
 | ||||
| 	wpa_supplicant_event(drv->ctx, EVENT_SURVEY, &data); | ||||
| 
 | ||||
| out_clean: | ||||
| 	clean_survey_results(survey_results); | ||||
| nla_put_failure: | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void nl80211_set_rekey_info(void *priv, const u8 *kek, const u8 *kck, | ||||
| 				   const u8 *replay_ctr) | ||||
| { | ||||
|  | @ -10597,4 +10776,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { | |||
| #endif /* CONFIG_TDLS */ | ||||
| 	.update_ft_ies = wpa_driver_nl80211_update_ft_ies, | ||||
| 	.get_mac_addr = wpa_driver_nl80211_get_macaddr, | ||||
| 	.get_survey = wpa_driver_nl80211_get_survey, | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Michal Kazior
						Michal Kazior