diff options
author | Jouni Malinen <jkmaline@cc.hut.fi> | 2005-05-13 06:54:16 +0400 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2005-05-13 06:54:16 +0400 |
commit | ff1d2767d5a43c85f944e86a45284b721f66196c (patch) | |
tree | 91c1b6dd20bd772bc112c0012830678b46b69604 /drivers/net/wireless/hostap/hostap_80211_tx.c | |
parent | 88d7bd8cb9eb8d64bf7997600b0d64f7834047c5 (diff) | |
download | linux-ff1d2767d5a43c85f944e86a45284b721f66196c.tar.xz |
Add HostAP wireless driver.
Includes minor cleanups from Adrian Bunk <bunk@stusta.de>.
Diffstat (limited to 'drivers/net/wireless/hostap/hostap_80211_tx.c')
-rw-r--r-- | drivers/net/wireless/hostap/hostap_80211_tx.c | 522 |
1 files changed, 522 insertions, 0 deletions
diff --git a/drivers/net/wireless/hostap/hostap_80211_tx.c b/drivers/net/wireless/hostap/hostap_80211_tx.c new file mode 100644 index 000000000000..8a94a80ec55f --- /dev/null +++ b/drivers/net/wireless/hostap/hostap_80211_tx.c @@ -0,0 +1,522 @@ +void hostap_dump_tx_80211(const char *name, struct sk_buff *skb) +{ + struct hostap_ieee80211_hdr *hdr; + u16 fc; + + hdr = (struct hostap_ieee80211_hdr *) skb->data; + + printk(KERN_DEBUG "%s: TX len=%d jiffies=%ld\n", + name, skb->len, jiffies); + + if (skb->len < 2) + return; + + fc = le16_to_cpu(hdr->frame_control); + printk(KERN_DEBUG " FC=0x%04x (type=%d:%d)%s%s", + fc, WLAN_FC_GET_TYPE(fc), WLAN_FC_GET_STYPE(fc), + fc & WLAN_FC_TODS ? " [ToDS]" : "", + fc & WLAN_FC_FROMDS ? " [FromDS]" : ""); + + if (skb->len < IEEE80211_DATA_HDR3_LEN) { + printk("\n"); + return; + } + + printk(" dur=0x%04x seq=0x%04x\n", le16_to_cpu(hdr->duration_id), + le16_to_cpu(hdr->seq_ctrl)); + + printk(KERN_DEBUG " A1=" MACSTR " A2=" MACSTR " A3=" MACSTR, + MAC2STR(hdr->addr1), MAC2STR(hdr->addr2), MAC2STR(hdr->addr3)); + if (skb->len >= 30) + printk(" A4=" MACSTR, MAC2STR(hdr->addr4)); + printk("\n"); +} + + +/* hard_start_xmit function for data interfaces (wlan#, wlan#wds#, wlan#sta) + * Convert Ethernet header into a suitable IEEE 802.11 header depending on + * device configuration. */ +int hostap_data_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct hostap_interface *iface; + local_info_t *local; + int need_headroom, need_tailroom = 0; + struct hostap_ieee80211_hdr hdr; + u16 fc, ethertype = 0; + enum { + WDS_NO = 0, WDS_OWN_FRAME, WDS_COMPLIANT_FRAME + } use_wds = WDS_NO; + u8 *encaps_data; + int hdr_len, encaps_len, skip_header_bytes; + int to_assoc_ap = 0; + struct hostap_skb_tx_data *meta; + + iface = netdev_priv(dev); + local = iface->local; + + if (skb->len < ETH_HLEN) { + printk(KERN_DEBUG "%s: hostap_data_start_xmit: short skb " + "(len=%d)\n", dev->name, skb->len); + kfree_skb(skb); + return 0; + } + + if (local->ddev != dev) { + use_wds = (local->iw_mode == IW_MODE_MASTER && + !(local->wds_type & HOSTAP_WDS_STANDARD_FRAME)) ? + WDS_OWN_FRAME : WDS_COMPLIANT_FRAME; + if (dev == local->stadev) { + to_assoc_ap = 1; + use_wds = WDS_NO; + } else if (dev == local->apdev) { + printk(KERN_DEBUG "%s: prism2_tx: trying to use " + "AP device with Ethernet net dev\n", dev->name); + kfree_skb(skb); + return 0; + } + } else { + if (local->iw_mode == IW_MODE_REPEAT) { + printk(KERN_DEBUG "%s: prism2_tx: trying to use " + "non-WDS link in Repeater mode\n", dev->name); + kfree_skb(skb); + return 0; + } else if (local->iw_mode == IW_MODE_INFRA && + (local->wds_type & HOSTAP_WDS_AP_CLIENT) && + memcmp(skb->data + ETH_ALEN, dev->dev_addr, + ETH_ALEN) != 0) { + /* AP client mode: send frames with foreign src addr + * using 4-addr WDS frames */ + use_wds = WDS_COMPLIANT_FRAME; + } + } + + /* Incoming skb->data: dst_addr[6], src_addr[6], proto[2], payload + * ==> + * Prism2 TX frame with 802.11 header: + * txdesc (address order depending on used mode; includes dst_addr and + * src_addr), possible encapsulation (RFC1042/Bridge-Tunnel; + * proto[2], payload {, possible addr4[6]} */ + + ethertype = (skb->data[12] << 8) | skb->data[13]; + + memset(&hdr, 0, sizeof(hdr)); + + /* Length of data after IEEE 802.11 header */ + encaps_data = NULL; + encaps_len = 0; + skip_header_bytes = ETH_HLEN; + if (ethertype == ETH_P_AARP || ethertype == ETH_P_IPX) { + encaps_data = bridge_tunnel_header; + encaps_len = sizeof(bridge_tunnel_header); + skip_header_bytes -= 2; + } else if (ethertype >= 0x600) { + encaps_data = rfc1042_header; + encaps_len = sizeof(rfc1042_header); + skip_header_bytes -= 2; + } + + fc = (WLAN_FC_TYPE_DATA << 2) | (WLAN_FC_STYPE_DATA << 4); + hdr_len = IEEE80211_DATA_HDR3_LEN; + + if (use_wds != WDS_NO) { + /* Note! Prism2 station firmware has problems with sending real + * 802.11 frames with four addresses; until these problems can + * be fixed or worked around, 4-addr frames needed for WDS are + * using incompatible format: FromDS flag is not set and the + * fourth address is added after the frame payload; it is + * assumed, that the receiving station knows how to handle this + * frame format */ + + if (use_wds == WDS_COMPLIANT_FRAME) { + fc |= WLAN_FC_FROMDS | WLAN_FC_TODS; + /* From&To DS: Addr1 = RA, Addr2 = TA, Addr3 = DA, + * Addr4 = SA */ + memcpy(&hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN); + hdr_len += ETH_ALEN; + } else { + /* bogus 4-addr format to workaround Prism2 station + * f/w bug */ + fc |= WLAN_FC_TODS; + /* From DS: Addr1 = DA (used as RA), + * Addr2 = BSSID (used as TA), Addr3 = SA (used as DA), + */ + + /* SA from skb->data + ETH_ALEN will be added after + * frame payload; use hdr.addr4 as a temporary buffer + */ + memcpy(&hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN); + need_tailroom += ETH_ALEN; + } + + /* send broadcast and multicast frames to broadcast RA, if + * configured; otherwise, use unicast RA of the WDS link */ + if ((local->wds_type & HOSTAP_WDS_BROADCAST_RA) && + skb->data[0] & 0x01) + memset(&hdr.addr1, 0xff, ETH_ALEN); + else if (iface->type == HOSTAP_INTERFACE_WDS) + memcpy(&hdr.addr1, iface->u.wds.remote_addr, + ETH_ALEN); + else + memcpy(&hdr.addr1, local->bssid, ETH_ALEN); + memcpy(&hdr.addr2, dev->dev_addr, ETH_ALEN); + memcpy(&hdr.addr3, skb->data, ETH_ALEN); + } else if (local->iw_mode == IW_MODE_MASTER && !to_assoc_ap) { + fc |= WLAN_FC_FROMDS; + /* From DS: Addr1 = DA, Addr2 = BSSID, Addr3 = SA */ + memcpy(&hdr.addr1, skb->data, ETH_ALEN); + memcpy(&hdr.addr2, dev->dev_addr, ETH_ALEN); + memcpy(&hdr.addr3, skb->data + ETH_ALEN, ETH_ALEN); + } else if (local->iw_mode == IW_MODE_INFRA || to_assoc_ap) { + fc |= WLAN_FC_TODS; + /* To DS: Addr1 = BSSID, Addr2 = SA, Addr3 = DA */ + memcpy(&hdr.addr1, to_assoc_ap ? + local->assoc_ap_addr : local->bssid, ETH_ALEN); + memcpy(&hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN); + memcpy(&hdr.addr3, skb->data, ETH_ALEN); + } else if (local->iw_mode == IW_MODE_ADHOC) { + /* not From/To DS: Addr1 = DA, Addr2 = SA, Addr3 = BSSID */ + memcpy(&hdr.addr1, skb->data, ETH_ALEN); + memcpy(&hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN); + memcpy(&hdr.addr3, local->bssid, ETH_ALEN); + } + + hdr.frame_control = cpu_to_le16(fc); + + skb_pull(skb, skip_header_bytes); + need_headroom = local->func->need_tx_headroom + hdr_len + encaps_len; + if (skb_tailroom(skb) < need_tailroom) { + skb = skb_unshare(skb, GFP_ATOMIC); + if (skb == NULL) { + iface->stats.tx_dropped++; + return 0; + } + if (pskb_expand_head(skb, need_headroom, need_tailroom, + GFP_ATOMIC)) { + kfree_skb(skb); + iface->stats.tx_dropped++; + return 0; + } + } else if (skb_headroom(skb) < need_headroom) { + struct sk_buff *tmp = skb; + skb = skb_realloc_headroom(skb, need_headroom); + kfree_skb(tmp); + if (skb == NULL) { + iface->stats.tx_dropped++; + return 0; + } + } else { + skb = skb_unshare(skb, GFP_ATOMIC); + if (skb == NULL) { + iface->stats.tx_dropped++; + return 0; + } + } + + if (encaps_data) + memcpy(skb_push(skb, encaps_len), encaps_data, encaps_len); + memcpy(skb_push(skb, hdr_len), &hdr, hdr_len); + if (use_wds == WDS_OWN_FRAME) { + memcpy(skb_put(skb, ETH_ALEN), &hdr.addr4, ETH_ALEN); + } + + iface->stats.tx_packets++; + iface->stats.tx_bytes += skb->len; + + skb->mac.raw = skb->data; + meta = (struct hostap_skb_tx_data *) skb->cb; + memset(meta, 0, sizeof(*meta)); + meta->magic = HOSTAP_SKB_TX_DATA_MAGIC; + meta->wds = use_wds; + meta->ethertype = ethertype; + meta->iface = iface; + + /* Send IEEE 802.11 encapsulated frame using the master radio device */ + skb->dev = local->dev; + dev_queue_xmit(skb); + return 0; +} + + +/* hard_start_xmit function for hostapd wlan#ap interfaces */ +int hostap_mgmt_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct hostap_interface *iface; + local_info_t *local; + struct hostap_skb_tx_data *meta; + struct hostap_ieee80211_hdr *hdr; + u16 fc; + + iface = netdev_priv(dev); + local = iface->local; + + if (skb->len < 10) { + printk(KERN_DEBUG "%s: hostap_mgmt_start_xmit: short skb " + "(len=%d)\n", dev->name, skb->len); + kfree_skb(skb); + return 0; + } + + iface->stats.tx_packets++; + iface->stats.tx_bytes += skb->len; + + meta = (struct hostap_skb_tx_data *) skb->cb; + memset(meta, 0, sizeof(*meta)); + meta->magic = HOSTAP_SKB_TX_DATA_MAGIC; + meta->iface = iface; + + if (skb->len >= IEEE80211_DATA_HDR3_LEN + sizeof(rfc1042_header) + 2) { + hdr = (struct hostap_ieee80211_hdr *) skb->data; + fc = le16_to_cpu(hdr->frame_control); + if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_DATA && + WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_DATA) { + u8 *pos = &skb->data[IEEE80211_DATA_HDR3_LEN + + sizeof(rfc1042_header)]; + meta->ethertype = (pos[0] << 8) | pos[1]; + } + } + + /* Send IEEE 802.11 encapsulated frame using the master radio device */ + skb->dev = local->dev; + dev_queue_xmit(skb); + return 0; +} + + +/* Called only from software IRQ */ +struct sk_buff * hostap_tx_encrypt(struct sk_buff *skb, + struct prism2_crypt_data *crypt) +{ + struct hostap_interface *iface; + local_info_t *local; + struct hostap_ieee80211_hdr *hdr; + u16 fc; + int hdr_len, res; + + iface = netdev_priv(skb->dev); + local = iface->local; + + if (skb->len < IEEE80211_DATA_HDR3_LEN) { + kfree_skb(skb); + return NULL; + } + + if (local->tkip_countermeasures && + crypt && crypt->ops && strcmp(crypt->ops->name, "TKIP") == 0) { + hdr = (struct hostap_ieee80211_hdr *) skb->data; + if (net_ratelimit()) { + printk(KERN_DEBUG "%s: TKIP countermeasures: dropped " + "TX packet to " MACSTR "\n", + local->dev->name, MAC2STR(hdr->addr1)); + } + kfree_skb(skb); + return NULL; + } + + skb = skb_unshare(skb, GFP_ATOMIC); + if (skb == NULL) + return NULL; + + if ((skb_headroom(skb) < crypt->ops->extra_prefix_len || + skb_tailroom(skb) < crypt->ops->extra_postfix_len) && + pskb_expand_head(skb, crypt->ops->extra_prefix_len, + crypt->ops->extra_postfix_len, GFP_ATOMIC)) { + kfree_skb(skb); + return NULL; + } + + hdr = (struct hostap_ieee80211_hdr *) skb->data; + fc = le16_to_cpu(hdr->frame_control); + hdr_len = hostap_80211_get_hdrlen(fc); + + /* Host-based IEEE 802.11 fragmentation for TX is not yet supported, so + * call both MSDU and MPDU encryption functions from here. */ + atomic_inc(&crypt->refcnt); + res = 0; + if (crypt->ops->encrypt_msdu) + res = crypt->ops->encrypt_msdu(skb, hdr_len, crypt->priv); + if (res == 0 && crypt->ops->encrypt_mpdu) + res = crypt->ops->encrypt_mpdu(skb, hdr_len, crypt->priv); + atomic_dec(&crypt->refcnt); + if (res < 0) { + kfree_skb(skb); + return NULL; + } + + return skb; +} + + +/* hard_start_xmit function for master radio interface wifi#. + * AP processing (TX rate control, power save buffering, etc.). + * Use hardware TX function to send the frame. */ +int hostap_master_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct hostap_interface *iface; + local_info_t *local; + int ret = 1; + u16 fc; + struct hostap_tx_data tx; + ap_tx_ret tx_ret; + struct hostap_skb_tx_data *meta; + int no_encrypt = 0; + struct hostap_ieee80211_hdr *hdr; + + iface = netdev_priv(dev); + local = iface->local; + + tx.skb = skb; + tx.sta_ptr = NULL; + + meta = (struct hostap_skb_tx_data *) skb->cb; + if (meta->magic != HOSTAP_SKB_TX_DATA_MAGIC) { + printk(KERN_DEBUG "%s: invalid skb->cb magic (0x%08x, " + "expected 0x%08x)\n", + dev->name, meta->magic, HOSTAP_SKB_TX_DATA_MAGIC); + ret = 0; + iface->stats.tx_dropped++; + goto fail; + } + + if (local->host_encrypt) { + /* Set crypt to default algorithm and key; will be replaced in + * AP code if STA has own alg/key */ + tx.crypt = local->crypt[local->tx_keyidx]; + tx.host_encrypt = 1; + } else { + tx.crypt = NULL; + tx.host_encrypt = 0; + } + + if (skb->len < 24) { + printk(KERN_DEBUG "%s: hostap_master_start_xmit: short skb " + "(len=%d)\n", dev->name, skb->len); + ret = 0; + iface->stats.tx_dropped++; + goto fail; + } + + /* FIX (?): + * Wi-Fi 802.11b test plan suggests that AP should ignore power save + * bit in authentication and (re)association frames and assume tha + * STA remains awake for the response. */ + tx_ret = hostap_handle_sta_tx(local, &tx); + skb = tx.skb; + meta = (struct hostap_skb_tx_data *) skb->cb; + hdr = (struct hostap_ieee80211_hdr *) skb->data; + fc = le16_to_cpu(hdr->frame_control); + switch (tx_ret) { + case AP_TX_CONTINUE: + break; + case AP_TX_CONTINUE_NOT_AUTHORIZED: + if (local->ieee_802_1x && + WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_DATA && + meta->ethertype != ETH_P_PAE && !meta->wds) { + printk(KERN_DEBUG "%s: dropped frame to unauthorized " + "port (IEEE 802.1X): ethertype=0x%04x\n", + dev->name, meta->ethertype); + hostap_dump_tx_80211(dev->name, skb); + + ret = 0; /* drop packet */ + iface->stats.tx_dropped++; + goto fail; + } + break; + case AP_TX_DROP: + ret = 0; /* drop packet */ + iface->stats.tx_dropped++; + goto fail; + case AP_TX_RETRY: + goto fail; + case AP_TX_BUFFERED: + /* do not free skb here, it will be freed when the + * buffered frame is sent/timed out */ + ret = 0; + goto tx_exit; + } + + /* Request TX callback if protocol version is 2 in 802.11 header; + * this version 2 is a special case used between hostapd and kernel + * driver */ + if (((fc & WLAN_FC_PVER) == BIT(1)) && + local->ap && local->ap->tx_callback_idx && meta->tx_cb_idx == 0) { + meta->tx_cb_idx = local->ap->tx_callback_idx; + + /* remove special version from the frame header */ + fc &= ~WLAN_FC_PVER; + hdr->frame_control = cpu_to_le16(fc); + } + + if (WLAN_FC_GET_TYPE(fc) != WLAN_FC_TYPE_DATA) { + no_encrypt = 1; + tx.crypt = NULL; + } + + if (local->ieee_802_1x && meta->ethertype == ETH_P_PAE && tx.crypt && + !(fc & WLAN_FC_ISWEP)) { + no_encrypt = 1; + PDEBUG(DEBUG_EXTRA2, "%s: TX: IEEE 802.1X - passing " + "unencrypted EAPOL frame\n", dev->name); + tx.crypt = NULL; /* no encryption for IEEE 802.1X frames */ + } + + if (tx.crypt && (!tx.crypt->ops || !tx.crypt->ops->encrypt_mpdu)) + tx.crypt = NULL; + else if ((tx.crypt || local->crypt[local->tx_keyidx]) && !no_encrypt) { + /* Add ISWEP flag both for firmware and host based encryption + */ + fc |= WLAN_FC_ISWEP; + hdr->frame_control = cpu_to_le16(fc); + } else if (local->drop_unencrypted && + WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_DATA && + meta->ethertype != ETH_P_PAE) { + if (net_ratelimit()) { + printk(KERN_DEBUG "%s: dropped unencrypted TX data " + "frame (drop_unencrypted=1)\n", dev->name); + } + iface->stats.tx_dropped++; + ret = 0; + goto fail; + } + + if (tx.crypt) { + skb = hostap_tx_encrypt(skb, tx.crypt); + if (skb == NULL) { + printk(KERN_DEBUG "%s: TX - encryption failed\n", + dev->name); + ret = 0; + goto fail; + } + meta = (struct hostap_skb_tx_data *) skb->cb; + if (meta->magic != HOSTAP_SKB_TX_DATA_MAGIC) { + printk(KERN_DEBUG "%s: invalid skb->cb magic (0x%08x, " + "expected 0x%08x) after hostap_tx_encrypt\n", + dev->name, meta->magic, + HOSTAP_SKB_TX_DATA_MAGIC); + ret = 0; + iface->stats.tx_dropped++; + goto fail; + } + } + + if (local->func->tx == NULL || local->func->tx(skb, dev)) { + ret = 0; + iface->stats.tx_dropped++; + } else { + ret = 0; + iface->stats.tx_packets++; + iface->stats.tx_bytes += skb->len; + } + + fail: + if (!ret && skb) + dev_kfree_skb(skb); + tx_exit: + if (tx.sta_ptr) + hostap_handle_sta_release(tx.sta_ptr); + return ret; +} + + +EXPORT_SYMBOL(hostap_dump_tx_80211); +EXPORT_SYMBOL(hostap_tx_encrypt); +EXPORT_SYMBOL(hostap_master_start_xmit); |