diff options
author | Jiri Benc <jbenc@suse.cz> | 2007-05-05 11:45:53 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2007-05-05 11:45:53 -0700 |
commit | f0706e828e96d0fa4e80c0d25aa98523f6d589a0 (patch) | |
tree | a03c7f94939d74c1e1b82fcd9a215871590d8b35 /net/mac80211/ieee80211_sta.c | |
parent | a9de8ce0943e03b425be18561f51159fcceb873d (diff) |
[MAC80211]: Add mac80211 wireless stack.
Add mac80211, the IEEE 802.11 software MAC layer.
Signed-off-by: Jiri Benc <jbenc@suse.cz>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/mac80211/ieee80211_sta.c')
-rw-r--r-- | net/mac80211/ieee80211_sta.c | 3060 |
1 files changed, 3060 insertions, 0 deletions
diff --git a/net/mac80211/ieee80211_sta.c b/net/mac80211/ieee80211_sta.c new file mode 100644 index 00000000000..822917debef --- /dev/null +++ b/net/mac80211/ieee80211_sta.c @@ -0,0 +1,3060 @@ +/* + * BSS client mode implementation + * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi> + * Copyright 2004, Instant802 Networks, Inc. + * Copyright 2005, Devicescape Software, Inc. + * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz> + * Copyright 2007, Michael Wu <flamingice@sourmilk.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* TODO: + * BSS table: use <BSSID,SSID> as the key to support multi-SSID APs + * order BSS list by RSSI(?) ("quality of AP") + * scan result table filtering (by capability (privacy, IBSS/BSS, WPA/RSN IE, + * SSID) + */ +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/wireless.h> +#include <linux/random.h> +#include <linux/etherdevice.h> +#include <linux/rtnetlink.h> +#include <net/iw_handler.h> +#include <asm/types.h> +#include <asm/delay.h> + +#include <net/mac80211.h> +#include "ieee80211_i.h" +#include "ieee80211_rate.h" +#include "hostapd_ioctl.h" + +#define IEEE80211_AUTH_TIMEOUT (HZ / 5) +#define IEEE80211_AUTH_MAX_TRIES 3 +#define IEEE80211_ASSOC_TIMEOUT (HZ / 5) +#define IEEE80211_ASSOC_MAX_TRIES 3 +#define IEEE80211_MONITORING_INTERVAL (2 * HZ) +#define IEEE80211_PROBE_INTERVAL (60 * HZ) +#define IEEE80211_RETRY_AUTH_INTERVAL (1 * HZ) +#define IEEE80211_SCAN_INTERVAL (2 * HZ) +#define IEEE80211_SCAN_INTERVAL_SLOW (15 * HZ) +#define IEEE80211_IBSS_JOIN_TIMEOUT (20 * HZ) + +#define IEEE80211_PROBE_DELAY (HZ / 33) +#define IEEE80211_CHANNEL_TIME (HZ / 33) +#define IEEE80211_PASSIVE_CHANNEL_TIME (HZ / 5) +#define IEEE80211_SCAN_RESULT_EXPIRE (10 * HZ) +#define IEEE80211_IBSS_MERGE_INTERVAL (30 * HZ) +#define IEEE80211_IBSS_INACTIVITY_LIMIT (60 * HZ) + +#define IEEE80211_IBSS_MAX_STA_ENTRIES 128 + + +#define IEEE80211_FC(type, stype) cpu_to_le16(type | stype) + +#define ERP_INFO_USE_PROTECTION BIT(1) + +static void ieee80211_send_probe_req(struct net_device *dev, u8 *dst, + u8 *ssid, size_t ssid_len); +static struct ieee80211_sta_bss * +ieee80211_rx_bss_get(struct net_device *dev, u8 *bssid); +static void ieee80211_rx_bss_put(struct net_device *dev, + struct ieee80211_sta_bss *bss); +static int ieee80211_sta_find_ibss(struct net_device *dev, + struct ieee80211_if_sta *ifsta); +static int ieee80211_sta_wep_configured(struct net_device *dev); +static int ieee80211_sta_start_scan(struct net_device *dev, + u8 *ssid, size_t ssid_len); +static int ieee80211_sta_config_auth(struct net_device *dev, + struct ieee80211_if_sta *ifsta); + + +/* Parsed Information Elements */ +struct ieee802_11_elems { + u8 *ssid; + u8 ssid_len; + u8 *supp_rates; + u8 supp_rates_len; + u8 *fh_params; + u8 fh_params_len; + u8 *ds_params; + u8 ds_params_len; + u8 *cf_params; + u8 cf_params_len; + u8 *tim; + u8 tim_len; + u8 *ibss_params; + u8 ibss_params_len; + u8 *challenge; + u8 challenge_len; + u8 *wpa; + u8 wpa_len; + u8 *rsn; + u8 rsn_len; + u8 *erp_info; + u8 erp_info_len; + u8 *ext_supp_rates; + u8 ext_supp_rates_len; + u8 *wmm_info; + u8 wmm_info_len; + u8 *wmm_param; + u8 wmm_param_len; +}; + +typedef enum { ParseOK = 0, ParseUnknown = 1, ParseFailed = -1 } ParseRes; + + +static ParseRes ieee802_11_parse_elems(u8 *start, size_t len, + struct ieee802_11_elems *elems) +{ + size_t left = len; + u8 *pos = start; + int unknown = 0; + + memset(elems, 0, sizeof(*elems)); + + while (left >= 2) { + u8 id, elen; + + id = *pos++; + elen = *pos++; + left -= 2; + + if (elen > left) { +#if 0 + if (net_ratelimit()) + printk(KERN_DEBUG "IEEE 802.11 element parse " + "failed (id=%d elen=%d left=%d)\n", + id, elen, left); +#endif + return ParseFailed; + } + + switch (id) { + case WLAN_EID_SSID: + elems->ssid = pos; + elems->ssid_len = elen; + break; + case WLAN_EID_SUPP_RATES: + elems->supp_rates = pos; + elems->supp_rates_len = elen; + break; + case WLAN_EID_FH_PARAMS: + elems->fh_params = pos; + elems->fh_params_len = elen; + break; + case WLAN_EID_DS_PARAMS: + elems->ds_params = pos; + elems->ds_params_len = elen; + break; + case WLAN_EID_CF_PARAMS: + elems->cf_params = pos; + elems->cf_params_len = elen; + break; + case WLAN_EID_TIM: + elems->tim = pos; + elems->tim_len = elen; + break; + case WLAN_EID_IBSS_PARAMS: + elems->ibss_params = pos; + elems->ibss_params_len = elen; + break; + case WLAN_EID_CHALLENGE: + elems->challenge = pos; + elems->challenge_len = elen; + break; + case WLAN_EID_WPA: + if (elen >= 4 && pos[0] == 0x00 && pos[1] == 0x50 && + pos[2] == 0xf2) { + /* Microsoft OUI (00:50:F2) */ + if (pos[3] == 1) { + /* OUI Type 1 - WPA IE */ + elems->wpa = pos; + elems->wpa_len = elen; + } else if (elen >= 5 && pos[3] == 2) { + if (pos[4] == 0) { + elems->wmm_info = pos; + elems->wmm_info_len = elen; + } else if (pos[4] == 1) { + elems->wmm_param = pos; + elems->wmm_param_len = elen; + } + } + } + break; + case WLAN_EID_RSN: + elems->rsn = pos; + elems->rsn_len = elen; + break; + case WLAN_EID_ERP_INFO: + elems->erp_info = pos; + elems->erp_info_len = elen; + break; + case WLAN_EID_EXT_SUPP_RATES: + elems->ext_supp_rates = pos; + elems->ext_supp_rates_len = elen; + break; + default: +#if 0 + printk(KERN_DEBUG "IEEE 802.11 element parse ignored " + "unknown element (id=%d elen=%d)\n", + id, elen); +#endif + unknown++; + break; + } + + left -= elen; + pos += elen; + } + + /* Do not trigger error if left == 1 as Apple Airport base stations + * send AssocResps that are one spurious byte too long. */ + + return unknown ? ParseUnknown : ParseOK; +} + + + + +static int ecw2cw(int ecw) +{ + int cw = 1; + while (ecw > 0) { + cw <<= 1; + ecw--; + } + return cw - 1; +} + + +static void ieee80211_sta_wmm_params(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + u8 *wmm_param, size_t wmm_param_len) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_tx_queue_params params; + size_t left; + int count; + u8 *pos; + + if (wmm_param_len < 8 || wmm_param[5] /* version */ != 1) + return; + count = wmm_param[6] & 0x0f; + if (count == ifsta->wmm_last_param_set) + return; + ifsta->wmm_last_param_set = count; + + pos = wmm_param + 8; + left = wmm_param_len - 8; + + memset(¶ms, 0, sizeof(params)); + + if (!local->ops->conf_tx) + return; + + local->wmm_acm = 0; + for (; left >= 4; left -= 4, pos += 4) { + int aci = (pos[0] >> 5) & 0x03; + int acm = (pos[0] >> 4) & 0x01; + int queue; + + switch (aci) { + case 1: + queue = IEEE80211_TX_QUEUE_DATA3; + if (acm) { + local->wmm_acm |= BIT(0) | BIT(3); + } + break; + case 2: + queue = IEEE80211_TX_QUEUE_DATA1; + if (acm) { + local->wmm_acm |= BIT(4) | BIT(5); + } + break; + case 3: + queue = IEEE80211_TX_QUEUE_DATA0; + if (acm) { + local->wmm_acm |= BIT(6) | BIT(7); + } + break; + case 0: + default: + queue = IEEE80211_TX_QUEUE_DATA2; + if (acm) { + local->wmm_acm |= BIT(1) | BIT(2); + } + break; + } + + params.aifs = pos[0] & 0x0f; + params.cw_max = ecw2cw((pos[1] & 0xf0) >> 4); + params.cw_min = ecw2cw(pos[1] & 0x0f); + /* TXOP is in units of 32 usec; burst_time in 0.1 ms */ + params.burst_time = (pos[2] | (pos[3] << 8)) * 32 / 100; + printk(KERN_DEBUG "%s: WMM queue=%d aci=%d acm=%d aifs=%d " + "cWmin=%d cWmax=%d burst=%d\n", + dev->name, queue, aci, acm, params.aifs, params.cw_min, + params.cw_max, params.burst_time); + /* TODO: handle ACM (block TX, fallback to next lowest allowed + * AC for now) */ + if (local->ops->conf_tx(local_to_hw(local), queue, ¶ms)) { + printk(KERN_DEBUG "%s: failed to set TX queue " + "parameters for queue %d\n", dev->name, queue); + } + } +} + + +static void ieee80211_sta_send_associnfo(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + char *buf; + size_t len; + int i; + union iwreq_data wrqu; + + if (!ifsta->assocreq_ies && !ifsta->assocresp_ies) + return; + + buf = kmalloc(50 + 2 * (ifsta->assocreq_ies_len + + ifsta->assocresp_ies_len), GFP_ATOMIC); + if (!buf) + return; + + len = sprintf(buf, "ASSOCINFO("); + if (ifsta->assocreq_ies) { + len += sprintf(buf + len, "ReqIEs="); + for (i = 0; i < ifsta->assocreq_ies_len; i++) { + len += sprintf(buf + len, "%02x", + ifsta->assocreq_ies[i]); + } + } + if (ifsta->assocresp_ies) { + if (ifsta->assocreq_ies) + len += sprintf(buf + len, " "); + len += sprintf(buf + len, "RespIEs="); + for (i = 0; i < ifsta->assocresp_ies_len; i++) { + len += sprintf(buf + len, "%02x", + ifsta->assocresp_ies[i]); + } + } + len += sprintf(buf + len, ")"); + + if (len > IW_CUSTOM_MAX) { + len = sprintf(buf, "ASSOCRESPIE="); + for (i = 0; i < ifsta->assocresp_ies_len; i++) { + len += sprintf(buf + len, "%02x", + ifsta->assocresp_ies[i]); + } + } + + memset(&wrqu, 0, sizeof(wrqu)); + wrqu.data.length = len; + wireless_send_event(dev, IWEVCUSTOM, &wrqu, buf); + + kfree(buf); +} + + +static void ieee80211_set_associated(struct net_device *dev, + struct ieee80211_if_sta *ifsta, int assoc) +{ + union iwreq_data wrqu; + + if (ifsta->associated == assoc) + return; + + ifsta->associated = assoc; + + if (assoc) { + struct ieee80211_sub_if_data *sdata; + sdata = IEEE80211_DEV_TO_SUB_IF(dev); + if (sdata->type != IEEE80211_IF_TYPE_STA) + return; + netif_carrier_on(dev); + ifsta->prev_bssid_set = 1; + memcpy(ifsta->prev_bssid, sdata->u.sta.bssid, ETH_ALEN); + memcpy(wrqu.ap_addr.sa_data, sdata->u.sta.bssid, ETH_ALEN); + ieee80211_sta_send_associnfo(dev, ifsta); + } else { + netif_carrier_off(dev); + memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN); + } + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); + ifsta->last_probe = jiffies; +} + +static void ieee80211_set_disassoc(struct net_device *dev, + struct ieee80211_if_sta *ifsta, int deauth) +{ + if (deauth) + ifsta->auth_tries = 0; + ifsta->assoc_tries = 0; + ieee80211_set_associated(dev, ifsta, 0); +} + +static void ieee80211_sta_tx(struct net_device *dev, struct sk_buff *skb, + int encrypt) +{ + struct ieee80211_sub_if_data *sdata; + struct ieee80211_tx_packet_data *pkt_data; + + sdata = IEEE80211_DEV_TO_SUB_IF(dev); + skb->dev = sdata->local->mdev; + skb_set_mac_header(skb, 0); + skb_set_network_header(skb, 0); + skb_set_transport_header(skb, 0); + + pkt_data = (struct ieee80211_tx_packet_data *) skb->cb; + memset(pkt_data, 0, sizeof(struct ieee80211_tx_packet_data)); + pkt_data->ifindex = sdata->dev->ifindex; + pkt_data->mgmt_iface = (sdata->type == IEEE80211_IF_TYPE_MGMT); + pkt_data->do_not_encrypt = !encrypt; + + dev_queue_xmit(skb); +} + + +static void ieee80211_send_auth(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + int transaction, u8 *extra, size_t extra_len, + int encrypt) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + + skb = dev_alloc_skb(local->hw.extra_tx_headroom + + sizeof(*mgmt) + 6 + extra_len); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for auth " + "frame\n", dev->name); + return; + } + skb_reserve(skb, local->hw.extra_tx_headroom); + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24 + 6); + memset(mgmt, 0, 24 + 6); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_AUTH); + if (encrypt) + mgmt->frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->u.auth.auth_alg = cpu_to_le16(ifsta->auth_alg); + mgmt->u.auth.auth_transaction = cpu_to_le16(transaction); + ifsta->auth_transaction = transaction + 1; + mgmt->u.auth.status_code = cpu_to_le16(0); + if (extra) + memcpy(skb_put(skb, extra_len), extra, extra_len); + + ieee80211_sta_tx(dev, skb, encrypt); +} + + +static void ieee80211_authenticate(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + ifsta->auth_tries++; + if (ifsta->auth_tries > IEEE80211_AUTH_MAX_TRIES) { + printk(KERN_DEBUG "%s: authentication with AP " MAC_FMT + " timed out\n", + dev->name, MAC_ARG(ifsta->bssid)); + ifsta->state = IEEE80211_DISABLED; + return; + } + + ifsta->state = IEEE80211_AUTHENTICATE; + printk(KERN_DEBUG "%s: authenticate with AP " MAC_FMT "\n", + dev->name, MAC_ARG(ifsta->bssid)); + + ieee80211_send_auth(dev, ifsta, 1, NULL, 0, 0); + + mod_timer(&ifsta->timer, jiffies + IEEE80211_AUTH_TIMEOUT); +} + + +static void ieee80211_send_assoc(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_hw_mode *mode; + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + u8 *pos, *ies; + int i, len; + u16 capab; + struct ieee80211_sta_bss *bss; + int wmm = 0; + + skb = dev_alloc_skb(local->hw.extra_tx_headroom + + sizeof(*mgmt) + 200 + ifsta->extra_ie_len + + ifsta->ssid_len); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for assoc " + "frame\n", dev->name); + return; + } + skb_reserve(skb, local->hw.extra_tx_headroom); + + mode = local->oper_hw_mode; + capab = ifsta->capab; + if (mode->mode == MODE_IEEE80211G) { + capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME | + WLAN_CAPABILITY_SHORT_PREAMBLE; + } + bss = ieee80211_rx_bss_get(dev, ifsta->bssid); + if (bss) { + if (bss->capability & WLAN_CAPABILITY_PRIVACY) + capab |= WLAN_CAPABILITY_PRIVACY; + if (bss->wmm_ie) { + wmm = 1; + } + ieee80211_rx_bss_put(dev, bss); + } + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + + if (ifsta->prev_bssid_set) { + skb_put(skb, 10); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_REASSOC_REQ); + mgmt->u.reassoc_req.capab_info = cpu_to_le16(capab); + mgmt->u.reassoc_req.listen_interval = cpu_to_le16(1); + memcpy(mgmt->u.reassoc_req.current_ap, ifsta->prev_bssid, + ETH_ALEN); + } else { + skb_put(skb, 4); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_ASSOC_REQ); + mgmt->u.assoc_req.capab_info = cpu_to_le16(capab); + mgmt->u.assoc_req.listen_interval = cpu_to_le16(1); + } + + /* SSID */ + ies = pos = skb_put(skb, 2 + ifsta->ssid_len); + *pos++ = WLAN_EID_SSID; + *pos++ = ifsta->ssid_len; + memcpy(pos, ifsta->ssid, ifsta->ssid_len); + + len = mode->num_rates; + if (len > 8) + len = 8; + pos = skb_put(skb, len + 2); + *pos++ = WLAN_EID_SUPP_RATES; + *pos++ = len; + for (i = 0; i < len; i++) { + int rate = mode->rates[i].rate; + if (mode->mode == MODE_ATHEROS_TURBO) + rate /= 2; + *pos++ = (u8) (rate / 5); + } + + if (mode->num_rates > len) { + pos = skb_put(skb, mode->num_rates - len + 2); + *pos++ = WLAN_EID_EXT_SUPP_RATES; + *pos++ = mode->num_rates - len; + for (i = len; i < mode->num_rates; i++) { + int rate = mode->rates[i].rate; + if (mode->mode == MODE_ATHEROS_TURBO) + rate /= 2; + *pos++ = (u8) (rate / 5); + } + } + + if (ifsta->extra_ie) { + pos = skb_put(skb, ifsta->extra_ie_len); + memcpy(pos, ifsta->extra_ie, ifsta->extra_ie_len); + } + + if (wmm && ifsta->wmm_enabled) { + pos = skb_put(skb, 9); + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = 7; /* len */ + *pos++ = 0x00; /* Microsoft OUI 00:50:F2 */ + *pos++ = 0x50; + *pos++ = 0xf2; + *pos++ = 2; /* WME */ + *pos++ = 0; /* WME info */ + *pos++ = 1; /* WME ver */ + *pos++ = 0; + } + + kfree(ifsta->assocreq_ies); + ifsta->assocreq_ies_len = (skb->data + skb->len) - ies; + ifsta->assocreq_ies = kmalloc(ifsta->assocreq_ies_len, GFP_ATOMIC); + if (ifsta->assocreq_ies) + memcpy(ifsta->assocreq_ies, ies, ifsta->assocreq_ies_len); + + ieee80211_sta_tx(dev, skb, 0); +} + + +static void ieee80211_send_deauth(struct net_device *dev, + struct ieee80211_if_sta *ifsta, u16 reason) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + + skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*mgmt)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for deauth " + "frame\n", dev->name); + return; + } + skb_reserve(skb, local->hw.extra_tx_headroom); + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_DEAUTH); + skb_put(skb, 2); + mgmt->u.deauth.reason_code = cpu_to_le16(reason); + + ieee80211_sta_tx(dev, skb, 0); +} + + +static void ieee80211_send_disassoc(struct net_device *dev, + struct ieee80211_if_sta *ifsta, u16 reason) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + + skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*mgmt)); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for disassoc " + "frame\n", dev->name); + return; + } + skb_reserve(skb, local->hw.extra_tx_headroom); + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + memcpy(mgmt->da, ifsta->bssid, ETH_ALEN); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + memcpy(mgmt->bssid, ifsta->bssid, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_DISASSOC); + skb_put(skb, 2); + mgmt->u.disassoc.reason_code = cpu_to_le16(reason); + + ieee80211_sta_tx(dev, skb, 0); +} + + +static int ieee80211_privacy_mismatch(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + struct ieee80211_sta_bss *bss; + int res = 0; + + if (!ifsta || ifsta->mixed_cell || + ifsta->key_mgmt != IEEE80211_KEY_MGMT_NONE) + return 0; + + bss = ieee80211_rx_bss_get(dev, ifsta->bssid); + if (!bss) + return 0; + + if (ieee80211_sta_wep_configured(dev) != + !!(bss->capability & WLAN_CAPABILITY_PRIVACY)) + res = 1; + + ieee80211_rx_bss_put(dev, bss); + + return res; +} + + +static void ieee80211_associate(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + ifsta->assoc_tries++; + if (ifsta->assoc_tries > IEEE80211_ASSOC_MAX_TRIES) { + printk(KERN_DEBUG "%s: association with AP " MAC_FMT + " timed out\n", + dev->name, MAC_ARG(ifsta->bssid)); + ifsta->state = IEEE80211_DISABLED; + return; + } + + ifsta->state = IEEE80211_ASSOCIATE; + printk(KERN_DEBUG "%s: associate with AP " MAC_FMT "\n", + dev->name, MAC_ARG(ifsta->bssid)); + if (ieee80211_privacy_mismatch(dev, ifsta)) { + printk(KERN_DEBUG "%s: mismatch in privacy configuration and " + "mixed-cell disabled - abort association\n", dev->name); + ifsta->state = IEEE80211_DISABLED; + return; + } + + ieee80211_send_assoc(dev, ifsta); + + mod_timer(&ifsta->timer, jiffies + IEEE80211_ASSOC_TIMEOUT); +} + + +static void ieee80211_associated(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct sta_info *sta; + int disassoc; + + /* TODO: start monitoring current AP signal quality and number of + * missed beacons. Scan other channels every now and then and search + * for better APs. */ + /* TODO: remove expired BSSes */ + + ifsta->state = IEEE80211_ASSOCIATED; + + sta = sta_info_get(local, ifsta->bssid); + if (!sta) { + printk(KERN_DEBUG "%s: No STA entry for own AP " MAC_FMT "\n", + dev->name, MAC_ARG(ifsta->bssid)); + disassoc = 1; + } else { + disassoc = 0; + if (time_after(jiffies, + sta->last_rx + IEEE80211_MONITORING_INTERVAL)) { + if (ifsta->probereq_poll) { + printk(KERN_DEBUG "%s: No ProbeResp from " + "current AP " MAC_FMT " - assume out of " + "range\n", + dev->name, MAC_ARG(ifsta->bssid)); + disassoc = 1; + sta_info_free(sta, 0); + ifsta->probereq_poll = 0; + } else { + ieee80211_send_probe_req(dev, ifsta->bssid, + local->scan_ssid, + local->scan_ssid_len); + ifsta->probereq_poll = 1; + } + } else { + ifsta->probereq_poll = 0; + if (time_after(jiffies, ifsta->last_probe + + IEEE80211_PROBE_INTERVAL)) { + ifsta->last_probe = jiffies; + ieee80211_send_probe_req(dev, ifsta->bssid, + ifsta->ssid, + ifsta->ssid_len); + } + } + sta_info_put(sta); + } + if (disassoc) { + union iwreq_data wrqu; + memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN); + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); + mod_timer(&ifsta->timer, jiffies + + IEEE80211_MONITORING_INTERVAL + 30 * HZ); + } else { + mod_timer(&ifsta->timer, jiffies + + IEEE80211_MONITORING_INTERVAL); + } +} + + +static void ieee80211_send_probe_req(struct net_device *dev, u8 *dst, + u8 *ssid, size_t ssid_len) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_hw_mode *mode; + struct sk_buff *skb; + struct ieee80211_mgmt *mgmt; + u8 *pos, *supp_rates, *esupp_rates = NULL; + int i; + + skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*mgmt) + 200); + if (!skb) { + printk(KERN_DEBUG "%s: failed to allocate buffer for probe " + "request\n", dev->name); + return; + } + skb_reserve(skb, local->hw.extra_tx_headroom); + + mgmt = (struct ieee80211_mgmt *) skb_put(skb, 24); + memset(mgmt, 0, 24); + mgmt->frame_control = IEEE80211_FC(IEEE80211_FTYPE_MGMT, + IEEE80211_STYPE_PROBE_REQ); + memcpy(mgmt->sa, dev->dev_addr, ETH_ALEN); + if (dst) { + memcpy(mgmt->da, dst, ETH_ALEN); + memcpy(mgmt->bssid, dst, ETH_ALEN); + } else { + memset(mgmt->da, 0xff, ETH_ALEN); + memset(mgmt->bssid, 0xff, ETH_ALEN); + } + pos = skb_put(skb, 2 + ssid_len); + *pos++ = WLAN_EID_SSID; + *pos++ = ssid_len; + memcpy(pos, ssid, ssid_len); + + supp_rates = skb_put(skb, 2); + supp_rates[0] = WLAN_EID_SUPP_RATES; + supp_rates[1] = 0; + mode = local->oper_hw_mode; + for (i = 0; i < mode->num_rates; i++) { + struct ieee80211_rate *rate = &mode->rates[i]; + if (!(rate->flags & IEEE80211_RATE_SUPPORTED)) + continue; + if (esupp_rates) { + pos = skb_put(skb, 1); + esupp_rates[1]++; + } else if (supp_rates[1] == 8) { + esupp_rates = skb_put(skb, 3); + esupp_rates[0] = WLAN_EID_EXT_SUPP_RATES; + esupp_rates[1] = 1; + pos = &esupp_rates[2]; + } else { + pos = skb_put(skb, 1); + supp_rates[1]++; + } + if (mode->mode == MODE_ATHEROS_TURBO) + *pos = rate->rate / 10; + else + *pos = rate->rate / 5; + } + + ieee80211_sta_tx(dev, skb, 0); +} + + +static int ieee80211_sta_wep_configured(struct net_device *dev) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + if (!sdata || !sdata->default_key || + sdata->default_key->alg != ALG_WEP) + return 0; + return 1; +} + + +static void ieee80211_auth_completed(struct net_device *dev, + struct ieee80211_if_sta *ifsta) +{ + printk(KERN_DEBUG "%s: authenticated\n", dev->name); + ifsta->authenticated = 1; + ieee80211_associate(dev, ifsta); +} + + +static void ieee80211_auth_challenge(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, + size_t len) +{ + u8 *pos; + struct ieee802_11_elems elems; + + printk(KERN_DEBUG "%s: replying to auth challenge\n", dev->name); + pos = mgmt->u.auth.variable; + if (ieee802_11_parse_elems(pos, len - (pos - (u8 *) mgmt), &elems) + == ParseFailed) { + printk(KERN_DEBUG "%s: failed to parse Auth(challenge)\n", + dev->name); + return; + } + if (!elems.challenge) { + printk(KERN_DEBUG "%s: no challenge IE in shared key auth " + "frame\n", dev->name); + return; + } + ieee80211_send_auth(dev, ifsta, 3, elems.challenge - 2, + elems.challenge_len + 2, 1); +} + + +static void ieee80211_rx_mgmt_auth(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, + size_t len) +{ + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); + u16 auth_alg, auth_transaction, status_code; + + if (ifsta->state != IEEE80211_AUTHENTICATE && + sdata->type != IEEE80211_IF_TYPE_IBSS) { + printk(KERN_DEBUG "%s: authentication frame received from " + MAC_FMT ", but not in authenticate state - ignored\n", + dev->name, MAC_ARG(mgmt->sa)); + return; + } + + if (len < 24 + 6) { + printk(KERN_DEBUG "%s: too short (%zd) authentication frame " + "received from " MAC_FMT " - ignored\n", + dev->name, len, MAC_ARG(mgmt->sa)); + return; + } + + if (sdata->type != IEEE80211_IF_TYPE_IBSS && + memcmp(ifsta->bssid, mgmt->sa, ETH_ALEN) != 0) { + printk(KERN_DEBUG "%s: authentication frame received from " + "unknown AP (SA=" MAC_FMT " BSSID=" MAC_FMT ") - " + "ignored\n", dev->name, MAC_ARG(mgmt->sa), + MAC_ARG(mgmt->bssid)); + return; + } + + if (sdata->type != IEEE80211_IF_TYPE_IBSS && + memcmp(ifsta->bssid, mgmt->bssid, ETH_ALEN) != 0) { + printk(KERN_DEBUG "%s: authentication frame received from " + "unknown BSSID (SA=" MAC_FMT " BSSID=" MAC_FMT ") - " + "ignored\n", dev->name, MAC_ARG(mgmt->sa), + MAC_ARG(mgmt->bssid)); + return; + } + + auth_alg = le16_to_cpu(mgmt->u.auth.auth_alg); + auth_transaction = le16_to_cpu(mgmt->u.auth.auth_transaction); + status_code = le16_to_cpu(mgmt->u.auth.status_code); + + printk(KERN_DEBUG "%s: RX authentication from " MAC_FMT " (alg=%d " + "transaction=%d status=%d)\n", + dev->name, MAC_ARG(mgmt->sa), auth_alg, + auth_transaction, status_code); + + if (sdata->type == IEEE80211_IF_TYPE_IBSS) { + /* IEEE 802.11 standard does not require authentication in IBSS + * networks and most implementations do not seem to use it. + * However, try to reply to authentication attempts if someone + * has actually implemented this. + * TODO: Could implement shared key authentication. */ + if (auth_alg != WLAN_AUTH_OPEN || auth_transaction != 1) { + printk(KERN_DEBUG "%s: unexpected IBSS authentication " + "frame (alg=%d transaction=%d)\n", + dev->name, auth_alg, auth_transaction); + return; + } + ieee80211_send_auth(dev, ifsta, 2, NULL, 0, 0); + } + + if (auth_alg != ifsta->auth_alg || + auth_transaction != ifsta->auth_transaction) { + printk(KERN_DEBUG "%s: unexpected authentication frame " + "(alg=%d transaction=%d)\n", + dev->name, auth_alg, auth_transaction); + return; + } + + if (status_code != WLAN_STATUS_SUCCESS) { + printk(KERN_DEBUG "%s: AP denied authentication (auth_alg=%d " + "code=%d)\n", dev->name, ifsta->auth_alg, status_code); + if (status_code == WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG) { + u8 algs[3]; + const int num_algs = ARRAY_SIZE(algs); + int i, pos; + algs[0] = algs[1] = algs[2] = 0xff; + if (ifsta->auth_algs & IEEE80211_AUTH_ALG_OPEN) + algs[0] = WLAN_AUTH_OPEN; + if (ifsta->auth_algs & IEEE80211_AUTH_ALG_SHARED_KEY) + algs[1] = WLAN_AUTH_SHARED_KEY; + if (ifsta->auth_algs & IEEE80211_AUTH_ALG_LEAP) + algs[2] = WLAN_AUTH_LEAP; + if (ifsta->auth_alg == WLAN_AUTH_OPEN) + pos = 0; + else if (ifsta->auth_alg == WLAN_AUTH_SHARED_KEY) + pos = 1; + else + pos = 2; + for (i = 0; i < num_algs; i++) { + pos++; + if (pos >= num_algs) + pos = 0; + if (algs[pos] == ifsta->auth_alg || + algs[pos] == 0xff) + continue; + if (algs[pos] == WLAN_AUTH_SHARED_KEY && + !ieee80211_sta_wep_configured(dev)) + continue; + ifsta->auth_alg = algs[pos]; + printk(KERN_DEBUG "%s: set auth_alg=%d for " + "next try\n", + dev->name, ifsta->auth_alg); + break; + } + } + return; + } + + switch (ifsta->auth_alg) { + case WLAN_AUTH_OPEN: + case WLAN_AUTH_LEAP: + ieee80211_auth_completed(dev, ifsta); + break; + case WLAN_AUTH_SHARED_KEY: + if (ifsta->auth_transaction == 4) + ieee80211_auth_completed(dev, ifsta); + else + ieee80211_auth_challenge(dev, ifsta, mgmt, len); + break; + } +} + + +static void ieee80211_rx_mgmt_deauth(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, + size_t len) +{ + u16 reason_code; + + if (len < 24 + 2) { + printk(KERN_DEBUG "%s: too short (%zd) deauthentication frame " + "received from " MAC_FMT " - ignored\n", + dev->name, len, MAC_ARG(mgmt->sa)); + return; + } + + if (memcmp(ifsta->bssid, mgmt->sa, ETH_ALEN) != 0) { + printk(KERN_DEBUG "%s: deauthentication frame received from " + "unknown AP (SA=" MAC_FMT " BSSID=" MAC_FMT ") - " + "ignored\n", dev->name, MAC_ARG(mgmt->sa), + MAC_ARG(mgmt->bssid)); + return; + } + + reason_code = le16_to_cpu(mgmt->u.deauth.reason_code); + + printk(KERN_DEBUG "%s: RX deauthentication from " MAC_FMT + " (reason=%d)\n", + dev->name, MAC_ARG(mgmt->sa), reason_code); + + if (ifsta->authenticated) { + printk(KERN_DEBUG "%s: deauthenticated\n", dev->name); + } + + if (ifsta->state == IEEE80211_AUTHENTICATE || + ifsta->state == IEEE80211_ASSOCIATE || + ifsta->state == IEEE80211_ASSOCIATED) { + ifsta->state = IEEE80211_AUTHENTICATE; + mod_timer(&ifsta->timer, jiffies + + IEEE80211_RETRY_AUTH_INTERVAL); + } + + ieee80211_set_disassoc(dev, ifsta, 1); + ifsta->authenticated = 0; +} + + +static void ieee80211_rx_mgmt_disassoc(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, + size_t len) +{ + u16 reason_code; + + if (len < 24 + 2) { + printk(KERN_DEBUG "%s: too short (%zd) disassociation frame " + "received from " MAC_FMT " - ignored\n", + dev->name, len, MAC_ARG(mgmt->sa)); + return; + } + + if (memcmp(ifsta->bssid, mgmt->sa, ETH_ALEN) != 0) { + printk(KERN_DEBUG "%s: disassociation frame received from " + "unknown AP (SA=" MAC_FMT " BSSID=" MAC_FMT ") - " + "ignored\n", dev->name, MAC_ARG(mgmt->sa), + MAC_ARG(mgmt->bssid)); + return; + } + + reason_code = le16_to_cpu(mgmt->u.disassoc.reason_code); + + printk(KERN_DEBUG "%s: RX disassociation from " MAC_FMT + " (reason=%d)\n", + dev->name, MAC_ARG(mgmt->sa), reason_code); + + if (ifsta->associated) + printk(KERN_DEBUG "%s: disassociated\n", dev->name); + + if (ifsta->state == IEEE80211_ASSOCIATED) { + ifsta->state = IEEE80211_ASSOCIATE; + mod_timer(&ifsta->timer, jiffies + + IEEE80211_RETRY_AUTH_INTERVAL); + } + + ieee80211_set_disassoc(dev, ifsta, 0); +} + + +static void ieee80211_rx_mgmt_assoc_resp(struct net_device *dev, + struct ieee80211_if_sta *ifsta, + struct ieee80211_mgmt *mgmt, + size_t len, + int reassoc) +{ + struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); + struct ieee80211_hw_mode *mode; + struct sta_info *sta; + u32 rates; + u16 capab_info, status_code, aid; + struct ieee802_11_elems elems; + u8 *pos; + int i, j; + + /* AssocResp and ReassocResp have identical structure, so process both + * of them in this function. */ + + if (ifsta->state != IEEE80211_ASSOCIATE) { + printk(KERN_DEBUG "%s: association frame received from " + MAC_FMT ", but not in associate state - ignored\n", + dev->name, MAC_ARG(mgmt->sa)); + return; + } + + if (len < 24 + 6) { + printk(KERN_DEBUG "%s: too short (%zd) association frame " + "received from " MAC_FMT " - ignored\n", + dev->name, len, MAC_ARG(mgmt->sa)); + return; + } + |