diff --git a/hostapd/ChangeLog b/hostapd/ChangeLog index d48bd151e..17ebc3b33 100644 --- a/hostapd/ChangeLog +++ b/hostapd/ChangeLog @@ -5,6 +5,7 @@ ChangeLog for hostapd configurable with a new command line options (-G) * 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) diff --git a/hostapd/driver.h b/hostapd/driver.h index ba89c1235..3ea019e11 100644 --- a/hostapd/driver.h +++ b/hostapd/driver.h @@ -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, diff --git a/hostapd/driver_i.h b/hostapd/driver_i.h index 2d9734390..280e56e86 100644 --- a/hostapd/driver_i.h +++ b/hostapd/driver_i.h @@ -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 */ diff --git a/hostapd/driver_nl80211.c b/hostapd/driver_nl80211.c index 3502be758..7946b1e17 100644 --- a/hostapd/driver_nl80211.c +++ b/hostapd/driver_nl80211.c @@ -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, }; diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 0602cb04a..fc3aa419c 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -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) diff --git a/hostapd/hw_features.c b/hostapd/hw_features.c index 3e6f7f252..22e1d8382 100644 --- a/hostapd/hw_features.c +++ b/hostapd/hw_features.c @@ -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))