hostapd: Add supported channel bandwidth checking infrastructure

This adds checks to common code to verify supported bandwidth options
for each channel using nl80211-provided info. No support of additional
modes is added, just additional checks. Such checks are needed because
driver/hardware can declare more strict limitations than declared in the
IEEE 802.11 standard. Without this patch hostapd might select
unsupported channel and that will fail because Linux kernel does check
channel bandwidth limitations.

Signed-off-by: Dmitry Lebed <dlebed@quantenna.com>
This commit is contained in:
Dmitry Lebed 2018-03-01 14:49:27 +03:00 committed by Jouni Malinen
parent 4299ad826d
commit ce6d9ce15b
3 changed files with 105 additions and 35 deletions

View file

@ -229,9 +229,6 @@ static int ieee80211n_allowed_ht40_channel_pair(struct hostapd_iface *iface)
{ {
int pri_chan, sec_chan; int pri_chan, sec_chan;
if (!iface->conf->secondary_channel)
return 1; /* HT40 not used */
pri_chan = iface->conf->channel; pri_chan = iface->conf->channel;
sec_chan = pri_chan + iface->conf->secondary_channel * 4; sec_chan = pri_chan + iface->conf->secondary_channel * 4;
@ -697,30 +694,25 @@ int hostapd_check_ht_capab(struct hostapd_iface *iface)
static int hostapd_is_usable_chan(struct hostapd_iface *iface, static int hostapd_is_usable_chan(struct hostapd_iface *iface,
int channel, int primary) int channel, int primary)
{ {
int i;
struct hostapd_channel_data *chan; struct hostapd_channel_data *chan;
if (!iface->current_mode) if (!iface->current_mode)
return 0; return 0;
for (i = 0; i < iface->current_mode->num_channels; i++) { chan = hw_get_channel_chan(iface->current_mode, channel, NULL);
chan = &iface->current_mode->channels[i]; if (!chan)
if (chan->chan != channel) return 0;
continue;
if (!(chan->flag & HOSTAPD_CHAN_DISABLED)) if ((primary && chan_pri_allowed(chan)) ||
(!primary && !(chan->flag & HOSTAPD_CHAN_DISABLED)))
return 1; return 1;
wpa_printf(MSG_DEBUG, wpa_printf(MSG_INFO,
"%schannel [%i] (%i) is disabled for use in AP mode, flags: 0x%x%s%s", "Channel %d (%s) not allowed for AP mode, flags: 0x%x%s%s",
primary ? "" : "Configured HT40 secondary ", channel, primary ? "primary" : "secondary",
i, chan->chan, chan->flag, chan->flag,
chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "", chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "",
chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : ""); chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
}
wpa_printf(MSG_INFO, "Channel %d (%s) not allowed for AP mode",
channel, primary ? "primary" : "secondary");
return 0; return 0;
} }
@ -728,6 +720,12 @@ static int hostapd_is_usable_chan(struct hostapd_iface *iface,
static int hostapd_is_usable_chans(struct hostapd_iface *iface) static int hostapd_is_usable_chans(struct hostapd_iface *iface)
{ {
int secondary_chan; int secondary_chan;
struct hostapd_channel_data *pri_chan;
pri_chan = hw_get_channel_chan(iface->current_mode,
iface->conf->channel, NULL);
if (!pri_chan)
return 0;
if (!hostapd_is_usable_chan(iface, iface->conf->channel, 1)) if (!hostapd_is_usable_chan(iface, iface->conf->channel, 1))
return 0; return 0;
@ -742,13 +740,15 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface)
/* Both HT40+ and HT40- are set, pick a valid secondary channel */ /* Both HT40+ and HT40- are set, pick a valid secondary channel */
secondary_chan = iface->conf->channel + 4; secondary_chan = iface->conf->channel + 4;
if (hostapd_is_usable_chan(iface, secondary_chan, 0)) { if (hostapd_is_usable_chan(iface, secondary_chan, 0) &&
(pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
iface->conf->secondary_channel = 1; iface->conf->secondary_channel = 1;
return 1; return 1;
} }
secondary_chan = iface->conf->channel - 4; secondary_chan = iface->conf->channel - 4;
if (hostapd_is_usable_chan(iface, secondary_chan, 0)) { if (hostapd_is_usable_chan(iface, secondary_chan, 0) &&
(pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
iface->conf->secondary_channel = -1; iface->conf->secondary_channel = -1;
return 1; return 1;
} }

View file

@ -87,30 +87,39 @@ int hw_get_chan(struct hostapd_hw_modes *mode, int freq)
int allowed_ht40_channel_pair(struct hostapd_hw_modes *mode, int pri_chan, int allowed_ht40_channel_pair(struct hostapd_hw_modes *mode, int pri_chan,
int sec_chan) int sec_chan)
{ {
int ok, j, first; int ok, first;
int allowed[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 140, int allowed[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 140,
149, 157, 165, 184, 192 }; 149, 157, 165, 184, 192 };
size_t k; size_t k;
struct hostapd_channel_data *p_chan, *s_chan;
const int ht40_plus = pri_chan < sec_chan;
if (pri_chan == sec_chan || !sec_chan) p_chan = hw_get_channel_chan(mode, pri_chan, NULL);
if (!p_chan)
return 0;
if (pri_chan == sec_chan || !sec_chan) {
if (chan_pri_allowed(p_chan))
return 1; /* HT40 not used */ return 1; /* HT40 not used */
wpa_printf(MSG_ERROR, "Channel %d is not allowed as primary",
pri_chan);
return 0;
}
s_chan = hw_get_channel_chan(mode, sec_chan, NULL);
if (!s_chan)
return 0;
wpa_printf(MSG_DEBUG, wpa_printf(MSG_DEBUG,
"HT40: control channel: %d secondary channel: %d", "HT40: control channel: %d secondary channel: %d",
pri_chan, sec_chan); pri_chan, sec_chan);
/* Verify that HT40 secondary channel is an allowed 20 MHz /* Verify that HT40 secondary channel is an allowed 20 MHz
* channel */ * channel */
ok = 0; if ((s_chan->flag & HOSTAPD_CHAN_DISABLED) ||
for (j = 0; j < mode->num_channels; j++) { (ht40_plus && !(p_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) ||
struct hostapd_channel_data *chan = &mode->channels[j]; (!ht40_plus && !(p_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M))) {
if (!(chan->flag & HOSTAPD_CHAN_DISABLED) &&
chan->chan == sec_chan) {
ok = 1;
break;
}
}
if (!ok) {
wpa_printf(MSG_ERROR, "HT40 secondary channel %d not allowed", wpa_printf(MSG_ERROR, "HT40 secondary channel %d not allowed",
sec_chan); sec_chan);
return 0; return 0;
@ -553,3 +562,59 @@ int ieee80211ac_cap_check(u32 hw, u32 conf)
} }
#endif /* CONFIG_IEEE80211AC */ #endif /* CONFIG_IEEE80211AC */
u32 num_chan_to_bw(int num_chans)
{
switch (num_chans) {
case 2:
case 4:
case 8:
return num_chans * 20;
default:
return 20;
}
}
/* check if BW is applicable for channel */
int chan_bw_allowed(const struct hostapd_channel_data *chan, u32 bw,
int ht40_plus, int pri)
{
u32 bw_mask;
switch (bw) {
case 20:
bw_mask = HOSTAPD_CHAN_WIDTH_20;
break;
case 40:
/* HT 40 MHz support declared only for primary channel,
* just skip 40 MHz secondary checking */
if (pri && ht40_plus)
bw_mask = HOSTAPD_CHAN_WIDTH_40P;
else if (pri && !ht40_plus)
bw_mask = HOSTAPD_CHAN_WIDTH_40M;
else
bw_mask = 0;
break;
case 80:
bw_mask = HOSTAPD_CHAN_WIDTH_80;
break;
case 160:
bw_mask = HOSTAPD_CHAN_WIDTH_160;
break;
default:
bw_mask = 0;
break;
}
return (chan->allowed_bw & bw_mask) == bw_mask;
}
/* check if channel is allowed to be used as primary */
int chan_pri_allowed(const struct hostapd_channel_data *chan)
{
return !(chan->flag & HOSTAPD_CHAN_DISABLED) &&
(chan->allowed_bw & HOSTAPD_CHAN_WIDTH_20);
}

View file

@ -39,4 +39,9 @@ void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps,
int disabled); int disabled);
int ieee80211ac_cap_check(u32 hw, u32 conf); int ieee80211ac_cap_check(u32 hw, u32 conf);
u32 num_chan_to_bw(int num_chans);
int chan_bw_allowed(const struct hostapd_channel_data *chan, u32 bw,
int ht40_plus, int pri);
int chan_pri_allowed(const struct hostapd_channel_data *chan);
#endif /* HW_FEATURES_COMMON_H */ #endif /* HW_FEATURES_COMMON_H */