/* * Some parts based on code from net80211 * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "ieee80211softmac_priv.h" /* Helper functions for inserting data into the frames */ /* * Adds an ESSID element to the frame * */ static u8 * ieee80211softmac_add_essid(u8 *dst, struct ieee80211softmac_essid *essid) { if (essid) { *dst++ = MFIE_TYPE_SSID; *dst++ = essid->len; memcpy(dst, essid->data, essid->len); return dst+essid->len; } else { *dst++ = MFIE_TYPE_SSID; *dst++ = 0; return dst; } } /* Adds Supported Rates and if required Extended Rates Information Element * to the frame, ASSUMES WE HAVE A SORTED LIST OF RATES */ static u8 * ieee80211softmac_frame_add_rates(u8 *dst, const struct ieee80211softmac_ratesinfo *r) { int cck_len, ofdm_len; *dst++ = MFIE_TYPE_RATES; for(cck_len=0; ieee80211_is_cck_rate(r->rates[cck_len]) && (cck_len < r->count);cck_len++); if(cck_len > IEEE80211SOFTMAC_MAX_RATES_LEN) cck_len = IEEE80211SOFTMAC_MAX_RATES_LEN; *dst++ = cck_len; memcpy(dst, r->rates, cck_len); dst += cck_len; if(cck_len < r->count){ for (ofdm_len=0; ieee80211_is_ofdm_rate(r->rates[ofdm_len + cck_len]) && (ofdm_len + cck_len < r->count); ofdm_len++); if (ofdm_len > 0) { if (ofdm_len > IEEE80211SOFTMAC_MAX_EX_RATES_LEN) ofdm_len = IEEE80211SOFTMAC_MAX_EX_RATES_LEN; *dst++ = MFIE_TYPE_RATES_EX; *dst++ = ofdm_len; memcpy(dst, r->rates + cck_len, ofdm_len); dst += ofdm_len; } } return dst; } /* Allocate a management frame */ static u8 * ieee80211softmac_alloc_mgt(u32 size) { u8 * data; /* Add the header and FCS to the size */ size = size + IEEE80211_3ADDR_LEN; if(size > IEEE80211_DATA_LEN) return NULL; /* Allocate the frame */ data = kzalloc(size, GFP_ATOMIC); return data; } /* * Add a 2 Address Header */ static void ieee80211softmac_hdr_2addr(struct ieee80211softmac_device *mac, struct ieee80211_hdr_2addr *header, u32 type, u8 *dest) { /* Fill in the frame control flags */ header->frame_ctl = cpu_to_le16(type); /* Control packets always have WEP turned off */ if(type > IEEE80211_STYPE_CFENDACK && type < IEEE80211_STYPE_PSPOLL) header->frame_ctl |= mac->ieee->sec.level ? cpu_to_le16(IEEE80211_FCTL_PROTECTED) : 0; /* Fill in the duration */ header->duration_id = 0; /* FIXME: How do I find this? * calculate. But most drivers just fill in 0 (except if it's a station id of course) */ /* Fill in the Destination Address */ if(dest == NULL) memset(header->addr1, 0xFF, ETH_ALEN); else memcpy(header->addr1, dest, ETH_ALEN); /* Fill in the Source Address */ memcpy(header->addr2, mac->ieee->dev->dev_addr, ETH_ALEN); } /* Add a 3 Address Header */ static void ieee80211softmac_hdr_3addr(struct ieee80211softmac_device *mac, struct ieee80211_hdr_3addr *header, u32 type, u8 *dest, u8 *bssid) { /* This is common with 2addr, so use that instead */ ieee80211softmac_hdr_2addr(mac, (struct ieee80211_hdr_2addr *)header, type, dest); /* Fill in the BSS ID */ if(bssid == NULL) memset(header->addr3, 0xFF, ETH_ALEN); else memcpy(header->addr3, bssid, ETH_ALEN); /* Fill in the sequence # */ /* FIXME: I need to add this to the softmac struct * shouldn't the sequence number be in ieee80211? */ } static u16 ieee80211softmac_capabilities(struct ieee80211softmac_device *mac, struct ieee80211softmac_network *net) { u16 capability = 0; /* ESS and IBSS bits are set according to the current mode */ switch (mac->ieee->iw_mode) { case IW_MODE_INFRA: capability = cpu_to_le16(WLAN_CAPABILITY_ESS); break; case IW_MODE_ADHOC: capability = cpu_to_le16(WLAN_CAPABILITY_IBSS); break; case IW_MODE_AUTO: capability = net->capabilities & (WLAN_CAPABILITY_ESS|WLAN_CAPABILITY_IBSS); break; default: /* bleh. we don't ever go to these modes */ printk(KERN_ERR PFX "invalid iw_mode!\n"); break; } /* CF Pollable / CF Poll Request */ /* Needs to be implemented, for now, the 0's == not supported */ /* Privacy Bit */ capability |= mac->ieee->sec.level ? cpu_to_le16(WLAN_CAPABILITY_PRIVACY) : 0; /* Short Preamble */ /* Always supported: we probably won't ever be powering devices which * dont support this... */ capability |= WLAN_CAPABILITY_SHORT_PREAMBLE; /* PBCC */ /* Not widely used */ /* Channel Agility */ /* Not widely used */ /* Short Slot */ /* Will be implemented later */ /* DSSS-OFDM */ /* Not widely used */ return capability; } /***************************************************************************** * Create Management packets *****************************************************************************/ /* Creates an association request packet */ static u32 ieee80211softmac_assoc_req(struct ieee80211_assoc_request **pkt, struct ieee80211softmac_device *mac, struct ieee80211softmac_network *net) { u8 *data; (*pkt) = (struct ieee80211_assoc_request *)ieee80211softmac_alloc_mgt( 2 + /* Capability Info */ 2 + /* Listen Interval */ /* SSID IE */ 1 + 1 + IW_ESSID_MAX_SIZE + /* Rates IE */ 1 + 1 + IEEE80211SOFTMAC_MAX_RATES_LEN + /* Extended Rates IE */ 1 + 1 + IEEE80211SOFTMAC_MAX_EX_RATES_LEN + /* WPA IE if present */ mac->wpa.IElen /* Other IE's? Optional? * Yeah, probably need an extra IE parameter -- lots of vendors like to * fill in their own IEs */ ); if (unlikely((*pkt) == NULL)) return 0; ieee80211softmac_hdr_3addr(mac, &((*pkt)->header), IEEE80211_STYPE_ASSOC_REQ, net->bssid, net->bssid); /* Fill in the capabilities */ (*pkt)->capability = ieee80211softmac_capabilities(mac, net); /* Fill in Listen Interval (?) */ (*pkt)->listen_interval = cpu_to_le16(10); data = (u8 *)(*pkt)->info_element; /* Add SSID */ data = ieee80211softmac_add_essid(data, &net->essid); /* Add Rates */ data = ieee80211softmac_frame_add_rates(data, &mac->ratesinfo); /* Add WPA IE */ if (mac->wpa.IElen && mac->wpa.IE) { memcpy(data, mac->wpa.IE, mac->wpa.IElen); data += mac->wpa.IElen; } /* Return the number of used bytes */ return (data - (u8*)(*pkt)); } /* Create a reassociation request packet */ static u32 ieee80211softmac_reassoc_req(struct ieee80211_reassoc_request **pkt, struct ieee80211softmac_device *mac, struct ieee80211softmac_network *net) { u8 *data; (*pkt) = (struct ieee80211_reassoc_request *)ieee80211softmac_alloc_mgt( 2 + /* Capability Info */ 2 + /* Listen Interval */ ETH_ALEN + /* AP MAC */ /* SSID IE */ 1 + 1 + IW_ESSID_MAX_SIZE + /* Rates IE */ 1 + 1 + IEEE80211SOFTMAC_MAX_RATES_LEN + /* Extended Rates IE */ 1 + 1 + IEEE80211SOFTMAC_MAX_EX_RATES_LEN /* Other IE's? */ ); if (unlikely((*pkt) == NULL)) return 0; ieee80211softmac_hdr_3addr(mac, &((*pkt)->header), IEEE80211_STYPE_REASSOC_REQ, net->bssid, net->bssid); /* Fill in the capabilities */ (*pkt)->capability = ieee80211softmac_capabilities(mac, net); /* Fill in Listen Interval (?) */ (*pkt)->listen_interval = cpu_to_le16(10); /* Fill in the current AP MAC */ memcpy((*pkt)->current_ap, mac->ieee->bssid, ETH_ALEN); data = (u8 *)(*pkt)->info_element; /* Add SSID */ data = ieee80211softmac_add_essid(data, &net->essid); /* Add Rates */ data = ieee80211softmac_frame_add_rates(data, &mac->ratesinfo); /* Return packet size */ return (data - (u8 *)(*pkt)); } /* Create an authentication packet */ static u32 ieee80211softmac_auth(struct ieee80211_auth **pkt, struct ieee80211softmac_device *mac, struct ieee80211softmac_network *net, u16 transaction, u16 status, int *encrypt_mpdu) { u8 *data; int auth_mode = mac->ieee->sec.auth_mode; int is_shared_response = (auth_mode == WLAN_AUTH_SHARED_KEY && transaction == IEEE80211SOFTMAC_AUTH_SHARED_RESPONSE); /* Allocate Packet */ (*pkt) = (struct ieee80211_auth *)ieee80211softmac_alloc_mgt( 2 + /* Auth Algorithm */ 2 + /* Auth Transaction Seq */ 2 + /* Status Code */ /* Challenge Text IE */ (is_shared_response ? 1 + 1 + net->challenge_len : 0) ); if (unlikely((*pkt) == NULL)) return 0; ieee80211softmac_hdr_3addr(mac, &((*pkt)->header), IEEE80211_STYPE_AUTH, net->bssid, net->bssid); /* Algorithm */ (*pkt)->algorithm = cpu_to_le16(auth_mode); /* Transaction */ (*pkt)->transaction = cpu_to_le16(transaction); /* Status */ (*pkt)->status = cpu_to_le16(status); data = (u8 *)(*pkt)->info_element; /* Challenge Text */ if (is_shared_response) { *data = MFIE_TYPE_CHALLENGE; data++; /* Copy the challenge in */ *data = net->challenge_len; data++; memcpy(data, net->challenge, net->challenge_len); data += net->challenge_len; /* Make sure this frame gets encrypted with the shared key */ *encrypt_mpdu = 1; } else *encrypt_mpdu = 0; /* Return the packet size */ return (data - (u8 *)(*pkt)); } /* Create a disassocation or deauthentication packet */ static u32 ieee80211softmac_disassoc_deauth(struct ieee80211_disassoc **pkt, struct ieee80211softmac_device *mac, struct ieee80211softmac_network *net, u16 type, u16 reason) { /* Allocate Packet */ (*pkt) = (struct ieee80211_disassoc *)ieee80211softmac_alloc_mgt(2); if (unlikely((*pkt) == NULL)) return 0; ieee80211softmac_hdr_3addr(mac, &((*pkt)->header), type, net->bssid, net->bssid); /* Reason */ (*pkt)->reason = cpu_to_le16(reason); /* Return the packet size */ return (2 + IEEE80211_3ADDR_LEN); } /* Create a probe request packet */ static u32 ieee80211softmac_probe_req(struct ieee80211_probe_request **pkt, struct ieee80211softmac_device *mac, struct ieee80211softmac_essid *essid) { u8 *data; /* Allocate Packet */ (*pkt) = (struct ieee80211_probe_request *)ieee80211softmac_alloc_mgt( /* SSID of requested network */ 1 + 1 + IW_ESSID_MAX_SIZE + /* Rates IE */ 1 + 1 + IEEE80211SOFTMAC_MAX_RATES_LEN + /* Extended Rates IE */ 1 + 1 + IEEE80211SOFTMAC_MAX_EX_RATES_LEN ); if (unlikely((*pkt) == NULL)) return 0; ieee80211softmac_hdr_3addr(mac, &((*pkt)->header), IEEE80211_STYPE_PROBE_REQ, NULL, NULL); data = (u8 *)(*pkt)->info_element; /* Add ESSID (can be NULL) */ data = ieee80211softmac_add_essid(data, essid); /* Add Rates */ data = ieee80211softmac_frame_add_rates(data, &mac->ratesinfo); /* Return packet size */ return (data - (u8 *)(*pkt)); } /* Create a probe response packet */ /* FIXME: Not complete */ static u32 ieee80211softmac_probe_resp(struct ieee80211_probe_response **pkt, struct ieee80211softmac_device *mac, struct ieee80211softmac_network *net) { u8 *data; /* Allocate Packet */ (*pkt) = (struct ieee80211_probe_response *)ieee80211softmac_alloc_mgt( 8 + /* Timestamp */ 2 + /* Beacon Interval */ 2 + /* Capability Info */ /* SSID IE */ 1 + 1 + IW_ESSID_MAX_SIZE + 7 + /* FH Parameter Set */ 2 + /* DS Parameter Set */ 8 + /* CF Parameter Set */ 4 /* IBSS Parameter Set */ ); if (unlikely((*pkt) == NULL)) return 0; ieee80211softmac_hdr_3addr(mac, &((*pkt)->header), IEEE80211_STYPE_PROBE_RESP, net->bssid, net->bssid); data = (u8 *)(*pkt)->info_element; /* Return the packet size */ return (data - (u8 *)(*pkt)); } /* Sends a manangement packet * FIXME: document the use of the arg parameter * for _AUTH: (transaction #) | (status << 16) */ int ieee80211softmac_send_mgt_frame(struct ieee80211softmac_device *mac, void *ptrarg, u32 type, u32 arg) { void *pkt = NULL; u32 pkt_size = 0; int encrypt_mpdu = 0; switch(type) { case IEEE80211_STYPE_ASSOC_REQ: pkt_size = ieee80211softmac_assoc_req((struct ieee80211_assoc_request **)(&pkt), mac, (struct ieee80211softmac_network *)ptrarg); break; case IEEE80211_STYPE_REASSOC_REQ: pkt_size = ieee80211softmac_reassoc_req((struct ieee80211_reassoc_request **)(&pkt), mac, (struct ieee80211softmac_network *)ptrarg); break; case IEEE80211_STYPE_AUTH: pkt_size = ieee80211softmac_auth((struct ieee80211_auth **)(&pkt), mac, (struct ieee80211softmac_network *)ptrarg, (u16)(arg & 0xFFFF), (u16) (arg >> 16), &encrypt_mpdu); break; case IEEE80211_STYPE_DISASSOC: case IEEE80211_STYPE_DEAUTH: pkt_size = ieee80211softmac_disassoc_deauth((struct ieee80211_disassoc **)(&pkt), mac, (struct ieee80211softmac_network *)ptrarg, type, (u16)(arg & 0xFFFF)); break; case IEEE80211_STYPE_PROBE_REQ: pkt_size = ieee80211softmac_probe_req((struct ieee80211_probe_request **)(&pkt), mac, (struct ieee80211softmac_essid *)ptrarg); break; case IEEE80211_STYPE_PROBE_RESP: pkt_size = ieee80211softmac_probe_resp((struct ieee80211_probe_response **)(&pkt), mac, (struct ieee80211softmac_network *)ptrarg); break; default: printkl(KERN_DEBUG PFX "Unsupported Management Frame type: %i\n", type); return -EINVAL; }; if(pkt_size == 0 || pkt == NULL) { printkl(KERN_DEBUG PFX "Error, packet is nonexistant or 0 length\n"); return -ENOMEM; } /* Send the packet to the ieee80211 layer for tx */ /* we defined softmac->mgmt_xmit for this. Should we keep it * as it is (that means we'd need to wrap this into a txb), * modify the prototype (so it matches this function), * or get rid of it alltogether? * Does this work for you now? */ ieee80211_tx_frame(mac->ieee, (struct ieee80211_hdr *)pkt, IEEE80211_3ADDR_LEN, pkt_size, encrypt_mpdu); kfree(pkt); return 0; } /* Beacon handling */ int ieee80211softmac_handle_beacon(struct net_device *dev, struct ieee80211_beacon *beacon, struct ieee80211_network *network) { struct ieee80211softmac_device *mac = ieee80211_priv(dev); /* This might race, but we don't really care and it's not worth * adding heavyweight locking in this fastpath. */ if (mac->associnfo.associated) { if (memcmp(network->bssid, mac->associnfo.bssid, ETH_ALEN) == 0) ieee80211softmac_process_erp(mac, network->erp_value); } return 0; }