hostapd/src/ap/dfs.c
Michal Kazior 813d4bac5a DFS: Add support for multi-BSS
If radar was detected single BSS is notified about it. This caused only
that single BSS to be stopped and restarted. However, due to nl80211
interface combinations the BSS was not started on a new channel and
other BSSes remained operating on the old channel.

The downside is that hostapd_disable_iface() causes deauth frames to be
sent. This is undesired but on the other hand it doesn't make sense to
create workarounds that imitate CSA's 'block tx'. For proper Tx
quiescing CSA should be properly implemented.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
2013-10-31 15:12:17 +02:00

689 lines
16 KiB
C

/*
* DFS - Dynamic Frequency Selection
* Copyright (c) 2002-2013, Jouni Malinen <j@w1.fi>
* Copyright (c) 2013, Qualcomm Atheros, 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 "hostapd.h"
#include "ap_drv_ops.h"
#include "drivers/driver.h"
#include "dfs.h"
static int dfs_get_used_n_chans(struct hostapd_data *hapd)
{
int n_chans = 1;
if (hapd->iconf->ieee80211n && hapd->iconf->secondary_channel)
n_chans = 2;
if (hapd->iconf->ieee80211ac) {
switch (hapd->iconf->vht_oper_chwidth) {
case VHT_CHANWIDTH_USE_HT:
break;
case VHT_CHANWIDTH_80MHZ:
n_chans = 4;
break;
case VHT_CHANWIDTH_160MHZ:
n_chans = 8;
break;
default:
break;
}
}
return n_chans;
}
static int dfs_channel_available(struct hostapd_channel_data *chan)
{
if (chan->flag & HOSTAPD_CHAN_DISABLED)
return 0;
if ((chan->flag & HOSTAPD_CHAN_RADAR) &&
((chan->flag & HOSTAPD_CHAN_DFS_MASK) ==
HOSTAPD_CHAN_DFS_UNAVAILABLE))
return 0;
return 1;
}
static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans)
{
/*
* The tables contain first valid channel number based on channel width.
* We will also choose this first channel as the control one.
*/
int allowed_40[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157,
184, 192 };
/*
* VHT80, valid channels based on center frequency:
* 42, 58, 106, 122, 138, 155
*/
int allowed_80[] = { 36, 52, 100, 116, 132, 149 };
int *allowed = allowed_40;
unsigned int i, allowed_no = 0;
switch (n_chans) {
case 2:
allowed = allowed_40;
allowed_no = ARRAY_SIZE(allowed_40);
break;
case 4:
allowed = allowed_80;
allowed_no = ARRAY_SIZE(allowed_80);
break;
default:
wpa_printf(MSG_DEBUG, "Unknown width for %d channels", n_chans);
break;
}
for (i = 0; i < allowed_no; i++) {
if (chan->chan == allowed[i])
return 1;
}
return 0;
}
static int dfs_chan_range_available(struct hostapd_hw_modes *mode,
int first_chan_idx, int num_chans)
{
struct hostapd_channel_data *first_chan, *chan;
int i;
if (first_chan_idx + num_chans >= mode->num_channels)
return 0;
first_chan = &mode->channels[first_chan_idx];
for (i = 0; i < num_chans; i++) {
chan = &mode->channels[first_chan_idx + i];
if (first_chan->freq + i * 20 != chan->freq)
return 0;
if (!dfs_channel_available(chan))
return 0;
}
return 1;
}
/*
* The function assumes HT40+ operation.
* Make sure to adjust the following variables after calling this:
* - hapd->secondary_channel
* - hapd->vht_oper_centr_freq_seg0_idx
* - hapd->vht_oper_centr_freq_seg1_idx
*/
static int 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, n_chans;
mode = hapd->iface->current_mode;
n_chans = dfs_get_used_n_chans(hapd);
wpa_printf(MSG_DEBUG, "DFS new chan checking %d channels", n_chans);
for (i = 0; i < mode->num_channels; i++) {
chan = &mode->channels[i];
/* Skip HT40/VHT incompatible channels */
if (hapd->iconf->ieee80211n &&
hapd->iconf->secondary_channel &&
!dfs_is_chan_allowed(chan, n_chans))
continue;
/* Skip incompatible chandefs */
if (!dfs_chan_range_available(mode, i, n_chans))
continue;
if (ret_chan && idx == channel_idx) {
wpa_printf(MSG_DEBUG, "Selected ch. #%d", chan->chan);
*ret_chan = chan;
return idx;
}
wpa_printf(MSG_DEBUG, "Adding channel: %d", chan->chan);
channel_idx++;
}
return channel_idx;
}
static void dfs_adjust_vht_center_freq(struct hostapd_data *hapd,
struct hostapd_channel_data *chan,
u8 *vht_oper_centr_freq_seg0_idx,
u8 *vht_oper_centr_freq_seg1_idx)
{
if (!hapd->iconf->ieee80211ac)
return;
if (!chan)
return;
*vht_oper_centr_freq_seg1_idx = 0;
switch (hapd->iconf->vht_oper_chwidth) {
case VHT_CHANWIDTH_USE_HT:
if (hapd->iconf->secondary_channel == 1)
*vht_oper_centr_freq_seg0_idx = chan->chan + 2;
else if (hapd->iconf->secondary_channel == -1)
*vht_oper_centr_freq_seg0_idx = chan->chan - 2;
else
*vht_oper_centr_freq_seg0_idx = chan->chan;
break;
case VHT_CHANWIDTH_80MHZ:
*vht_oper_centr_freq_seg0_idx = chan->chan + 6;
break;
case VHT_CHANWIDTH_160MHZ:
*vht_oper_centr_freq_seg0_idx = chan->chan + 14;
break;
default:
wpa_printf(MSG_INFO, "DFS only VHT20/40/80/160 is supported now");
break;
}
wpa_printf(MSG_DEBUG, "DFS adjusting VHT center frequency: %d, %d",
*vht_oper_centr_freq_seg0_idx,
*vht_oper_centr_freq_seg1_idx);
}
/* Return start channel idx we will use for mode->channels[idx] */
static int dfs_get_start_chan_idx(struct hostapd_data *hapd)
{
struct hostapd_hw_modes *mode;
struct hostapd_channel_data *chan;
int channel_no = hapd->iconf->channel;
int res = -1, i;
/* HT40- */
if (hapd->iconf->ieee80211n && hapd->iconf->secondary_channel == -1)
channel_no -= 4;
/* VHT */
if (hapd->iconf->ieee80211ac) {
switch (hapd->iconf->vht_oper_chwidth) {
case VHT_CHANWIDTH_USE_HT:
break;
case VHT_CHANWIDTH_80MHZ:
channel_no =
hapd->iconf->vht_oper_centr_freq_seg0_idx - 6;
break;
case VHT_CHANWIDTH_160MHZ:
channel_no =
hapd->iconf->vht_oper_centr_freq_seg0_idx - 14;
break;
default:
wpa_printf(MSG_INFO,
"DFS only VHT20/40/80/160 is supported now");
channel_no = -1;
break;
}
}
/* Get idx */
mode = hapd->iface->current_mode;
for (i = 0; i < mode->num_channels; i++) {
chan = &mode->channels[i];
if (chan->chan == channel_no) {
res = i;
break;
}
}
if (res == -1)
wpa_printf(MSG_DEBUG, "DFS chan_idx seems wrong: -1");
return res;
}
/* At least one channel have radar flag */
static int dfs_check_chans_radar(struct hostapd_data *hapd, int start_chan_idx,
int n_chans)
{
struct hostapd_channel_data *channel;
struct hostapd_hw_modes *mode;
int i, res = 0;
mode = hapd->iface->current_mode;
for (i = 0; i < n_chans; i++) {
channel = &mode->channels[start_chan_idx + i];
if (channel->flag & HOSTAPD_CHAN_RADAR)
res++;
}
return res;
}
/* All channels available */
static int dfs_check_chans_available(struct hostapd_data *hapd,
int start_chan_idx, int n_chans)
{
struct hostapd_channel_data *channel;
struct hostapd_hw_modes *mode;
int i;
mode = hapd->iface->current_mode;
for(i = 0; i < n_chans; i++) {
channel = &mode->channels[start_chan_idx + i];
if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) !=
HOSTAPD_CHAN_DFS_AVAILABLE)
break;
}
return i == n_chans;
}
/* At least one channel unavailable */
static int dfs_check_chans_unavailable(struct hostapd_data *hapd,
int start_chan_idx,
int n_chans)
{
struct hostapd_channel_data *channel;
struct hostapd_hw_modes *mode;
int i, res = 0;
mode = hapd->iface->current_mode;
for(i = 0; i < n_chans; i++) {
channel = &mode->channels[start_chan_idx + i];
if (channel->flag & HOSTAPD_CHAN_DISABLED)
res++;
if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) ==
HOSTAPD_CHAN_DFS_UNAVAILABLE)
res++;
}
return res;
}
static struct hostapd_channel_data *
dfs_get_valid_channel(struct hostapd_data *hapd,
int *secondary_channel,
u8 *vht_oper_centr_freq_seg0_idx,
u8 *vht_oper_centr_freq_seg1_idx)
{
struct hostapd_hw_modes *mode;
struct hostapd_channel_data *chan = NULL;
int num_available_chandefs;
int chan_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 the count first */
num_available_chandefs = dfs_find_channel(hapd, NULL, 0);
if (num_available_chandefs == 0)
return NULL;
os_get_random((u8 *) &_rand, sizeof(_rand));
chan_idx = _rand % num_available_chandefs;
dfs_find_channel(hapd, &chan, chan_idx);
/* dfs_find_channel() calculations assume HT40+ */
if (hapd->iconf->secondary_channel)
*secondary_channel = 1;
else
*secondary_channel = 0;
dfs_adjust_vht_center_freq(hapd, chan,
vht_oper_centr_freq_seg0_idx,
vht_oper_centr_freq_seg1_idx);
return chan;
}
static int set_dfs_state_freq(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;
wpa_printf(MSG_DEBUG, "set_dfs_state 0x%X for %d MHz", state, freq);
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;
}
static int set_dfs_state(struct hostapd_data *hapd, int freq, int ht_enabled,
int chan_offset, int chan_width, int cf1,
int cf2, u32 state)
{
int n_chans = 1, i;
struct hostapd_hw_modes *mode;
int frequency = freq;
int ret = 0;
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;
}
/* Seems cf1 and chan_width is enough here */
switch (chan_width) {
case CHAN_WIDTH_20_NOHT:
case CHAN_WIDTH_20:
n_chans = 1;
frequency = cf1;
break;
case CHAN_WIDTH_40:
n_chans = 2;
frequency = cf1 - 10;
break;
case CHAN_WIDTH_80:
n_chans = 4;
frequency = cf1 - 30;
break;
case CHAN_WIDTH_160:
n_chans = 8;
frequency = cf1 - 70;
break;
default:
wpa_printf(MSG_INFO, "DFS chan_width %d not supported",
chan_width);
break;
}
wpa_printf(MSG_DEBUG, "DFS freq: %dMHz, n_chans: %d", frequency,
n_chans);
for (i = 0; i < n_chans; i++) {
ret += set_dfs_state_freq(hapd, frequency, state);
frequency = frequency + 20;
}
return ret;
}
static int dfs_are_channels_overlapped(struct hostapd_data *hapd, int freq,
int chan_width, int cf1, int cf2)
{
int start_chan_idx;
struct hostapd_hw_modes *mode;
struct hostapd_channel_data *chan;
int n_chans, i, j, frequency = freq, radar_n_chans = 1;
u8 radar_chan;
int res = 0;
/* Our configuration */
mode = hapd->iface->current_mode;
start_chan_idx = dfs_get_start_chan_idx(hapd);
n_chans = dfs_get_used_n_chans(hapd);
/* Check we are on DFS channel(s) */
if (!dfs_check_chans_radar(hapd, start_chan_idx, n_chans))
return 0;
/* Reported via radar event */
switch (chan_width) {
case CHAN_WIDTH_20_NOHT:
case CHAN_WIDTH_20:
radar_n_chans = 1;
frequency = cf1;
break;
case CHAN_WIDTH_40:
radar_n_chans = 2;
frequency = cf1 - 10;
break;
case CHAN_WIDTH_80:
radar_n_chans = 4;
frequency = cf1 - 30;
break;
case CHAN_WIDTH_160:
radar_n_chans = 8;
frequency = cf1 - 70;
break;
default:
wpa_printf(MSG_INFO, "DFS chan_width %d not supported",
chan_width);
break;
}
ieee80211_freq_to_chan(frequency, &radar_chan);
for (i = 0; i < n_chans; i++) {
chan = &mode->channels[start_chan_idx + i];
if (!(chan->flag & HOSTAPD_CHAN_RADAR))
continue;
for (j = 0; j < radar_n_chans; j++) {
wpa_printf(MSG_DEBUG, "checking our: %d, radar: %d",
chan->chan, radar_chan + j * 4);
if (chan->chan == radar_chan + j * 4)
res++;
}
}
wpa_printf(MSG_DEBUG, "overlapped: %d", res);
return res;
}
/*
* 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)
{
struct hostapd_channel_data *channel;
int res, n_chans, start_chan_idx;
hapd->cac_started = 0;
do {
/* Get start (first) channel for current configuration */
start_chan_idx = dfs_get_start_chan_idx(hapd);
if (start_chan_idx == -1)
return -1;
/* Get number of used channels, depend on width */
n_chans = dfs_get_used_n_chans(hapd);
/* Check if any of configured channels require DFS */
res = dfs_check_chans_radar(hapd, start_chan_idx, n_chans);
wpa_printf(MSG_DEBUG,
"DFS %d channels required radar detection",
res);
if (!res)
return 1;
/* Check if all channels are DFS available */
res = dfs_check_chans_available(hapd, start_chan_idx, n_chans);
wpa_printf(MSG_DEBUG,
"DFS all channels available, (SKIP CAC): %s",
res ? "yes" : "no");
if (res)
return 1;
/* Check if any of configured channels is unavailable */
res = dfs_check_chans_unavailable(hapd, start_chan_idx,
n_chans);
wpa_printf(MSG_DEBUG, "DFS %d chans unavailable - choose other channel: %s",
res, res ? "yes": "no");
if (res) {
int sec;
u8 cf1, cf2;
channel = dfs_get_valid_channel(hapd, &sec, &cf1, &cf2);
if (!channel) {
wpa_printf(MSG_ERROR, "could not get valid channel");
return -1;
}
hapd->iface->freq = channel->freq;
hapd->iconf->channel = channel->chan;
hapd->iconf->secondary_channel = sec;
hapd->iconf->vht_oper_centr_freq_seg0_idx = cf1;
hapd->iconf->vht_oper_centr_freq_seg1_idx = cf2;
}
} while (res);
/* Finally start CAC */
wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz", hapd->iface->freq);
if (hostapd_start_dfs_cac(hapd, hapd->iconf->hw_mode,
hapd->iface->freq,
hapd->iconf->channel,
hapd->iconf->ieee80211n,
hapd->iconf->ieee80211ac,
hapd->iconf->secondary_channel,
hapd->iconf->vht_oper_chwidth,
hapd->iconf->vht_oper_centr_freq_seg0_idx,
hapd->iconf->vht_oper_centr_freq_seg1_idx)) {
wpa_printf(MSG_DEBUG, "DFS start_dfs_cac() failed");
return -1;
}
return 0;
}
int hostapd_dfs_complete_cac(struct hostapd_data *hapd, int success, int freq,
int ht_enabled, int chan_offset, int chan_width,
int cf1, int cf2)
{
if (success) {
/* Complete iface/ap configuration */
set_dfs_state(hapd, freq, ht_enabled, chan_offset,
chan_width, cf1, cf2,
HOSTAPD_CHAN_DFS_AVAILABLE);
hapd->cac_started = 0;
hostapd_setup_interface_complete(hapd->iface, 0);
}
return 0;
}
static int hostapd_dfs_start_channel_switch(struct hostapd_data *hapd)
{
struct hostapd_channel_data *channel;
int err = 1;
int secondary_channel;
u8 vht_oper_centr_freq_seg0_idx;
u8 vht_oper_centr_freq_seg1_idx;
wpa_printf(MSG_DEBUG, "%s called", __func__);
channel = dfs_get_valid_channel(hapd, &secondary_channel,
&vht_oper_centr_freq_seg0_idx,
&vht_oper_centr_freq_seg1_idx);
if (channel) {
wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d",
channel->chan);
hapd->iface->freq = channel->freq;
hapd->iconf->channel = channel->chan;
hapd->iconf->secondary_channel = secondary_channel;
hapd->iconf->vht_oper_centr_freq_seg0_idx =
vht_oper_centr_freq_seg0_idx;
hapd->iconf->vht_oper_centr_freq_seg1_idx =
vht_oper_centr_freq_seg1_idx;
err = 0;
} else {
wpa_printf(MSG_ERROR, "No valid channel available");
}
if (hapd->cac_started) {
wpa_printf(MSG_DEBUG, "DFS radar detected during CAC");
hapd->cac_started = 0;
/* FIXME: Wait for channel(s) to become available if no channel
* has been found */
hostapd_setup_interface_complete(hapd->iface, err);
return err;
}
if (err) {
/* FIXME: Wait for channel(s) to become available */
hostapd_disable_iface(hapd->iface);
return err;
}
wpa_printf(MSG_DEBUG, "DFS radar detected");
hostapd_disable_iface(hapd->iface);
hostapd_enable_iface(hapd->iface);
return 0;
}
int hostapd_dfs_radar_detected(struct hostapd_data *hapd, int freq,
int ht_enabled, int chan_offset, int chan_width,
int cf1, int cf2)
{
int res;
if (!hapd->iconf->ieee80211h)
return 0;
/* mark radar frequency as invalid */
res = set_dfs_state(hapd, freq, ht_enabled, chan_offset,
chan_width, cf1, cf2,
HOSTAPD_CHAN_DFS_UNAVAILABLE);
/* Skip if reported radar event not overlapped our channels */
res = dfs_are_channels_overlapped(hapd, freq, chan_width, cf1, cf2);
if (!res)
return 0;
/* radar detected while operating, switch the channel. */
res = hostapd_dfs_start_channel_switch(hapd);
return res;
}
int hostapd_dfs_nop_finished(struct hostapd_data *hapd, int freq,
int ht_enabled, int chan_offset, int chan_width,
int cf1, int cf2)
{
/* TODO add correct implementation here */
set_dfs_state(hapd, freq, ht_enabled, chan_offset, chan_width, cf1, cf2,
HOSTAPD_CHAN_DFS_USABLE);
return 0;
}