802.11n: scan for overlapping BSSes before starting 20/40 MHz channel
Try to match PRI/SEC channel with neighboring 20/40 MHz BSSes per IEEE 802.11n/D7.0 11.14.3.2. This is not yet complete implementation, but at least some parts of the 40 MHz coex are improved. 40 MHz operation maybe rejected (i.e., fall back to using 20 MHz) or pri/sec channels may be switched if needed.
This commit is contained in:
parent
30fd5f1467
commit
5eb4e3d024
6 changed files with 585 additions and 4 deletions
|
@ -5,6 +5,7 @@ ChangeLog for hostapd
|
|||
configurable with a new command line options (-G<seconds>)
|
||||
* driver_nl80211: use Linux socket filter to improve performance
|
||||
* added support for external Registrars with WPS (UPnP transport)
|
||||
* 802.11n: scan for overlapping BSSes before starting 20/40 MHz channel
|
||||
|
||||
2009-01-06 - v0.6.7
|
||||
* added support for Wi-Fi Protected Setup (WPS)
|
||||
|
|
|
@ -54,6 +54,14 @@ enum hostapd_driver_if_type {
|
|||
HOSTAPD_IF_VLAN, HOSTAPD_IF_WDS
|
||||
};
|
||||
|
||||
struct hostapd_neighbor_bss {
|
||||
u8 bssid[ETH_ALEN];
|
||||
int freq; /* MHz */
|
||||
unsigned int ht:1;
|
||||
int pri_chan;
|
||||
int sec_chan; /* 0 for 20 MHz channels */
|
||||
};
|
||||
|
||||
struct wpa_driver_ops {
|
||||
const char *name; /* as appears in the config file */
|
||||
|
||||
|
@ -208,6 +216,9 @@ struct wpa_driver_ops {
|
|||
const u8 *ie, size_t len);
|
||||
int (*set_wps_probe_resp_ie)(const char *ifname, void *priv,
|
||||
const u8 *ie, size_t len);
|
||||
|
||||
const struct hostapd_neighbor_bss *
|
||||
(*get_neighbor_bss)(void *priv, size_t *num);
|
||||
};
|
||||
|
||||
void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
|
||||
|
|
|
@ -607,4 +607,12 @@ hostapd_set_wps_probe_resp_ie(struct hostapd_data *hapd, const u8 *ie,
|
|||
hapd->drv_priv, ie, len);
|
||||
}
|
||||
|
||||
static inline const struct hostapd_neighbor_bss *
|
||||
hostapd_driver_get_neighbor_bss(struct hostapd_data *hapd, size_t *num)
|
||||
{
|
||||
if (hapd->driver == NULL || hapd->driver->get_neighbor_bss == NULL)
|
||||
return NULL;
|
||||
return hapd->driver->get_neighbor_bss(hapd->drv_priv, num);
|
||||
}
|
||||
|
||||
#endif /* DRIVER_I_H */
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "radiotap.h"
|
||||
#include "radiotap_iter.h"
|
||||
#include "ieee802_11_defs.h"
|
||||
#include "ieee802_11_common.h"
|
||||
|
||||
#ifdef CONFIG_LIBNL20
|
||||
/* libnl 2.0 compatibility code */
|
||||
|
@ -76,9 +77,12 @@ struct i802_driver_data {
|
|||
int dtim_period, beacon_int;
|
||||
unsigned int beacon_set:1;
|
||||
unsigned int ieee802_1x_active:1;
|
||||
unsigned int ht_40mhz_scan:1;
|
||||
|
||||
int last_freq;
|
||||
int last_freq_ht;
|
||||
struct hostapd_neighbor_bss *neighbors;
|
||||
size_t num_neighbors;
|
||||
};
|
||||
|
||||
|
||||
|
@ -2088,8 +2092,8 @@ static int nl80211_create_monitor_interface(struct i802_driver_data *drv)
|
|||
}
|
||||
|
||||
|
||||
static int nl80211_set_master_mode(struct i802_driver_data *drv,
|
||||
const char *ifname)
|
||||
static int nl80211_set_mode(struct i802_driver_data *drv, const char *ifname,
|
||||
int mode)
|
||||
{
|
||||
struct nl_msg *msg;
|
||||
int ret = -ENOBUFS;
|
||||
|
@ -2102,7 +2106,7 @@ static int nl80211_set_master_mode(struct i802_driver_data *drv,
|
|||
0, NL80211_CMD_SET_INTERFACE, 0);
|
||||
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX,
|
||||
if_nametoindex(ifname));
|
||||
NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP);
|
||||
NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, mode);
|
||||
|
||||
ret = send_and_recv_msgs(drv, msg, NULL, NULL);
|
||||
if (!ret)
|
||||
|
@ -2114,6 +2118,377 @@ static int nl80211_set_master_mode(struct i802_driver_data *drv,
|
|||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_IEEE80211N
|
||||
static void i802_add_neighbor(struct i802_driver_data *drv, u8 *bssid,
|
||||
int freq, u8 *ie, size_t ie_len)
|
||||
{
|
||||
struct ieee802_11_elems elems;
|
||||
int ht, pri_chan = 0, sec_chan = 0;
|
||||
struct ieee80211_ht_operation *oper;
|
||||
struct hostapd_neighbor_bss *nnei;
|
||||
|
||||
ieee802_11_parse_elems(ie, ie_len, &elems, 0);
|
||||
ht = elems.ht_capabilities || elems.ht_operation;
|
||||
if (elems.ht_operation && elems.ht_operation_len >= sizeof(*oper)) {
|
||||
oper = (struct ieee80211_ht_operation *) elems.ht_operation;
|
||||
pri_chan = oper->control_chan;
|
||||
if (oper->ht_param & HT_INFO_HT_PARAM_REC_TRANS_CHNL_WIDTH) {
|
||||
if (oper->ht_param &
|
||||
HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
|
||||
sec_chan = pri_chan + 4;
|
||||
else if (oper->ht_param &
|
||||
HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
|
||||
sec_chan = pri_chan - 4;
|
||||
}
|
||||
}
|
||||
|
||||
wpa_printf(MSG_DEBUG, "nl80211: Neighboring BSS - bssid=" MACSTR
|
||||
" freq=%d MHz HT=%d pri_chan=%d sec_chan=%d",
|
||||
MAC2STR(bssid), freq, ht, pri_chan, sec_chan);
|
||||
|
||||
nnei = os_realloc(drv->neighbors, (drv->num_neighbors + 1) *
|
||||
sizeof(struct hostapd_neighbor_bss));
|
||||
if (nnei == NULL)
|
||||
return;
|
||||
drv->neighbors = nnei;
|
||||
nnei = &nnei[drv->num_neighbors];
|
||||
os_memcpy(nnei->bssid, bssid, ETH_ALEN);
|
||||
nnei->freq = freq;
|
||||
nnei->ht = !!ht;
|
||||
nnei->pri_chan = pri_chan;
|
||||
nnei->sec_chan = sec_chan;
|
||||
drv->num_neighbors++;
|
||||
}
|
||||
|
||||
|
||||
static int i802_get_scan_freq(struct iw_event *iwe, int *freq)
|
||||
{
|
||||
int divi = 1000000, i;
|
||||
|
||||
if (iwe->u.freq.e == 0) {
|
||||
/*
|
||||
* Some drivers do not report frequency, but a channel.
|
||||
* Try to map this to frequency by assuming they are using
|
||||
* IEEE 802.11b/g. But don't overwrite a previously parsed
|
||||
* frequency if the driver sends both frequency and channel,
|
||||
* since the driver may be sending an A-band channel that we
|
||||
* don't handle here.
|
||||
*/
|
||||
|
||||
if (*freq)
|
||||
return 0;
|
||||
|
||||
if (iwe->u.freq.m >= 1 && iwe->u.freq.m <= 13) {
|
||||
*freq = 2407 + 5 * iwe->u.freq.m;
|
||||
return 0;
|
||||
} else if (iwe->u.freq.m == 14) {
|
||||
*freq = 2484;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (iwe->u.freq.e > 6) {
|
||||
wpa_printf(MSG_DEBUG, "Invalid freq in scan results: "
|
||||
"m=%d e=%d", iwe->u.freq.m, iwe->u.freq.e);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < iwe->u.freq.e; i++)
|
||||
divi /= 10;
|
||||
*freq = iwe->u.freq.m / divi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int i802_parse_scan(struct i802_driver_data *drv, u8 *res_buf,
|
||||
size_t len)
|
||||
{
|
||||
size_t ap_num = 0;
|
||||
int first;
|
||||
struct iw_event iwe_buf, *iwe = &iwe_buf;
|
||||
char *pos, *end, *custom;
|
||||
u8 bssid[ETH_ALEN];
|
||||
int freq = 0;
|
||||
u8 *ie = NULL;
|
||||
size_t ie_len = 0;
|
||||
|
||||
ap_num = 0;
|
||||
first = 1;
|
||||
|
||||
pos = (char *) res_buf;
|
||||
end = (char *) res_buf + len;
|
||||
|
||||
while (pos + IW_EV_LCP_LEN <= end) {
|
||||
/* Event data may be unaligned, so make a local, aligned copy
|
||||
* before processing. */
|
||||
os_memcpy(&iwe_buf, pos, IW_EV_LCP_LEN);
|
||||
if (iwe->len <= IW_EV_LCP_LEN)
|
||||
break;
|
||||
|
||||
custom = pos + IW_EV_POINT_LEN;
|
||||
if (iwe->cmd == IWEVGENIE) {
|
||||
/* WE-19 removed the pointer from struct iw_point */
|
||||
char *dpos = (char *) &iwe_buf.u.data.length;
|
||||
int dlen = dpos - (char *) &iwe_buf;
|
||||
os_memcpy(dpos, pos + IW_EV_LCP_LEN,
|
||||
sizeof(struct iw_event) - dlen);
|
||||
} else {
|
||||
os_memcpy(&iwe_buf, pos, sizeof(struct iw_event));
|
||||
custom += IW_EV_POINT_OFF;
|
||||
}
|
||||
|
||||
switch (iwe->cmd) {
|
||||
case SIOCGIWAP:
|
||||
if (!first)
|
||||
i802_add_neighbor(drv, bssid, freq, ie,
|
||||
ie_len);
|
||||
first = 0;
|
||||
os_memcpy(bssid, iwe->u.ap_addr.sa_data, ETH_ALEN);
|
||||
freq = 0;
|
||||
ie = NULL;
|
||||
ie_len = 0;
|
||||
break;
|
||||
case SIOCGIWFREQ:
|
||||
i802_get_scan_freq(iwe, &freq);
|
||||
break;
|
||||
case IWEVGENIE:
|
||||
if (custom + iwe->u.data.length > end) {
|
||||
wpa_printf(MSG_ERROR, "IWEVGENIE overflow");
|
||||
return -1;
|
||||
}
|
||||
ie = (u8 *) custom;
|
||||
ie_len = iwe->u.data.length;
|
||||
break;
|
||||
}
|
||||
|
||||
pos += iwe->len;
|
||||
}
|
||||
|
||||
if (!first)
|
||||
i802_add_neighbor(drv, bssid, freq, ie, ie_len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int i802_get_ht_scan_res(struct i802_driver_data *drv)
|
||||
{
|
||||
struct iwreq iwr;
|
||||
u8 *res_buf;
|
||||
size_t res_buf_len;
|
||||
int res;
|
||||
|
||||
res_buf_len = IW_SCAN_MAX_DATA;
|
||||
for (;;) {
|
||||
res_buf = os_malloc(res_buf_len);
|
||||
if (res_buf == NULL)
|
||||
return -1;
|
||||
os_memset(&iwr, 0, sizeof(iwr));
|
||||
os_strlcpy(iwr.ifr_name, drv->iface, IFNAMSIZ);
|
||||
iwr.u.data.pointer = res_buf;
|
||||
iwr.u.data.length = res_buf_len;
|
||||
|
||||
if (ioctl(drv->ioctl_sock, SIOCGIWSCAN, &iwr) == 0)
|
||||
break;
|
||||
|
||||
if (errno == E2BIG && res_buf_len < 100000) {
|
||||
os_free(res_buf);
|
||||
res_buf = NULL;
|
||||
res_buf_len *= 2;
|
||||
wpa_printf(MSG_DEBUG, "Scan results did not fit - "
|
||||
"trying larger buffer (%lu bytes)",
|
||||
(unsigned long) res_buf_len);
|
||||
} else {
|
||||
perror("ioctl[SIOCGIWSCAN]");
|
||||
os_free(res_buf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (iwr.u.data.length > res_buf_len) {
|
||||
os_free(res_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = i802_parse_scan(drv, res_buf, iwr.u.data.length);
|
||||
os_free(res_buf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static int i802_is_event_wireless_scan_complete(char *data, int len)
|
||||
{
|
||||
struct iw_event iwe_buf, *iwe = &iwe_buf;
|
||||
char *pos, *end;
|
||||
|
||||
pos = data;
|
||||
end = data + len;
|
||||
|
||||
while (pos + IW_EV_LCP_LEN <= end) {
|
||||
/* Event data may be unaligned, so make a local, aligned copy
|
||||
* before processing. */
|
||||
os_memcpy(&iwe_buf, pos, IW_EV_LCP_LEN);
|
||||
if (iwe->cmd == SIOCGIWSCAN)
|
||||
return 1;
|
||||
|
||||
pos += iwe->len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int i802_is_rtm_scan_complete(int ifindex, struct nlmsghdr *h, int len)
|
||||
{
|
||||
struct ifinfomsg *ifi;
|
||||
int attrlen, _nlmsg_len, rta_len;
|
||||
struct rtattr *attr;
|
||||
|
||||
if (len < (int) sizeof(*ifi))
|
||||
return 0;
|
||||
|
||||
ifi = NLMSG_DATA(h);
|
||||
|
||||
if (ifindex != ifi->ifi_index)
|
||||
return 0; /* event for foreign ifindex */
|
||||
|
||||
_nlmsg_len = NLMSG_ALIGN(sizeof(struct ifinfomsg));
|
||||
|
||||
attrlen = h->nlmsg_len - _nlmsg_len;
|
||||
if (attrlen < 0)
|
||||
return 0;
|
||||
|
||||
attr = (struct rtattr *) (((char *) ifi) + _nlmsg_len);
|
||||
|
||||
rta_len = RTA_ALIGN(sizeof(struct rtattr));
|
||||
while (RTA_OK(attr, attrlen)) {
|
||||
if (attr->rta_type == IFLA_WIRELESS &&
|
||||
i802_is_event_wireless_scan_complete(
|
||||
((char *) attr) + rta_len,
|
||||
attr->rta_len - rta_len))
|
||||
return 1;
|
||||
attr = RTA_NEXT(attr, attrlen);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int i802_is_scan_complete(int s, int ifindex)
|
||||
{
|
||||
char buf[1024];
|
||||
int left;
|
||||
struct nlmsghdr *h;
|
||||
|
||||
left = recv(s, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
if (left < 0) {
|
||||
perror("recv(netlink)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
h = (struct nlmsghdr *) buf;
|
||||
while (left >= (int) sizeof(*h)) {
|
||||
int len, plen;
|
||||
|
||||
len = h->nlmsg_len;
|
||||
plen = len - sizeof(*h);
|
||||
if (len > left || plen < 0) {
|
||||
wpa_printf(MSG_DEBUG, "Malformed netlink message: "
|
||||
"len=%d left=%d plen=%d",
|
||||
len, left, plen);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (h->nlmsg_type) {
|
||||
case RTM_NEWLINK:
|
||||
if (i802_is_rtm_scan_complete(ifindex, h, plen))
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
|
||||
len = NLMSG_ALIGN(len);
|
||||
left -= len;
|
||||
h = (struct nlmsghdr *) ((char *) h + len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int i802_ht_scan(struct i802_driver_data *drv)
|
||||
{
|
||||
struct iwreq iwr;
|
||||
int s, res, ifindex;
|
||||
struct sockaddr_nl local;
|
||||
time_t now, end;
|
||||
fd_set rfds;
|
||||
struct timeval tv;
|
||||
|
||||
wpa_printf(MSG_DEBUG, "nl80211: Scanning overlapping BSSes before "
|
||||
"starting HT 20/40 MHz BSS");
|
||||
|
||||
/* Request a new scan */
|
||||
/* TODO: would be enough to scan the selected band */
|
||||
os_memset(&iwr, 0, sizeof(iwr));
|
||||
os_strlcpy(iwr.ifr_name, drv->iface, IFNAMSIZ);
|
||||
if (ioctl(drv->ioctl_sock, SIOCSIWSCAN, &iwr) < 0) {
|
||||
perror("ioctl[SIOCSIWSCAN]");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ifindex = if_nametoindex(drv->iface);
|
||||
|
||||
/* Wait for scan completion event or timeout */
|
||||
s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if (s < 0) {
|
||||
perror("socket(PF_NETLINK,SOCK_RAW,NETLINK_ROUTE)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
os_memset(&local, 0, sizeof(local));
|
||||
local.nl_family = AF_NETLINK;
|
||||
local.nl_groups = RTMGRP_LINK;
|
||||
if (bind(s, (struct sockaddr *) &local, sizeof(local)) < 0) {
|
||||
perror("bind(netlink)");
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
time(&end);
|
||||
end += 30; /* Wait at most 30 seconds for scan results */
|
||||
for (;;) {
|
||||
time(&now);
|
||||
tv.tv_sec = end > now ? end - now : 0;
|
||||
tv.tv_usec = 0;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(s, &rfds);
|
||||
res = select(s + 1, &rfds, NULL, NULL, &tv);
|
||||
if (res < 0) {
|
||||
perror("select");
|
||||
/* Assume results are ready after 10 seconds wait */
|
||||
os_sleep(10, 0);
|
||||
break;
|
||||
} else if (res) {
|
||||
if (i802_is_scan_complete(s, ifindex)) {
|
||||
wpa_printf(MSG_DEBUG, "nl80211: Scan "
|
||||
"completed");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
wpa_printf(MSG_DEBUG, "nl80211: Scan timeout");
|
||||
/* Assume results are ready to be read now */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
||||
return i802_get_ht_scan_res(drv);
|
||||
}
|
||||
#endif /* CONFIG_IEEE80211N */
|
||||
|
||||
|
||||
static int i802_init_sockets(struct i802_driver_data *drv, const u8 *bssid)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
|
@ -2181,11 +2556,24 @@ static int i802_init_sockets(struct i802_driver_data *drv, const u8 *bssid)
|
|||
return -1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IEEE80211N
|
||||
if (drv->ht_40mhz_scan) {
|
||||
if (nl80211_set_mode(drv, drv->iface, NL80211_IFTYPE_STATION)
|
||||
|| hostapd_set_iface_flags(drv, drv->iface, 1) ||
|
||||
i802_ht_scan(drv) ||
|
||||
hostapd_set_iface_flags(drv, drv->iface, 0)) {
|
||||
wpa_printf(MSG_ERROR, "Failed to scan channels for "
|
||||
"HT 40 MHz operations");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_IEEE80211N */
|
||||
|
||||
/* Initialise a monitor interface */
|
||||
if (nl80211_create_monitor_interface(drv))
|
||||
return -1;
|
||||
|
||||
if (nl80211_set_master_mode(drv, drv->iface))
|
||||
if (nl80211_set_mode(drv, drv->iface, NL80211_IFTYPE_AP))
|
||||
goto fail1;
|
||||
|
||||
if (hostapd_set_iface_flags(drv, drv->iface, 1))
|
||||
|
@ -2538,6 +2926,15 @@ static int i802_sta_disassoc(void *priv, const u8 *addr, int reason)
|
|||
}
|
||||
|
||||
|
||||
static const struct hostapd_neighbor_bss *
|
||||
i802_get_neighbor_bss(void *priv, size_t *num)
|
||||
{
|
||||
struct i802_driver_data *drv = priv;
|
||||
*num = drv->num_neighbors;
|
||||
return drv->neighbors;
|
||||
}
|
||||
|
||||
|
||||
static void *i802_init_bssid(struct hostapd_data *hapd, const u8 *bssid)
|
||||
{
|
||||
struct i802_driver_data *drv;
|
||||
|
@ -2554,6 +2951,7 @@ static void *i802_init_bssid(struct hostapd_data *hapd, const u8 *bssid)
|
|||
drv->num_if_indices = sizeof(drv->default_if_indices) / sizeof(int);
|
||||
drv->if_indices = drv->default_if_indices;
|
||||
drv->bridge = if_nametoindex(hapd->conf->bridge);
|
||||
drv->ht_40mhz_scan = hapd->iconf->secondary_channel != 0;
|
||||
|
||||
if (i802_init_sockets(drv, bssid))
|
||||
goto failed;
|
||||
|
@ -2610,6 +3008,7 @@ static void i802_deinit(void *priv)
|
|||
if (drv->if_indices != drv->default_if_indices)
|
||||
free(drv->if_indices);
|
||||
|
||||
os_free(drv->neighbors);
|
||||
free(drv);
|
||||
}
|
||||
|
||||
|
@ -2660,4 +3059,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
|
|||
.get_hw_feature_data = i802_get_hw_feature_data,
|
||||
.set_sta_vlan = i802_set_sta_vlan,
|
||||
.set_country = i802_set_country,
|
||||
.get_neighbor_bss = i802_get_neighbor_bss,
|
||||
};
|
||||
|
|
|
@ -391,6 +391,10 @@ wme_ac_vo_acm=0
|
|||
# 5 GHz 40,48,56,64 36,44,52,60
|
||||
# (depending on the location, not all of these channels may be available
|
||||
# for use)
|
||||
# Please note that 40 MHz channels may switch their primary and secondary
|
||||
# channels if needed or creation of 40 MHz channel maybe rejected based
|
||||
# on overlapping BSSes. These changes are done automatically when hostapd
|
||||
# is setting up the 40 MHz channel.
|
||||
# Spatial Multiplexing (SM) Power Save: [SMPS-STATIC] or [SMPS-DYNAMIC]
|
||||
# (SMPS disabled if neither is set)
|
||||
# HT-greenfield: [GF] (disabled if not set)
|
||||
|
|
|
@ -236,6 +236,162 @@ static int ieee80211n_allowed_ht40_channel_pair(struct hostapd_iface *iface)
|
|||
}
|
||||
|
||||
|
||||
static void ieee80211n_switch_pri_sec(struct hostapd_iface *iface)
|
||||
{
|
||||
if (iface->conf->secondary_channel > 0) {
|
||||
iface->conf->channel += 4;
|
||||
iface->conf->secondary_channel = -1;
|
||||
} else {
|
||||
iface->conf->channel -= 4;
|
||||
iface->conf->secondary_channel = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int ieee80211n_check_40mhz_5g(struct hostapd_iface *iface)
|
||||
{
|
||||
int pri_chan, sec_chan, pri_freq, sec_freq, pri_bss, sec_bss;
|
||||
const struct hostapd_neighbor_bss *n;
|
||||
size_t i, num;
|
||||
int match;
|
||||
|
||||
pri_chan = iface->conf->channel;
|
||||
sec_chan = iface->conf->secondary_channel * 4;
|
||||
pri_freq = hostapd_hw_get_freq(iface->bss[0], pri_chan);
|
||||
if (iface->conf->secondary_channel > 0)
|
||||
sec_freq = pri_freq + 20;
|
||||
else
|
||||
sec_freq = pri_freq - 20;
|
||||
|
||||
n = hostapd_driver_get_neighbor_bss(iface->bss[0], &num);
|
||||
|
||||
/*
|
||||
* Switch PRI/SEC channels if Beacons were detected on selected SEC
|
||||
* channel, but not on selected PRI channel.
|
||||
*/
|
||||
pri_bss = sec_bss = 0;
|
||||
for (i = 0; n && i < num; i++) {
|
||||
if (n[i].freq == pri_freq)
|
||||
pri_bss++;
|
||||
else if (n[i].freq == sec_freq)
|
||||
sec_bss++;
|
||||
}
|
||||
if (sec_bss && !pri_bss) {
|
||||
wpa_printf(MSG_INFO, "Switch own primary and secondary "
|
||||
"channel to get secondary channel with no Beacons "
|
||||
"from other BSSes");
|
||||
ieee80211n_switch_pri_sec(iface);
|
||||
}
|
||||
|
||||
/*
|
||||
* Match PRI/SEC channel with any existing HT40 BSS on the same
|
||||
* channels that we are about to use (if already mixed order in
|
||||
* existing BSSes, use own preference).
|
||||
*/
|
||||
match = 0;
|
||||
for (i = 0; n && i < num; i++) {
|
||||
if (pri_chan == n[i].pri_chan &&
|
||||
sec_chan == n[i].sec_chan) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
for (i = 0; n && i < num; i++) {
|
||||
if (pri_chan == n[i].sec_chan &&
|
||||
sec_chan == n[i].pri_chan) {
|
||||
wpa_printf(MSG_INFO, "Switch own primary and "
|
||||
"secondary channel due to BSS "
|
||||
"overlap with " MACSTR,
|
||||
MAC2STR(n[i].bssid));
|
||||
ieee80211n_switch_pri_sec(iface);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface)
|
||||
{
|
||||
int pri_freq, sec_freq;
|
||||
int affected_start, affected_end;
|
||||
const struct hostapd_neighbor_bss *n;
|
||||
size_t i, num;
|
||||
|
||||
pri_freq = hostapd_hw_get_freq(iface->bss[0], iface->conf->channel);
|
||||
if (iface->conf->secondary_channel > 0)
|
||||
sec_freq = pri_freq + 20;
|
||||
else
|
||||
sec_freq = pri_freq - 20;
|
||||
affected_start = (pri_freq + sec_freq) / 2 - 25;
|
||||
affected_end = (pri_freq + sec_freq) / 2 + 25;
|
||||
wpa_printf(MSG_DEBUG, "40 MHz affected channel range: [%d,%d] MHz",
|
||||
affected_start, affected_end);
|
||||
n = hostapd_driver_get_neighbor_bss(iface->bss[0], &num);
|
||||
for (i = 0; n && i < num; i++) {
|
||||
int pri = n[i].freq;
|
||||
int sec = pri;
|
||||
if (n[i].sec_chan) {
|
||||
if (n[i].sec_chan < n[i].pri_chan)
|
||||
sec = pri - 20;
|
||||
else
|
||||
sec = pri + 20;
|
||||
}
|
||||
|
||||
if ((pri < affected_start || pri > affected_end) &&
|
||||
(sec < affected_start || sec > affected_end))
|
||||
continue; /* not within affected channel range */
|
||||
|
||||
if (n[i].sec_chan) {
|
||||
if (pri_freq != pri || sec_freq != sec) {
|
||||
wpa_printf(MSG_DEBUG, "40 MHz pri/sec "
|
||||
"mismatch with BSS " MACSTR
|
||||
" <%d,%d> (chan=%d%c) vs. <%d,%d>",
|
||||
MAC2STR(n[i].bssid),
|
||||
pri, sec, n[i].pri_chan,
|
||||
sec > pri ? '+' : '-',
|
||||
pri_freq, sec_freq);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: 40 MHz intolerant */
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void ieee80211n_check_40mhz(struct hostapd_iface *iface)
|
||||
{
|
||||
int oper40;
|
||||
|
||||
if (!iface->conf->secondary_channel)
|
||||
return; /* HT40 not used */
|
||||
|
||||
/* Check list of neighboring BSSes (from scan) to see whether 40 MHz is
|
||||
* allowed per IEEE 802.11n/D7.0, 11.14.3.2 */
|
||||
|
||||
if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A)
|
||||
oper40 = ieee80211n_check_40mhz_5g(iface);
|
||||
else
|
||||
oper40 = ieee80211n_check_40mhz_2g4(iface);
|
||||
|
||||
if (!oper40) {
|
||||
wpa_printf(MSG_INFO, "20/40 MHz operation not permitted on "
|
||||
"channel pri=%d sec=%d based on overlapping BSSes",
|
||||
iface->conf->channel,
|
||||
iface->conf->channel +
|
||||
iface->conf->secondary_channel * 4);
|
||||
iface->conf->secondary_channel = 0;
|
||||
iface->conf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int ieee80211n_supported_ht_capab(struct hostapd_iface *iface)
|
||||
{
|
||||
u16 hw = iface->current_mode->ht_capab;
|
||||
|
@ -402,6 +558,7 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_IEEE80211N
|
||||
ieee80211n_check_40mhz(iface);
|
||||
if (!ieee80211n_allowed_ht40_channel_pair(iface))
|
||||
return -1;
|
||||
if (!ieee80211n_supported_ht_capab(iface))
|
||||
|
|
Loading…
Reference in a new issue