diff options
Diffstat (limited to 'net/wireless')
-rw-r--r-- | net/wireless/core.c | 43 | ||||
-rw-r--r-- | net/wireless/core.h | 6 | ||||
-rw-r--r-- | net/wireless/lib80211_crypt_ccmp.c | 2 | ||||
-rw-r--r-- | net/wireless/lib80211_crypt_tkip.c | 23 | ||||
-rw-r--r-- | net/wireless/nl80211.c | 134 | ||||
-rw-r--r-- | net/wireless/reg.c | 480 | ||||
-rw-r--r-- | net/wireless/reg.h | 29 | ||||
-rw-r--r-- | net/wireless/scan.c | 158 | ||||
-rw-r--r-- | net/wireless/sme.c | 42 | ||||
-rw-r--r-- | net/wireless/sysfs.c | 20 | ||||
-rw-r--r-- | net/wireless/util.c | 5 | ||||
-rw-r--r-- | net/wireless/wext-compat.c | 34 | ||||
-rw-r--r-- | net/wireless/wext-proc.c | 4 |
13 files changed, 862 insertions, 118 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c index c2a2c563d21..71b6b3a9cf1 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1,7 +1,7 @@ /* * This is the linux wireless configuration interface. * - * Copyright 2006-2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> */ #include <linux/if.h> @@ -31,15 +31,10 @@ MODULE_AUTHOR("Johannes Berg"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("wireless configuration support"); -/* RCU might be appropriate here since we usually - * only read the list, and that can happen quite - * often because we need to do it for each command */ +/* RCU-protected (and cfg80211_mutex for writers) */ LIST_HEAD(cfg80211_rdev_list); int cfg80211_rdev_list_generation; -/* - * This is used to protect the cfg80211_rdev_list - */ DEFINE_MUTEX(cfg80211_mutex); /* for debugfs */ @@ -402,6 +397,7 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) rdev->wiphy.retry_long = 4; rdev->wiphy.frag_threshold = (u32) -1; rdev->wiphy.rts_threshold = (u32) -1; + rdev->wiphy.coverage_class = 0; return &rdev->wiphy; } @@ -417,6 +413,18 @@ int wiphy_register(struct wiphy *wiphy) int i; u16 ifmodes = wiphy->interface_modes; + if (WARN_ON(wiphy->addresses && !wiphy->n_addresses)) + return -EINVAL; + + if (WARN_ON(wiphy->addresses && + !is_zero_ether_addr(wiphy->perm_addr) && + memcmp(wiphy->perm_addr, wiphy->addresses[0].addr, + ETH_ALEN))) + return -EINVAL; + + if (wiphy->addresses) + memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN); + /* sanity check ifmodes */ WARN_ON(!ifmodes); ifmodes &= ((1 << __NL80211_IFTYPE_AFTER_LAST) - 1) & ~1; @@ -476,7 +484,7 @@ int wiphy_register(struct wiphy *wiphy) /* set up regulatory info */ wiphy_update_regulatory(wiphy, NL80211_REGDOM_SET_BY_CORE); - list_add(&rdev->list, &cfg80211_rdev_list); + list_add_rcu(&rdev->list, &cfg80211_rdev_list); cfg80211_rdev_list_generation++; mutex_unlock(&cfg80211_mutex); @@ -553,7 +561,8 @@ void wiphy_unregister(struct wiphy *wiphy) * it impossible to find from userspace. */ debugfs_remove_recursive(rdev->wiphy.debugfsdir); - list_del(&rdev->list); + list_del_rcu(&rdev->list); + synchronize_rcu(); /* * Try to grab rdev->mtx. If a command is still in progress, @@ -669,7 +678,7 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, INIT_LIST_HEAD(&wdev->event_list); spin_lock_init(&wdev->event_lock); mutex_lock(&rdev->devlist_mtx); - list_add(&wdev->list, &rdev->netdev_list); + list_add_rcu(&wdev->list, &rdev->netdev_list); rdev->devlist_generation++; /* can only change netns with wiphy */ dev->features |= NETIF_F_NETNS_LOCAL; @@ -745,9 +754,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, mutex_unlock(&rdev->devlist_mtx); dev_put(dev); } -#ifdef CONFIG_CFG80211_WEXT cfg80211_lock_rdev(rdev); mutex_lock(&rdev->devlist_mtx); +#ifdef CONFIG_CFG80211_WEXT wdev_lock(wdev); switch (wdev->iftype) { case NL80211_IFTYPE_ADHOC: @@ -760,10 +769,10 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, break; } wdev_unlock(wdev); +#endif rdev->opencount++; mutex_unlock(&rdev->devlist_mtx); cfg80211_unlock_rdev(rdev); -#endif break; case NETDEV_UNREGISTER: /* @@ -781,13 +790,21 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, */ if (!list_empty(&wdev->list)) { sysfs_remove_link(&dev->dev.kobj, "phy80211"); - list_del_init(&wdev->list); + list_del_rcu(&wdev->list); rdev->devlist_generation++; #ifdef CONFIG_CFG80211_WEXT kfree(wdev->wext.keys); #endif } mutex_unlock(&rdev->devlist_mtx); + /* + * synchronise (so that we won't find this netdev + * from other code any more) and then clear the list + * head so that the above code can safely check for + * !list_empty() to avoid double-cleanup. + */ + synchronize_rcu(); + INIT_LIST_HEAD(&wdev->list); break; case NETDEV_PRE_UP: if (!(wdev->wiphy->interface_modes & BIT(wdev->iftype))) diff --git a/net/wireless/core.h b/net/wireless/core.h index 30ec95f05b5..c326a667022 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -1,7 +1,7 @@ /* * Wireless configuration interface internals. * - * Copyright 2006-2009 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> */ #ifndef __NET_WIRELESS_CORE_H #define __NET_WIRELESS_CORE_H @@ -48,6 +48,7 @@ struct cfg80211_registered_device { /* associate netdev list */ struct mutex devlist_mtx; + /* protected by devlist_mtx or RCU */ struct list_head netdev_list; int devlist_generation; int opencount; /* also protected by devlist_mtx */ @@ -111,7 +112,8 @@ struct cfg80211_internal_bss { unsigned long ts; struct kref ref; atomic_t hold; - bool ies_allocated; + bool beacon_ies_allocated; + bool proberesp_ies_allocated; /* must be last because of priv member */ struct cfg80211_bss pub; diff --git a/net/wireless/lib80211_crypt_ccmp.c b/net/wireless/lib80211_crypt_ccmp.c index 2301dc1edc4..b7fa31d5fd1 100644 --- a/net/wireless/lib80211_crypt_ccmp.c +++ b/net/wireless/lib80211_crypt_ccmp.c @@ -237,7 +237,6 @@ static int lib80211_ccmp_encrypt(struct sk_buff *skb, int hdr_len, void *priv) return -1; pos = skb->data + hdr_len + CCMP_HDR_LEN; - mic = skb_put(skb, CCMP_MIC_LEN); hdr = (struct ieee80211_hdr *)skb->data; ccmp_init_blocks(key->tfm, hdr, key->tx_pn, data_len, b0, b, s0); @@ -257,6 +256,7 @@ static int lib80211_ccmp_encrypt(struct sk_buff *skb, int hdr_len, void *priv) pos += len; } + mic = skb_put(skb, CCMP_MIC_LEN); for (i = 0; i < CCMP_MIC_LEN; i++) mic[i] = b[i] ^ s0[i]; diff --git a/net/wireless/lib80211_crypt_tkip.c b/net/wireless/lib80211_crypt_tkip.c index c36287399d7..8cbdb32ff31 100644 --- a/net/wireless/lib80211_crypt_tkip.c +++ b/net/wireless/lib80211_crypt_tkip.c @@ -36,6 +36,8 @@ MODULE_AUTHOR("Jouni Malinen"); MODULE_DESCRIPTION("lib80211 crypt: TKIP"); MODULE_LICENSE("GPL"); +#define TKIP_HDR_LEN 8 + struct lib80211_tkip_data { #define TKIP_KEY_LEN 32 u8 key[TKIP_KEY_LEN]; @@ -314,13 +316,12 @@ static int lib80211_tkip_hdr(struct sk_buff *skb, int hdr_len, u8 * rc4key, int keylen, void *priv) { struct lib80211_tkip_data *tkey = priv; - int len; u8 *pos; struct ieee80211_hdr *hdr; hdr = (struct ieee80211_hdr *)skb->data; - if (skb_headroom(skb) < 8 || skb->len < hdr_len) + if (skb_headroom(skb) < TKIP_HDR_LEN || skb->len < hdr_len) return -1; if (rc4key == NULL || keylen < 16) @@ -333,9 +334,8 @@ static int lib80211_tkip_hdr(struct sk_buff *skb, int hdr_len, } tkip_mixing_phase2(rc4key, tkey->key, tkey->tx_ttak, tkey->tx_iv16); - len = skb->len - hdr_len; - pos = skb_push(skb, 8); - memmove(pos, pos + 8, hdr_len); + pos = skb_push(skb, TKIP_HDR_LEN); + memmove(pos, pos + TKIP_HDR_LEN, hdr_len); pos += hdr_len; *pos++ = *rc4key; @@ -353,7 +353,7 @@ static int lib80211_tkip_hdr(struct sk_buff *skb, int hdr_len, tkey->tx_iv32++; } - return 8; + return TKIP_HDR_LEN; } static int lib80211_tkip_encrypt(struct sk_buff *skb, int hdr_len, void *priv) @@ -384,9 +384,8 @@ static int lib80211_tkip_encrypt(struct sk_buff *skb, int hdr_len, void *priv) if ((lib80211_tkip_hdr(skb, hdr_len, rc4key, 16, priv)) < 0) return -1; - icv = skb_put(skb, 4); - crc = ~crc32_le(~0, pos, len); + icv = skb_put(skb, 4); icv[0] = crc; icv[1] = crc >> 8; icv[2] = crc >> 16; @@ -434,7 +433,7 @@ static int lib80211_tkip_decrypt(struct sk_buff *skb, int hdr_len, void *priv) return -1; } - if (skb->len < hdr_len + 8 + 4) + if (skb->len < hdr_len + TKIP_HDR_LEN + 4) return -1; pos = skb->data + hdr_len; @@ -462,7 +461,7 @@ static int lib80211_tkip_decrypt(struct sk_buff *skb, int hdr_len, void *priv) } iv16 = (pos[0] << 8) | pos[2]; iv32 = pos[4] | (pos[5] << 8) | (pos[6] << 16) | (pos[7] << 24); - pos += 8; + pos += TKIP_HDR_LEN; if (tkip_replay_check(iv32, iv16, tkey->rx_iv32, tkey->rx_iv16)) { #ifdef CONFIG_LIB80211_DEBUG @@ -523,8 +522,8 @@ static int lib80211_tkip_decrypt(struct sk_buff *skb, int hdr_len, void *priv) tkey->rx_iv16_new = iv16; /* Remove IV and ICV */ - memmove(skb->data + 8, skb->data, hdr_len); - skb_pull(skb, 8); + memmove(skb->data + TKIP_HDR_LEN, skb->data, hdr_len); + skb_pull(skb, TKIP_HDR_LEN); skb_trim(skb, skb->len - 4); return keyidx; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index e3bee3cecdf..5b79ecf17be 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -69,6 +69,7 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { [NL80211_ATTR_WIPHY_RETRY_LONG] = { .type = NLA_U8 }, [NL80211_ATTR_WIPHY_FRAG_THRESHOLD] = { .type = NLA_U32 }, [NL80211_ATTR_WIPHY_RTS_THRESHOLD] = { .type = NLA_U32 }, + [NL80211_ATTR_WIPHY_COVERAGE_CLASS] = { .type = NLA_U8 }, [NL80211_ATTR_IFTYPE] = { .type = NLA_U32 }, [NL80211_ATTR_IFINDEX] = { .type = NLA_U32 }, @@ -143,6 +144,7 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { .len = WLAN_PMKID_LEN }, [NL80211_ATTR_DURATION] = { .type = NLA_U32 }, [NL80211_ATTR_COOKIE] = { .type = NLA_U64 }, + [NL80211_ATTR_TX_RATES] = { .type = NLA_NESTED }, }; /* policy for the attributes */ @@ -444,6 +446,8 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, dev->wiphy.frag_threshold); NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_RTS_THRESHOLD, dev->wiphy.rts_threshold); + NLA_PUT_U8(msg, NL80211_ATTR_WIPHY_COVERAGE_CLASS, + dev->wiphy.coverage_class); NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS, dev->wiphy.max_scan_ssids); @@ -572,6 +576,7 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, CMD(del_pmksa, DEL_PMKSA); CMD(flush_pmksa, FLUSH_PMKSA); CMD(remain_on_channel, REMAIN_ON_CHANNEL); + CMD(set_bitrate_mask, SET_TX_BITRATE_MASK); if (dev->wiphy.flags & WIPHY_FLAG_NETNS_OK) { i++; NLA_PUT_U32(msg, i, NL80211_CMD_SET_WIPHY_NETNS); @@ -684,6 +689,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) u32 changed; u8 retry_short = 0, retry_long = 0; u32 frag_threshold = 0, rts_threshold = 0; + u8 coverage_class = 0; rtnl_lock(); @@ -806,9 +812,16 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) changed |= WIPHY_PARAM_RTS_THRESHOLD; } + if (info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]) { + coverage_class = nla_get_u8( + info->attrs[NL80211_ATTR_WIPHY_COVERAGE_CLASS]); + changed |= WIPHY_PARAM_COVERAGE_CLASS; + } + if (changed) { u8 old_retry_short, old_retry_long; u32 old_frag_threshold, old_rts_threshold; + u8 old_coverage_class; if (!rdev->ops->set_wiphy_params) { result = -EOPNOTSUPP; @@ -819,6 +832,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) old_retry_long = rdev->wiphy.retry_long; old_frag_threshold = rdev->wiphy.frag_threshold; old_rts_threshold = rdev->wiphy.rts_threshold; + old_coverage_class = rdev->wiphy.coverage_class; if (changed & WIPHY_PARAM_RETRY_SHORT) rdev->wiphy.retry_short = retry_short; @@ -828,6 +842,8 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) rdev->wiphy.frag_threshold = frag_threshold; if (changed & WIPHY_PARAM_RTS_THRESHOLD) rdev->wiphy.rts_threshold = rts_threshold; + if (changed & WIPHY_PARAM_COVERAGE_CLASS) + rdev->wiphy.coverage_class = coverage_class; result = rdev->ops->set_wiphy_params(&rdev->wiphy, changed); if (result) { @@ -835,6 +851,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) rdev->wiphy.retry_long = old_retry_long; rdev->wiphy.frag_threshold = old_frag_threshold; rdev->wiphy.rts_threshold = old_rts_threshold; + rdev->wiphy.coverage_class = old_coverage_class; } } @@ -3146,6 +3163,10 @@ static int nl80211_send_bss(struct sk_buff *msg, u32 pid, u32 seq, int flags, NLA_PUT(msg, NL80211_BSS_INFORMATION_ELEMENTS, res->len_information_elements, res->information_elements); + if (res->beacon_ies && res->len_beacon_ies && + res->beacon_ies != res->information_elements) + NLA_PUT(msg, NL80211_BSS_BEACON_IES, + res->len_beacon_ies, res->beacon_ies); if (res->tsf) NLA_PUT_U64(msg, NL80211_BSS_TSF, res->tsf); if (res->beacon_interval) @@ -3550,6 +3571,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev; struct net_device *dev; + struct wireless_dev *wdev; struct cfg80211_crypto_settings crypto; struct ieee80211_channel *chan, *fixedchan; const u8 *bssid, *ssid, *ie = NULL, *prev_bssid = NULL; @@ -3595,7 +3617,8 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) } mutex_lock(&rdev->devlist_mtx); - fixedchan = rdev_fixed_channel(rdev, NULL); + wdev = dev->ieee80211_ptr; + fixedchan = rdev_fixed_channel(rdev, wdev); if (fixedchan && chan != fixedchan) { err = -EBUSY; mutex_unlock(&rdev->devlist_mtx); @@ -4423,6 +4446,109 @@ static int nl80211_cancel_remain_on_channel(struct sk_buff *skb, return err; } +static u32 rateset_to_mask(struct ieee80211_supported_band *sband, + u8 *rates, u8 rates_len) +{ + u8 i; + u32 mask = 0; + + for (i = 0; i < rates_len; i++) { + int rate = (rates[i] & 0x7f) * 5; + int ridx; + for (ridx = 0; ridx < sband->n_bitrates; ridx++) { + struct ieee80211_rate *srate = + &sband->bitrates[ridx]; + if (rate == srate->bitrate) { + mask |= 1 << ridx; + break; + } + } + if (ridx == sband->n_bitrates) + return 0; /* rate not found */ + } + + return mask; +} + +static struct nla_policy +nl80211_txattr_policy[NL80211_TXRATE_MAX + 1] __read_mostly = { + [NL80211_TXRATE_LEGACY] = { .type = NLA_BINARY, + .len = NL80211_MAX_SUPP_RATES }, +}; + +static int nl80211_set_tx_bitrate_mask(struct sk_buff *skb, + struct genl_info *info) +{ + struct nlattr *tb[NL80211_TXRATE_MAX + 1]; + struct cfg80211_registered_device *rdev; + struct cfg80211_bitrate_mask mask; + int err, rem, i; + struct net_device *dev; + struct nlattr *tx_rates; + struct ieee80211_supported_band *sband; + + if (info->attrs[NL80211_ATTR_TX_RATES] == NULL) + return -EINVAL; + + rtnl_lock(); + + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); + if (err) + goto unlock_rtnl; + + if (!rdev->ops->set_bitrate_mask) { + err = -EOPNOTSUPP; + goto unlock; + } + + memset(&mask, 0, sizeof(mask)); + /* Default to all rates enabled */ + for (i = 0; i < IEEE80211_NUM_BANDS; i++) { + sband = rdev->wiphy.bands[i]; + mask.control[i].legacy = + sband ? (1 << sband->n_bitrates) - 1 : 0; + } + + /* + * The nested attribute uses enum nl80211_band as the index. This maps + * directly to the enum ieee80211_band values used in cfg80211. + */ + nla_for_each_nested(tx_rates, info->attrs[NL80211_ATTR_TX_RATES], rem) + { + enum ieee80211_band band = nla_type(tx_rates); + if (band < 0 || band >= IEEE80211_NUM_BANDS) { + err = -EINVAL; + goto unlock; + } + sband = rdev->wiphy.bands[band]; + if (sband == NULL) { + err = -EINVAL; + goto unlock; + } + nla_parse(tb, NL80211_TXRATE_MAX, nla_data(tx_rates), + nla_len(tx_rates), nl80211_txattr_policy); + if (tb[NL80211_TXRATE_LEGACY]) { + mask.control[band].legacy = rateset_to_mask( + sband, + nla_data(tb[NL80211_TXRATE_LEGACY]), + nla_len(tb[NL80211_TXRATE_LEGACY])); + if (mask.control[band].legacy == 0) { + err = -EINVAL; + goto unlock; + } + } + } + + err = rdev->ops->set_bitrate_mask(&rdev->wiphy, dev, NULL, &mask); + + unlock: + dev_put(dev); + cfg80211_unlock_rdev(rdev); + unlock_rtnl: + rtnl_unlock(); + return err; +} + static struct genl_ops nl80211_ops[] = { { .cmd = NL80211_CMD_GET_WIPHY, @@ -4697,6 +4823,12 @@ static struct genl_ops nl80211_ops[] = { .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, }, + { + .cmd = NL80211_CMD_SET_TX_BITRATE_MASK, + .doit = nl80211_set_tx_bitrate_mask, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + }, }; static struct genl_multicast_group nl80211_mlme_mcgrp = { diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 87ea60d84c3..ed89c59bb43 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -43,6 +43,15 @@ #include "regdb.h" #include "nl80211.h" +#ifdef CONFIG_CFG80211_REG_DEBUG +#define REG_DBG_PRINT(format, args...) \ + do { \ + printk(KERN_DEBUG format , ## args); \ + } while (0) +#else +#define REG_DBG_PRINT(args...) +#endif + /* Receipt of information from last regulatory request */ static struct regulatory_request *last_request; @@ -125,6 +134,7 @@ static const struct ieee80211_regdomain *cfg80211_world_regdom = &world_regdom; static char *ieee80211_regdom = "00"; +static char user_alpha2[2]; module_param(ieee80211_regdom, charp, 0444); MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); @@ -243,6 +253,27 @@ static bool regdom_changes(const char *alpha2) return true; } +/* + * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets + * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER + * has ever been issued. + */ +static bool is_user_regdom_saved(void) +{ + if (user_alpha2[0] == '9' && user_alpha2[1] == '7') + return false; + + /* This would indicate a mistake on the design */ + if (WARN((!is_world_regdom(user_alpha2) && + !is_an_alpha2(user_alpha2)), + "Unexpected user alpha2: %c%c\n", + user_alpha2[0], + user_alpha2[1])) + return false; + + return true; +} + /** * country_ie_integrity_changes - tells us if the country IE has changed * @checksum: checksum of country IE of fields we are interested in @@ -476,12 +507,212 @@ static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, } /* + * This is a work around for sanity checking ieee80211_channel_to_frequency()'s + * work. ieee80211_channel_to_frequency() can for example currently provide a + * 2 GHz channel when in fact a 5 GHz channel was desired. An example would be + * an AP providing channel 8 on a country IE triplet when it sent this on the + * 5 GHz band, that channel is designed to be channel 8 on 5 GHz, not a 2 GHz + * channel. + * + * This can be removed once ieee80211_channel_to_frequency() takes in a band. + */ +static bool chan_in_band(int chan, enum ieee80211_band band) +{ + int center_freq = ieee80211_channel_to_frequency(chan); + + switch (band) { + case IEEE80211_BAND_2GHZ: + if (center_freq <= 2484) + return true; + return false; + case IEEE80211_BAND_5GHZ: + if (center_freq >= 5005) + return true; + return false; + default: + return false; + } +} + +/* + * Some APs may send a country IE triplet for each channel they + * support and while this is completely overkill and silly we still + * need to support it. We avoid making a single rule for each channel + * though and to help us with this we use this helper to find the + * actual subband end channel. These type of country IE triplet + * scenerios are handled then, all yielding two regulaotry rules from + * parsing a country IE: + * + * [1] + * [2] + * [36] + * [40] + * + * [1] + * [2-4] + * [5-12] + * [36] + * [40-44] + * + * [1-4] + * [5-7] + * [36-44] + * [48-64] + * + * [36-36] + * [40-40] + * [44-44] + * [48-48] + * [52-52] + * [56-56] + * [60-60] + * [64-64] + * [100-100] + * [104-104] + * [108-108] + * [112-112] + * [116-116] + * [120-120] + * [124-124] + * [128-128] + * [132-132] + * [136-136] + * [140-140] + * + * Returns 0 if the IE has been found to be invalid in the middle + * somewhere. + */ +static int max_subband_chan(enum ieee80211_band band, + int orig_cur_chan, + int orig_end_channel, + s8 orig_max_power, + u8 **country_ie, + u8 *country_ie_len) +{ + u8 *triplets_start = *country_ie; + u8 len_at_triplet = *country_ie_len; + int end_subband_chan = orig_end_channel; + + /* + * We'll deal with padding for the caller unless + * its not immediate and we don't process any channels + */ + if (*country_ie_len == 1) { + *country_ie += 1; + *country_ie_len -= 1; + return orig_end_channel; + } + + /* Move to the next triplet and then start search */ + *country_ie += 3; + *country_ie_len -= 3; + + if (!chan_in_band(orig_cur_chan, band)) + return 0; + + while (*country_ie_len >= 3) { + int end_channel = 0; + struct ieee80211_country_ie_triplet *triplet = + (struct ieee80211_country_ie_triplet *) *country_ie; + int cur_channel = 0, next_expected_chan; + + /* means last triplet is completely unrelated to this one */ + if (triplet->ext.reg_extension_id >= + IEEE80211_COUNTRY_EXTENSION_ID) { + *country_ie -= 3; + *country_ie_len += 3; + break; + } + + if (triplet->chans.first_channel == 0) { + *country_ie += 1; + *country_ie_len -= 1; + if (*country_ie_len != 0) + return 0; + break; + } + + if (triplet->chans.num_channels == 0) + return 0; + + /* Monitonically increasing channel order */ + if (triplet->chans.first_channel <= end_subband_chan) + return 0; + + if (!chan_in_band(triplet->chans.first_channel, band)) + return 0; + + /* 2 GHz */ + if (triplet->chans.first_channel <= 14) { + end_channel = triplet->chans.first_channel + + triplet->chans.num_channels - 1; + } + else { + end_channel = triplet->chans.first_channel + + (4 * (triplet->chans.num_channels - 1)); + } + + if (!chan_in_band(end_channel, band)) + return 0; + + if (orig_max_power != triplet->chans.max_power) { + *country_ie -= 3; + *country_ie_len += 3; + break; + } + + cur_channel = triplet->chans.first_channel; + + /* The key is finding the right next expected channel */ + if (band == IEEE80211_BAND_2GHZ) + next_expected_chan = end_subband_chan + 1; + else + next_expected_chan = end_subband_chan + 4; + + if (cur_channel != next_expected_chan) { + *country_ie -= 3; + *country_ie_len += 3; + break; + } + + end_subband_chan = end_channel; + + /* Move to the next one */ + *country_ie += 3; + *country_ie_len -= 3; + + /* + * Padding needs to be dealt with if we processed + * some channels. + */ + if (*country_ie_len == 1) { + *country_ie += 1; + *country_ie_len -= 1; + break; + } + + /* If seen, the IE is invalid */ + if (*country_ie_len == 2) + return 0; + } + + if (end_subband_chan == orig_end_channel) { + *country_ie = triplets_start; + *country_ie_len = len_at_triplet; + return orig_end_channel; + } + + return end_subband_chan; +} + +/* * Converts a country IE to a regulatory domain. A regulatory domain * structure has a lot of information which the IE doesn't yet have, * so for the other values we use upper max values as we will intersect * with our userspace regulatory agent to get lower bounds. */ static struct ieee80211_regdomain *country_ie_2_rd( + enum ieee80211_band band, u8 *country_ie, u8 country_ie_len, u32 *checksum) @@ -543,10 +774,29 @@ static struct ieee80211_regdomain *country_ie_2_rd( continue; } + /* + * APs can add padding to make length divisible + * by two, required by the spec. + */ + if (triplet->chans.first_channel == 0) { + country_ie++; + country_ie_len--; + /* This is expected to be at the very end only */ + if (country_ie_len != 0) + return NULL; + break; + } + + if (triplet->chans.num_channels == 0) + return NULL; + + if (!chan_in_band(triplet->chans.first_channel, band)) + return NULL; + /* 2 GHz */ - if (triplet->chans.first_channel <= 14) + if (band == IEEE80211_BAND_2GHZ) end_channel = triplet->chans.first_channel + - triplet->chans.num_channels; + triplet->chans.num_channels - 1; else /* * 5 GHz -- For example in country IEs if the first @@ -561,6 +811,24 @@ static struct ieee80211_regdomain *country_ie_2_rd( (4 * (triplet->chans.num_channels - 1)); cur_channel = triplet->chans.first_channel; + + /* + * Enhancement for APs that send a triplet for every channel + * or for whatever reason sends triplets with multiple channels + * separated when in fact they should be together. + */ + end_channel = max_subband_chan(band, + cur_channel, + end_channel, + triplet->chans.max_power, + &country_ie, + &country_ie_len); + if (!end_channel) + return NULL; + + if (!chan_in_band(end_channel, band)) + return NULL; + cur_sub_max_channel = end_channel; /* Basic sanity check */ @@ -591,10 +859,13 @@ static struct ieee80211_regdomain *country_ie_2_rd( last_sub_max_channel = cur_sub_max_channel; - country_ie += 3; - country_ie_len -= 3; num_rules++; + if (country_ie_len >= 3) { + country_ie += 3; + country_ie_len -= 3; + } + /* * Note: this is not a IEEE requirement but * simply a memory requirement @@ -637,6 +908,12 @@ static struct ieee80211_regdomain *country_ie_2_rd( continue; } + if (triplet->chans.first_channel == 0) { + country_ie++; + country_ie_len--; + break; + } + reg_rule = &rd->reg_rules[i]; freq_range = ®_rule->freq_range; power_rule = ®_rule->power_rule; @@ -644,13 +921,20 @@ static struct ieee80211_regdomain *country_ie_2_rd( reg_rule->flags = flags; /* 2 GHz */ - if (triplet->chans.first_channel <= 14) + if (band == IEEE80211_BAND_2GHZ) end_channel = triplet->chans.first_channel + - triplet->chans.num_channels; + triplet->chans.num_channels -1; else end_channel = triplet->chans.first_channel + (4 * (triplet->chans.num_channels - 1)); + end_channel = max_subband_chan(band, + triplet->chans.first_channel, + end_channel, + triplet->chans.max_power, + &country_ie, + &country_ie_len); + /* * The +10 is since the regulatory domain expects * the actual band edge, not the center of freq for @@ -671,12 +955,15 @@ static struct ieee80211_regdomain *country_ie_2_rd( */ freq_range->max_bandwidth_khz = MHZ_TO_KHZ(40); power_rule->max_antenna_gain = DBI_TO_MBI(100); - power_rule->max_eirp = DBM_TO_MBM(100); + power_rule->max_eirp = DBM_TO_MBM(triplet->chans.max_power); - country_ie += 3; - country_ie_len -= 3; i++; + if (country_ie_len >= 3) { + country_ie += 3; + country_ie_len -= 3; + } + BUG_ON(i > NL80211_MAX_SUPP_REG_RULES); } @@ -972,25 +1259,21 @@ static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band, if (r == -ERANGE && last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { -#ifdef CONFIG_CFG80211_REG_DEBUG - printk(KERN_DEBUG "cfg80211: Leaving channel %d MHz " + REG_DBG_PRINT("cfg80211: Leaving channel %d MHz " "intact on %s - no rule found in band on " "Country IE\n", - chan->center_freq, wiphy_name(wiphy)); -#endif + chan->center_freq, wiphy_name(wiphy)); } else { /* * In this case we know the country IE has at least one reg rule * for the band so we respect its band definitions */ -#ifdef CONFIG_CFG80211_REG_DEBUG if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) - printk(KERN_DEBUG "cfg80211: Disabling " + REG_DBG_PRINT("cfg80211: Disabling " "channel %d MHz on %s due to " "Country IE\n", chan->center_freq, wiphy_name(wiphy)); -#endif flags |= IEEE80211_CHAN_DISABLED; chan->flags = flags; } @@ -1385,7 +1668,7 @@ static int ignore_request(struct wiphy *wiphy, switch (pending_request->initiator) { case NL80211_REGDOM_SET_BY_CORE: - return -EINVAL; + return 0; case NL80211_REGDOM_SET_BY_COUNTRY_IE: last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); @@ -1524,6 +1807,11 @@ new_request: pending_request = NULL; + if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) { + user_alpha2[0] = last_request->alpha2[0]; + user_alpha2[1] = last_request->alpha2[1]; + } + /* When r == REG_INTERSECT we do need to call CRDA */ if (r < 0) { /* @@ -1643,12 +1931,16 @@ static void queue_regulatory_request(struct regulatory_request *request) schedule_work(®_work); } -/* Core regulatory hint -- happens once during cfg80211_init() */ +/* + * Core regulatory hint -- happens during cfg80211_init() + * and when we restore regulatory settings. + */ static int regulatory_hint_core(const char *alpha2) { struct regulatory_request *request; - BUG_ON(last_request); + kfree(last_request); + last_request = NULL; request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); @@ -1659,14 +1951,12 @@ static int regulatory_hint_core(const char *alpha2) request->alpha2[1] = alpha2[1]; request->initiator = NL80211_REGDOM_SET_BY_CORE; - queue_regulatory_request(request); - /* * This ensures last_request is populated once modules * come swinging in and calling regulatory hints and * wiphy_apply_custom_regulatory(). */ - flush_scheduled_work(); + reg_process_hint(request); return 0; } @@ -1685,7 +1975,7 @@ int regulatory_hint_user(const char *alpha2) request->wiphy_idx = WIPHY_IDX_STALE; request->alpha2[0] = alpha2[0]; request->alpha2[1] = alpha2[1]; - request->initiator = NL80211_REGDOM_SET_BY_USER, + request->initiator = NL80211_REGDOM_SET_BY_USER; queue_regulatory_request(request); @@ -1753,8 +2043,9 @@ static bool reg_same_country_ie_hint(struct wiphy *wiphy, * therefore cannot iterate over the rdev list here. */ void regulatory_hint_11d(struct wiphy *wiphy, - u8 *country_ie, - u8 country_ie_len) + enum ieee80211_band band, + u8 *country_ie, + u8 country_ie_len) { struct ieee80211_regdomain *rd = NULL; char alpha2[2]; @@ -1800,9 +2091,11 @@ void regulatory_hint_11d(struct wiphy *wiphy, wiphy_idx_valid(last_request->wiphy_idx))) goto out; - rd = country_ie_2_rd(country_ie, country_ie_len, &checksum); - if (!rd) + rd = country_ie_2_rd(band, country_ie, country_ie_len, &checksum); + if (!rd) { + REG_DBG_PRINT("cfg80211: Ignoring bogus country IE\n"); goto out; + } /* * This will not happen right now but we leave it here for the @@ -1845,6 +2138,123 @@ out: mutex_unlock(®_mutex); } +static void restore_alpha2(char *alpha2, bool reset_user) +{ + /* indicates there is no alpha2 to consider for restoration */ + alpha2[0] = '9'; + alpha2[1] = '7'; + + /* The user setting has precedence over the module parameter */ + if (is_user_regdom_saved()) { + /* Unless we're asked to ignore it and reset it */ + if (reset_user) { + REG_DBG_PRINT("cfg80211: Restoring regulatory settings " + "including user preference\n"); + user_alpha2[0] = '9'; + user_alpha2[1] = '7'; + + /* + * If we're ignoring user settings, we still need to + * check the module parameter to ensure we put things + * back as they were for a full restore. + */ + if (!is_world_regdom(ieee80211_regdom)) { + REG_DBG_PRINT("cfg80211: Keeping preference on " + "module parameter ieee80211_regdom: %c%c\n", + ieee80211_regdom[0], + ieee80211_regdom[1]); + alpha2[0] = ieee80211_regdom[0]; + alpha2[1] = ieee80211_regdom[1]; + } + } else { + REG_DBG_PRINT("cfg80211: Restoring regulatory settings " + "while preserving user preference for: %c%c\n", + user_alpha2[0], + user_alpha2[1]); + alpha2[0] = user_alpha2[0]; + alpha2[1] = user_alpha2[1]; + } + } else if (!is_world_regdom(ieee80211_regdom)) { + REG_DBG_PRINT("cfg80211: Keeping preference on " + "module parameter ieee80211_regdom: %c%c\n", + ieee80211_regdom[0], + ieee80211_regdom[1]); + alpha2[0] = ieee80211_regdom[0]; + alpha2[1] = ieee80211_regdom[1]; + } else + REG_DBG_PRINT("cfg80211: Restoring regulatory settings\n"); +} + +/* + * Restoring regulatory settings involves ingoring any + * possibly stale country IE information and user regulatory + * settings if so desired, this includes any beacon hints + * learned as we could have traveled outside to another country + * after disconnection. To restore regulatory settings we do + * exactly what we did at bootup: + * + * - send a core regulatory hint + * - send a user regulatory hint if applicable + * + * Device drivers that send a regulatory hint for a specific country + * keep their own regulatory domain on wiphy->regd so that does does + * not need to be remembered. + */ +static void restore_regulatory_settings(bool reset_user) +{ + char alpha2[2]; + struct reg_beacon *reg_beacon, *btmp; + + mutex_lock(&cfg80211_mutex); + mutex_lock(®_mutex); + + reset_regdomains(); + restore_alpha2(alpha2, reset_user); + + /* Clear beacon hints */ + spin_lock_bh(®_pending_beacons_lock); + if (!list_empty(®_pending_beacons)) { + list_for_each_entry_safe(reg_beacon, btmp, + ®_pending_beacons, list) { + list_del(®_beacon->list); + kfree(reg_beacon); + } + } + spin_unlock_bh(®_pending_beacons_lock); + + if (!list_empty(®_beacon_list)) { + list_for_each_entry_safe(reg_beacon, btmp, + ®_beacon_list, list) { + list_del(®_beacon->list); + kfree(reg_beacon); + } + } + + /* First restore to the basic regulatory settings */ + cfg80211_regdomain = cfg80211_world_regdom; + + mutex_unlock(®_mutex); + mutex_unlock(&cfg80211_mutex); + + regulatory_hint_core(cfg80211_regdomain->alpha2); + + /* + * This restores the ieee80211_regdom module parameter + * preference or the last user requested regulatory + * settings, user regulatory settings takes precedence. + */ + if (is_an_alpha2(alpha2)) + regulatory_hint_user(user_alpha2); +} + + +void regulatory_hint_disconnect(void) +{ + REG_DBG_PRINT("cfg80211: All devices are disconnected, going to " + "restore regulatory settings\n"); + restore_regulatory_settings(false); +} + static bool freq_is_chan_12_13_14(u16 freq) { if (freq == ieee80211_channel_to_frequency(12) || @@ -1870,13 +2280,12 @@ int regulatory_hint_found_beacon(struct wiphy *wiphy, if (!reg_beacon) return -ENOMEM; -#ifdef CONFIG_CFG80211_REG_DEBUG - printk(KERN_DEBUG "cfg80211: Found new beacon on " - "frequency: %d MHz (Ch %d) on %s\n", - beacon_chan->center_freq, - ieee80211_frequency_to_channel(beacon_chan->center_freq), - wiphy_name(wiphy)); -#endif + REG_DBG_PRINT("cfg80211: Found new beacon on " + "frequency: %d MHz (Ch %d) on %s\n", + beacon_chan->center_freq, + ieee80211_frequency_to_channel(beacon_chan->center_freq), + wiphy_name(wiphy)); + memcpy(®_beacon->chan, beacon_chan, sizeof(struct ieee80211_channel)); @@ -2235,6 +2644,9 @@ int regulatory_init(void) cfg80211_regdomain = cfg80211_world_regdom; + user_alpha2[0] = '9'; + user_alpha2[1] = '7'; + /* We always try to get an update for the static regdomain */ err = regulatory_hint_core(cfg80211_regdomain->alpha2); if (err) { diff --git a/net/wireless/reg.h b/net/wireless/reg.h index 3362c7c069b..b26224a9f3b 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -41,15 +41,44 @@ int regulatory_hint_found_beacon(struct wiphy *wiphy, * regulatory_hint_11d - hints a country IE as a regulatory domain * @wiphy: the wireless device giving the hint (used only for reporting * conflicts) + * @band: the band on which the country IE was received on. This determines + * the band we'll process the country IE channel triplets for. * @country_ie: pointer to the country IE * @country_ie_len: length of the country IE * * We will intersect the rd with the what CRDA tells us should apply * for the alpha2 this country IE belongs to, this prevents APs from * sending us incorrect or outdated information against a country. + * + * The AP is expected to provide Country IE channel triplets for the + * band it is on. It is technically possible for APs to send channel + * country IE triplets even for channels outside of the band they are + * in but for that they would have to use the regulatory extension + * in combination with a triplet but this behaviour is currently + * not observed. For this reason if a triplet is seen with channel + * information for a band the BSS is not present in it will be ignored. */ void regulatory_hint_11d(struct wiphy *wiphy, + enum ieee80211_band band, u8 *country_ie, u8 country_ie_len); +/** + * regulatory_hint_disconnect - informs all devices have been disconneted + * + * Regulotory rules can be enhanced further upon scanning and upon + * connection to an AP. These rules become stale if we disconnect + * and go to another country, whether or not we suspend and resume. + * If we suspend, go to another country and resume we'll automatically + * get disconnected shortly after resuming and things will be reset as well. + * This routine is a helper to restore regulatory settings to how they were + * prior to our first connect attempt. This includes ignoring country IE and + * beacon regulatory hints. The ieee80211_regdom module parameter will always + * be respected but if a user had set the regulatory domain that will take + * precedence. + * + * Must be called from process context. + */ +void regulatory_hint_disconnect(void); + #endif /* __NET_WIRELESS_REG_H */ diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 0c2cbbebca9..978cac3414b 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -100,8 +100,10 @@ static void bss_release(struct kref *ref) if (bss->pub.free_priv) bss->pub.free_priv(&bss->pub); - if (bss->ies_allocated) - kfree(bss->pub.information_elements); + if (bss->beacon_ies_allocated) + kfree(bss->pub.beacon_ies); + if (bss->proberesp_ies_allocated) + kfree(bss->pub.proberesp_ies); BUG_ON(atomic_read(&bss->hold)); @@ -141,9 +143,9 @@ void cfg80211_bss_expire(struct cfg80211_registered_device *dev) dev->bss_generation++; } -static u8 *find_ie(u8 num, u8 *ies, int len) +const u8 *cfg80211_find_ie(u8 eid, const u8 *ies, int len) { - while (len > 2 && ies[0] != num) { + while (len > 2 && ies[0] != eid) { len -= ies[1] + 2; ies += ies[1] + 2; } @@ -153,11 +155,12 @@ static u8 *find_ie(u8 num, u8 *ies, int len) return NULL; return ies; } +EXPORT_SYMBOL(cfg80211_find_ie); static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2) { - const u8 *ie1 = find_ie(num, ies1, len1); - const u8 *ie2 = find_ie(num, ies2, len2); + const u8 *ie1 = cfg80211_find_ie(num, ies1, len1); + const u8 *ie2 = cfg80211_find_ie(num, ies2, len2); int r; if (!ie1 && !ie2) @@ -183,9 +186,9 @@ static bool is_bss(struct cfg80211_bss *a, if (!ssid) return true; - ssidie = find_ie(WLAN_EID_SSID, - a->information_elements, - a->len_information_elements); + ssidie = cfg80211_find_ie(WLAN_EID_SSID, + a->information_elements, + a->len_information_elements); if (!ssidie) return false; if (ssidie[1] != ssid_len) @@ -202,9 +205,9 @@ static bool is_mesh(struct cfg80211_bss *a, if (!is_zero_ether_addr(a->bssid)) return false; - ie = find_ie(WLAN_EID_MESH_ID, - a->information_elements, - a->len_information_elements); + ie = cfg80211_find_ie(WLAN_EID_MESH_ID, + a->information_elements, + a->len_information_elements); if (!ie) return false; if (ie[1] != meshidlen) @@ -212,9 +215,9 @@ static bool is_mesh(struct cfg80211_bss *a, if (memcmp(ie + 2, meshid, meshidlen)) return false; - ie = find_ie(WLAN_EID_MESH_CONFIG, - a->information_elements, - a->len_information_elements); + ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, + a->information_elements, + a->len_information_elements); if (!ie) return false; if (ie[1] != sizeof(struct ieee80211_meshconf_ie)) @@ -375,8 +378,7 @@ rb_find_bss(struct cfg80211_registered_device *dev, static struct cfg80211_internal_bss * cfg80211_bss_update(struct cfg80211_registered_device *dev, - struct cfg80211_internal_bss *res, - bool overwrite) + struct cfg80211_internal_bss *res) { struct cfg80211_internal_bss *found = NULL; const u8 *meshid, *meshcfg; @@ -394,11 +396,12 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, if (is_zero_ether_addr(res->pub.bssid)) { /* must be mesh, verify */ - meshid = find_ie(WLAN_EID_MESH_ID, res->pub.information_elements, - res->pub.len_information_elements); - meshcfg = find_ie(WLAN_EID_MESH_CONFIG, - res->pub.information_elements, - res->pub.len_information_elements); + meshid = cfg80211_find_ie(WLAN_EID_MESH_ID, + res->pub.information_elements, + res->pub.len_information_elements); + meshcfg = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, + res->pub.information_elements, + res->pub.len_information_elements); if (!meshid || !meshcfg || meshcfg[1] != sizeof(struct ieee80211_meshconf_ie)) { /* bogus mesh */ @@ -418,28 +421,64 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, found->pub.capability = res->pub.capability; found->ts = res->ts; - /* overwrite IEs */ - if (overwrite) { + /* Update IEs */ + if (res->pub.proberesp_ies) { size_t used = dev->wiphy.bss_priv_size + sizeof(*res); - size_t ielen = res->pub.len_information_elements; + size_t ielen = res->pub.len_proberesp_ies; + + if (found->pub.proberesp_ies && + !found->proberesp_ies_allocated && + ksize(found) >= used + ielen) { + memcpy(found->pub.proberesp_ies, + res->pub.proberesp_ies, ielen); + found->pub.len_proberesp_ies = ielen; + } else { + u8 *ies = found->pub.proberesp_ies; + + if (found->proberesp_ies_allocated) + ies = krealloc(ies, ielen, GFP_ATOMIC); + else + ies = kmalloc(ielen, GFP_ATOMIC); + + if (ies) { + memcpy(ies, res->pub.proberesp_ies, + ielen); + found->proberesp_ies_allocated = true; + found->pub.proberesp_ies = ies; + found->pub.len_proberesp_ies = ielen; + } + } - if (!found->ies_allocated && ksize(found) >= used + ielen) { - memcpy(found->pub.information_elements, - res->pub.information_elements, ielen); - found->pub.len_information_elements = ielen; + /* Override possible earlier Beacon frame IEs */ + found->pub.information_elements = + found->pub.proberesp_ies; + found->pub.len_information_elements = + found->pub.len_proberesp_ies; + } + if (res->pub.beacon_ies) { + size_t used = dev->wiphy.bss_priv_size + sizeof(*res); + size_t ielen = res->pub.len_beacon_ies; + + if (found->pub.beacon_ies && + !found->beacon_ies_allocated && + ksize(found) >= used + ielen) { + memcpy(found->pub.beacon_ies, + res->pub.beacon_ies, ielen); + found->pub.len_beacon_ies = ielen; } else { - u8 *ies = found->pub.information_elements; + u8 *ies = found->pub.beacon_ies; - if (found->ies_allocated) + if (found->beacon_ies_allocated) ies = krealloc(ies, ielen, GFP_ATOMIC); else ies = kmalloc(ielen, GFP_ATOMIC); if (ies) { - memcpy(ies, res->pub.information_elements, ielen); - found->ies_allocated = true; - found->pub.information_elements = ies; - found->pub.len_information_elements = ielen; + memcpy(ies, res->pub.beacon_ies, + ielen); + found->beacon_ies_allocated = true; + found->pub.beacon_ies = ies; + found->pub.len_beacon_ies = ielen; } } } @@ -489,14 +528,26 @@ cfg80211_inform_bss(struct wiphy *wiphy, res->pub.tsf = timestamp; res->pub.beacon_interval = beacon_interval; res->pub.capability = capability; - /* point to after the private area */ - res->pub.information_elements = (u8 *)res + sizeof(*res) + privsz; - memcpy(res->pub.information_elements, ie, ielen); - res->pub.len_information_elements = ielen; + /* + * Since we do not know here whether the IEs are from a Beacon or Probe + * Response frame, we need to pick one of the options and only use it + * with the driver that does not provide the full Beacon/Probe Response + * frame. Use Beacon frame pointer to avoid indicating that this should + * override the information_elements pointer should we have received an + * earlier indication of Probe Response data. + * + * The initial buffer for the IEs is allocated with the BSS entry and + * is located after the private area. + */ + res->pub.beacon_ies = (u8 *)res + sizeof(*res) + privsz; + memcpy(res->pub.beacon_ies, ie, ielen); + res->pub.len_beacon_ies = ielen; + res->pub.information_elements = res->pub.beacon_ies; + res->pub.len_information_elements = res->pub.len_beacon_ies; kref_init(&res->ref); - res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, 0); + res = cfg80211_bss_update(wiphy_to_dev(wiphy), res); if (!res) return NULL; @@ -517,7 +568,6 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy, struct cfg80211_internal_bss *res; size_t ielen = len - offsetof(struct ieee80211_mgmt, u.probe_resp.variable); - bool overwrite; size_t privsz = wiphy->bss_priv_size; if (WARN_ON(wiphy->signal_type == NL80211_BSS_SIGNAL_UNSPEC && @@ -538,16 +588,28 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy, res->pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); res->pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); res->pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); - /* point to after the private area */ - res->pub.information_elements = (u8 *)res + sizeof(*res) + privsz; - memcpy(res->pub.information_elements, mgmt->u.probe_resp.variable, ielen); - res->pub.len_information_elements = ielen; + /* + * The initial buffer for the IEs is allocated with the BSS entry and + * is located after the private area. + */ + if (ieee80211_is_probe_resp(mgmt->frame_control)) { + res->pub.proberesp_ies = (u8 *) res + sizeof(*res) + privsz; + memcpy(res->pub.proberesp_ies, mgmt->u.probe_resp.variable, + ielen); + res->pub.len_proberesp_ies = ielen; + res->pub.information_elements = res->pub.proberesp_ies; + res->pub.len_information_elements = res->pub.len_proberesp_ies; + } else { + res->pub.beacon_ies = (u8 *) res + sizeof(*res) + privsz; + memcpy(res->pub.beacon_ies, mgmt->u.beacon.variable, ielen); + res->pub.len_beacon_ies = ielen; + res->pub.information_elements = res->pub.beacon_ies; + res->pub.len_information_elements = res->pub.len_beacon_ies; + } kref_init(&res->ref); - overwrite = ieee80211_is_probe_resp(mgmt->frame_control); - - res = cfg80211_bss_update(wiphy_to_dev(wiphy), res, overwrite); + res = cfg80211_bss_update(wiphy_to_dev(wiphy), res); if (!res) return NULL; diff --git a/net/wireless/sme.c b/net/wireless/sme.c index 2333d78187e..17fde0da1b0 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -34,6 +34,44 @@ struct cfg80211_conn { bool auto_auth, prev_bssid_valid; }; +bool cfg80211_is_all_idle(void) +{ + struct cfg80211_registered_device *rdev; + struct wireless_dev *wdev; + bool is_all_idle = true; + + mutex_lock(&cfg80211_mutex); + + /* + * All devices must be idle as otherwise if you are actively + * scanning some new beacon hints could be learned and would + * count as new regulatory hints. + */ + list_for_each_entry(rdev, &cfg80211_rdev_list, list) { + cfg80211_lock_rdev(rdev); + list_for_each_entry(wdev, &rdev->netdev_list, list) { + wdev_lock(wdev); + if (wdev->sme_state != CFG80211_SME_IDLE) + is_all_idle = false; + wdev_unlock(wdev); + } + cfg80211_unlock_rdev(rdev); + } + + mutex_unlock(&cfg80211_mutex); + + return is_all_idle; +} + +static void disconnect_work(struct work_struct *work) +{ + if (!cfg80211_is_all_idle()) + return; + + regulatory_hint_disconnect(); +} + +static DECLARE_WORK(cfg80211_disconnect_work, disconnect_work); static int cfg80211_conn_scan(struct wireless_dev *wdev) { @@ -454,6 +492,7 @@ void __cfg80211_connect_result(struct net_device *dev, const u8 *bssid, * - and country_ie[1] which is the IE length */ regulatory_hint_11d(wdev->wiphy, + bss->channel->band, country_ie + 2, country_ie[1]); } @@ -655,7 +694,10 @@ void __cfg80211_disconnected(struct net_device *dev, const u8 *ie, memset(&wrqu, 0, sizeof(wrqu)); wrqu.ap_addr.sa_family = ARPHRD_ETHER; wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); + wdev->wext.connect.ssid_len = 0; #endif + + schedule_work(&cfg80211_disconnect_work); } void cfg80211_disconnected(struct net_device *dev, u16 reason, diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c index efe3c5c92b2..9f2cef3e0ca 100644 --- a/net/wireless/sysfs.c +++ b/net/wireless/sysfs.c @@ -33,10 +33,30 @@ static ssize_t name ## _show(struct device *dev, \ SHOW_FMT(index, "%d", wiphy_idx); SHOW_FMT(macaddress, "%pM", wiphy.perm_addr); +SHOW_FMT(address_mask, "%pM", wiphy.addr_mask); + +static ssize_t addresses_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct wiphy *wiphy = &dev_to_rdev(dev)->wiphy; + char *start = buf; + int i; + + if (!wiphy->addresses) + return sprintf(buf, "%pM\n", wiphy->perm_addr); + + for (i = 0; i < wiphy->n_addresses; i++) + buf += sprintf(buf, "%pM\n", &wiphy->addresses[i].addr); + + return buf - start; +} static struct device_attribute ieee80211_dev_attrs[] = { __ATTR_RO(index), __ATTR_RO(macaddress), + __ATTR_RO(address_mask), + __ATTR_RO(addresses), {} }; diff --git a/net/wireless/util.c b/net/wireless/util.c index 23557c1d0a9..be2ab8c59e3 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -227,8 +227,11 @@ unsigned int ieee80211_hdrlen(__le16 fc) if (ieee80211_is_data(fc)) { if (ieee80211_has_a4(fc)) hdrlen = 30; - if (ieee80211_is_data_qos(fc)) + if (ieee80211_is_data_qos(fc)) { hdrlen += IEEE80211_QOS_CTL_LEN; + if (ieee80211_has_order(fc)) + hdrlen += IEEE80211_HT_CTL_LEN; + } goto out; } diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c index 4198243a3df..b17eeae448d 100644 --- a/net/wireless/wext-compat.c +++ b/net/wireless/wext-compat.c @@ -1204,21 +1204,47 @@ int cfg80211_wext_siwrate(struct net_device *dev, struct wireless_dev *wdev = dev->ieee80211_ptr; struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy); struct cfg80211_bitrate_mask mask; + u32 fixed, maxrate; + struct ieee80211_supported_band *sband; + int band, ridx; + bool match = false; if (!rdev->ops->set_bitrate_mask) return -EOPNOTSUPP; - mask.fixed = 0; - mask.maxrate = 0; + memset(&mask, 0, sizeof(mask)); + fixed = 0; + maxrate = (u32)-1; if (rate->value < 0) { /* nothing */ } else if (rate->fixed) { - mask.fixed = rate->value / 1000; /* kbps */ + fixed = rate->value / 100000; } else { - mask.maxrate = rate->value / 1000; /* kbps */ + maxrate = rate->value / 100000; + } + + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + sband = wdev->wiphy->bands[band]; + if (sband == NULL) + continue; + for (ridx = 0; ridx < sband->n_bitrates; ridx++) { + struct ieee80211_rate *srate = &sband->bitrates[ridx]; + if (fixed == srate->bitrate) { + mask.control[band].legacy = 1 << ridx; + match = true; + break; + } + if (srate->bitrate <= maxrate) { + mask.control[band].legacy |= 1 << ridx; + match = true; + } + } } + if (!match) + return -EINVAL; + return rdev->ops->set_bitrate_mask(wdev->wiphy, dev, NULL, &mask); } EXPORT_SYMBOL_GPL(cfg80211_wext_siwrate); diff --git a/net/wireless/wext-proc.c b/net/wireless/wext-proc.c index 273a7f77c83..8bafa31fa9f 100644 --- a/net/wireless/wext-proc.c +++ b/net/wireless/wext-proc.c @@ -140,7 +140,7 @@ static const struct file_operations wireless_seq_fops = { .release = seq_release_net, }; -int wext_proc_init(struct net *net) +int __net_init wext_proc_init(struct net *net) { /* Create /proc/net/wireless entry */ if (!proc_net_fops_create(net, "wireless", S_IRUGO, &wireless_seq_fops)) @@ -149,7 +149,7 @@ int wext_proc_init(struct net *net) return 0; } -void wext_proc_exit(struct net *net) +void __net_exit wext_proc_exit(struct net *net) { proc_net_remove(net, "wireless"); } |