[RFC 3/4] hostapd: Add survey dump support
Michal Kazior
michal.kazior
Thu Jul 25 04:11:46 PDT 2013
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 at tieto.com>
---
src/ap/ap_drv_ops.h | 10 +++
src/ap/drv_callbacks.c | 74 +++++++++++++++++
src/ap/hostapd.h | 8 ++
src/drivers/driver.h | 99 +++++++++++++++++++++-
src/drivers/driver_common.c | 1 +
src/drivers/driver_nl80211.c | 185 ++++++++++++++++++++++++++++++++++++++++++
6 files changed, 375 insertions(+), 2 deletions(-)
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 6d2b343..1bb0bc9 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -236,4 +236,14 @@ static inline int hostapd_drv_remain_on_channel(struct hostapd_data *hapd,
return hapd->driver->remain_on_channel(hapd->drv_priv, freq, duration);
}
+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 */
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 2851f4d..d8daff1 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -777,6 +777,77 @@ static void hostapd_event_roc_cancel(struct hostapd_data *hapd,
}
+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, "ACS: 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)
+ goto fail;
+ 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++;
+ }
+
+ return;
+fail:
+ iface->chans_surveyed = 0;
+}
+
+
void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
union wpa_event_data *data)
{
@@ -926,6 +997,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;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 2fc2a04..1552bfd 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -284,6 +284,14 @@ struct hostapd_iface {
/* Offchannel operation helper */
unsigned int off_channel_freq_idx;
+ /* 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);
};
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index d78bdd0..661f427 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -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)
@@ -2461,6 +2473,7 @@ struct wpa_driver_ops {
* i.e., if it sets WPA_DRIVER_FLAGS_P2P_MGMT in
* struct wpa_driver_capa.
*/
+
int (*p2p_sd_response)(void *priv, int freq, const u8 *dst,
u8 dialog_token,
const struct wpabuf *resp_tlvs);
@@ -2726,6 +2739,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);
};
@@ -3210,11 +3248,57 @@ 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 it's returned. 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
*/
union wpa_event_data {
@@ -3856,6 +3940,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;
};
/**
diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c
index 12ccc14..8d1d22e 100644
--- a/src/drivers/driver_common.c
+++ b/src/drivers/driver_common.c
@@ -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";
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 83d4a23..53ea704 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -9931,6 +9931,190 @@ 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_rx,
+ (unsigned long int) survey->channel_time_tx,
+ 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;
+
+ if (freq_filter == surveyed_freq)
+ return 1;
+
+ return 0;
+}
+
+
+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 {
+ 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)
{
@@ -10557,4 +10741,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,
};
--
1.7.9.5
More information about the Hostap
mailing list