From e76da5052980f301fe61f2fc0e1e7a5789716061 Mon Sep 17 00:00:00 2001 From: Janusz Dziedzic Date: Tue, 15 Oct 2013 20:27:25 +0300 Subject: [PATCH] hostapd: Add AP DFS support Add DFS structures/events handlers, CAC handling, and radar detection. By default, after radar is detected or the channel became unavailable, a random channel will be chosen. This patches are based on the original work by Boris Presman and Victor Goldenshtein. Most of the DFS code is moved to a new dfs.c/dfs.h files. Cc: Boris Presman Cc: Victor Goldenshtein Signed-hostap: Simon Wunderlich Signed-hostap: Janusz Dziedzic --- hostapd/Makefile | 1 + src/ap/ap_drv_ops.c | 27 +++++++ src/ap/ap_drv_ops.h | 1 + src/ap/dfs.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++ src/ap/dfs.h | 18 +++++ src/ap/drv_callbacks.c | 84 ++++++++++++++++++++ src/ap/hostapd.c | 15 ++++ src/ap/hw_features.c | 78 +++++++++++++++--- src/ap/hw_features.h | 1 + wpa_supplicant/Makefile | 1 + 10 files changed, 423 insertions(+), 10 deletions(-) create mode 100644 src/ap/dfs.c create mode 100644 src/ap/dfs.h diff --git a/hostapd/Makefile b/hostapd/Makefile index fda4e4e..c56d368 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -782,6 +782,7 @@ OBJS += ../src/ap/wmm.o OBJS += ../src/ap/ap_list.o OBJS += ../src/ap/ieee802_11.o OBJS += ../src/ap/hw_features.o +OBJS += ../src/ap/dfs.o CFLAGS += -DNEED_AP_MLME endif ifdef CONFIG_IEEE80211N diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 3072562..cedfe8e 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -699,3 +699,30 @@ int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq, hapd->own_addr, hapd->own_addr, data, len, 0); } + + +int hostapd_start_dfs_cac(struct hostapd_data *hapd, int freq, int flags) +{ + if (!hapd->driver || !hapd->driver->start_dfs_cac) + return 0; + + if (!(flags & HOSTAPD_CHAN_RADAR)) { + wpa_printf(MSG_ERROR, "Can't start DFS_CAC, the channel %u is " + "not DFS channel", hapd->iconf->channel); + return -1; + } + + if (!hapd->iface->conf->ieee80211h) { + wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality " + "is not enabled"); + return -1; + } + + if (hapd->iface->conf->secondary_channel) { + wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality " + "on HT40 is not supported"); + return -1; + } + + return hapd->driver->start_dfs_cac(hapd->drv_priv, freq); +} diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 23277b6..d56aa91 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -101,6 +101,7 @@ int hostapd_sta_assoc(struct hostapd_data *hapd, const u8 *addr, int reassoc, u16 status, const u8 *ie, size_t len); int hostapd_add_tspec(struct hostapd_data *hapd, const u8 *addr, u8 *tspec_ie, size_t tspec_ielen); +int hostapd_start_dfs_cac(struct hostapd_data *hapd, int freq, int flags); #include "drivers/driver.h" diff --git a/src/ap/dfs.c b/src/ap/dfs.c new file mode 100644 index 0000000..387b625 --- /dev/null +++ b/src/ap/dfs.c @@ -0,0 +1,207 @@ +/* + * DFS - Dynamic Frequency Selection + * Copyright (c) 2002-2013, Jouni Malinen + * + * 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 "hostapd.h" +#include "hw_features.h" +#include "ap_drv_ops.h" +#include "drivers/driver.h" +#include "dfs.h" + + +static int hostapd_dfs_find_channel(struct hostapd_data *hapd, + struct hostapd_channel_data **ret_chan, + int idx) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan; + int i, channel_idx = 0; + + mode = hapd->iface->current_mode; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + + if (chan->flag & HOSTAPD_CHAN_DISABLED) + continue; + + if (chan->flag & HOSTAPD_CHAN_RADAR && + chan->flag & HOSTAPD_CHAN_DFS_UNAVAILABLE) + continue; + + if (ret_chan && idx == channel_idx) { + wpa_printf(MSG_DEBUG, "Selected ch. #%d", chan->chan); + *ret_chan = chan; + return idx; + } + channel_idx++; + } + return channel_idx; +} + + +struct hostapd_channel_data * hostapd_dfs_get_valid_channel( + struct hostapd_data *hapd) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan = NULL; + int channel_idx, new_channel_idx; + u32 _rand; + + wpa_printf(MSG_DEBUG, "DFS: Selecting random channel"); + + if (hapd->iface->current_mode == NULL) + return NULL; + + mode = hapd->iface->current_mode; + if (mode->mode != HOSTAPD_MODE_IEEE80211A) + return NULL; + + /* get random available channel */ + channel_idx = hostapd_dfs_find_channel(hapd, NULL, 0); + if (channel_idx > 0) { + os_get_random((u8 *) &_rand, sizeof(_rand)); + new_channel_idx = _rand % channel_idx; + hostapd_dfs_find_channel(hapd, &chan, new_channel_idx); + } + + return chan; +} + + +int ieee802_11_set_dfs_state(struct hostapd_data *hapd, int freq, u32 state) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan = NULL; + int i; + + mode = hapd->iface->current_mode; + if (mode == NULL) + return 0; + + if (mode->mode != HOSTAPD_MODE_IEEE80211A) { + wpa_printf(MSG_WARNING, "current_mode != IEEE80211A"); + return 0; + } + + for (i = 0; i < hapd->iface->current_mode->num_channels; i++) { + chan = &hapd->iface->current_mode->channels[i]; + if (chan->freq == freq) { + if (chan->flag & HOSTAPD_CHAN_RADAR) { + chan->flag &= ~HOSTAPD_CHAN_DFS_MASK; + chan->flag |= state; + return 1; /* Channel found */ + } + } + } + wpa_printf(MSG_WARNING, "Can't set DFS state for freq %d MHz", freq); + return 0; +} + + +/* + * Main DFS handler + * 1 - continue channel/ap setup + * 0 - channel/ap setup will be continued after CAC + * -1 - hit critical error + */ +int hostapd_handle_dfs(struct hostapd_data *hapd) +{ + int flags; + struct hostapd_channel_data *channel; + + /* Handle DFS channel */ +check_dfs_chan_again: + flags = hostapd_hw_get_channel_flag(hapd, hapd->iconf->channel); + if (flags & HOSTAPD_CHAN_RADAR) { + switch (flags & HOSTAPD_CHAN_DFS_MASK) { + case HOSTAPD_CHAN_DFS_USABLE: + wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz", + hapd->iface->freq); + if (hostapd_start_dfs_cac(hapd, + hapd->iface->freq, + flags)) { + wpa_printf(MSG_DEBUG, "DFS start_dfs_cac() failed"); + return -1; + } + /* Continue initialisation after CAC */ + return 0; + case HOSTAPD_CHAN_DFS_UNAVAILABLE: + wpa_printf(MSG_DEBUG, "HOSTAPD_CHAN_DFS_UNAVAILABLE, get new chan"); + /* find other channel here */ + channel = hostapd_dfs_get_valid_channel(hapd); + if (!channel) { + wpa_printf(MSG_ERROR, "could not get valid channel"); + return -1; + } + hapd->iconf->channel = channel->chan; + hapd->iface->freq = channel->freq; + goto check_dfs_chan_again; + case HOSTAPD_CHAN_DFS_AVAILABLE: + /* We don't need CAC here */ + wpa_printf(MSG_DEBUG, "HOSTAPD_CHAN_DFS_AVAILABLE, skip CAC"); + break; + default: + break; + } + } + + return 1; +} + + +int ieee802_11_complete_cac(struct hostapd_data *hapd, int success, int freq) +{ + struct hostapd_channel_data *channel; + int err = 1; + + if (success) { + /* Complete iface/ap configuration */ + ieee802_11_set_dfs_state(hapd, freq, + HOSTAPD_CHAN_DFS_AVAILABLE); + hostapd_setup_interface_complete(hapd->iface, 0); + } else { + /* Switch to new channel */ + ieee802_11_set_dfs_state(hapd, freq, + HOSTAPD_CHAN_DFS_UNAVAILABLE); + channel = hostapd_dfs_get_valid_channel(hapd); + if (channel) { + hapd->iconf->channel = channel->chan; + hapd->iface->freq = channel->freq; + err = 0; + } else + wpa_printf(MSG_ERROR, "No valid channel available"); + + hostapd_setup_interface_complete(hapd->iface, err); + } + + return 0; +} + + +int ieee802_11_start_channel_switch(struct hostapd_data *hapd) +{ + struct hostapd_channel_data *channel; + int err = 1; + + wpa_printf(MSG_DEBUG, "%s called", __func__); + channel = hostapd_dfs_get_valid_channel(hapd); + if (channel) { + hapd->iconf->channel = channel->chan; + hapd->iface->freq = channel->freq; + err = 0; + } + + hapd->driver->stop_ap(hapd->drv_priv); + + hostapd_setup_interface_complete(hapd->iface, err); + return 0; +} diff --git a/src/ap/dfs.h b/src/ap/dfs.h new file mode 100644 index 0000000..3a4e875 --- /dev/null +++ b/src/ap/dfs.h @@ -0,0 +1,18 @@ +/* + * DFS - Dynamic Frequency Selection + * Copyright (c) 2002-2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +#ifndef DFS_H +#define DFS_H + +struct hostapd_channel_data * hostapd_dfs_get_valid_channel( + struct hostapd_data *hapd); +int ieee802_11_complete_cac(struct hostapd_data *hapd, int success, int freq); +int ieee802_11_set_dfs_state(struct hostapd_data *hapd, int freq, u32 state); +int ieee802_11_start_channel_switch(struct hostapd_data *hapd); +int hostapd_handle_dfs(struct hostapd_data *hapd); + +#endif /* DFS_H */ diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index d6bc98d..6fa83c9 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -29,6 +29,7 @@ #include "ap_drv_ops.h" #include "ap_config.h" #include "hw_features.h" +#include "dfs.h" int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, @@ -785,6 +786,61 @@ static void hostapd_event_get_survey(struct hostapd_data *hapd, } +#ifdef NEED_AP_MLME + +static void hostapd_event_dfs_radar_detected(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + int res; + + wpa_printf(MSG_DEBUG, "DFS radar detected on %d MHz", radar->freq); + + if (!hapd->iconf->ieee80211h) + return; + + /* mark radar frequency as invalid */ + res = ieee802_11_set_dfs_state(hapd, radar->freq, + HOSTAPD_CHAN_DFS_UNAVAILABLE); + + /* other frequency, just mark it and return. */ + if (hapd->iface->freq != radar->freq) + return; + + /* we are working on non-DFS channel - skip event */ + if (res == 0) + return; + + /* radar detected while operating, switch the channel. */ + ieee802_11_start_channel_switch(hapd); +} + + +static void hostapd_event_dfs_cac_finished(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS CAC finished on %d MHz", radar->freq); + ieee802_11_complete_cac(hapd, 1, radar->freq); +} + + +static void hostapd_event_dfs_cac_aborted(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS CAC aborted on %d MHz", radar->freq); + ieee802_11_complete_cac(hapd, 0, radar->freq); +} + + +static void hostapd_event_dfs_nop_finished(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS NOP finished on %d MHz", radar->freq); + ieee802_11_set_dfs_state(hapd, radar->freq, HOSTAPD_CHAN_DFS_USABLE); +} + +#endif /* NEED_AP_MLME */ + + void wpa_supplicant_event(void *ctx, enum wpa_event_type event, union wpa_event_data *data) { @@ -929,6 +985,34 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, case EVENT_SURVEY: hostapd_event_get_survey(hapd, &data->survey_results); break; +#ifdef NEED_AP_MLME + case EVENT_DFS_RADAR_DETECTED: + if (!data) + break; + hostapd_event_dfs_radar_detected(hapd, &data->dfs_event); + break; + case EVENT_DFS_CAC_FINISHED: + if (!data) + break; + hostapd_event_dfs_cac_finished(hapd, &data->dfs_event); + break; + case EVENT_DFS_CAC_ABORTED: + if (!data) + break; + hostapd_event_dfs_cac_aborted(hapd, &data->dfs_event); + break; + case EVENT_DFS_NOP_FINISHED: + if (!data) + break; + hostapd_event_dfs_nop_finished(hapd, &data->dfs_event); + break; + case EVENT_CHANNEL_LIST_CHANGED: + /* channel list changed (regulatory?), update channel list */ + /* TODO: check this. hostapd_get_hw_features() initializes + * too much stuff. */ + /* hostapd_get_hw_features(hapd->iface); */ + break; +#endif /* NEED_AP_MLME */ default: wpa_printf(MSG_DEBUG, "Unknown event %d", event); break; diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 575ef2a..3bca385 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -32,6 +32,7 @@ #include "ap_config.h" #include "p2p_hostapd.h" #include "gas_serv.h" +#include "dfs.h" static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); @@ -931,6 +932,9 @@ static int setup_interface(struct hostapd_iface *iface) "be completed in a callback"); return 0; } + + if (iface->conf->ieee80211h) + wpa_printf(MSG_DEBUG, "DFS support is enabled"); } return hostapd_setup_interface_complete(iface, 0); } @@ -950,12 +954,23 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) wpa_printf(MSG_DEBUG, "Completing interface initialization"); if (hapd->iconf->channel) { +#ifdef NEED_AP_MLME + int res; +#endif /* NEED_AP_MLME */ + iface->freq = hostapd_hw_get_freq(hapd, hapd->iconf->channel); wpa_printf(MSG_DEBUG, "Mode: %s Channel: %d " "Frequency: %d MHz", hostapd_hw_mode_txt(hapd->iconf->hw_mode), hapd->iconf->channel, iface->freq); +#ifdef NEED_AP_MLME + /* Check DFS */ + res = hostapd_handle_dfs(hapd); + if (res <= 0) + return res; +#endif /* NEED_AP_MLME */ + if (hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, hapd->iconf->channel, hapd->iconf->ieee80211n, diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index 8a239f4..7e351d7 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -45,6 +45,36 @@ void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, } +#ifndef CONFIG_NO_STDOUT_DEBUG +static char * dfs_info(struct hostapd_channel_data *chan) +{ + static char info[256]; + char *state; + + switch (chan->flag & HOSTAPD_CHAN_DFS_MASK) { + case HOSTAPD_CHAN_DFS_UNKNOWN: + state = "unknown"; + break; + case HOSTAPD_CHAN_DFS_USABLE: + state = "usable"; + break; + case HOSTAPD_CHAN_DFS_UNAVAILABLE: + state = "unavailable"; + break; + case HOSTAPD_CHAN_DFS_AVAILABLE: + state = "available"; + break; + default: + return ""; + } + os_snprintf(info, sizeof(info), " (DFS state = %s)", state); + info[sizeof(info) - 1] = '\0'; + + return info; +} +#endif /* CONFIG_NO_STDOUT_DEBUG */ + + int hostapd_get_hw_features(struct hostapd_iface *iface) { struct hostapd_data *hapd = iface->bss[0]; @@ -71,30 +101,40 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) for (i = 0; i < num_modes; i++) { struct hostapd_hw_modes *feature = &modes[i]; + int dfs_enabled = hapd->iconf->ieee80211h && + (iface->drv_flags & WPA_DRIVER_FLAGS_RADAR); + /* set flag for channels we can use in current regulatory * domain */ for (j = 0; j < feature->num_channels; j++) { + int dfs = 0; + /* * Disable all channels that are marked not to allow - * IBSS operation or active scanning. In addition, - * disable all channels that require radar detection, - * since that (in addition to full DFS) is not yet - * supported. + * IBSS operation or active scanning. + * Use radar channels only if the driver supports DFS. */ - if (feature->channels[j].flag & - (HOSTAPD_CHAN_NO_IBSS | - HOSTAPD_CHAN_PASSIVE_SCAN | - HOSTAPD_CHAN_RADAR)) + if ((feature->channels[j].flag & + HOSTAPD_CHAN_RADAR) && dfs_enabled) { + dfs = 1; + } else if (feature->channels[j].flag & + (HOSTAPD_CHAN_NO_IBSS | + HOSTAPD_CHAN_PASSIVE_SCAN | + HOSTAPD_CHAN_RADAR)) { feature->channels[j].flag |= HOSTAPD_CHAN_DISABLED; + } + if (feature->channels[j].flag & HOSTAPD_CHAN_DISABLED) continue; + wpa_printf(MSG_MSGDUMP, "Allowed channel: mode=%d " - "chan=%d freq=%d MHz max_tx_power=%d dBm", + "chan=%d freq=%d MHz max_tx_power=%d dBm%s", feature->mode, feature->channels[j].chan, feature->channels[j].freq, - feature->channels[j].max_tx_power); + feature->channels[j].max_tx_power, + dfs ? dfs_info(&feature->channels[j]) : ""); } } @@ -849,3 +889,21 @@ int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq) return 0; } + + +int hostapd_hw_get_channel_flag(struct hostapd_data *hapd, int chan) +{ + int i; + + if (!hapd->iface->current_mode) + return 0; + + for (i = 0; i < hapd->iface->current_mode->num_channels; i++) { + struct hostapd_channel_data *ch = + &hapd->iface->current_mode->channels[i]; + if (ch->chan == chan) + return ch->flag; + } + + return 0; +} diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h index abadcd1..b8e287b 100644 --- a/src/ap/hw_features.h +++ b/src/ap/hw_features.h @@ -28,6 +28,7 @@ int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq); int hostapd_check_ht_capab(struct hostapd_iface *iface); int hostapd_prepare_rates(struct hostapd_iface *iface, struct hostapd_hw_modes *mode); +int hostapd_hw_get_channel_flag(struct hostapd_data *hapd, int chan); #else /* NEED_AP_MLME */ static inline void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 5698619..7c44241 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -772,6 +772,7 @@ OBJS += ../src/ap/wmm.o OBJS += ../src/ap/ap_list.o OBJS += ../src/ap/ieee802_11.o OBJS += ../src/ap/hw_features.o +OBJS += ../src/ap/dfs.o CFLAGS += -DNEED_AP_MLME endif ifdef CONFIG_WPS -- 2.1.4