/*- * 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, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * 3. Neither the names of the above-listed copyright holders nor the names * of any contributors may 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. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. * * $Id$ */ /* * Wireless extensions support for 802.11 common code. */ #ifndef AUTOCONF_INCLUDED #include #endif #include #include #include #include #include /* for ARPHRD_ETHER */ #include #include #include #if WIRELESS_EXT < 15 #error "Wireless extensions v15 or better is needed." #endif #include #include #include #include #include #include "ah.h" #define IS_UP(_dev) \ (((_dev)->flags & (IFF_RUNNING|IFF_UP)) == (IFF_RUNNING|IFF_UP)) #define IS_UP_AUTO(_vap) \ (IS_UP((_vap)->iv_dev) && \ (_vap)->iv_ic->ic_roaming == IEEE80211_ROAMING_AUTO) #define RESCAN 1 static void pre_announced_chanswitch(struct net_device *dev, u_int32_t channel, u_int32_t tbtt); static int preempt_scan(struct net_device *dev, int max_grace, int max_wait) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; int total_delay = 0; int canceled = 0, ready = 0; while (!ready && total_delay < max_grace + max_wait) { if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { ready = 1; } else { if (!canceled && (total_delay > max_grace)) { /* Cancel any existing active scan, so that any new parameters * in this scan ioctl (or the defaults) can be honored, then * wait around a while to see if the scan cancels properly. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: cancel pending scan request\n", __func__); (void) ieee80211_cancel_scan(vap); canceled = 1; } mdelay (1); total_delay += 1; } } if (!ready) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: Timeout canceling current scan.\n", __func__); } return ready; } static struct iw_statistics * ieee80211_iw_getstats(struct net_device *dev) { struct ieee80211vap *vap = dev->priv; struct iw_statistics *is = &vap->iv_iwstats; struct ieee80211com *ic = vap->iv_ic; set_quality(&is->qual, ieee80211_getrssi(vap->iv_ic), ic->ic_channoise); is->status = vap->iv_state; is->discard.nwid = vap->iv_stats.is_rx_wrongbss + vap->iv_stats.is_rx_ssidmismatch; is->discard.code = vap->iv_stats.is_rx_wepfail + vap->iv_stats.is_rx_decryptcrc; is->discard.fragment = 0; is->discard.retries = 0; is->discard.misc = 0; is->miss.beacon = 0; return is; } static int ieee80211_ioctl_giwname(struct net_device *dev, struct iw_request_info *info, char *name, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211_channel *c = vap->iv_ic->ic_curchan; if (IEEE80211_IS_CHAN_108G(c)) strncpy(name, "IEEE 802.11Tg", IFNAMSIZ); else if (IEEE80211_IS_CHAN_108A(c)) strncpy(name, "IEEE 802.11Ta", IFNAMSIZ); else if (IEEE80211_IS_CHAN_TURBO(c)) strncpy(name, "IEEE 802.11T", IFNAMSIZ); else if (IEEE80211_IS_CHAN_ANYG(c)) strncpy(name, "IEEE 802.11g", IFNAMSIZ); else if (IEEE80211_IS_CHAN_A(c)) strncpy(name, "IEEE 802.11a", IFNAMSIZ); else if (IEEE80211_IS_CHAN_B(c)) strncpy(name, "IEEE 802.11b", IFNAMSIZ); else strncpy(name, "IEEE 802.11", IFNAMSIZ); /* XXX FHSS */ return 0; } /* * Get a key index from a request. If nothing is * specified in the request we use the current xmit * key index. Otherwise we just convert the index * to be base zero. */ static int getiwkeyix(struct ieee80211vap *vap, const struct iw_point *erq, ieee80211_keyix_t *rkix) { ieee80211_keyix_t kix; kix = erq->flags & IW_ENCODE_INDEX; if ((erq->flags & IW_ENCODE_INDEX) == (u_int8_t)IEEE80211_KEYIX_NONE) kix = IEEE80211_KEYIX_NONE; if (kix < 1 || kix > IEEE80211_WEP_NKID) { kix = vap->iv_def_txkey; if (kix == IEEE80211_KEYIX_NONE) kix = 0; } else --kix; if (kix < IEEE80211_WEP_NKID) { *rkix = kix; return 0; } else return -EINVAL; } static int ieee80211_ioctl_siwencode(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *keybuf) { struct ieee80211vap *vap = dev->priv; int error; int wepchange = 0; ieee80211_keyix_t kix; if ((erq->flags & IW_ENCODE_DISABLED) == 0) { /* Check WEP support, load WEP module if needed */ if (!ieee80211_crypto_available(vap, IEEE80211_CIPHER_WEP)) return -EOPNOTSUPP; /* * Enable crypto, set key contents, and * set the default transmit key. */ error = getiwkeyix(vap, erq, &kix); if (error < 0) return error; if (erq->length > IEEE80211_KEYBUF_SIZE) return -E2BIG; /* XXX no way to install 0-length key */ ieee80211_key_update_begin(vap); if (erq->length > 0) { struct ieee80211_key *k = &vap->iv_nw_keys[kix]; /* * Set key contents. This interface only supports WEP. * Indicate intended key index. */ k->wk_keyix = kix; if (ieee80211_crypto_newkey(vap, IEEE80211_CIPHER_WEP, IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV, k)) { k->wk_keylen = erq->length; memcpy(k->wk_key, keybuf, erq->length); memset(k->wk_key + erq->length, 0, IEEE80211_KEYBUF_SIZE - erq->length); if (!ieee80211_crypto_setkey(vap, k, vap->iv_myaddr, NULL)) error = -EINVAL; /* XXX */ } else error = -EINVAL; } else { /* * When the length is zero the request only changes * the default transmit key. Verify the new key has * a non-zero length. */ if (vap->iv_nw_keys[kix].wk_keylen == 0) error = -EINVAL; } if (error == 0) { /* * The default transmit key is only changed when: * 1. Privacy is enabled and no key matter is * specified. * 2. Privacy is currently disabled. * This is deduced from the iwconfig man page. */ if (erq->length == 0 || (vap->iv_flags & IEEE80211_F_PRIVACY) == 0) vap->iv_def_txkey = kix; wepchange = (vap->iv_flags & IEEE80211_F_PRIVACY) == 0; vap->iv_flags |= IEEE80211_F_PRIVACY; } ieee80211_key_update_end(vap); } else { if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) return 0; vap->iv_flags &= ~IEEE80211_F_PRIVACY; wepchange = 1; error = 0; } if (error == 0) { /* Set policy for unencrypted frames */ if ((erq->flags & IW_ENCODE_OPEN) && (!(erq->flags & IW_ENCODE_RESTRICTED))) { vap->iv_flags &= ~IEEE80211_F_DROPUNENC; } else if (!(erq->flags & IW_ENCODE_OPEN) && (erq->flags & IW_ENCODE_RESTRICTED)) { vap->iv_flags |= IEEE80211_F_DROPUNENC; } else { /* Default policy */ if (vap->iv_flags & IEEE80211_F_PRIVACY) vap->iv_flags |= IEEE80211_F_DROPUNENC; else vap->iv_flags &= ~IEEE80211_F_DROPUNENC; } } if ((error == 0) && IS_UP(vap->iv_dev)) { /* * Device is up and running; we must kick it to * effect the change. If we're enabling/disabling * crypto use then we must re-initialize the device * so the 802.11 state machine is reset. Otherwise * the key state should have been updated above. */ if (wepchange && IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } #ifdef ATH_SUPERG_XR /* set the same params on the xr vap device if exists */ if (!error && vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR)) ieee80211_ioctl_siwencode(vap->iv_xrvap->iv_dev, info, erq, keybuf); #endif return error; } static int ieee80211_ioctl_giwencode(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *key) { struct ieee80211vap *vap = dev->priv; struct ieee80211_key *k; int error; ieee80211_keyix_t kix; if (vap->iv_flags & IEEE80211_F_PRIVACY) { error = getiwkeyix(vap, erq, &kix); if (error < 0) return error; k = &vap->iv_nw_keys[kix]; /* XXX no way to return cipher/key type */ erq->flags = kix + 1; /* NB: base 1 */ if (erq->length > k->wk_keylen) erq->length = k->wk_keylen; memcpy(key, k->wk_key, erq->length); erq->flags |= IW_ENCODE_ENABLED; } else { erq->length = 0; erq->flags = IW_ENCODE_DISABLED; } if (vap->iv_flags & IEEE80211_F_DROPUNENC) erq->flags |= IW_ENCODE_RESTRICTED; else erq->flags |= IW_ENCODE_OPEN; return 0; } #ifndef ifr_media #define ifr_media ifr_ifru.ifru_ivalue #endif static int ieee80211_ioctl_siwrate(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { static const u_int mopts[] = { IFM_AUTO, IFM_IEEE80211_11A, IFM_IEEE80211_11B, IFM_IEEE80211_11G, IFM_IEEE80211_FH, IFM_IEEE80211_11A | IFM_IEEE80211_TURBO, IFM_IEEE80211_11G | IFM_IEEE80211_TURBO, }; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ifreq ifr; int rate, retv; if (vap->iv_media.ifm_cur == NULL) return -EINVAL; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_media = vap->iv_media.ifm_cur->ifm_media &~ (IFM_MMASK|IFM_TMASK); ifr.ifr_media |= mopts[vap->iv_des_mode]; if (rrq->fixed) { /* XXX fudge checking rates */ rate = ieee80211_rate2media(ic, 2 * rrq->value / 1000000, vap->iv_des_mode); if (rate == IFM_AUTO) /* NB: unknown rate */ return -EINVAL; } else rate = IFM_AUTO; ifr.ifr_media |= IFM_SUBTYPE(rate); /* refresh media capabilities based on channel */ ifmedia_removeall(&vap->iv_media); (void) ieee80211_media_setup(ic, &vap->iv_media, vap->iv_caps, vap->iv_media.ifm_change, vap->iv_media.ifm_status); retv = ifmedia_ioctl(vap->iv_dev, &ifr, &vap->iv_media, SIOCSIFMEDIA); if (retv == -ENETRESET) retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0; if (retv) IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s returns %d\n", __func__, retv); return retv; } static int ieee80211_ioctl_giwrate(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ifmediareq imr; int rate; memset(&imr, 0, sizeof(imr)); vap->iv_media.ifm_status(vap->iv_dev, &imr); rrq->fixed = IFM_SUBTYPE(vap->iv_media.ifm_media) != IFM_AUTO; /* media status will have the current xmit rate if available */ rate = ieee80211_media2rate(imr.ifm_active); if (rate == -1) /* IFM_AUTO */ rate = 0; rrq->value = 1000000 * (rate / 2); return 0; } static int ieee80211_ioctl_siwsens(struct net_device *dev, struct iw_request_info *info, struct iw_param *sens, char *extra) { return -EOPNOTSUPP; } static int ieee80211_ioctl_giwsens(struct net_device *dev, struct iw_request_info *info, struct iw_param *sens, char *extra) { sens->value = 1; sens->fixed = 1; return 0; } static int ieee80211_ioctl_siwrts(struct net_device *dev, struct iw_request_info *info, struct iw_param *rts, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; u16 val; if (rts->disabled) val = IEEE80211_RTS_MAX; else if ((IEEE80211_RTS_MIN <= rts->value) && (rts->value <= IEEE80211_RTS_MAX)) val = rts->value; else return -EINVAL; if (val != vap->iv_rtsthreshold) { vap->iv_rtsthreshold = val; if (IS_UP(vap->iv_dev)) return ic->ic_reset(ic->ic_dev); } return 0; } static int ieee80211_ioctl_giwrts(struct net_device *dev, struct iw_request_info *info, struct iw_param *rts, char *extra) { struct ieee80211vap *vap = dev->priv; rts->value = vap->iv_rtsthreshold; rts->disabled = (rts->value == IEEE80211_RTS_MAX); rts->fixed = 1; return 0; } static int ieee80211_ioctl_siwfrag(struct net_device *dev, struct iw_request_info *info, struct iw_param *rts, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; u16 val; if (rts->disabled) val = 2346; else if ((rts->value < 256) || (rts->value > 2346)) return -EINVAL; else val = (rts->value & ~0x1); if (val != vap->iv_fragthreshold) { vap->iv_fragthreshold = val; if (IS_UP(ic->ic_dev)) return ic->ic_reset(ic->ic_dev); } return 0; } static int ieee80211_ioctl_giwfrag(struct net_device *dev, struct iw_request_info *info, struct iw_param *rts, char *extra) { struct ieee80211vap *vap = dev->priv; rts->value = vap->iv_fragthreshold; rts->disabled = (rts->value == 2346); rts->fixed = 1; return 0; } static int ieee80211_ioctl_siwap(struct net_device *dev, struct iw_request_info *info, struct sockaddr *ap_addr, char *extra) { struct ieee80211vap *vap = dev->priv; /* NB: should not be set when in AP mode */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) return -EOPNOTSUPP; if (vap->iv_opmode == IEEE80211_M_WDS) IEEE80211_ADDR_COPY(vap->wds_mac, &ap_addr->sa_data); /* * zero address corresponds to 'iwconfig ath0 ap off', which means * enable automatic choice of AP without actually forcing a * reassociation. * * broadcast address corresponds to 'iwconfig ath0 ap any', which * means scan for the current best AP. * * anything else specifies a particular AP. */ vap->iv_flags &= ~IEEE80211_F_DESBSSID; if (!IEEE80211_ADDR_NULL(&ap_addr->sa_data)) { if (!IEEE80211_ADDR_EQ(&ap_addr->sa_data, vap->iv_dev->broadcast)) vap->iv_flags |= IEEE80211_F_DESBSSID; IEEE80211_ADDR_COPY(vap->iv_des_bssid, &ap_addr->sa_data); if (IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } return 0; } static int ieee80211_ioctl_giwap(struct net_device *dev, struct iw_request_info *info, struct sockaddr *ap_addr, char *extra) { struct ieee80211vap *vap = dev->priv; if (vap->iv_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->iv_des_bssid); else { if (vap->iv_state == IEEE80211_S_RUN) if (vap->iv_opmode != IEEE80211_M_WDS) IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->iv_bssid); else IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->wds_mac); else IEEE80211_ADDR_SET_NULL(&ap_addr->sa_data); } ap_addr->sa_family = ARPHRD_ETHER; return 0; } static int ieee80211_ioctl_siwnickn(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *nickname) { struct ieee80211vap *vap = dev->priv; if (data->length > IEEE80211_NWID_LEN) return -E2BIG; memset(vap->iv_nickname, 0, IEEE80211_NWID_LEN); memcpy(vap->iv_nickname, nickname, data->length); vap->iv_nicknamelen = data->length; return 0; } static int ieee80211_ioctl_giwnickn(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *nickname) { struct ieee80211vap *vap = dev->priv; if (data->length > vap->iv_nicknamelen + 1) data->length = vap->iv_nicknamelen + 1; if (data->length > 0) { memcpy(nickname, vap->iv_nickname, data->length - 1); /* XXX: strcpy? */ nickname[data->length-1] = '\0'; } return 0; } static int find11gchannel(struct ieee80211com *ic, int i, int freq) { for (; i < ic->ic_nchans; i++) { const struct ieee80211_channel *c = &ic->ic_channels[i]; if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c)) return 1; } return 0; } static struct ieee80211_channel * findchannel(struct ieee80211com *ic, int ieee, int mode) { static const u_int chanflags[] = { 0, /* IEEE80211_MODE_AUTO */ IEEE80211_CHAN_A, /* IEEE80211_MODE_11A */ IEEE80211_CHAN_B, /* IEEE80211_MODE_11B */ IEEE80211_CHAN_PUREG, /* IEEE80211_MODE_11G */ IEEE80211_CHAN_FHSS, /* IEEE80211_MODE_FH */ IEEE80211_CHAN_108A, /* IEEE80211_MODE_TURBO_A */ IEEE80211_CHAN_108G, /* IEEE80211_MODE_TURBO_G */ IEEE80211_CHAN_ST, /* IEEE80211_MODE_TURBO_STATIC_A */ }; u_int modeflags; unsigned int i; modeflags = chanflags[mode]; for (i = 0; i < ic->ic_nchans; i++) { struct ieee80211_channel *c = &ic->ic_channels[i]; if (c->ic_ieee != ieee) continue; if (mode == IEEE80211_MODE_AUTO) { /* ignore turbo channels for autoselect */ if (!(ic->ic_ath_cap & IEEE80211_ATHC_TURBOP) && IEEE80211_IS_CHAN_TURBO(c)) continue; /* * XXX special-case 11b/g channels so we * always select the g channel if both * are present. */ if (!IEEE80211_IS_CHAN_B(c) || !find11gchannel(ic, i + 1, c->ic_freq)) return c; } else { if ((c->ic_flags & modeflags) == modeflags) return c; } } return NULL; } #define IEEE80211_MODE_TURBO_STATIC_A IEEE80211_MODE_MAX static int ieee80211_check_mode_consistency(struct ieee80211com *ic, int mode, struct ieee80211_channel *c) { if (c == IEEE80211_CHAN_ANYC) return 0; switch (mode) { case IEEE80211_MODE_11B: if (IEEE80211_IS_CHAN_B(c)) return 0; else return 1; break; case IEEE80211_MODE_11G: if (IEEE80211_IS_CHAN_ANYG(c)) return 0; else return 1; break; case IEEE80211_MODE_11A: if (IEEE80211_IS_CHAN_A(c)) return 0; else return 1; break; case IEEE80211_MODE_TURBO_STATIC_A: if (IEEE80211_IS_CHAN_A(c) && IEEE80211_IS_CHAN_STURBO(c)) return 0; else return 1; break; case IEEE80211_MODE_AUTO: return 0; break; } return 1; } #undef IEEE80211_MODE_TURBO_STATIC_A static int ieee80211_ioctl_siwfreq(struct net_device *dev, struct iw_request_info *info, struct iw_freq *freq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_channel *c, *c2; int i; if (freq->e > 1) return -EINVAL; if (freq->e == 1) i = (ic->ic_mhz2ieee)(ic, freq->m / 100000, 0); else i = freq->m; if ((i != 0) && (i != -1)) { if (i > IEEE80211_CHAN_MAX) return -EINVAL; c = findchannel(ic, i, vap->iv_des_mode); if (c == NULL) { c = findchannel(ic, i, IEEE80211_MODE_AUTO); if (c == NULL) /* no channel */ return -EINVAL; } /* * Fine tune channel selection based on desired mode: * if 11b is requested, find the 11b version of any * 11g channel returned, * if static turbo, find the turbo version of any * 11a channel return, * otherwise we should be ok with what we've got. */ switch (vap->iv_des_mode) { case IEEE80211_MODE_11B: if (IEEE80211_IS_CHAN_ANYG(c)) { c2 = findchannel(ic, i, IEEE80211_MODE_11B); /* NB: should not happen, =>'s 11g w/o 11b */ if (c2 != NULL) c = c2; } break; case IEEE80211_MODE_TURBO_A: if (IEEE80211_IS_CHAN_A(c)) { c2 = findchannel(ic, i, IEEE80211_MODE_TURBO_A); if (c2 != NULL) c = c2; } break; default: /* NB: no static turboG */ break; } if (ieee80211_check_mode_consistency(ic, vap->iv_des_mode, c)) { if (vap->iv_opmode == IEEE80211_M_HOSTAP) return -EINVAL; } if ((vap->iv_state == IEEE80211_S_RUN) && (c == vap->iv_des_chan)) return 0; /* no change, return */ /* Don't allow to change to channel with radar found */ if (c->ic_flags & IEEE80211_CHAN_RADAR) return -EINVAL; /* * Mark desired channel and if running force a * radio change. */ vap->iv_des_chan = c; } else { /* * Intepret channel 0 to mean "no desired channel"; * otherwise there's no way to undo fixing the desired * channel. */ if (vap->iv_des_chan == IEEE80211_CHAN_ANYC) return 0; vap->iv_des_chan = IEEE80211_CHAN_ANYC; } #if 0 if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) { int mode = ieee80211_chan2mode(vap->iv_des_chan); if (mode != ic->ic_curmode) ieee80211_setmode(ic, mode); } #endif if ((vap->iv_opmode == IEEE80211_M_MONITOR || vap->iv_opmode == IEEE80211_M_WDS) && vap->iv_des_chan != IEEE80211_CHAN_ANYC) { /* Monitor and wds modes can switch directly. */ ic->ic_curchan = vap->iv_des_chan; if (vap->iv_state == IEEE80211_S_RUN) { ic->ic_set_channel(ic); } } else if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* Need to use channel switch announcement on beacon if we are * up and running. We use ic_set_channel directly if we are * "running" but not "up". Otherwise, iv_des_chan will take * effect when we are transitioned to RUN state later. */ if (IS_UP(vap->iv_dev) && (0 == (vap->iv_des_chan->ic_flags & CHANNEL_DFS))) { pre_announced_chanswitch(dev, ieee80211_chan2ieee(ic, vap->iv_des_chan), IEEE80211_DEFAULT_CHANCHANGE_TBTT_COUNT); } else if (vap->iv_state == IEEE80211_S_RUN) { ic->ic_curchan = vap->iv_des_chan; ic->ic_set_channel(ic); } } else { /* Need to go through the state machine in case we need * to reassociate or the like. The state machine will * pickup the desired channel and avoid scanning. */ if (IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } return 0; } static int ieee80211_ioctl_giwfreq(struct net_device *dev, struct iw_request_info *info, struct iw_freq *freq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; if (vap->iv_state == IEEE80211_S_RUN && vap->iv_opmode != IEEE80211_M_MONITOR) { /* * NB: use curchan for monitor mode so you can see * manual scanning by apps like kismet. */ KASSERT(ic->ic_bsschan != IEEE80211_CHAN_ANYC, ("bss channel not set")); freq->m = ic->ic_bsschan->ic_freq; } else if (vap->iv_state != IEEE80211_S_INIT) /* e.g. when scanning */ freq->m = ic->ic_curchan->ic_freq; else if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) freq->m = vap->iv_des_chan->ic_freq; else freq->m = 0; freq->m *= 100000; freq->e = 1; return 0; } #ifdef ATH_SUPERG_XR /* * Copy desired ssid state from one vap to another. */ static void copy_des_ssid(struct ieee80211vap *dst, const struct ieee80211vap *src) { dst->iv_des_nssid = src->iv_des_nssid; memcpy(dst->iv_des_ssid, src->iv_des_ssid, src->iv_des_nssid * sizeof(src->iv_des_ssid[0])); } #endif /* ATH_SUPERG_XR */ static int ieee80211_ioctl_siwessid(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *ssid) { struct ieee80211vap *vap = dev->priv; if (vap->iv_opmode == IEEE80211_M_WDS) return -EOPNOTSUPP; if (data->flags == 0) /* ANY */ vap->iv_des_nssid = 0; else { if (data->length > IEEE80211_NWID_LEN) data->length = IEEE80211_NWID_LEN; /* NB: always use entry 0 */ memcpy(vap->iv_des_ssid[0].ssid, ssid, data->length); vap->iv_des_ssid[0].len = data->length; vap->iv_des_nssid = 1; #if WIRELESS_EXT < 21 /* * Deduct a trailing \0 since iwconfig passes a string * length that includes this. Unfortunately this means * that specifying a string with multiple trailing \0's * won't be handled correctly. Not sure there's a good * solution; the API is botched (the length should be * exactly those bytes that are meaningful and not include * extraneous stuff). */ /* The API was fixed in WE21 */ if (data->length > 0 && vap->iv_des_ssid[0].ssid[data->length - 1] == '\0') vap->iv_des_ssid[0].len--; #endif /* WIRELESS_EXT < 21 */ } #ifdef ATH_SUPERG_XR if (vap->iv_xrvap != NULL && !(vap->iv_flags & IEEE80211_F_XR)) { if (data->flags == 0) vap->iv_des_nssid = 0; else copy_des_ssid(vap->iv_xrvap, vap); } #endif return IS_UP_AUTO(vap) ? ieee80211_init(vap->iv_dev, RESCAN) : 0; } static int ieee80211_ioctl_giwessid(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *essid) { struct ieee80211vap *vap = dev->priv; if (vap->iv_opmode == IEEE80211_M_WDS) return -EOPNOTSUPP; data->flags = 1; /* active */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) { if (vap->iv_des_nssid > 0) { if (data->length > vap->iv_des_ssid[0].len) data->length = vap->iv_des_ssid[0].len; memcpy(essid, vap->iv_des_ssid[0].ssid, data->length); } else data->length = 0; } else { if (vap->iv_des_nssid == 0) { if (data->length > vap->iv_bss->ni_esslen) data->length = vap->iv_bss->ni_esslen; memcpy(essid, vap->iv_bss->ni_essid, data->length); } else { if (data->length > vap->iv_des_ssid[0].len) data->length = vap->iv_des_ssid[0].len; memcpy(essid, vap->iv_des_ssid[0].ssid, data->length); } } return 0; } static int ieee80211_ioctl_giwrange(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni = vap->iv_bss; struct iw_range *range = (struct iw_range *)extra; struct ieee80211_rateset *rs; u_int8_t reported[IEEE80211_CHAN_BYTES]; /* XXX stack usage? */ int i, r; int step = 0; data->length = sizeof(struct iw_range); memset(range, 0, sizeof(struct iw_range)); /* txpower (128 values, but will print out only IW_MAX_TXPOWER) */ range->num_txpower = (ic->ic_txpowlimit >= 8) ? IW_MAX_TXPOWER : ic->ic_txpowlimit; step = ic->ic_txpowlimit / (2 * (IW_MAX_TXPOWER - 1)); range->txpower[0] = 0; for (i = 1; i < IW_MAX_TXPOWER; i++) range->txpower[i] = (ic->ic_txpowlimit/2) - (IW_MAX_TXPOWER - i - 1) * step; range->txpower_capa = IW_TXPOW_DBM; if (vap->iv_opmode == IEEE80211_M_STA || vap->iv_opmode == IEEE80211_M_IBSS) { range->min_pmp = 1 * 1024; range->max_pmp = 65535 * 1024; range->min_pmt = 1 * 1024; range->max_pmt = 1000 * 1024; range->pmp_flags = IW_POWER_PERIOD; range->pmt_flags = IW_POWER_TIMEOUT; range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_UNICAST_R | IW_POWER_ALL_R; } range->we_version_compiled = WIRELESS_EXT; range->we_version_source = 18; range->retry_capa = IW_RETRY_LIMIT; range->retry_flags = IW_RETRY_LIMIT; range->min_retry = 0; range->max_retry = 255; range->num_channels = ic->ic_nchans; range->num_frequency = 0; memset(reported, 0, sizeof(reported)); for (i = 0; i < ic->ic_nchans; i++) { const struct ieee80211_channel *c = &ic->ic_channels[i]; /* discard if previously reported (e.g. b/g) */ if (isclr(reported, c->ic_ieee)) { setbit(reported, c->ic_ieee); range->freq[range->num_frequency].i = c->ic_ieee; range->freq[range->num_frequency].m = ic->ic_channels[i].ic_freq * 100000; range->freq[range->num_frequency].e = 1; if (++range->num_frequency == IW_MAX_FREQUENCIES) break; } } /* Atheros' RSSI value is SNR: 0 -> 60 for old chipsets. Range * for newer chipsets is unknown. This value is arbitarily chosen * to give an indication that full rate will be available and to be * a practicable maximum. */ range->max_qual.qual = 70; #if WIRELESS_EXT >= 19 /* XXX: This should be updated to use the current noise floor. */ /* These are negative full bytes. * Min. quality is noise + 1 */ range->max_qual.updated |= IW_QUAL_DBM; range->max_qual.level = ATH_DEFAULT_NOISE + 1; range->max_qual.noise = ATH_DEFAULT_NOISE; #else /* Values larger than the maximum are assumed to be absolute */ range->max_qual.level = 0; range->max_qual.noise = 0; #endif range->sensitivity = 1; range->max_encoding_tokens = IEEE80211_WEP_NKID; /* XXX query driver to find out supported key sizes */ range->num_encoding_sizes = 3; range->encoding_size[0] = 5; /* 40-bit */ range->encoding_size[1] = 13; /* 104-bit */ range->encoding_size[2] = 16; /* 128-bit */ /* XXX this only works for station mode */ rs = &ni->ni_rates; range->num_bitrates = rs->rs_nrates; if (range->num_bitrates > IW_MAX_BITRATES) range->num_bitrates = IW_MAX_BITRATES; for (i = 0; i < range->num_bitrates; i++) { r = rs->rs_rates[i] & IEEE80211_RATE_VAL; range->bitrate[i] = (r * 1000000) / 2; } /* estimated maximum TCP throughput values (bps) */ range->throughput = 5500000; range->min_rts = 0; range->max_rts = 2347; range->min_frag = 256; range->max_frag = 2346; #if WIRELESS_EXT >= 17 /* Event capability (kernel) */ IW_EVENT_CAPA_SET_KERNEL(range->event_capa); /* Event capability (driver) */ if (vap->iv_opmode == IEEE80211_M_STA || vap->iv_opmode == IEEE80211_M_IBSS || vap->iv_opmode == IEEE80211_M_AHDEMO) { /* for now, only ibss, ahdemo, sta has this cap */ IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN); } if (vap->iv_opmode == IEEE80211_M_STA) { /* for sta only */ IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP); IW_EVENT_CAPA_SET(range->event_capa, IWEVREGISTERED); IW_EVENT_CAPA_SET(range->event_capa, IWEVEXPIRED); } /* this is used for reporting replay failure, which is used by the different encoding schemes */ IW_EVENT_CAPA_SET(range->event_capa, IWEVCUSTOM); #endif #if WIRELESS_EXT >= 18 /* report supported WPA/WPA2 capabilities to userspace */ range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 | IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP; #endif return 0; } static int ieee80211_ioctl_setspy(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { /* save the list of node addresses */ struct ieee80211vap *vap = dev->priv; struct sockaddr address[IW_MAX_SPY]; unsigned int number = data->length; int i; if (number > IW_MAX_SPY) return -E2BIG; /* get the addresses into the driver */ if (data->pointer) { if (copy_from_user(address, data->pointer, sizeof(struct sockaddr) * number)) return -EFAULT; } else return -EINVAL; /* copy the MAC addresses into a list */ if (number > 0) { /* extract the MAC addresses */ for (i = 0; i < number; i++) memcpy(&vap->iv_spy.mac[i * IEEE80211_ADDR_LEN], address[i].sa_data, IEEE80211_ADDR_LEN); /* init rssi timestamps to 0 */ memset(vap->iv_spy.ts_rssi, 0, sizeof(vap->iv_spy.ts_rssi)); } vap->iv_spy.num = number; return 0; } static int ieee80211_ioctl_getspy(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { /* * locate nodes by mac (ieee80211_find_node()), * copy out rssi, set updated flag appropriately */ struct ieee80211vap *vap = dev->priv; struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta; struct ieee80211_node *ni; struct ieee80211com *ic = vap->iv_ic; struct sockaddr *address; struct iw_quality *spy_stat; unsigned int number = vap->iv_spy.num; int i; address = (struct sockaddr *)extra; spy_stat = (struct iw_quality *)(extra + number * sizeof(struct sockaddr)); for (i = 0; i < number; i++) { memcpy(address[i].sa_data, &vap->iv_spy.mac[i * IEEE80211_ADDR_LEN], IEEE80211_ADDR_LEN); address[i].sa_family = AF_PACKET; } /* locate a node, read its rssi, check if updated, convert to dBm */ for (i = 0; i < number; i++) { ni = ieee80211_find_node(nt, &vap->iv_spy.mac[i * IEEE80211_ADDR_LEN]); /* check we are associated w/ this vap */ if (ni) { if (ni->ni_vap == vap) { set_quality(&spy_stat[i], ic->ic_rssi_ewma ? ic->ic_node_getrssi(ni) : ni->ni_rssi, ic->ic_channoise); if (ni->ni_rtsf != vap->iv_spy.ts_rssi[i]) { vap->iv_spy.ts_rssi[i] = ni->ni_rtsf; } else { spy_stat[i].updated = 0; } } ieee80211_unref_node(&ni); } else { spy_stat[i].updated = IW_QUAL_ALL_INVALID; } } /* copy results to userspace */ data->length = number; return 0; } #if WIRELESS_EXT >= 16 /* Enhanced iwspy support */ static int ieee80211_ioctl_setthrspy(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct iw_thrspy threshold; if (data->length != 1) return -EINVAL; /* get the threshold values into the driver */ if (data->pointer) { if (copy_from_user(&threshold, data->pointer, sizeof(struct iw_thrspy))) return -EFAULT; } else return -EINVAL; if (threshold.low.level == 0) { /* disable threshold */ vap->iv_spy.thr_low = 0; vap->iv_spy.thr_high = 0; IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: disabled iw_spy threshold\n", __func__); } else { /* We are passed a signal level/strength - calculate * corresponding RSSI values */ /* XXX: We should use current noise value. */ vap->iv_spy.thr_low = threshold.low.level + ATH_DEFAULT_NOISE; vap->iv_spy.thr_high = threshold.high.level + ATH_DEFAULT_NOISE; IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s: enabled iw_spy threshold\n", __func__); } return 0; } static int ieee80211_ioctl_getthrspy(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct iw_thrspy *threshold; threshold = (struct iw_thrspy *)extra; /* set threshold values */ set_quality(&(threshold->low), vap->iv_spy.thr_low, ic->ic_channoise); set_quality(&(threshold->high), vap->iv_spy.thr_high, ic->ic_channoise); /* copy results to userspace */ data->length = 1; return 0; } #endif static int ieee80211_ioctl_siwmode(struct net_device *dev, struct iw_request_info *info, __u32 *mode, char *extra) { struct ieee80211vap *vap = dev->priv; struct ifmediareq imr; int valid = 0; memset(&imr, 0, sizeof(imr)); vap->iv_media.ifm_status(vap->iv_dev, &imr); if (imr.ifm_active & IFM_IEEE80211_HOSTAP) valid = (*mode == IW_MODE_MASTER); else if (imr.ifm_active & IFM_IEEE80211_MONITOR) valid = (*mode == IW_MODE_MONITOR); else if (imr.ifm_active & IFM_IEEE80211_ADHOC) valid = (*mode == IW_MODE_ADHOC); else if (imr.ifm_active & IFM_IEEE80211_WDS) valid = (*mode == IW_MODE_REPEAT); else valid = (*mode == IW_MODE_INFRA); return valid ? 0 : -EINVAL; } static int ieee80211_ioctl_giwmode(struct net_device *dev, struct iw_request_info *info, __u32 *mode, char *extra) { struct ieee80211vap *vap = dev->priv; struct ifmediareq imr; memset(&imr, 0, sizeof(imr)); vap->iv_media.ifm_status(vap->iv_dev, &imr); if (imr.ifm_active & IFM_IEEE80211_HOSTAP) *mode = IW_MODE_MASTER; else if (imr.ifm_active & IFM_IEEE80211_MONITOR) *mode = IW_MODE_MONITOR; else if (imr.ifm_active & IFM_IEEE80211_ADHOC) *mode = IW_MODE_ADHOC; else if (imr.ifm_active & IFM_IEEE80211_WDS) *mode = IW_MODE_REPEAT; else *mode = IW_MODE_INFRA; return 0; } static int ieee80211_ioctl_siwpower(struct net_device *dev, struct iw_request_info *info, struct iw_param *wrq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; /* XXX: These values, flags, and caps do not seem to be used elsewhere * at all? */ if ((ic->ic_caps & IEEE80211_C_PMGT) == 0) return -EOPNOTSUPP; if (wrq->disabled) { ic->ic_flags &= ~IEEE80211_F_PMGTON; } else { switch (wrq->flags & IW_POWER_MODE) { case IW_POWER_UNICAST_R: case IW_POWER_ALL_R: case IW_POWER_ON: if (wrq->flags & IW_POWER_PERIOD) { if (IEEE80211_BINTVAL_VALID(wrq->value)) ic->ic_lintval = IEEE80211_MS_TO_TU(wrq->value); else return -EINVAL; } if (wrq->flags & IW_POWER_TIMEOUT) ic->ic_holdover = IEEE80211_MS_TO_TU(wrq->value); ic->ic_flags |= IEEE80211_F_PMGTON; break; default: return -EINVAL; } } return IS_UP(ic->ic_dev) ? ic->ic_reset(ic->ic_dev) : 0; } static int ieee80211_ioctl_giwpower(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; rrq->disabled = (ic->ic_flags & IEEE80211_F_PMGTON) == 0; if (!rrq->disabled) { switch (rrq->flags & IW_POWER_TYPE) { case IW_POWER_TIMEOUT: rrq->flags = IW_POWER_TIMEOUT; rrq->value = IEEE80211_TU_TO_MS(ic->ic_holdover); break; case IW_POWER_PERIOD: rrq->flags = IW_POWER_PERIOD; rrq->value = IEEE80211_TU_TO_MS(ic->ic_lintval); break; } rrq->flags |= IW_POWER_ALL_R; } return 0; } static int ieee80211_ioctl_siwretry(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; if (rrq->disabled) { if (vap->iv_flags & IEEE80211_F_SWRETRY) { vap->iv_flags &= ~IEEE80211_F_SWRETRY; goto done; } return 0; } if ((vap->iv_caps & IEEE80211_C_SWRETRY) == 0) return -EOPNOTSUPP; if (rrq->flags == IW_RETRY_LIMIT) { if (rrq->value >= 0) { vap->iv_txmin = rrq->value; vap->iv_txmax = rrq->value; /* XXX */ vap->iv_txlifetime = 0; /* XXX */ vap->iv_flags |= IEEE80211_F_SWRETRY; } else { vap->iv_flags &= ~IEEE80211_F_SWRETRY; } return 0; } done: return IS_UP(vap->iv_dev) ? ic->ic_reset(vap->iv_dev) : 0; } static int ieee80211_ioctl_giwretry(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ieee80211vap *vap = dev->priv; rrq->disabled = (vap->iv_flags & IEEE80211_F_SWRETRY) == 0; if (!rrq->disabled) { switch (rrq->flags & IW_RETRY_TYPE) { case IW_RETRY_LIFETIME: rrq->flags = IW_RETRY_LIFETIME; rrq->value = IEEE80211_TU_TO_MS(vap->iv_txlifetime); break; case IW_RETRY_LIMIT: rrq->flags = IW_RETRY_LIMIT; switch (rrq->flags & IW_RETRY_MODIFIER) { case IW_RETRY_MIN: rrq->flags |= IW_RETRY_MAX; rrq->value = vap->iv_txmin; break; case IW_RETRY_MAX: rrq->flags |= IW_RETRY_MAX; rrq->value = vap->iv_txmax; break; } break; } } return 0; } static int ieee80211_ioctl_siwtxpow(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; int fixed, disabled; fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED); disabled = (fixed && vap->iv_bss->ni_txpower == 0); if (rrq->disabled) { if (!disabled) { if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0) return -EOPNOTSUPP; ic->ic_flags |= IEEE80211_F_TXPOW_FIXED; vap->iv_bss->ni_txpower = 0; goto done; } return 0; } if (rrq->fixed) { if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0) return -EOPNOTSUPP; if (rrq->flags != IW_TXPOW_DBM) return -EINVAL; if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) { if ((ic->ic_bsschan->ic_maxregpower >= rrq->value) && (ic->ic_txpowlimit/2 >= rrq->value)) { vap->iv_bss->ni_txpower = 2 * rrq->value; ic->ic_newtxpowlimit = 2 * rrq->value; ic->ic_flags |= IEEE80211_F_TXPOW_FIXED; } else return -EINVAL; } else { /* * No channel set yet */ if (ic->ic_txpowlimit/2 >= rrq->value) { vap->iv_bss->ni_txpower = 2 * rrq->value; ic->ic_newtxpowlimit = 2 * rrq->value; ic->ic_flags |= IEEE80211_F_TXPOW_FIXED; } else return -EINVAL; } } else { if (!fixed) /* no change */ return 0; ic->ic_newtxpowlimit = IEEE80211_TXPOWER_MAX; ic->ic_flags &= ~IEEE80211_F_TXPOW_FIXED; } done: return IS_UP(ic->ic_dev) ? ic->ic_reset(ic->ic_dev) : 0; } static int ieee80211_get_txcont(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_get_txcont(ic); return 0; } static int ieee80211_get_dfs_cac_time(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_get_dfs_cac_time(ic); return 0; } static int ieee80211_get_dfs_excl_period(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_get_dfs_excl_period(ic); return 0; } static int ieee80211_set_dfs_cac_time(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; ic->ic_set_dfs_cac_time(ic, params[1]); return 0; } static int ieee80211_set_dfs_excl_period (struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; ic->ic_set_dfs_excl_period(ic, params[1]); return 0; } static int ieee80211_get_dfs_testmode(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_get_dfs_testmode(ic); return 0; } static int ieee80211_get_txcont_rate(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_get_txcont_rate(ic); return 0; } static int ieee80211_set_txcont(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; ic->ic_set_txcont(ic, params[1]); return 0; } static int ieee80211_set_dfs_testmode(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; ic->ic_set_dfs_testmode(ic, params[1]); return 0; } static int ieee80211_set_txcont_rate(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; ic->ic_set_txcont_rate(ic, params[1]); return 0; } static int ieee80211_set_txcont_power(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; ic->ic_set_txcont_power(ic, params[1]); return 0; } static int ieee80211_get_txcont_power(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_get_txcont_power(ic); return 0; } static int ieee80211_ioctl_hal_map(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; params[0] = ic->ic_dump_hal_map(ic); return 0; } static int ieee80211_ioctl_radar(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { int *params = (int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; if (!(ic->ic_flags & IEEE80211_F_DOTH)) return 0; params[0] = ic->ic_test_radar(ic); return 0; } static int ieee80211_ioctl_giwtxpow(struct net_device *dev, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; rrq->value = vap->iv_bss->ni_txpower / 2; rrq->fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED) != 0; rrq->disabled = (rrq->fixed && rrq->value == 0); rrq->flags = IW_TXPOW_DBM; return 0; } #ifdef ATH_REVERSE_ENGINEERING static int ieee80211_dump_registers(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { unsigned int *params = (unsigned int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; switch (params[1]) { case 2: ic->ic_registers_mark(ic); break; case 1: ic->ic_registers_dump_delta(ic); break; case 0: default: ic->ic_registers_dump(ic); break; } return 0; } #endif /* #ifdef ATH_REVERSE_ENGINEERING */ #ifdef ATH_REVERSE_ENGINEERING static int ieee80211_ioctl_writereg(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { unsigned int *params = (unsigned int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; return ic->ic_write_register(ic, params[0], params[1]); } #endif /* #ifdef ATH_REVERSE_ENGINEERING */ #ifdef ATH_REVERSE_ENGINEERING static int ieee80211_ioctl_readreg(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { unsigned int *params = (unsigned int *)extra; struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; return ic->ic_read_register(ic, params[0], ¶ms[0]); } #endif /* #ifdef ATH_REVERSE_ENGINEERING */ struct waplistreq { /* XXX: not the right place for declaration? */ struct ieee80211vap *vap; struct sockaddr addr[IW_MAX_AP]; struct iw_quality qual[IW_MAX_AP]; int i; }; static int waplist_cb(void *arg, const struct ieee80211_scan_entry *se) { struct waplistreq *req = arg; int i = req->i; if (i >= IW_MAX_AP) return 0; req->addr[i].sa_family = ARPHRD_ETHER; if (req->vap->iv_opmode == IEEE80211_M_HOSTAP) IEEE80211_ADDR_COPY(req->addr[i].sa_data, se->se_macaddr); else IEEE80211_ADDR_COPY(req->addr[i].sa_data, se->se_bssid); set_quality(&req->qual[i], se->se_rssi, ATH_DEFAULT_NOISE); req->i++; return 0; } static int ieee80211_ioctl_iwaplist(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct waplistreq req; /* XXX off stack */ req.vap = vap; req.i = 0; ieee80211_scan_iterate(ic, waplist_cb, &req); data->length = req.i; memcpy(extra, &req.addr, req.i * sizeof(req.addr[0])); data->flags = 1; /* signal quality present (sort of) */ memcpy(extra + req.i * sizeof(req.addr[0]), &req.qual, req.i * sizeof(req.qual[0])); return 0; } #ifdef SIOCGIWSCAN static int ieee80211_ioctl_siwscan(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; /* * XXX don't permit a scan to be started unless we * know the device is ready. For the moment this means * the device is marked up as this is the required to * initialize the hardware. It would be better to permit * scanning prior to being up but that'll require some * changes to the infrastructure. */ if (!IS_UP(vap->iv_dev)) return -ENETDOWN; /* XXX always manual... */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: active scan request\n", __func__); preempt_scan(dev, 100, 100); #if WIRELESS_EXT > 17 if (data && (data->flags & IW_SCAN_THIS_ESSID)) { struct iw_scan_req req; struct ieee80211_scan_ssid ssid; int copyLength; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: SCAN_THIS_ESSID requested\n", __func__); if (data->length > sizeof req) { copyLength = sizeof req; } else { copyLength = data->length; } memset(&req, 0, sizeof req); if (copy_from_user(&req, data->pointer, copyLength)) return -EFAULT; memcpy(&ssid.ssid, req.essid, sizeof ssid.ssid); ssid.len = req.essid_len; IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN, "%s: requesting scan of essid '%s'\n", __func__, ssid.ssid); (void) ieee80211_start_scan(vap, IEEE80211_SCAN_ACTIVE | IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_ONCE, IEEE80211_SCAN_FOREVER, 1, &ssid); return 0; } #endif (void) ieee80211_start_scan(vap, IEEE80211_SCAN_ACTIVE | IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_ONCE, IEEE80211_SCAN_FOREVER, /* XXX use ioctl params */ vap->iv_des_nssid, vap->iv_des_ssid); return 0; } /* * Encode a WPA or RSN information element as a custom * element using the hostap format. */ static u_int encode_ie(void *buf, size_t bufsize, const u_int8_t *ie, size_t ielen, const char *leader, size_t leader_len) { u_int8_t *p; int i; if (bufsize < leader_len) return 0; p = buf; memcpy(p, leader, leader_len); bufsize -= leader_len; p += leader_len; for (i = 0; i < ielen && bufsize > 2; i++) { p += sprintf(p, "%02x", ie[i]); bufsize -= 2; } return (i == ielen ? p - (u_int8_t *)buf : 0); } struct iwscanreq { /* XXX: right place for this declaration? */ struct ieee80211vap *vap; struct iw_request_info *info; char *current_ev; char *end_buf; int mode; }; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) #define iwe_stream_add_event(a, b, c, d, e) iwe_stream_add_event(b, c, d, e) #define iwe_stream_add_point(a, b, c, d, e) iwe_stream_add_point(b, c, d, e) #define iwe_stream_add_value(a, b, c, d, e, f) \ iwe_stream_add_value(b, c, d, e, f) #endif static int giwscan_cb(void *arg, const struct ieee80211_scan_entry *se) { struct iwscanreq *req = arg; struct ieee80211vap *vap = req->vap; char *current_ev = req->current_ev; char *end_buf = req->end_buf; char *last_ev; #define MAX_IE_LENGTH 64 * 2 + 30 char buf[MAX_IE_LENGTH]; #ifndef IWEVGENIE static const char rsn_leader[] = "rsn_ie="; static const char wpa_leader[] = "wpa_ie="; #endif struct iw_event iwe; char *current_val; int j; if (current_ev >= end_buf) return E2BIG; /* WPA/!WPA sort criteria */ if ((req->mode != 0) ^ (se->se_wpa_ie != NULL)) return 0; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = SIOCGIWAP; iwe.u.ap_addr.sa_family = ARPHRD_ETHER; if (vap->iv_opmode == IEEE80211_M_HOSTAP) IEEE80211_ADDR_COPY(iwe.u.ap_addr.sa_data, se->se_macaddr); else IEEE80211_ADDR_COPY(iwe.u.ap_addr.sa_data, se->se_bssid); current_ev = iwe_stream_add_event(req->info, current_ev, end_buf, &iwe, IW_EV_ADDR_LEN); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = SIOCGIWESSID; iwe.u.data.flags = 1; iwe.u.data.length = se->se_ssid[1]; current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, (char *)se->se_ssid + 2); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; if (se->se_capinfo & (IEEE80211_CAPINFO_ESS|IEEE80211_CAPINFO_IBSS)) { memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = SIOCGIWMODE; iwe.u.mode = se->se_capinfo & IEEE80211_CAPINFO_ESS ? IW_MODE_MASTER : IW_MODE_ADHOC; current_ev = iwe_stream_add_event(req->info, current_ev, end_buf, &iwe, IW_EV_UINT_LEN); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; } memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = SIOCGIWFREQ; iwe.u.freq.m = se->se_chan->ic_freq * 100000; iwe.u.freq.e = 1; current_ev = iwe_stream_add_event(req->info, current_ev, end_buf, &iwe, IW_EV_FREQ_LEN); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = IWEVQUAL; set_quality(&iwe.u.qual, se->se_rssi, ATH_DEFAULT_NOISE); current_ev = iwe_stream_add_event(req->info, current_ev, end_buf, &iwe, IW_EV_QUAL_LEN); /* We ran out of space in the buffer */ if (last_ev == current_ev) return E2BIG; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = SIOCGIWENCODE; if (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; else iwe.u.data.flags = IW_ENCODE_DISABLED; iwe.u.data.length = 0; current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, ""); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = SIOCGIWRATE; current_val = current_ev + IW_EV_LCP_LEN; /* NB: not sorted, does it matter? */ for (j = 0; j < se->se_rates[1]; j++) { int r = se->se_rates[2 + j] & IEEE80211_RATE_VAL; if (r != 0) { iwe.u.bitrate.value = r * (1000000 / 2); current_val = iwe_stream_add_value(req->info, current_ev, current_val, end_buf, &iwe, IW_EV_PARAM_LEN); } } for (j = 0; j < se->se_xrates[1]; j++) { int r = se->se_xrates[2+j] & IEEE80211_RATE_VAL; if (r != 0) { iwe.u.bitrate.value = r * (1000000 / 2); current_val = iwe_stream_add_value(req->info, current_ev, current_val, end_buf, &iwe, IW_EV_PARAM_LEN); } } /* remove fixed header if no rates were added */ if ((current_val - current_ev) > IW_EV_LCP_LEN) { current_ev = current_val; } else { /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; } memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = IWEVCUSTOM; snprintf(buf, sizeof(buf), "bcn_int=%d", se->se_intval); iwe.u.data.length = strlen(buf); current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, buf); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; if (se->se_rsn_ie != NULL) { last_ev = current_ev; #ifdef IWEVGENIE memset(&iwe, 0, sizeof(iwe)); if ((se->se_rsn_ie[1] + 2) > MAX_IE_LENGTH) return E2BIG; memcpy(buf, se->se_rsn_ie, se->se_rsn_ie[1] + 2); iwe.cmd = IWEVGENIE; iwe.u.data.length = se->se_rsn_ie[1] + 2; #else memset(&iwe, 0, sizeof(iwe)); iwe.cmd = IWEVCUSTOM; if (se->se_rsn_ie[0] == IEEE80211_ELEMID_RSN) iwe.u.data.length = encode_ie(buf, sizeof(buf), se->se_rsn_ie, se->se_rsn_ie[1] + 2, rsn_leader, sizeof(rsn_leader) - 1); #endif if (iwe.u.data.length != 0) { current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, buf); /* We ran out of space in the buffer */ if (last_ev == current_ev) return E2BIG; } } if (se->se_wpa_ie != NULL) { last_ev = current_ev; #ifdef IWEVGENIE memset(&iwe, 0, sizeof(iwe)); if ((se->se_wpa_ie[1] + 2) > MAX_IE_LENGTH) return E2BIG; memcpy(buf, se->se_wpa_ie, se->se_wpa_ie[1] + 2); iwe.cmd = IWEVGENIE; iwe.u.data.length = se->se_wpa_ie[1] + 2; #else memset(&iwe, 0, sizeof(iwe)); iwe.cmd = IWEVCUSTOM; iwe.u.data.length = encode_ie(buf, sizeof(buf), se->se_wpa_ie, se->se_wpa_ie[1] + 2, wpa_leader, sizeof(wpa_leader) - 1); #endif if (iwe.u.data.length != 0) { current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, buf); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; } } if (se->se_wme_ie != NULL) { static const char wme_leader[] = "wme_ie="; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = IWEVCUSTOM; iwe.u.data.length = encode_ie(buf, sizeof(buf), se->se_wme_ie, se->se_wme_ie[1] + 2, wme_leader, sizeof(wme_leader) - 1); if (iwe.u.data.length != 0) { current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, buf); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; } } if (se->se_ath_ie != NULL) { static const char ath_leader[] = "ath_ie="; memset(&iwe, 0, sizeof(iwe)); last_ev = current_ev; iwe.cmd = IWEVCUSTOM; iwe.u.data.length = encode_ie(buf, sizeof(buf), se->se_ath_ie, se->se_ath_ie[1] + 2, ath_leader, sizeof(ath_leader) - 1); if (iwe.u.data.length != 0) { current_ev = iwe_stream_add_point(req->info, current_ev, end_buf, &iwe, buf); /* We ran out of space in the buffer. */ if (last_ev == current_ev) return E2BIG; } } req->current_ev = current_ev; return 0; } static int ieee80211_ioctl_giwscan(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct iwscanreq req; int res = 0; req.vap = vap; req.info = info; req.current_ev = extra; if (data->length == 0) { req.end_buf = extra + IW_SCAN_MAX_DATA; } else { req.end_buf = extra + data->length; } /* * NB: This is no longer needed, as long as the caller supports * large scan results. * * Do two passes to ensure WPA/non-WPA scan candidates * are sorted to the front. This is a hack to deal with * the wireless extensions capping scan results at * IW_SCAN_MAX_DATA bytes. In densely populated environments * it's easy to overflow this buffer (especially with WPA/RSN * information elements). Note this sorting hack does not * guarantee we won't overflow anyway. */ req.mode = vap->iv_flags & IEEE80211_F_WPA; res = ieee80211_scan_iterate(ic, giwscan_cb, &req); if (res == 0) { req.mode = req.mode ? 0 : IEEE80211_F_WPA; res = ieee80211_scan_iterate(ic, giwscan_cb, &req); } data->length = req.current_ev - extra; if (res != 0) { return -res; } return res; } #endif /* SIOCGIWSCAN */ static int cipher2cap(int cipher) { switch (cipher) { case IEEE80211_CIPHER_WEP: return IEEE80211_C_WEP; case IEEE80211_CIPHER_AES_OCB: return IEEE80211_C_AES; case IEEE80211_CIPHER_AES_CCM: return IEEE80211_C_AES_CCM; case IEEE80211_CIPHER_CKIP: return IEEE80211_C_CKIP; case IEEE80211_CIPHER_TKIP: return IEEE80211_C_TKIP; } return 0; } #define IEEE80211_MODE_TURBO_STATIC_A IEEE80211_MODE_MAX static int ieee80211_convert_mode(const char *mode) { #define TOUPPER(c) ((((c) > 0x60) && ((c) < 0x7b)) ? ((c) - 0x20) : (c)) static const struct { char *name; int mode; } mappings[] = { /* NB: need to order longest strings first for overlaps */ { "11AST" , IEEE80211_MODE_TURBO_STATIC_A }, { "AUTO" , IEEE80211_MODE_AUTO }, { "11A" , IEEE80211_MODE_11A }, { "11B" , IEEE80211_MODE_11B }, { "11G" , IEEE80211_MODE_11G }, { "FH" , IEEE80211_MODE_FH }, { "0" , IEEE80211_MODE_AUTO }, { "1" , IEEE80211_MODE_11A }, { "2" , IEEE80211_MODE_11B }, { "3" , IEEE80211_MODE_11G }, { "4" , IEEE80211_MODE_FH }, { "5" , IEEE80211_MODE_TURBO_STATIC_A }, { NULL } }; int i, j; const char *cp; for (i = 0; mappings[i].name != NULL; i++) { cp = mappings[i].name; for (j = 0; j < strlen(mode) + 1; j++) { /* convert user-specified string to upper case */ if (TOUPPER(mode[j]) != cp[j]) break; if (cp[j] == '\0') return mappings[i].mode; } } return -1; #undef TOUPPER } static int ieee80211_ioctl_setmode(struct net_device *dev, struct iw_request_info *info, struct iw_point *wri, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ifreq ifr; char s[6]; /* big enough for ``11adt'' */ int retv, mode, ifr_mode; if (ic->ic_media.ifm_cur == NULL) return -EINVAL; /* XXX: Wrong error */ if (wri->length > sizeof(s)) /* silently truncate */ wri->length = sizeof(s); if (copy_from_user(s, wri->pointer, wri->length)) return -EFAULT; s[sizeof(s) - 1] = '\0'; /* ensure null termination */ mode = ieee80211_convert_mode(s); if (mode < 0) return -EINVAL; if (ieee80211_check_mode_consistency(ic, mode, vap->iv_des_chan)) { /* * error in AP mode. * overwrite channel selection in other modes. */ if (vap->iv_opmode == IEEE80211_M_HOSTAP) return -EINVAL; else vap->iv_des_chan = IEEE80211_CHAN_ANYC; } ifr_mode = mode; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media & ~IFM_MMASK; if (mode == IEEE80211_MODE_TURBO_STATIC_A) ifr_mode = IEEE80211_MODE_11A; ifr.ifr_media |= IFM_MAKEMODE(ifr_mode); retv = ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); if ((!retv || retv == -ENETRESET) && (mode != vap->iv_des_mode)) { if (preempt_scan(dev, 100, 100)) ieee80211_scan_flush(ic); /* NB: could optimize */ else return -ETIMEDOUT; vap->iv_des_mode = mode; if (IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); retv = 0; } #ifdef ATH_SUPERG_XR /* set the same params on the xr vap device if exists */ if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR)) vap->iv_xrvap->iv_des_mode = mode; #endif return -retv; } #undef IEEE80211_MODE_TURBO_STATIC_A #ifdef ATH_SUPERG_XR static void ieee80211_setupxr(struct ieee80211vap *vap) { struct net_device *dev = vap->iv_dev; struct ieee80211com *ic = vap->iv_ic; if (!(vap->iv_flags & IEEE80211_F_XR)) { if ((vap->iv_ath_cap & IEEE80211_ATHC_XR) && !vap->iv_xrvap) { struct ieee80211vap *xrvap = NULL; char name[IFNAMSIZ]; strcpy(name, dev->name); strcat(name, "-xr"); /* * Create a new XR vap. If the normal VAP is already up, * bring up the XR vap aswell. */ vap->iv_ath_cap &= ~IEEE80211_ATHC_TURBOP; /* turn off turbo */ ieee80211_scan_flush(ic); /* NB: could optimize */ if (!(xrvap = ic->ic_vap_create(ic, name, IEEE80211_M_HOSTAP, IEEE80211_VAP_XR | IEEE80211_CLONE_BSSID, dev))) return; /* We use iv_xrvap to link to the parent VAP as well */ xrvap->iv_xrvap = vap; xrvap->iv_ath_cap = vap->iv_ath_cap; xrvap->iv_fragthreshold = IEEE80211_XR_FRAG_THRESHOLD; xrvap->iv_des_mode = vap->iv_des_mode; copy_des_ssid(xrvap, vap); vap->iv_xrvap = xrvap; } else if (!(vap->iv_ath_cap & IEEE80211_ATHC_XR) && vap->iv_xrvap) { /* * Destroy the XR vap. If the XR VAP is up, bring * it down before destroying. */ if (vap->iv_xrvap) { ieee80211_stop(vap->iv_xrvap->iv_dev); ic->ic_vap_delete(vap->iv_xrvap); } vap->iv_xrvap = NULL; } } } #endif static int ieee80211_setathcap(struct ieee80211vap *vap, int cap, int setting) { struct ieee80211com *ic = vap->iv_ic; int ocap; if ((ic->ic_ath_cap & cap) == 0) return EINVAL; ocap = vap->iv_ath_cap; if (setting) vap->iv_ath_cap |= cap; else vap->iv_ath_cap &= ~cap; return (vap->iv_ath_cap != ocap ? ENETRESET : 0); } static int ieee80211_set_turbo(struct net_device *dev, int flag) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ifreq ifr; struct ieee80211vap *tmpvap = dev->priv; int nvap = 0; TAILQ_FOREACH(tmpvap, &ic->ic_vaps, iv_next) nvap++; if ((nvap > 1) && flag) return -EINVAL; ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media &~ IFM_MMASK; if (flag) ifr.ifr_media |= IFM_IEEE80211_TURBO; else ifr.ifr_media &= ~IFM_IEEE80211_TURBO; (void) ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); return 0; } static int ieee80211_ioctl_setparam(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_rsnparms *rsn = &vap->iv_bss->ni_rsn; unsigned int *i = (unsigned int *)extra; unsigned int param = i[0]; /* parameter id is 1st */ unsigned int value = i[1]; /* NB: most values are TYPE_INT */ int retv = 0; int j, caps, bmiss; const struct ieee80211_authenticator *auth; const struct ieee80211_aclator *acl; switch (param) { case IEEE80211_PARAM_AUTHMODE: switch (value) { case IEEE80211_AUTH_WPA: /* WPA */ case IEEE80211_AUTH_8021X: /* 802.1x */ case IEEE80211_AUTH_OPEN: /* open */ case IEEE80211_AUTH_SHARED: /* shared-key */ case IEEE80211_AUTH_AUTO: /* auto */ auth = ieee80211_authenticator_get(value); if (auth == NULL) return -EINVAL; /* XXX: Wrong error */ break; default: return -EINVAL; } switch (value) { case IEEE80211_AUTH_WPA: /* WPA w/ 802.1x */ value = IEEE80211_AUTH_8021X; break; case IEEE80211_AUTH_OPEN: /* open */ vap->iv_flags &= ~(IEEE80211_F_WPA); break; case IEEE80211_AUTH_SHARED: /* shared-key */ case IEEE80211_AUTH_AUTO: /* auto */ case IEEE80211_AUTH_8021X: /* 802.1x */ vap->iv_flags &= ~IEEE80211_F_WPA; break; } /* NB: authenticator attach/detach happens on state change */ vap->iv_bss->ni_authmode = value; /* XXX mixed/mode/usage? */ vap->iv_auth = auth; retv = ENETRESET; break; case IEEE80211_PARAM_PROTMODE: if (value > IEEE80211_PROT_RTSCTS) return -EINVAL; ic->ic_protmode = value; /* NB: if not operating in 11g this can wait */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan)) retv = ENETRESET; break; case IEEE80211_PARAM_RSSI_EWMA: ic->ic_rssi_ewma = value; break; case IEEE80211_PARAM_MCASTCIPHER: if ((vap->iv_caps & cipher2cap(value)) == 0 && !ieee80211_crypto_available(vap, value)) return -EINVAL; rsn->rsn_mcastcipher = value; if (vap->iv_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_MCASTKEYLEN: if (value > IEEE80211_KEYBUF_SIZE) return -EINVAL; /* XXX no way to verify driver capability */ rsn->rsn_mcastkeylen = value; if (vap->iv_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_UCASTCIPHERS: /* * NB: this logic intentionally ignores unknown and * unsupported ciphers so folks can specify 0xff or * similar and get all available ciphers. */ /* caps are really ciphers */ caps = 0; for (j = 1; j < 32; j++) /* NB: skip WEP */ if ((value & (1 << j)) && ((vap->iv_caps & cipher2cap(j)) || ieee80211_crypto_available(vap, j))) caps |= 1 << j; if (caps == 0) /* nothing available */ return -EINVAL; /* XXX verify ciphers ok for unicast use? */ /* XXX disallow if running as it'll have no effect */ rsn->rsn_ucastcipherset = caps; if (vap->iv_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_UCASTCIPHER: if (!(vap->iv_caps & cipher2cap(value)) && !ieee80211_crypto_available(vap, value)) return -EINVAL; rsn->rsn_ucastcipher = value; if (vap->iv_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_UCASTKEYLEN: if (value > IEEE80211_KEYBUF_SIZE) return -EINVAL; /* XXX no way to verify driver capability */ rsn->rsn_ucastkeylen = value; break; case IEEE80211_PARAM_KEYMGTALGS: /* XXX check */ rsn->rsn_keymgmtset = value; if (vap->iv_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_RSNCAPS: /* XXX check */ rsn->rsn_caps = value; if (vap->iv_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_WPA: if (value > 3) return -EINVAL; /* XXX verify ciphers available */ vap->iv_flags &= ~IEEE80211_F_WPA; switch (value) { case 1: vap->iv_flags |= IEEE80211_F_WPA1; break; case 2: vap->iv_flags |= IEEE80211_F_WPA2; break; case 3: vap->iv_flags |= IEEE80211_F_WPA1 | IEEE80211_F_WPA2; break; } retv = ENETRESET; /* XXX? */ break; case IEEE80211_PARAM_ROAMING: switch (value) { case IEEE80211_ROAMING_DEVICE: case IEEE80211_ROAMING_AUTO: case IEEE80211_ROAMING_MANUAL: ic->ic_roaming = value; break; default: return -EINVAL; } break; case IEEE80211_PARAM_PRIVACY: if (value) { /* XXX check for key state? */ vap->iv_flags |= IEEE80211_F_PRIVACY; } else vap->iv_flags &= ~IEEE80211_F_PRIVACY; break; case IEEE80211_PARAM_DROPUNENCRYPTED: if (value) vap->iv_flags |= IEEE80211_F_DROPUNENC; else vap->iv_flags &= ~IEEE80211_F_DROPUNENC; break; case IEEE80211_PARAM_DROPUNENC_EAPOL: if (value) IEEE80211_VAP_DROPUNENC_EAPOL_ENABLE(vap); else IEEE80211_VAP_DROPUNENC_EAPOL_DISABLE(vap); break; case IEEE80211_PARAM_COUNTERMEASURES: if (value) { if ((vap->iv_flags & IEEE80211_F_WPA) == 0) return -EINVAL; vap->iv_flags |= IEEE80211_F_COUNTERM; } else vap->iv_flags &= ~IEEE80211_F_COUNTERM; break; case IEEE80211_PARAM_DRIVER_CAPS: vap->iv_caps = value; /* NB: for testing */ break; case IEEE80211_PARAM_MACCMD: acl = vap->iv_acl; switch (value) { case IEEE80211_MACCMD_POLICY_OPEN: case IEEE80211_MACCMD_POLICY_ALLOW: case IEEE80211_MACCMD_POLICY_DENY: if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(vap)) return -EINVAL; vap->iv_acl = acl; } acl->iac_setpolicy(vap, value); break; case IEEE80211_MACCMD_FLUSH: if (acl != NULL) acl->iac_flush(vap); /* NB: silently ignore when not in use */ break; case IEEE80211_MACCMD_DETACH: if (acl != NULL) { vap->iv_acl = NULL; acl->iac_detach(vap); } break; } break; case IEEE80211_PARAM_WMM: if (ic->ic_caps & IEEE80211_C_WME){ retv = ENETRESET; /* Renegotiate for capabilities */ if (value) { /* All TKIP keys need resetting to use software MIC. * They aren't, so this is disabled. * XXX: Can never turn it back on. */ if (!(vap->iv_ic->ic_caps & IEEE80211_C_WME_TKIPMIC)) retv = EBUSY; else { vap->iv_flags |= IEEE80211_F_WME; vap->iv_ic->ic_flags |= IEEE80211_F_WME; } } else { vap->iv_flags &= ~IEEE80211_F_WME; { struct ieee80211vap *v = NULL; int all = 1; TAILQ_FOREACH(v, &vap->iv_ic->ic_vaps, iv_next) { if (v->iv_flags & IEEE80211_F_WME) { all = 0; break; } } if (all) vap->iv_ic->ic_flags &= ~IEEE80211_F_WME; } } } break; case IEEE80211_PARAM_HIDESSID: if (value) vap->iv_flags |= IEEE80211_F_HIDESSID; else vap->iv_flags &= ~IEEE80211_F_HIDESSID; retv = ENETRESET; break; case IEEE80211_PARAM_APBRIDGE: if (value == 0) vap->iv_flags |= IEEE80211_F_NOBRIDGE; else vap->iv_flags &= ~IEEE80211_F_NOBRIDGE; break; case IEEE80211_PARAM_INACT: vap->iv_inact_run = value / IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_AUTH: vap->iv_inact_auth = value / IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_INIT: vap->iv_inact_init = value / IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_ABOLT: caps = 0; /* * Map abolt settings to capability bits; * this also strips unknown/unwanted bits. */ if (value & IEEE80211_ABOLT_TURBO_PRIME) caps |= IEEE80211_ATHC_TURBOP; if (value & IEEE80211_ABOLT_COMPRESSION) caps |= IEEE80211_ATHC_COMP; if (value & IEEE80211_ABOLT_FAST_FRAME) caps |= IEEE80211_ATHC_FF; if (value & IEEE80211_ABOLT_XR) caps |= IEEE80211_ATHC_XR; if (value & IEEE80211_ABOLT_AR) caps |= IEEE80211_ATHC_AR; if (value & IEEE80211_ABOLT_BURST) caps |= IEEE80211_ATHC_BURST; /* verify requested capabilities are supported */ if ((caps & ic->ic_ath_cap) != caps) return -EINVAL; if (vap->iv_ath_cap != caps) { if ((vap->iv_ath_cap ^ caps) & IEEE80211_ATHC_TURBOP) { /* no turbo and XR at the same time */ if ((caps & IEEE80211_ATHC_TURBOP) && (caps & IEEE80211_ATHC_XR)) return -EINVAL; if (ieee80211_set_turbo(dev, (caps & IEEE80211_ATHC_TURBOP))) return -EINVAL; ieee80211_scan_flush(ic); } vap->iv_ath_cap = caps; #ifdef ATH_SUPERG_XR ieee80211_setupxr(vap); #endif retv = ENETRESET; } break; case IEEE80211_PARAM_DTIM_PERIOD: if ((vap->iv_opmode != IEEE80211_M_HOSTAP) && (vap->iv_opmode != IEEE80211_M_IBSS)) return -EOPNOTSUPP; if ((IEEE80211_DTIM_MIN <= value) && (value <= IEEE80211_DTIM_MAX)) { vap->iv_dtim_period = value; retv = ENETRESET; /* requires restart */ } else retv = EINVAL; break; case IEEE80211_PARAM_DRAINTXQ: /* fallthrough */ case IEEE80211_PARAM_STOP_QUEUE: /* fallthrough */ case IEEE80211_PARAM_ATHRESET: /* fallthrough */ case IEEE80211_PARAM_TXTIMEOUT: /* fallthrough */ case IEEE80211_PARAM_RESETTXBUFS: /* fallthrough */ case IEEE80211_PARAM_SCANBUFS: /* fallthrough */ case IEEE80211_PARAM_LEAKTXBUFS: /* fallthrough */ retv = ic->ic_debug_ath_iwpriv(ic, param, value); break; case IEEE80211_PARAM_BEACON_MISS_THRESH_MS: if ((vap->iv_opmode != IEEE80211_M_IBSS) && (vap->iv_opmode != IEEE80211_M_STA)) return -EOPNOTSUPP; /* Convert ms to TU to next highest integral # beacons */ bmiss = howmany(IEEE80211_MS_TO_TU(value), ic->ic_lintval); if (IEEE80211_BMISSTHRESH_VALID(bmiss)) { ic->ic_bmissthreshold = bmiss; retv = ENETRESET; /* requires restart */ } else retv = EINVAL; break; case IEEE80211_PARAM_BEACON_MISS_THRESH: if ((vap->iv_opmode != IEEE80211_M_IBSS) && (vap->iv_opmode != IEEE80211_M_STA)) return -EOPNOTSUPP; if (IEEE80211_BMISSTHRESH_VALID(value)) { ic->ic_bmissthreshold = value; retv = ENETRESET; /* requires restart */ } else retv = EINVAL; break; case IEEE80211_PARAM_BEACON_INTERVAL: if ((vap->iv_opmode != IEEE80211_M_HOSTAP) && (vap->iv_opmode != IEEE80211_M_IBSS)) return -EOPNOTSUPP; if (IEEE80211_BINTVAL_VALID(value)) { /* Convert ms to TU to next highest integral * # beacons. */ bmiss = howmany(ic->ic_bmissthreshold * ic->ic_lintval, value); /* Adjust beacon miss interval during a beacon interval * change so that the duration of missed beacons allowed * is greater than or equal to the old allowed duration * of missed beacons. */ ic->ic_bmissthreshold = bmiss; ic->ic_lintval = value; retv = ENETRESET; /* requires restart */ } else retv = EINVAL; break; case IEEE80211_PARAM_DOTH: if (value) ic->ic_flags |= IEEE80211_F_DOTH; else ic->ic_flags &= ~IEEE80211_F_DOTH; retv = ENETRESET; /* XXX: need something this drastic? */ break; case IEEE80211_PARAM_SHPREAMBLE: if (value) { ic->ic_caps |= IEEE80211_C_SHPREAMBLE; ic->ic_flags |= IEEE80211_F_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_USEBARKER; } else { ic->ic_caps &= ~IEEE80211_C_SHPREAMBLE; ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; ic->ic_flags |= IEEE80211_F_USEBARKER; } retv = ENETRESET; /* requires restart */ break; case IEEE80211_PARAM_PWRTARGET: ic->ic_curchanmaxpwr = value; break; case IEEE80211_PARAM_GENREASSOC: IEEE80211_SEND_MGMT(vap->iv_bss, IEEE80211_FC0_SUBTYPE_REASSOC_REQ, 0); break; case IEEE80211_PARAM_DOTH_ALGORITHM: ic->ic_sc_algorithm = value; break; case IEEE80211_PARAM_DOTH_MINCOM: ic->ic_sc_mincom = value; break; case IEEE80211_PARAM_DOTH_SLCG: ic->ic_sc_slcg = value; break; case IEEE80211_PARAM_DOTH_SLDG: ic->ic_sc_sldg = value; break; case IEEE80211_PARAM_DFS_TESTMODE: ieee80211_set_dfs_testmode(dev, info, w, extra); break; case IEEE80211_PARAM_TXCONT: ieee80211_set_txcont(dev, info, w, extra); break; case IEEE80211_PARAM_TXCONT_RATE: ieee80211_set_txcont_rate(dev, info, w, extra); break; case IEEE80211_PARAM_TXCONT_POWER: ieee80211_set_txcont_power(dev, info, w, extra); break; case IEEE80211_PARAM_DFS_CACTIME: ieee80211_set_dfs_cac_time(dev, info, w, extra); break; case IEEE80211_PARAM_DFS_EXCLPERIOD: ieee80211_set_dfs_excl_period(dev, info, w, extra); break; case IEEE80211_PARAM_COMPRESSION: retv = ieee80211_setathcap(vap, IEEE80211_ATHC_COMP, value); break; case IEEE80211_PARAM_FF: retv = ieee80211_setathcap(vap, IEEE80211_ATHC_FF, value); break; case IEEE80211_PARAM_TURBO: retv = ieee80211_setathcap(vap, IEEE80211_ATHC_TURBOP, value); if (retv == ENETRESET) { /* no turbo and XR at the same time */ if ((vap->iv_ath_cap & IEEE80211_ATHC_XR) && value) return -EINVAL; if (ieee80211_set_turbo(dev, value)) return -EINVAL; ieee80211_scan_flush(ic); } break; case IEEE80211_PARAM_XR: /* no turbo and XR at the same time */ if ((vap->iv_ath_cap & IEEE80211_ATHC_TURBOP) && value) return -EINVAL; retv = ieee80211_setathcap(vap, IEEE80211_ATHC_XR, value); #ifdef ATH_SUPERG_XR ieee80211_setupxr(vap); #endif break; case IEEE80211_PARAM_BURST: retv = ieee80211_setathcap(vap, IEEE80211_ATHC_BURST, value); break; case IEEE80211_PARAM_AR: retv = ieee80211_setathcap(vap, IEEE80211_ATHC_AR, value); break; case IEEE80211_PARAM_PUREG: if (value) vap->iv_flags |= IEEE80211_F_PUREG; else vap->iv_flags &= ~IEEE80211_F_PUREG; /* NB: reset only if we're operating on an 11g channel */ if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan)) retv = ENETRESET; break; case IEEE80211_PARAM_WDS: if ((vap->iv_opmode == IEEE80211_M_IBSS) || (vap->iv_opmode == IEEE80211_M_AHDEMO)) return -EOPNOTSUPP; if (value) vap->iv_flags_ext |= IEEE80211_FEXT_WDS; else vap->iv_flags_ext &= ~IEEE80211_FEXT_WDS; break; case IEEE80211_PARAM_BGSCAN: if (value) { if ((vap->iv_caps & IEEE80211_C_BGSCAN) == 0) return -EINVAL; vap->iv_flags |= IEEE80211_F_BGSCAN; } else { /* XXX racey? */ vap->iv_flags &= ~IEEE80211_F_BGSCAN; ieee80211_cancel_scan(vap); /* anything current */ } break; case IEEE80211_PARAM_BGSCAN_IDLE: if (value >= IEEE80211_BGSCAN_IDLE_MIN) vap->iv_bgscanidle = msecs_to_jiffies(value); else retv = EINVAL; break; case IEEE80211_PARAM_BGSCAN_INTERVAL: if (value >= IEEE80211_BGSCAN_INTVAL_MIN) vap->iv_bgscanintvl = value * HZ; else retv = EINVAL; break; case IEEE80211_PARAM_MCAST_RATE: /* units are in KILObits per second */ if (value >= 256 && value <= 54000) vap->iv_mcast_rate = value; else retv = EINVAL; break; case IEEE80211_PARAM_COVERAGE_CLASS: if (value <= IEEE80211_COVERAGE_CLASS_MAX) { ic->ic_coverageclass = value; if (IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); retv = 0; } else retv = EINVAL; break; case IEEE80211_PARAM_COUNTRY_IE: if (value) ic->ic_flags_ext |= IEEE80211_FEXT_COUNTRYIE; else ic->ic_flags_ext &= ~IEEE80211_FEXT_COUNTRYIE; retv = ENETRESET; break; case IEEE80211_PARAM_REGCLASS: if (value) ic->ic_flags_ext |= IEEE80211_FEXT_REGCLASS; else ic->ic_flags_ext &= ~IEEE80211_FEXT_REGCLASS; retv = ENETRESET; break; case IEEE80211_PARAM_SCANVALID: vap->iv_scanvalid = value * HZ; break; case IEEE80211_PARAM_ROAM_RSSI_11A: vap->iv_roam.rssi11a = value; break; case IEEE80211_PARAM_ROAM_RSSI_11B: vap->iv_roam.rssi11bOnly = value; break; case IEEE80211_PARAM_ROAM_RSSI_11G: vap->iv_roam.rssi11g = value; break; case IEEE80211_PARAM_ROAM_RATE_11A: vap->iv_roam.rate11a = value; break; case IEEE80211_PARAM_ROAM_RATE_11B: vap->iv_roam.rate11bOnly = value; break; case IEEE80211_PARAM_ROAM_RATE_11G: vap->iv_roam.rate11g = value; break; case IEEE80211_PARAM_UAPSDINFO: if (vap->iv_opmode == IEEE80211_M_HOSTAP) { if (ic->ic_caps & IEEE80211_C_UAPSD) { if (value) IEEE80211_VAP_UAPSD_ENABLE(vap); else IEEE80211_VAP_UAPSD_DISABLE(vap); retv = ENETRESET; } } else if (vap->iv_opmode == IEEE80211_M_STA) { vap->iv_uapsdinfo = value; IEEE80211_VAP_UAPSD_ENABLE(vap); retv = ENETRESET; } break; case IEEE80211_PARAM_SLEEP: /* XXX: Forced sleep for testing. Does not actually place the * HW in sleep mode yet. this only makes sense for STAs. */ if (value) { /* goto sleep */ IEEE80211_VAP_GOTOSLEEP(vap); } else { /* wakeup */ IEEE80211_VAP_WAKEUP(vap); } ieee80211_send_nulldata(ieee80211_ref_node(vap->iv_bss)); break; case IEEE80211_PARAM_QOSNULL: /* Force a QoS Null for testing. */ ieee80211_send_qosnulldata(vap->iv_bss, value); break; case IEEE80211_PARAM_PSPOLL: /* Force a PS-POLL for testing. */ ieee80211_send_pspoll(vap->iv_bss); break; case IEEE80211_PARAM_EOSPDROP: if (vap->iv_opmode == IEEE80211_M_HOSTAP) { if (value) IEEE80211_VAP_EOSPDROP_ENABLE(vap); else IEEE80211_VAP_EOSPDROP_DISABLE(vap); } break; case IEEE80211_PARAM_MARKDFS: if (value) ic->ic_flags_ext |= IEEE80211_FEXT_MARKDFS; else ic->ic_flags_ext &= ~IEEE80211_FEXT_MARKDFS; break; #ifdef ATH_REVERSE_ENGINEERING case IEEE80211_PARAM_DUMPREGS: ieee80211_dump_registers(dev, info, w, extra); break; #endif /* #ifdef ATH_REVERSE_ENGINEERING */ default: retv = EOPNOTSUPP; break; } #ifdef ATH_SUPERG_XR /* set the same params on the xr vap device if exists */ if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR)) { ieee80211_ioctl_setparam(vap->iv_xrvap->iv_dev, info, w, extra); /* XR vap does not support any superG features */ vap->iv_xrvap->iv_ath_cap &= IEEE80211_ATHC_XR; } /* * do not reset the xr vap , which is automatically * reset by the state machine now. */ if (!vap->iv_xrvap || (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR))) { if (retv == ENETRESET) retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0; } #else /* XXX should any of these cause a rescan? */ if (retv == ENETRESET) retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0; #endif return -retv; } #if 0 static int cap2cipher(int flag) { switch (flag) { case IEEE80211_C_WEP: return IEEE80211_CIPHER_WEP; case IEEE80211_C_AES: return IEEE80211_CIPHER_AES_OCB; case IEEE80211_C_AES_CCM: return IEEE80211_CIPHER_AES_CCM; case IEEE80211_C_CKIP: return IEEE80211_CIPHER_CKIP; case IEEE80211_C_TKIP: return IEEE80211_CIPHER_TKIP; } return -1; } #endif static int ieee80211_ioctl_getmode(struct net_device *dev, struct iw_request_info *info, struct iw_point *wri, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ifmediareq imr; ic->ic_media.ifm_status(ic->ic_dev, &imr); switch (IFM_MODE(imr.ifm_active)) { case IFM_IEEE80211_11A: strcpy(extra, "11a"); break; case IFM_IEEE80211_11B: strcpy(extra, "11b"); break; case IFM_IEEE80211_11G: strcpy(extra, "11g"); break; case IFM_IEEE80211_FH: strcpy(extra, "FH"); break; case IFM_AUTO: strcpy(extra, "auto"); break; default: return -EINVAL; } if (ic->ic_media.ifm_media & IFM_IEEE80211_TURBO) { if (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP) strcat(extra, "T"); else strcat(extra, "ST"); } wri->length = strlen(extra); return 0; } static int ieee80211_ioctl_getparam(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_rsnparms *rsn = &vap->iv_bss->ni_rsn; unsigned int *param = (unsigned int *)extra; switch (param[0]) { case IEEE80211_PARAM_AUTHMODE: if (vap->iv_flags & IEEE80211_F_WPA) param[0] = IEEE80211_AUTH_WPA; else param[0] = vap->iv_bss->ni_authmode; break; case IEEE80211_PARAM_PROTMODE: param[0] = ic->ic_protmode; break; case IEEE80211_PARAM_RSSI_EWMA: param[0] = ic->ic_rssi_ewma; break; case IEEE80211_PARAM_MCASTCIPHER: param[0] = rsn->rsn_mcastcipher; break; case IEEE80211_PARAM_MCASTKEYLEN: param[0] = rsn->rsn_mcastkeylen; break; case IEEE80211_PARAM_UCASTCIPHERS: param[0] = rsn->rsn_ucastcipherset; break; case IEEE80211_PARAM_UCASTCIPHER: param[0] = rsn->rsn_ucastcipher; break; case IEEE80211_PARAM_UCASTKEYLEN: param[0] = rsn->rsn_ucastkeylen; break; case IEEE80211_PARAM_KEYMGTALGS: param[0] = rsn->rsn_keymgmtset; break; case IEEE80211_PARAM_RSNCAPS: param[0] = rsn->rsn_caps; break; case IEEE80211_PARAM_WPA: switch (vap->iv_flags & IEEE80211_F_WPA) { case IEEE80211_F_WPA1: param[0] = 1; break; case IEEE80211_F_WPA2: param[0] = 2; break; case IEEE80211_F_WPA1 | IEEE80211_F_WPA2: param[0] = 3; break; default: param[0] = 0; break; } break; case IEEE80211_PARAM_ROAMING: param[0] = ic->ic_roaming; break; case IEEE80211_PARAM_PRIVACY: param[0] = (vap->iv_flags & IEEE80211_F_PRIVACY) != 0; break; case IEEE80211_PARAM_DROPUNENCRYPTED: param[0] = (vap->iv_flags & IEEE80211_F_DROPUNENC) != 0; break; case IEEE80211_PARAM_DROPUNENC_EAPOL: param[0] = IEEE80211_VAP_DROPUNENC_EAPOL(vap); break; case IEEE80211_PARAM_COUNTERMEASURES: param[0] = (vap->iv_flags & IEEE80211_F_COUNTERM) != 0; break; case IEEE80211_PARAM_DRIVER_CAPS: param[0] = vap->iv_caps; break; case IEEE80211_PARAM_WMM: param[0] = (vap->iv_flags & IEEE80211_F_WME) != 0; break; case IEEE80211_PARAM_HIDESSID: param[0] = (vap->iv_flags & IEEE80211_F_HIDESSID) != 0; break; case IEEE80211_PARAM_APBRIDGE: param[0] = (vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0; break; case IEEE80211_PARAM_INACT: param[0] = vap->iv_inact_run * IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_AUTH: param[0] = vap->iv_inact_auth * IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_INIT: param[0] = vap->iv_inact_init * IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_ABOLT: /* * Map capability bits to abolt settings. */ param[0] = 0; if (vap->iv_ath_cap & IEEE80211_ATHC_COMP) param[0] |= IEEE80211_ABOLT_COMPRESSION; if (vap->iv_ath_cap & IEEE80211_ATHC_FF) param[0] |= IEEE80211_ABOLT_FAST_FRAME; if (vap->iv_ath_cap & IEEE80211_ATHC_XR) param[0] |= IEEE80211_ABOLT_XR; if (vap->iv_ath_cap & IEEE80211_ATHC_BURST) param[0] |= IEEE80211_ABOLT_BURST; if (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP) param[0] |= IEEE80211_ABOLT_TURBO_PRIME; if (vap->iv_ath_cap & IEEE80211_ATHC_AR) param[0] |= IEEE80211_ABOLT_AR; break; case IEEE80211_PARAM_COMPRESSION: param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_COMP) != 0; break; case IEEE80211_PARAM_FF: param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_FF) != 0; break; case IEEE80211_PARAM_XR: param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_XR) != 0; break; case IEEE80211_PARAM_BURST: param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_BURST) != 0; break; case IEEE80211_PARAM_AR: param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_AR) != 0; break; case IEEE80211_PARAM_TURBO: param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP) != 0; break; case IEEE80211_PARAM_DTIM_PERIOD: param[0] = vap->iv_dtim_period; break; case IEEE80211_PARAM_BEACON_MISS_THRESH_MS: param[0] = IEEE80211_TU_TO_MS( vap->iv_ic->ic_bmissthreshold * vap->iv_ic->ic_lintval); break; case IEEE80211_PARAM_BEACON_MISS_THRESH: param[0] = vap->iv_ic->ic_bmissthreshold; break; case IEEE80211_PARAM_BEACON_INTERVAL: /* NB: get from ic_bss for station mode */ param[0] = vap->iv_bss->ni_intval; break; case IEEE80211_PARAM_DOTH: param[0] = (ic->ic_flags & IEEE80211_F_DOTH) != 0; break; case IEEE80211_PARAM_SHPREAMBLE: param[0] = (ic->ic_caps & IEEE80211_C_SHPREAMBLE) != 0; break; case IEEE80211_PARAM_PWRTARGET: param[0] = ic->ic_curchanmaxpwr; break; case IEEE80211_PARAM_DOTH_ALGORITHM: param[0] = ic->ic_sc_algorithm; break; case IEEE80211_PARAM_DOTH_MINCOM: param[0] = ic->ic_sc_mincom; break; case IEEE80211_PARAM_DOTH_SLCG: param[0] = ic->ic_sc_slcg; break; case IEEE80211_PARAM_DOTH_SLDG: param[0] = ic->ic_sc_sldg; break; case IEEE80211_PARAM_DFS_TESTMODE: ieee80211_get_dfs_testmode(dev, info, w, extra); break; case IEEE80211_PARAM_TXCONT: ieee80211_get_txcont(dev, info, w, extra); break; case IEEE80211_PARAM_TXCONT_RATE: ieee80211_get_txcont_rate(dev, info, w, extra); break; case IEEE80211_PARAM_TXCONT_POWER: ieee80211_get_txcont_power(dev, info, w, extra); break; case IEEE80211_PARAM_DFS_CACTIME: ieee80211_get_dfs_cac_time(dev, info, w, extra); break; case IEEE80211_PARAM_DFS_EXCLPERIOD: ieee80211_get_dfs_excl_period(dev, info, w, extra); break; case IEEE80211_PARAM_PUREG: param[0] = (vap->iv_flags & IEEE80211_F_PUREG) != 0; break; case IEEE80211_PARAM_WDS: param[0] = ((vap->iv_flags_ext & IEEE80211_FEXT_WDS) == IEEE80211_FEXT_WDS); break; case IEEE80211_PARAM_BGSCAN: param[0] = (vap->iv_flags & IEEE80211_F_BGSCAN) != 0; break; case IEEE80211_PARAM_BGSCAN_IDLE: param[0] = jiffies_to_msecs(vap->iv_bgscanidle); /* ms */ break; case IEEE80211_PARAM_BGSCAN_INTERVAL: param[0] = vap->iv_bgscanintvl / HZ; /* seconds */ break; case IEEE80211_PARAM_MCAST_RATE: param[0] = vap->iv_mcast_rate; /* seconds */ break; case IEEE80211_PARAM_COVERAGE_CLASS: param[0] = ic->ic_coverageclass; break; case IEEE80211_PARAM_COUNTRY_IE: param[0] = (ic->ic_flags_ext & IEEE80211_FEXT_COUNTRYIE) != 0; break; case IEEE80211_PARAM_REGCLASS: param[0] = (ic->ic_flags_ext & IEEE80211_FEXT_REGCLASS) != 0; break; case IEEE80211_PARAM_SCANVALID: param[0] = vap->iv_scanvalid / HZ; /* seconds */ break; case IEEE80211_PARAM_ROAM_RSSI_11A: param[0] = vap->iv_roam.rssi11a; break; case IEEE80211_PARAM_ROAM_RSSI_11B: param[0] = vap->iv_roam.rssi11bOnly; break; case IEEE80211_PARAM_ROAM_RSSI_11G: param[0] = vap->iv_roam.rssi11g; break; case IEEE80211_PARAM_ROAM_RATE_11A: param[0] = vap->iv_roam.rate11a; break; case IEEE80211_PARAM_ROAM_RATE_11B: param[0] = vap->iv_roam.rate11bOnly; break; case IEEE80211_PARAM_ROAM_RATE_11G: param[0] = vap->iv_roam.rate11g; break; case IEEE80211_PARAM_UAPSDINFO: if (vap->iv_opmode == IEEE80211_M_HOSTAP) { if (IEEE80211_VAP_UAPSD_ENABLED(vap)) param[0] = 1; else param[0] = 0; } else if (vap->iv_opmode == IEEE80211_M_STA) param[0] = vap->iv_uapsdinfo; break; case IEEE80211_PARAM_SLEEP: param[0] = vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT; break; case IEEE80211_PARAM_EOSPDROP: param[0] = IEEE80211_VAP_EOSPDROP_ENABLED(vap); break; case IEEE80211_PARAM_MARKDFS: if (ic->ic_flags_ext & IEEE80211_FEXT_MARKDFS) param[0] = 1; else param[0] = 0; break; default: return -EOPNOTSUPP; } return 0; } /* returns non-zero if ID is for a system IE (not for app use) */ static int is_sys_ie(u_int8_t ie_id) { /* XXX review this list */ switch (ie_id) { case IEEE80211_ELEMID_SSID: case IEEE80211_ELEMID_RATES: case IEEE80211_ELEMID_FHPARMS: case IEEE80211_ELEMID_DSPARMS: case IEEE80211_ELEMID_CFPARMS: case IEEE80211_ELEMID_TIM: case IEEE80211_ELEMID_IBSSPARMS: case IEEE80211_ELEMID_COUNTRY: case IEEE80211_ELEMID_REQINFO: case IEEE80211_ELEMID_CHALLENGE: case IEEE80211_ELEMID_PWRCNSTR: case IEEE80211_ELEMID_PWRCAP: case IEEE80211_ELEMID_TPCREQ: case IEEE80211_ELEMID_TPCREP: case IEEE80211_ELEMID_SUPPCHAN: case IEEE80211_ELEMID_CHANSWITCHANN: case IEEE80211_ELEMID_MEASREQ: case IEEE80211_ELEMID_MEASREP: case IEEE80211_ELEMID_QUIET: case IEEE80211_ELEMID_IBSSDFS: case IEEE80211_ELEMID_ERP: case IEEE80211_ELEMID_RSN: case IEEE80211_ELEMID_XRATES: case IEEE80211_ELEMID_TPC: case IEEE80211_ELEMID_CCKM: return 1; default: return 0; } } /* returns non-zero if the buffer appears to contain a valid IE list */ static int is_valid_ie_list(u_int32_t buf_len, void *buf, int exclude_sys_ies) { struct ieee80211_ie *ie = (struct ieee80211_ie *)buf; while (buf_len >= sizeof(*ie)) { int ie_elem_len = sizeof(*ie) + ie->len; if (buf_len < ie_elem_len) break; if (exclude_sys_ies && is_sys_ie(ie->id)) break; buf_len -= ie_elem_len; ie = (struct ieee80211_ie *)(ie->info + ie->len); } return (buf_len == 0) ? 1 : 0; } static int ieee80211_ioctl_setoptie(struct net_device *dev, struct iw_request_info *info, struct iw_point *wri, char *extra) { struct ieee80211vap *vap = dev->priv; void *ie; /* * NB: Doing this for ap operation could be useful (e.g. for * WPA and/or WME) except that it typically is worthless * without being able to intervene when processing * association response frames--so disallow it for now. */ if (vap->iv_opmode != IEEE80211_M_STA) return -EOPNOTSUPP; if (!is_valid_ie_list(wri->length, extra, 0)) return -EINVAL; /* NB: wri->length is validated by the wireless extensions code */ MALLOC(ie, void *, wri->length, M_DEVBUF, M_WAITOK); if (ie == NULL) return -ENOMEM; memcpy(ie, extra, wri->length); if (vap->iv_opt_ie != NULL) FREE(vap->iv_opt_ie, M_DEVBUF); vap->iv_opt_ie = ie; vap->iv_opt_ie_len = wri->length; #ifdef ATH_SUPERG_XR /* set the same params on the xr vap device if exists */ if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR)) ieee80211_ioctl_setoptie(vap->iv_xrvap->iv_dev, info, wri, extra); #endif return 0; } static int ieee80211_ioctl_getoptie(struct net_device *dev, struct iw_request_info *info, struct iw_point *wri, char *extra) { struct ieee80211vap *vap = dev->priv; if (vap->iv_opt_ie == NULL) { wri->length = 0; return 0; } wri->length = vap->iv_opt_ie_len; memcpy(extra, vap->iv_opt_ie, vap->iv_opt_ie_len); return 0; } /* the following functions are used by the set/get appiebuf functions */ static int add_app_ie(unsigned int frame_type_index, struct ieee80211vap *vap, struct ieee80211req_getset_appiebuf *iebuf) { struct ieee80211_ie *ie; if (!is_valid_ie_list(iebuf->app_buflen, iebuf->app_buf, 1)) return -EINVAL; /* NB: data.length is validated by the wireless extensions code */ MALLOC(ie, struct ieee80211_ie *, iebuf->app_buflen, M_DEVBUF, M_WAITOK); if (ie == NULL) return -ENOMEM; memcpy(ie, iebuf->app_buf, iebuf->app_buflen); if (vap->app_ie[frame_type_index].ie != NULL) FREE(vap->app_ie[frame_type_index].ie, M_DEVBUF); vap->app_ie[frame_type_index].ie = ie; vap->app_ie[frame_type_index].length = iebuf->app_buflen; return 0; } static int remove_app_ie(unsigned int frame_type_index, struct ieee80211vap *vap) { struct ieee80211_app_ie *app_ie = &vap->app_ie[frame_type_index]; if (app_ie->ie != NULL) { FREE(app_ie->ie, M_DEVBUF); app_ie->ie = NULL; app_ie->length = 0; } return 0; } static int get_app_ie(unsigned int frame_type_index, struct ieee80211vap *vap, struct ieee80211req_getset_appiebuf *iebuf) { struct ieee80211_app_ie *app_ie = &vap->app_ie[frame_type_index]; if (iebuf->app_buflen < app_ie->length) return -E2BIG; iebuf->app_buflen = app_ie->length; memcpy(iebuf->app_buf, app_ie->ie, app_ie->length); return 0; } static int ieee80211_ioctl_setappiebuf(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211req_getset_appiebuf *iebuf = (struct ieee80211req_getset_appiebuf *)extra; enum ieee80211_opmode chk_opmode; int iebuf_len; int rc = 0; iebuf_len = data->length - sizeof(struct ieee80211req_getset_appiebuf); if ((iebuf_len < 0) || (iebuf_len != iebuf->app_buflen) || (iebuf->app_buflen > IEEE80211_APPIE_MAX)) return -EINVAL; switch (iebuf->app_frmtype) { case IEEE80211_APPIE_FRAME_BEACON: case IEEE80211_APPIE_FRAME_PROBE_RESP: case IEEE80211_APPIE_FRAME_ASSOC_RESP: chk_opmode = IEEE80211_M_HOSTAP; break; case IEEE80211_APPIE_FRAME_PROBE_REQ: case IEEE80211_APPIE_FRAME_ASSOC_REQ: chk_opmode = IEEE80211_M_STA; break; default: return -EINVAL; } if (vap->iv_opmode != chk_opmode) return -EOPNOTSUPP; if (iebuf->app_buflen) rc = add_app_ie(iebuf->app_frmtype, vap, iebuf); else rc = remove_app_ie(iebuf->app_frmtype, vap); if ((iebuf->app_frmtype == IEEE80211_APPIE_FRAME_BEACON) && (rc == 0)) vap->iv_flags_ext |= IEEE80211_FEXT_APPIE_UPDATE; return rc; } static int ieee80211_ioctl_getappiebuf(struct net_device *dev, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211req_getset_appiebuf *iebuf = (struct ieee80211req_getset_appiebuf *)extra; int max_iebuf_len; int rc = 0; max_iebuf_len = data->length - sizeof(struct ieee80211req_getset_appiebuf); if (max_iebuf_len < 0) return -EINVAL; if (copy_from_user(iebuf, data->pointer, sizeof(struct ieee80211req_getset_appiebuf))) return -EFAULT; if (iebuf->app_buflen > max_iebuf_len) iebuf->app_buflen = max_iebuf_len; switch (iebuf->app_frmtype) { case IEEE80211_APPIE_FRAME_BEACON: case IEEE80211_APPIE_FRAME_PROBE_RESP: case IEEE80211_APPIE_FRAME_ASSOC_RESP: if (vap->iv_opmode == IEEE80211_M_STA) return -EOPNOTSUPP; break; case IEEE80211_APPIE_FRAME_PROBE_REQ: case IEEE80211_APPIE_FRAME_ASSOC_REQ: if (vap->iv_opmode != IEEE80211_M_STA) return -EOPNOTSUPP; break; default: return -EINVAL; } rc = get_app_ie(iebuf->app_frmtype, vap, iebuf); data->length = sizeof(struct ieee80211req_getset_appiebuf) + iebuf->app_buflen; return rc; } static int ieee80211_ioctl_setfilter(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211req_set_filter *app_filter = (struct ieee80211req_set_filter *)extra; if ((extra == NULL) || (app_filter->app_filterype & ~IEEE80211_FILTER_TYPE_ALL)) return -EINVAL; vap->app_filter = app_filter->app_filterype; return 0; } static int ieee80211_ioctl_setkey(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211req_key *ik = (struct ieee80211req_key *)extra; struct ieee80211_node *ni; struct ieee80211_key *wk; ieee80211_keyix_t kix; int error, flags, i; /* Check cipher support, load crypto modules if needed */ if (!ieee80211_crypto_available(vap, ik->ik_type)) return -EOPNOTSUPP; /* NB: this also checks ik->ik_keylen > sizeof(wk->wk_key) */ if (ik->ik_keylen > sizeof(ik->ik_keydata)) return -E2BIG; kix = ik->ik_keyix; if (kix == IEEE80211_KEYIX_NONE) { /* XXX unicast keys currently must be tx/rx */ if (ik->ik_flags != (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV)) return -EINVAL; if (vap->iv_opmode == IEEE80211_M_STA) { ni = ieee80211_ref_node(vap->iv_bss); /* XXX: Untested use of iv_bssid. */ if (!IEEE80211_ADDR_EQ(ik->ik_macaddr, vap->iv_bssid)) { ieee80211_unref_node(&ni); return -EADDRNOTAVAIL; } } else ni = ieee80211_find_node(&ic->ic_sta, ik->ik_macaddr); if (ni == NULL) return -ENOENT; wk = &ni->ni_ucastkey; } else if (((uint8_t)IEEE80211_KEYIX_NONE <= kix) && (kix < IEEE80211_KEYIX_NONE)) { /* These values must never be used as they are ambiguous as * some of the API uses 8-bit integers for keyix. */ return -EINVAL; } else { if (kix >= IEEE80211_WEP_NKID) return -EINVAL; wk = &vap->iv_nw_keys[kix]; ni = NULL; /* XXX auto-add group key flag until applications are updated */ if ((ik->ik_flags & IEEE80211_KEY_XMIT) == 0) /* XXX */ ik->ik_flags |= IEEE80211_KEY_GROUP; /* XXX */ } error = 0; flags = ik->ik_flags & IEEE80211_KEY_COMMON; ieee80211_key_update_begin(vap); if (ieee80211_crypto_newkey(vap, ik->ik_type, flags, wk)) { wk->wk_keylen = ik->ik_keylen; /* NB: MIC presence is implied by cipher type */ if (wk->wk_keylen > IEEE80211_KEYBUF_SIZE) wk->wk_keylen = IEEE80211_KEYBUF_SIZE; for (i = 0; i < IEEE80211_TID_SIZE; i++) wk->wk_keyrsc[i] = ik->ik_keyrsc; wk->wk_keytsc = 0; /* new key, reset */ memset(wk->wk_key, 0, sizeof(wk->wk_key)); memcpy(wk->wk_key, ik->ik_keydata, ik->ik_keylen); if (!ieee80211_crypto_setkey(vap, wk, (ni != NULL) ? ni->ni_macaddr : ik->ik_macaddr, ni)) error = -EIO; else if ((ik->ik_flags & IEEE80211_KEY_DEFAULT)) vap->iv_def_txkey = kix; } else error = -ENXIO; ieee80211_key_update_end(vap); if (ni != NULL) ieee80211_unref_node(&ni); #ifdef ATH_SUPERG_XR /* set the same params on the xr vap device if exists */ if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR)) ieee80211_ioctl_setkey(vap->iv_xrvap->iv_dev, info, w, extra); #endif return error; } static int ieee80211_ioctl_getkey(struct net_device *dev, struct iwreq *iwr) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; struct ieee80211req_key ik; struct ieee80211_key *wk; const struct ieee80211_cipher *cip; ieee80211_keyix_t kix; if (iwr->u.data.length != sizeof(ik)) return -EINVAL; if (copy_from_user(&ik, iwr->u.data.pointer, sizeof(ik))) return -EFAULT; kix = ik.ik_keyix; if (kix == IEEE80211_KEYIX_NONE) { ni = ieee80211_find_node(&ic->ic_sta, ik.ik_macaddr); if (ni == NULL) return -ENOENT; wk = &ni->ni_ucastkey; } else if (((uint8_t)IEEE80211_KEYIX_NONE <= kix) && (kix < IEEE80211_KEYIX_NONE)) { /* These values must never be used as they are ambiguous as * some of the API uses 8-bit integers for keyix. */ return -EINVAL; } else { if (kix >= IEEE80211_WEP_NKID) return -EINVAL; wk = &vap->iv_nw_keys[kix]; IEEE80211_ADDR_COPY(&ik.ik_macaddr, vap->iv_bss->ni_macaddr); ni = NULL; } cip = wk->wk_cipher; ik.ik_type = cip->ic_cipher; ik.ik_keylen = wk->wk_keylen; ik.ik_flags = wk->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV); if (wk->wk_keyix == vap->iv_def_txkey) ik.ik_flags |= IEEE80211_KEY_DEFAULT; if (capable(CAP_NET_ADMIN)) { /* NB: only root can read key data */ ik.ik_keyrsc = wk->wk_keyrsc[0]; ik.ik_keytsc = wk->wk_keytsc; memcpy(ik.ik_keydata, wk->wk_key, wk->wk_keylen); if (cip->ic_cipher == IEEE80211_CIPHER_TKIP) { memcpy(ik.ik_keydata+wk->wk_keylen, wk->wk_key + IEEE80211_KEYBUF_SIZE, IEEE80211_MICBUF_SIZE); ik.ik_keylen += IEEE80211_MICBUF_SIZE; } } else { ik.ik_keyrsc = 0; ik.ik_keytsc = 0; memset(ik.ik_keydata, 0, sizeof(ik.ik_keydata)); } if (ni != NULL) ieee80211_unref_node(&ni); return (copy_to_user(iwr->u.data.pointer, &ik, sizeof(ik)) ? -EFAULT : 0); } static int ieee80211_ioctl_delkey(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211req_del_key *dk = (struct ieee80211req_del_key *)extra; ieee80211_keyix_t kix; kix = dk->idk_keyix; if (dk->idk_keyix == (u_int8_t)IEEE80211_KEYIX_NONE) kix = IEEE80211_KEYIX_NONE; if (kix == IEEE80211_KEYIX_NONE) { struct ieee80211_node *ni; ni = ieee80211_find_node(&ic->ic_sta, dk->idk_macaddr); if (ni == NULL) return -ENOENT; /* XXX error return */ ieee80211_crypto_delkey(vap, &ni->ni_ucastkey, ni); ieee80211_unref_node(&ni); } else { if (kix >= IEEE80211_WEP_NKID) return -EINVAL; /* XXX error return */ ieee80211_crypto_delkey(vap, &vap->iv_nw_keys[kix], NULL); } return 0; } static void domlme(void *arg, struct ieee80211_node *ni) { struct ieee80211req_mlme *mlme = arg; if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, mlme->im_op == IEEE80211_MLME_DEAUTH ? IEEE80211_FC0_SUBTYPE_DEAUTH : IEEE80211_FC0_SUBTYPE_DISASSOC, mlme->im_reason); } ieee80211_node_leave(ni); } struct scanlookup { /* XXX: right place for declaration? */ const u_int8_t *mac; int esslen; const u_int8_t *essid; const struct ieee80211_scan_entry *se; }; /* * Match mac address and any ssid. */ static int mlmelookup(void *arg, const struct ieee80211_scan_entry *se) { struct scanlookup *look = arg; if (!IEEE80211_ADDR_EQ(look->mac, se->se_macaddr)) return 0; if (look->esslen != 0) { if (se->se_ssid[1] != look->esslen) return 0; if (memcmp(look->essid, se->se_ssid + 2, look->esslen)) return 0; } look->se = se; return 0; } static int ieee80211_ioctl_setmlme(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211req_mlme *mlme = (struct ieee80211req_mlme *)extra; struct ieee80211_node *ni; if (!IS_UP(dev)) { switch (mlme->im_op) { case IEEE80211_MLME_DISASSOC: case IEEE80211_MLME_DEAUTH: case IEEE80211_MLME_UNAUTHORIZE: return 0; default: return -ENETDOWN; } } switch (mlme->im_op) { case IEEE80211_MLME_ASSOC: if (vap->iv_opmode == IEEE80211_M_STA) { struct scanlookup lookup; lookup.se = NULL; lookup.mac = mlme->im_macaddr; /* XXX use revised api w/ explicit ssid */ lookup.esslen = vap->iv_des_ssid[0].len; lookup.essid = vap->iv_des_ssid[0].ssid; ieee80211_scan_iterate(ic, mlmelookup, &lookup); if (lookup.se != NULL) { vap->iv_nsdone = 0; vap->iv_nsparams.result = 0; if (ieee80211_sta_join(vap, lookup.se)) while (!vap->iv_nsdone) IEEE80211_RESCHEDULE(); if (!vap->iv_nsparams.result) return 0; return -EINVAL; } else return -ENOENT; } else return -EOPNOTSUPP; case IEEE80211_MLME_DISASSOC: case IEEE80211_MLME_DEAUTH: switch (vap->iv_opmode) { case IEEE80211_M_STA: /* XXX not quite right */ ieee80211_new_state(vap, IEEE80211_S_INIT, mlme->im_reason); break; case IEEE80211_M_HOSTAP: /* NB: the broadcast address means do 'em all */ if (!IEEE80211_ADDR_EQ(mlme->im_macaddr, vap->iv_dev->broadcast)) { ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr); if (ni != NULL) { if (dev == ni->ni_vap->iv_dev) domlme(mlme, ni); ieee80211_unref_node(&ni); } else return -ENOENT; } else ieee80211_iterate_dev_nodes(dev, &ic->ic_sta, domlme, mlme); break; default: return -EOPNOTSUPP; } break; case IEEE80211_MLME_AUTHORIZE: case IEEE80211_MLME_UNAUTHORIZE: if (vap->iv_opmode != IEEE80211_M_HOSTAP) return -EOPNOTSUPP; ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr); if (ni != NULL) { if (mlme->im_op == IEEE80211_MLME_AUTHORIZE) ieee80211_node_authorize(ni); else ieee80211_node_unauthorize(ni); ieee80211_unref_node(&ni); } else return -ENOENT; break; case IEEE80211_MLME_CLEAR_STATS: if (vap->iv_opmode != IEEE80211_M_HOSTAP) return -EOPNOTSUPP; ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr); if (ni != NULL) { /* clear statistics */ memset(&ni->ni_stats, 0, sizeof(struct ieee80211_nodestats)); ieee80211_unref_node(&ni); } else return -ENOENT; break; default: return -EOPNOTSUPP; } return 0; } static int ieee80211_ioctl_wdsmac(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct sockaddr *sa = (struct sockaddr *)extra; if (!IEEE80211_ADDR_NULL(vap->wds_mac)) { printk("%s: Failed to add WDS MAC: " MAC_FMT "\n", dev->name, MAC_ADDR(sa->sa_data)); printk("%s: Device already has WDS mac address attached," " remove first\n", dev->name); return -1; } memcpy(vap->wds_mac, sa->sa_data, IEEE80211_ADDR_LEN); printk("%s: Added WDS MAC: " MAC_FMT "\n", dev->name, MAC_ADDR(vap->wds_mac)); if (IS_UP(vap->iv_dev)) { /* Force us back to scan state to force us to go back through RUN * state and create/pin the WDS peer node into memory. */ return ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); } return 0; } static int ieee80211_ioctl_wdsdelmac(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct sockaddr *sa = (struct sockaddr *)extra; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *wds_ni; /* WDS Mac address filed already? */ if (IEEE80211_ADDR_NULL(vap->wds_mac)) return 0; /* Compare suplied MAC address with WDS MAC of this interface * remove when mac address is known */ if (memcmp(vap->wds_mac, sa->sa_data, IEEE80211_ADDR_LEN) == 0) { if (IS_UP(vap->iv_dev)) { wds_ni = ieee80211_find_txnode(vap, vap->wds_mac); if (wds_ni != NULL) { /* Release reference created by find node */ ieee80211_unref_node(&wds_ni); /* Release reference created by transition to RUN state, * [pinning peer node into the table] */ ieee80211_unref_node(&wds_ni); } } memset(vap->wds_mac, 0x00, IEEE80211_ADDR_LEN); if (IS_UP(vap->iv_dev)) { /* This leaves a dead WDS node, until started again */ return ic->ic_reset(ic->ic_dev); } return 0; } printk("%s: WDS MAC address " MAC_FMT " is not known by this interface\n", dev->name, MAC_ADDR(sa->sa_data)); return -1; } /* * kick associated station with the given MAC address. */ static int ieee80211_ioctl_kickmac(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct sockaddr *sa = (struct sockaddr *)extra; struct ieee80211req_mlme mlme; if (sa->sa_family != ARPHRD_ETHER) return -EINVAL; /* Setup a MLME request for disassociation of the given MAC */ mlme.im_op = IEEE80211_MLME_DISASSOC; mlme.im_reason = IEEE80211_REASON_UNSPECIFIED; IEEE80211_ADDR_COPY(&(mlme.im_macaddr), sa->sa_data); /* Send the MLME request and return the result. */ return ieee80211_ioctl_setmlme(dev, info, w, (char *)&mlme); } static int ieee80211_ioctl_addmac(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct sockaddr *sa = (struct sockaddr *)extra; const struct ieee80211_aclator *acl = vap->iv_acl; if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(vap)) return -EINVAL; vap->iv_acl = acl; } acl->iac_add(vap, sa->sa_data); return 0; } static int ieee80211_ioctl_delmac(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct sockaddr *sa = (struct sockaddr *)extra; const struct ieee80211_aclator *acl = vap->iv_acl; if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(vap)) return -EINVAL; vap->iv_acl = acl; } acl->iac_remove(vap, sa->sa_data); return 0; } static int ieee80211_ioctl_setchanlist(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211req_chanlist *list = (struct ieee80211req_chanlist *)extra; u_char chanlist[IEEE80211_CHAN_BYTES]; int i, j, nchan; memset(chanlist, 0, sizeof(chanlist)); /* * Since channel 0 is not available for DS, channel 1 * is assigned to LSB on WaveLAN. */ if (ic->ic_phytype == IEEE80211_T_DS) i = 1; else i = 0; nchan = 0; for (j = 0; i <= IEEE80211_CHAN_MAX; i++, j++) { /* * NB: silently discard unavailable channels so users * can specify 1-255 to get all available channels. */ if (isset(list->ic_channels, j) && isset(ic->ic_chan_avail, i)) { setbit(chanlist, i); nchan++; } } if (nchan == 0) /* no valid channels, disallow */ return -EINVAL; if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && /* XXX */ isclr(chanlist, ic->ic_bsschan->ic_ieee)) ic->ic_bsschan = IEEE80211_CHAN_ANYC; /* invalidate */ memcpy(ic->ic_chan_active, chanlist, sizeof(ic->ic_chan_active)); /* update Supported Channels information element */ ieee80211_build_sc_ie(ic); if (IS_UP_AUTO(vap)) ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); return 0; } static int ieee80211_ioctl_getchanlist(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; memcpy(extra, ic->ic_chan_active, sizeof(ic->ic_chan_active)); return 0; } static int ieee80211_ioctl_getchaninfo(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211req_chaninfo chans; u_int8_t reported[IEEE80211_CHAN_BYTES]; /* XXX stack usage? */ int i; memset(&chans, 0, sizeof(chans)); memset(&reported, 0, sizeof(reported)); for (i = 0; i < ic->ic_nchans; i++) { const struct ieee80211_channel *c = &ic->ic_channels[i]; const struct ieee80211_channel *c1 = c; if (isclr(reported, c->ic_ieee)) { setbit(reported, c->ic_ieee); /* pick turbo channel over non-turbo channel, and * 11g channel over 11b channel */ if (IEEE80211_IS_CHAN_A(c)) c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_TURBO_A); if (IEEE80211_IS_CHAN_ANYG(c)) c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_TURBO_G); else if (IEEE80211_IS_CHAN_B(c)) { c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_TURBO_G); if (!c1) c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_11G); } if (c1) c = c1; /* Copy the entire structure, whereas it used to just copy a few fields */ memcpy(&chans.ic_chans[chans.ic_nchans], c, sizeof(struct ieee80211_channel)); if (++chans.ic_nchans >= IEEE80211_CHAN_MAX) break; } } memcpy(extra, &chans, sizeof(struct ieee80211req_chaninfo)); return 0; } static int ieee80211_ioctl_setwmmparams(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; unsigned int *param = (unsigned int *)extra; unsigned int ac = (param[1] < WME_NUM_AC) ? param[1] : WME_AC_BE; unsigned int bss = param[2]; struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; switch (param[0]) { case IEEE80211_WMMPARAMS_CWMIN: if (param[3] > 15) return -EINVAL; if (bss) { wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_logcwmin = param[3]; if ((wme->wme_flags & WME_F_AGGRMODE) == 0) wme->wme_bssChanParams.cap_wmeParams[ac].wmep_logcwmin = param[3]; } else { wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_logcwmin = param[3]; wme->wme_chanParams.cap_wmeParams[ac].wmep_logcwmin = param[3]; } ieee80211_wme_updateparams(vap); break; case IEEE80211_WMMPARAMS_CWMAX: if (param[3] > 15) return -EINVAL; if (bss) { wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_logcwmax = param[3]; if ((wme->wme_flags & WME_F_AGGRMODE) == 0) wme->wme_bssChanParams.cap_wmeParams[ac].wmep_logcwmax = param[3]; } else { wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_logcwmax = param[3]; wme->wme_chanParams.cap_wmeParams[ac].wmep_logcwmax = param[3]; } ieee80211_wme_updateparams(vap); break; case IEEE80211_WMMPARAMS_AIFS: if (param[3] > 15) return -EINVAL; if (bss) { wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_aifsn = param[3]; if ((wme->wme_flags & WME_F_AGGRMODE) == 0) wme->wme_bssChanParams.cap_wmeParams[ac].wmep_aifsn = param[3]; } else { wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_aifsn = param[3]; wme->wme_chanParams.cap_wmeParams[ac].wmep_aifsn = param[3]; } ieee80211_wme_updateparams(vap); break; case IEEE80211_WMMPARAMS_TXOPLIMIT: if (param[3] > 8192) return -EINVAL; if (bss) { wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_txopLimit = IEEE80211_US_TO_TXOP(param[3]); if ((wme->wme_flags & WME_F_AGGRMODE) == 0) wme->wme_bssChanParams.cap_wmeParams[ac].wmep_txopLimit = IEEE80211_US_TO_TXOP(param[3]); } else { wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_txopLimit = IEEE80211_US_TO_TXOP(param[3]); wme->wme_chanParams.cap_wmeParams[ac].wmep_txopLimit = IEEE80211_US_TO_TXOP(param[3]); } ieee80211_wme_updateparams(vap); break; case IEEE80211_WMMPARAMS_ACM: if (!bss || param[3] > 1) return -EINVAL; /* ACM bit applies to BSS case only */ wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm = param[3]; if ((wme->wme_flags & WME_F_AGGRMODE) == 0) wme->wme_bssChanParams.cap_wmeParams[ac].wmep_acm = param[3]; break; case IEEE80211_WMMPARAMS_NOACKPOLICY: if (bss || param[3] > 1) return -EINVAL; /* ack policy applies to non-BSS case only */ wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy = param[3]; wme->wme_chanParams.cap_wmeParams[ac].wmep_noackPolicy = param[3]; break; default: break; } return 0; } static int ieee80211_ioctl_getwmmparams(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; unsigned int *param = (unsigned int *)extra; unsigned int ac = (param[1] < WME_NUM_AC) ? param[1] : WME_AC_BE; struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; struct chanAccParams *chanParams = (param[2] == 0) ? &(wme->wme_chanParams) : &(wme->wme_bssChanParams); switch (param[0]) { case IEEE80211_WMMPARAMS_CWMIN: param[0] = chanParams->cap_wmeParams[ac].wmep_logcwmin; break; case IEEE80211_WMMPARAMS_CWMAX: param[0] = chanParams->cap_wmeParams[ac].wmep_logcwmax; break; case IEEE80211_WMMPARAMS_AIFS: param[0] = chanParams->cap_wmeParams[ac].wmep_aifsn; break; case IEEE80211_WMMPARAMS_TXOPLIMIT: param[0] = IEEE80211_TXOP_TO_US(chanParams->cap_wmeParams[ac].wmep_txopLimit); break; case IEEE80211_WMMPARAMS_ACM: param[0] = wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm; break; case IEEE80211_WMMPARAMS_NOACKPOLICY: param[0] = wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy; break; default: break; } return 0; } static int ieee80211_ioctl_getwpaie(struct net_device *dev, struct iwreq *iwr) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; struct ieee80211req_wpaie wpaie; if (iwr->u.data.length != sizeof(wpaie)) return -EINVAL; if (copy_from_user(&wpaie, iwr->u.data.pointer, IEEE80211_ADDR_LEN)) return -EFAULT; ni = ieee80211_find_node(&ic->ic_sta, wpaie.wpa_macaddr); if (ni != NULL) { memset(wpaie.wpa_ie, 0, sizeof(wpaie.wpa_ie)); if (ni->ni_wpa_ie != NULL) { int ielen = ni->ni_wpa_ie[1] + 2; if (ielen > sizeof(wpaie.wpa_ie)) ielen = sizeof(wpaie.wpa_ie); memcpy(wpaie.wpa_ie, ni->ni_wpa_ie, ielen); } if (ni->ni_rsn_ie != NULL) { int ielen = ni->ni_rsn_ie[1] + 2; if (ielen > sizeof(wpaie.rsn_ie)) ielen = sizeof(wpaie.rsn_ie); memcpy(wpaie.rsn_ie, ni->ni_rsn_ie, ielen); } ieee80211_unref_node(&ni); return (copy_to_user(iwr->u.data.pointer, &wpaie, sizeof(wpaie)) ? -EFAULT : 0); } else return -ENOENT; } static int ieee80211_ioctl_getstastats(struct net_device *dev, struct iwreq *iwr) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_node *ni; u_int8_t macaddr[IEEE80211_ADDR_LEN]; const int off = __offsetof(struct ieee80211req_sta_stats, is_stats); int error; if (iwr->u.data.length < off) return -E2BIG; if (copy_from_user(macaddr, iwr->u.data.pointer, IEEE80211_ADDR_LEN)) return -EFAULT; ni = ieee80211_find_node(&ic->ic_sta, macaddr); if (ni != NULL) { if (iwr->u.data.length > sizeof(struct ieee80211req_sta_stats)) iwr->u.data.length = sizeof(struct ieee80211req_sta_stats); /* NB: copy out only the statistics */ error = copy_to_user(iwr->u.data.pointer + off, &ni->ni_stats, iwr->u.data.length - off); ieee80211_unref_node(&ni); return (error ? -EFAULT : 0); } else return -ENOENT; } struct scanreq { /* XXX: right place for declaration? */ struct ieee80211req_scan_result *sr; size_t space; }; static size_t scan_space(const struct ieee80211_scan_entry *se, int *ielen) { *ielen = 0; if (se->se_rsn_ie != NULL) *ielen += 2 + se->se_rsn_ie[1]; if (se->se_wpa_ie != NULL) *ielen += 2 + se->se_wpa_ie[1]; if (se->se_wme_ie != NULL) *ielen += 2 + se->se_wme_ie[1]; if (se->se_ath_ie != NULL) *ielen += 2 + se->se_ath_ie[1]; return roundup(sizeof(struct ieee80211req_scan_result) + se->se_ssid[1] + *ielen, sizeof(u_int32_t)); } static int get_scan_space(void *arg, const struct ieee80211_scan_entry *se) { struct scanreq *req = arg; int ielen; req->space += scan_space(se, &ielen); return 0; } static int get_scan_result(void *arg, const struct ieee80211_scan_entry *se) { struct scanreq *req = arg; struct ieee80211req_scan_result *sr; int ielen, len, nr, nxr; u_int8_t *cp; len = scan_space(se, &ielen); if (len > req->space) { printk("[madwifi] %s() : Not enough space.\n", __FUNCTION__); return 0; } sr = req->sr; memset(sr, 0, sizeof(*sr)); sr->isr_ssid_len = se->se_ssid[1]; /* XXX watch for overflow */ sr->isr_ie_len = ielen; sr->isr_len = len; sr->isr_freq = se->se_chan->ic_freq; sr->isr_flags = se->se_chan->ic_flags; sr->isr_rssi = se->se_rssi; sr->isr_intval = se->se_intval; sr->isr_capinfo = se->se_capinfo; sr->isr_erp = se->se_erp; IEEE80211_ADDR_COPY(sr->isr_bssid, se->se_bssid); /* XXX bounds check */ nr = se->se_rates[1]; memcpy(sr->isr_rates, se->se_rates + 2, nr); nxr = se->se_xrates[1]; memcpy(sr->isr_rates+nr, se->se_xrates + 2, nxr); sr->isr_nrates = nr + nxr; cp = (u_int8_t *)(sr + 1); memcpy(cp, se->se_ssid + 2, sr->isr_ssid_len); cp += sr->isr_ssid_len; if (se->se_rsn_ie != NULL) { memcpy(cp, se->se_rsn_ie, 2 + se->se_rsn_ie[1]); cp += 2 + se->se_rsn_ie[1]; } if (se->se_wpa_ie != NULL) { memcpy(cp, se->se_wpa_ie, 2 + se->se_wpa_ie[1]); cp += 2 + se->se_wpa_ie[1]; } if (se->se_wme_ie != NULL) { memcpy(cp, se->se_wme_ie, 2 + se->se_wme_ie[1]); cp += 2 + se->se_wme_ie[1]; } if (se->se_ath_ie != NULL) { memcpy(cp, se->se_ath_ie, 2 + se->se_ath_ie[1]); cp += 2 + se->se_ath_ie[1]; } req->space -= len; req->sr = (struct ieee80211req_scan_result *)(((u_int8_t *)sr) + len); return 0; } static int ieee80211_ioctl_getscanresults(struct net_device *dev, struct iwreq *iwr) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct scanreq req; int error; if (iwr->u.data.length < sizeof(struct scanreq)) return -E2BIG; error = 0; req.space = 0; ieee80211_scan_iterate(ic, get_scan_space, &req); if (req.space > iwr->u.data.length) req.space = iwr->u.data.length; if (req.space > 0) { size_t space; void *p; space = req.space; MALLOC(p, void *, space, M_TEMP, M_WAITOK); if (p == NULL) return -ENOMEM; req.sr = p; ieee80211_scan_iterate(ic, get_scan_result, &req); iwr->u.data.length = space - req.space; error = copy_to_user(iwr->u.data.pointer, p, iwr->u.data.length); FREE(p, M_TEMP); } else iwr->u.data.length = 0; return (error ? -EFAULT : 0); } struct stainforeq { /* XXX: right place for declaration? */ struct ieee80211vap *vap; struct ieee80211req_sta_info *si; size_t space; }; static size_t sta_space(const struct ieee80211_node *ni, size_t *ielen) { *ielen = 0; if (ni->ni_rsn_ie != NULL) *ielen += 2+ni->ni_rsn_ie[1]; if (ni->ni_wpa_ie != NULL) *ielen += 2+ni->ni_wpa_ie[1]; if (ni->ni_wme_ie != NULL) *ielen += 2+ni->ni_wme_ie[1]; if (ni->ni_ath_ie != NULL) *ielen += 2+ni->ni_ath_ie[1]; return roundup(sizeof(struct ieee80211req_sta_info) + *ielen, sizeof(u_int32_t)); } static void get_sta_space(void *arg, struct ieee80211_node *ni) { struct stainforeq *req = arg; struct ieee80211vap *vap = ni->ni_vap; size_t ielen; if (vap != req->vap && vap != req->vap->iv_xrvap) /* only entries for this vap */ return; if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_WDS) && ni->ni_associd == 0) /* only associated stations or a WDS peer */ return; req->space += sta_space(ni, &ielen); } static void get_sta_info(void *arg, struct ieee80211_node *ni) { struct stainforeq *req = arg; struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211req_sta_info *si; size_t ielen, len; u_int8_t *cp; if (vap != req->vap && vap != req->vap->iv_xrvap) /* only entries for this vap (or) xrvap */ return; if ((vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_WDS) && ni->ni_associd == 0) /* only associated stations or a WDS peer */ return; if (ni->ni_chan == IEEE80211_CHAN_ANYC) /* XXX bogus entry */ return; len = sta_space(ni, &ielen); if (len > req->space) return; si = req->si; si->isi_len = len; si->isi_ie_len = ielen; si->isi_freq = ni->ni_chan->ic_freq; si->isi_flags = ni->ni_chan->ic_flags; si->isi_state = ni->ni_flags; si->isi_authmode = ni->ni_authmode; si->isi_rssi = ic->ic_node_getrssi(ni); si->isi_capinfo = ni->ni_capinfo; si->isi_athflags = ni->ni_ath_flags; si->isi_erp = ni->ni_erp; IEEE80211_ADDR_COPY(si->isi_macaddr, ni->ni_macaddr); si->isi_nrates = ni->ni_rates.rs_nrates; if (si->isi_nrates > 15) si->isi_nrates = 15; memcpy(si->isi_rates, ni->ni_rates.rs_rates, si->isi_nrates); si->isi_txrate = ni->ni_txrate; si->isi_ie_len = ielen; si->isi_associd = ni->ni_associd; si->isi_txpower = ni->ni_txpower; si->isi_vlan = ni->ni_vlan; if (ni->ni_flags & IEEE80211_NODE_QOS) { memcpy(si->isi_txseqs, ni->ni_txseqs, sizeof(ni->ni_txseqs)); memcpy(si->isi_rxseqs, ni->ni_rxseqs, sizeof(ni->ni_rxseqs)); } else { si->isi_txseqs[0] = ni->ni_txseqs[0]; si->isi_rxseqs[0] = ni->ni_rxseqs[0]; } si->isi_uapsd = ni->ni_uapsd; if (vap == req->vap->iv_xrvap) si->isi_opmode = IEEE80211_STA_OPMODE_XR; else si->isi_opmode = IEEE80211_STA_OPMODE_NORMAL; /* NB: leave all cases in case we relax ni_associd == 0 check */ if (ieee80211_node_is_authorized(ni)) si->isi_inact = vap->iv_inact_run; else if (ni->ni_associd != 0) si->isi_inact = vap->iv_inact_auth; else si->isi_inact = vap->iv_inact_init; si->isi_inact = (si->isi_inact - ni->ni_inact) * IEEE80211_INACT_WAIT; cp = (u_int8_t *)(si+1); if (ni->ni_rsn_ie != NULL) { memcpy(cp, ni->ni_rsn_ie, 2 + ni->ni_rsn_ie[1]); cp += 2 + ni->ni_rsn_ie[1]; } if (ni->ni_wpa_ie != NULL) { memcpy(cp, ni->ni_wpa_ie, 2 + ni->ni_wpa_ie[1]); cp += 2 + ni->ni_wpa_ie[1]; } if (ni->ni_wme_ie != NULL) { memcpy(cp, ni->ni_wme_ie, 2 + ni->ni_wme_ie[1]); cp += 2 + ni->ni_wme_ie[1]; } if (ni->ni_ath_ie != NULL) { memcpy(cp, ni->ni_ath_ie, 2 + ni->ni_ath_ie[1]); cp += 2 + ni->ni_ath_ie[1]; } req->si = (struct ieee80211req_sta_info *)(((u_int8_t *)si) + len); req->space -= len; } static int ieee80211_ioctl_getstainfo(struct net_device *dev, struct iwreq *iwr) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; struct stainforeq req; int error; if (iwr->u.data.length < sizeof(struct stainforeq)) return -E2BIG; /* estimate space required for station info */ error = 0; req.space = sizeof(struct stainforeq); req.vap = vap; ieee80211_iterate_nodes(&ic->ic_sta, get_sta_space, &req); if (req.space > iwr->u.data.length) req.space = iwr->u.data.length; if (req.space > 0) { size_t space; void *p; space = req.space; MALLOC(p, void *, space, M_TEMP, M_WAITOK); req.si = (struct ieee80211req_sta_info *)p; ieee80211_iterate_nodes(&ic->ic_sta, get_sta_info, &req); iwr->u.data.length = space - req.space; error = copy_to_user(iwr->u.data.pointer, p, iwr->u.data.length); FREE(p, M_TEMP); } else iwr->u.data.length = 0; return (error ? -EFAULT : 0); } static void pre_announced_chanswitch(struct net_device *dev, u_int32_t channel, u_int32_t tbtt) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; /* now flag the beacon update to include the channel switch IE */ ic->ic_flags |= IEEE80211_F_CHANSWITCH; ic->ic_chanchange_chan = channel; ic->ic_chanchange_tbtt = tbtt; } static int ieee80211_ioctl_chanswitch(struct net_device *dev, struct iw_request_info *info, void *w, char *extra) { struct ieee80211vap *vap = dev->priv; struct ieee80211com *ic = vap->iv_ic; unsigned int *param = (unsigned int *)extra; if (!(ic->ic_flags & IEEE80211_F_DOTH)) return 0; pre_announced_chanswitch(dev, param[0], param[1]); return 0; } #if WIRELESS_EXT >= 18 static int ieee80211_ioctl_siwmlme(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *data) { struct ieee80211req_mlme mlme; struct iw_mlme *wextmlme = (struct iw_mlme *)data; memset(&mlme, 0, sizeof(mlme)); switch (wextmlme->cmd) { case IW_MLME_DEAUTH: mlme.im_op = IEEE80211_MLME_DEAUTH; break; case IW_MLME_DISASSOC: mlme.im_op = IEEE80211_MLME_DISASSOC; break; default: return -EOPNOTSUPP; } mlme.im_reason = wextmlme->reason_code; memcpy(mlme.im_macaddr, wextmlme->addr.sa_data, IEEE80211_ADDR_LEN); return ieee80211_ioctl_setmlme(dev, NULL, NULL, (char *)&mlme); } static int ieee80211_ioctl_giwgenie(struct net_device *dev, struct iw_request_info *info, struct iw_point *out, char *buf) { struct ieee80211vap *vap = dev->priv; if (out->length < vap->iv_opt_ie_len) return -E2BIG; return ieee80211_ioctl_getoptie(dev, info, out, buf); } static int ieee80211_ioctl_siwgenie(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *data) { return ieee80211_ioctl_setoptie(dev, info, erq, data); } static int siwauth_wpa_version(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int ver = erq->value; int args[2]; args[0] = IEEE80211_PARAM_WPA; if ((ver & IW_AUTH_WPA_VERSION_WPA) && (ver & IW_AUTH_WPA_VERSION_WPA2)) args[1] = 3; else if (ver & IW_AUTH_WPA_VERSION_WPA2) args[1] = 2; else if (ver & IW_AUTH_WPA_VERSION_WPA) args[1] = 1; else args[1] = 0; return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int iwcipher2ieee80211cipher(int iwciph) { switch (iwciph) { case IW_AUTH_CIPHER_NONE: return IEEE80211_CIPHER_NONE; case IW_AUTH_CIPHER_WEP40: case IW_AUTH_CIPHER_WEP104: return IEEE80211_CIPHER_WEP; case IW_AUTH_CIPHER_TKIP: return IEEE80211_CIPHER_TKIP; case IW_AUTH_CIPHER_CCMP: return IEEE80211_CIPHER_AES_CCM; } return -1; } static int ieee80211cipher2iwcipher(int ieee80211ciph) { switch (ieee80211ciph) { case IEEE80211_CIPHER_NONE: return IW_AUTH_CIPHER_NONE; case IEEE80211_CIPHER_WEP: return IW_AUTH_CIPHER_WEP104; case IEEE80211_CIPHER_TKIP: return IW_AUTH_CIPHER_TKIP; case IEEE80211_CIPHER_AES_CCM: return IW_AUTH_CIPHER_CCMP; } return -1; } /* TODO We don't enforce wep key lengths. */ static int siwauth_cipher_pairwise(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int iwciph = erq->value; int args[2]; args[0] = IEEE80211_PARAM_UCASTCIPHER; args[1] = iwcipher2ieee80211cipher(iwciph); if (args[1] < 0) { printk(KERN_WARNING "%s: unknown pairwise cipher %d\n", dev->name, iwciph); return -EINVAL; } return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } /* TODO We don't enforce wep key lengths. */ static int siwauth_cipher_group(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int iwciph = erq->value; int args[2]; args[0] = IEEE80211_PARAM_MCASTCIPHER; args[1] = iwcipher2ieee80211cipher(iwciph); if (args[1] < 0) { printk(KERN_WARNING "%s: unknown group cipher %d\n", dev->name, iwciph); return -EINVAL; } return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_key_mgmt(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int iwkm = erq->value; int args[2]; args[0] = IEEE80211_PARAM_KEYMGTALGS; args[1] = WPA_ASE_NONE; if (iwkm & IW_AUTH_KEY_MGMT_802_1X) args[1] |= WPA_ASE_8021X_UNSPEC; if (iwkm & IW_AUTH_KEY_MGMT_PSK) args[1] |= WPA_ASE_8021X_PSK; return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_tkip_countermeasures(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int args[2]; args[0] = IEEE80211_PARAM_COUNTERMEASURES; args[1] = erq->value; return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_drop_unencrypted(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int args[2]; args[0] = IEEE80211_PARAM_DROPUNENCRYPTED; args[1] = erq->value; return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_80211_auth_alg(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { #define VALID_ALGS_MASK (IW_AUTH_ALG_OPEN_SYSTEM|IW_AUTH_ALG_SHARED_KEY|IW_AUTH_ALG_LEAP) int mode = erq->value; int args[2]; args[0] = IEEE80211_PARAM_AUTHMODE; if (mode & ~VALID_ALGS_MASK) return -EINVAL; if (mode & IW_AUTH_ALG_LEAP) { args[1] = IEEE80211_AUTH_8021X; } else if ((mode & IW_AUTH_ALG_SHARED_KEY) && (mode & IW_AUTH_ALG_OPEN_SYSTEM)) { args[1] = IEEE80211_AUTH_AUTO; } else if (mode & IW_AUTH_ALG_SHARED_KEY) { args[1] = IEEE80211_AUTH_SHARED; } else { args[1] = IEEE80211_AUTH_OPEN; } return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_wpa_enabled(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int enabled = erq->value; int args[2]; args[0] = IEEE80211_PARAM_WPA; if (enabled) args[1] = 3; /* enable WPA1 and WPA2 */ else args[1] = 0; /* disable WPA1 and WPA2 */ return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_rx_unencrypted_eapol(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rxunenc = erq->value; int args[2]; args[0] = IEEE80211_PARAM_DROPUNENC_EAPOL; if (rxunenc) args[1] = 1; else args[1] = 0; return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_roaming_control(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int roam = erq->value; int args[2]; args[0] = IEEE80211_PARAM_ROAMING; switch (roam) { case IW_AUTH_ROAMING_ENABLE: args[1] = IEEE80211_ROAMING_AUTO; break; case IW_AUTH_ROAMING_DISABLE: args[1] = IEEE80211_ROAMING_MANUAL; break; default: return -EINVAL; } return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } static int siwauth_privacy_invoked(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int args[2]; args[0] = IEEE80211_PARAM_PRIVACY; args[1] = erq->value; return ieee80211_ioctl_setparam(dev, NULL, NULL, (char *)args); } /* * If this function is invoked it means someone is using the wireless extensions * API instead of the private madwifi ioctls. That's fine. We translate their * request into the format used by the private ioctls. Note that the * iw_request_info and iw_param structures are not the same ones as the * private ioctl handler expects. Luckily, the private ioctl handler doesn't * do anything with those at the moment. We pass NULL for those, because in * case someone does modify the ioctl handler to use those values, a null * pointer will be easier to debug than other bad behavior. */ static int ieee80211_ioctl_siwauth(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc = -EOPNOTSUPP; switch (erq->flags & IW_AUTH_INDEX) { case IW_AUTH_WPA_VERSION: rc = siwauth_wpa_version(dev, info, erq, buf); break; case IW_AUTH_CIPHER_PAIRWISE: rc = siwauth_cipher_pairwise(dev, info, erq, buf); break; case IW_AUTH_CIPHER_GROUP: rc = siwauth_cipher_group(dev, info, erq, buf); break; case IW_AUTH_KEY_MGMT: rc = siwauth_key_mgmt(dev, info, erq, buf); break; case IW_AUTH_TKIP_COUNTERMEASURES: rc = siwauth_tkip_countermeasures(dev, info, erq, buf); break; case IW_AUTH_DROP_UNENCRYPTED: rc = siwauth_drop_unencrypted(dev, info, erq, buf); break; case IW_AUTH_80211_AUTH_ALG: rc = siwauth_80211_auth_alg(dev, info, erq, buf); break; case IW_AUTH_WPA_ENABLED: rc = siwauth_wpa_enabled(dev, info, erq, buf); break; case IW_AUTH_RX_UNENCRYPTED_EAPOL: rc = siwauth_rx_unencrypted_eapol(dev, info, erq, buf); break; case IW_AUTH_ROAMING_CONTROL: rc = siwauth_roaming_control(dev, info, erq, buf); break; case IW_AUTH_PRIVACY_INVOKED: rc = siwauth_privacy_invoked(dev, info, erq, buf); break; default: printk(KERN_WARNING "%s: unknown SIOCSIWAUTH flag %d\n", dev->name, erq->flags); break; } return rc; } static int giwauth_wpa_version(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int ver; int rc; int arg = IEEE80211_PARAM_WPA; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; switch (arg) { case 1: ver = IW_AUTH_WPA_VERSION_WPA; break; case 2: ver = IW_AUTH_WPA_VERSION_WPA2; break; case 3: ver = IW_AUTH_WPA_VERSION|IW_AUTH_WPA_VERSION_WPA2; break; default: ver = IW_AUTH_WPA_VERSION_DISABLED; break; } erq->value = ver; return rc; } static int giwauth_cipher_pairwise(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc; int arg = IEEE80211_PARAM_UCASTCIPHER; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = ieee80211cipher2iwcipher(arg); if (erq->value < 0) return -EINVAL; return 0; } static int giwauth_cipher_group(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc; int arg = IEEE80211_PARAM_MCASTCIPHER; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = ieee80211cipher2iwcipher(arg); if (erq->value < 0) return -EINVAL; return 0; } static int giwauth_key_mgmt(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int arg; int rc; arg = IEEE80211_PARAM_KEYMGTALGS; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = 0; if (arg & WPA_ASE_8021X_UNSPEC) erq->value |= IW_AUTH_KEY_MGMT_802_1X; if (arg & WPA_ASE_8021X_PSK) erq->value |= IW_AUTH_KEY_MGMT_PSK; return 0; } static int giwauth_tkip_countermeasures(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int arg; int rc; arg = IEEE80211_PARAM_COUNTERMEASURES; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = arg; return 0; } static int giwauth_drop_unencrypted(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int arg; int rc; arg = IEEE80211_PARAM_DROPUNENCRYPTED; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = arg; return 0; } static int giwauth_80211_auth_alg(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { return -EOPNOTSUPP; } static int giwauth_wpa_enabled(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc; int arg = IEEE80211_PARAM_WPA; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = arg; return 0; } static int giwauth_rx_unencrypted_eapol(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { return -EOPNOTSUPP; } static int giwauth_roaming_control(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc; int arg; arg = IEEE80211_PARAM_ROAMING; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; switch (arg) { case IEEE80211_ROAMING_DEVICE: case IEEE80211_ROAMING_AUTO: erq->value = IW_AUTH_ROAMING_ENABLE; break; default: erq->value = IW_AUTH_ROAMING_DISABLE; break; } return 0; } static int giwauth_privacy_invoked(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc; int arg; arg = IEEE80211_PARAM_PRIVACY; rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char *)&arg); if (rc) return rc; erq->value = arg; return 0; } static int ieee80211_ioctl_giwauth(struct net_device *dev, struct iw_request_info *info, struct iw_param *erq, char *buf) { int rc = -EOPNOTSUPP; switch (erq->flags & IW_AUTH_INDEX) { case IW_AUTH_WPA_VERSION: rc = giwauth_wpa_version(dev, info, erq, buf); break; case IW_AUTH_CIPHER_PAIRWISE: rc = giwauth_cipher_pairwise(dev, info, erq, buf); break; case IW_AUTH_CIPHER_GROUP: rc = giwauth_cipher_group(dev, info, erq, buf); break; case IW_AUTH_KEY_MGMT: rc = giwauth_key_mgmt(dev, info, erq, buf); break; case IW_AUTH_TKIP_COUNTERMEASURES: rc = giwauth_tkip_countermeasures(dev, info, erq, buf); break; case IW_AUTH_DROP_UNENCRYPTED: rc = giwauth_drop_unencrypted(dev, info, erq, buf); break; case IW_AUTH_80211_AUTH_ALG: rc = giwauth_80211_auth_alg(dev, info, erq, buf); break; case IW_AUTH_WPA_ENABLED: rc = giwauth_wpa_enabled(dev, info, erq, buf); break; case IW_AUTH_RX_UNENCRYPTED_EAPOL: rc = giwauth_rx_unencrypted_eapol(dev, info, erq, buf); break; case IW_AUTH_ROAMING_CONTROL: rc = giwauth_roaming_control(dev, info, erq, buf); break; case IW_AUTH_PRIVACY_INVOKED: rc = giwauth_privacy_invoked(dev, info, erq, buf); break; default: printk(KERN_WARNING "%s: unknown SIOCGIWAUTH flag %d\n", dev->name, erq->flags); break; } return rc; } /* * Retrieve information about a key. Open question: should we allow * callers to retrieve unicast keys based on a supplied MAC address? * The ipw2200 reference implementation doesn't, so we don't either. */ static int ieee80211_ioctl_giwencodeext(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *extra) { struct ieee80211vap *vap = dev->priv; struct iw_encode_ext *ext; struct ieee80211_key *wk; ieee80211_keyix_t kix; int max_key_len; int error; if (!capable(CAP_NET_ADMIN)) return -EPERM; max_key_len = erq->length - sizeof(*ext); if (max_key_len < 0) return -EINVAL; ext = (struct iw_encode_ext *)extra; error = getiwkeyix(vap, erq, &kix); if (error < 0) return error; wk = &vap->iv_nw_keys[kix]; if (wk->wk_keylen > max_key_len) return -E2BIG; erq->flags = kix + 1; memset(ext, 0, sizeof(*ext)); ext->key_len = wk->wk_keylen; memcpy(ext->key, wk->wk_key, wk->wk_keylen); /* flags */ if (wk->wk_flags & IEEE80211_KEY_GROUP) ext->ext_flags |= IW_ENCODE_EXT_GROUP_KEY; /* algorithm */ switch (wk->wk_cipher->ic_cipher) { case IEEE80211_CIPHER_NONE: ext->alg = IW_ENCODE_ALG_NONE; erq->flags |= IW_ENCODE_DISABLED; break; case IEEE80211_CIPHER_WEP: ext->alg = IW_ENCODE_ALG_WEP; break; case IEEE80211_CIPHER_TKIP: ext->alg = IW_ENCODE_ALG_TKIP; break; case IEEE80211_CIPHER_AES_OCB: case IEEE80211_CIPHER_AES_CCM: case IEEE80211_CIPHER_CKIP: ext->alg = IW_ENCODE_ALG_CCMP; break; default: return -EINVAL; } return 0; } static int ieee80211_ioctl_siwencodeext(struct net_device *dev, struct iw_request_info *info, struct iw_point *erq, char *extra) { struct ieee80211vap *vap = dev->priv; struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; struct ieee80211req_key kr; ieee80211_keyix_t kix; int error; error = getiwkeyix(vap, erq, &kix); if (error < 0) return error; if (ext->key_len > (erq->length - sizeof(struct iw_encode_ext))) return -E2BIG; if (ext->alg == IW_ENCODE_ALG_NONE) { /* convert to the format used by IEEE_80211_IOCTL_DELKEY */ struct ieee80211req_del_key dk; memset(&dk, 0, sizeof(dk)); dk.idk_keyix = kix; memcpy(&dk.idk_macaddr, ext->addr.sa_data, IEEE80211_ADDR_LEN); return ieee80211_ioctl_delkey(dev, NULL, NULL, (char *)&dk); } /* TODO This memcmp for the broadcast address seems hackish, but * mimics what wpa supplicant was doing. The wpa supplicant comments * make it sound like they were having trouble with * IEEE80211_IOCTL_SETKEY and static WEP keys. It might be worth * figuring out what their trouble was so the rest of this function * can be implemented in terms of ieee80211_ioctl_setkey */ if ((ext->alg == IW_ENCODE_ALG_WEP) && IEEE80211_ADDR_EQ(ext->addr.sa_data, vap->iv_dev->broadcast)) { /* convert to the format used by SIOCSIWENCODE. The old * format just had the key in the extra buf, whereas the * new format has the key tacked on to the end of the * iw_encode_ext structure */ struct iw_request_info oldinfo; struct iw_point olderq; char *key; memset(&oldinfo, 0, sizeof(oldinfo)); oldinfo.cmd = SIOCSIWENCODE; oldinfo.flags = info->flags; memset(&olderq, 0, sizeof(olderq)); olderq.flags = erq->flags; olderq.pointer = erq->pointer; olderq.length = ext->key_len; key = ext->key; return ieee80211_ioctl_siwencode(dev, &oldinfo, &olderq, key); } /* convert to the format used by IEEE_80211_IOCTL_SETKEY */ memset(&kr, 0, sizeof(kr)); switch (ext->alg) { case IW_ENCODE_ALG_WEP: kr.ik_type = IEEE80211_CIPHER_WEP; break; case IW_ENCODE_ALG_TKIP: kr.ik_type = IEEE80211_CIPHER_TKIP; break; case IW_ENCODE_ALG_CCMP: kr.ik_type = IEEE80211_CIPHER_AES_CCM; break; default: printk(KERN_WARNING "%s: unknown algorithm %d\n", dev->name, ext->alg); return -EINVAL; } kr.ik_keyix = kix; if (ext->key_len > sizeof(kr.ik_keydata)) { printk(KERN_WARNING "%s: key size %d is too large\n", dev->name, ext->key_len); return -E2BIG; } memcpy(kr.ik_keydata, ext->key, ext->key_len); kr.ik_keylen = ext->key_len; kr.ik_flags = IEEE80211_KEY_RECV; if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) kr.ik_flags |= IEEE80211_KEY_GROUP; if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { kr.ik_flags |= IEEE80211_KEY_XMIT | IEEE80211_KEY_DEFAULT; memcpy(kr.ik_macaddr, ext->addr.sa_data, IEEE80211_ADDR_LEN); } if (ext->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) { memcpy(&kr.ik_keyrsc, ext->rx_seq, sizeof(kr.ik_keyrsc)); } return ieee80211_ioctl_setkey(dev, NULL, NULL, (char *)&kr); } #endif /* WIRELESS_EXT >= 18 */ #define IW_PRIV_TYPE_OPTIE \ IW_PRIV_BLOB_TYPE_ENCODING(IEEE80211_MAX_OPT_IE) #define IW_PRIV_TYPE_KEY \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_key)) #define IW_PRIV_TYPE_DELKEY \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_del_key)) #define IW_PRIV_TYPE_MLME \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_mlme)) #define IW_PRIV_TYPE_CHANLIST \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_chanlist)) #define IW_PRIV_TYPE_CHANINFO \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_chaninfo)) #define IW_PRIV_TYPE_APPIEBUF \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_getset_appiebuf) + IEEE80211_APPIE_MAX) #define IW_PRIV_TYPE_FILTER \ IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_set_filter)) static const struct iw_priv_args ieee80211_priv_args[] = { /* NB: setoptie & getoptie are !IW_PRIV_SIZE_FIXED */ { IEEE80211_IOCTL_SETOPTIE, IW_PRIV_TYPE_OPTIE, 0, "setoptie" }, { IEEE80211_IOCTL_GETOPTIE, 0, IW_PRIV_TYPE_OPTIE, "getoptie" }, { IEEE80211_IOCTL_SETKEY, IW_PRIV_TYPE_KEY | IW_PRIV_SIZE_FIXED, 0, "setkey" }, { IEEE80211_IOCTL_DELKEY, IW_PRIV_TYPE_DELKEY | IW_PRIV_SIZE_FIXED, 0, "delkey" }, { IEEE80211_IOCTL_SETMLME, IW_PRIV_TYPE_MLME | IW_PRIV_SIZE_FIXED, 0, "setmlme" }, { IEEE80211_IOCTL_ADDMAC, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"addmac" }, { IEEE80211_IOCTL_DELMAC, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"delmac" }, { IEEE80211_IOCTL_KICKMAC, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0, "kickmac"}, { IEEE80211_IOCTL_WDSADDMAC, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"wds_add" }, { IEEE80211_IOCTL_WDSDELMAC, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"wds_del" }, { IEEE80211_IOCTL_SETCHANLIST, IW_PRIV_TYPE_CHANLIST | IW_PRIV_SIZE_FIXED, 0,"setchanlist" }, { IEEE80211_IOCTL_GETCHANLIST, 0, IW_PRIV_TYPE_CHANLIST | IW_PRIV_SIZE_FIXED,"getchanlist" }, { IEEE80211_IOCTL_GETCHANINFO, 0, IW_PRIV_TYPE_CHANINFO | IW_PRIV_SIZE_FIXED,"getchaninfo" }, { IEEE80211_IOCTL_SETMODE, IW_PRIV_TYPE_CHAR | 6, 0, "mode" }, { IEEE80211_IOCTL_GETMODE, 0, IW_PRIV_TYPE_CHAR | 6, "get_mode" }, { IEEE80211_IOCTL_SETWMMPARAMS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 4, 0,"setwmmparams" }, { IEEE80211_IOCTL_GETWMMPARAMS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "getwmmparams" }, { IEEE80211_IOCTL_RADAR, 0, 0, "doth_radar" }, { IEEE80211_IOCTL_HALMAP, 0, 0, "dump_hal_map" }, /* * These depends on sub-ioctl support which added in version 12. */ { IEEE80211_IOCTL_SETWMMPARAMS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"" }, { IEEE80211_IOCTL_GETWMMPARAMS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "" }, /* sub-ioctl handlers */ { IEEE80211_WMMPARAMS_CWMIN, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"cwmin" }, { IEEE80211_WMMPARAMS_CWMIN, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_cwmin" }, { IEEE80211_WMMPARAMS_CWMAX, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"cwmax" }, { IEEE80211_WMMPARAMS_CWMAX, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_cwmax" }, { IEEE80211_WMMPARAMS_AIFS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"aifs" }, { IEEE80211_WMMPARAMS_AIFS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_aifs" }, { IEEE80211_WMMPARAMS_TXOPLIMIT, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"txoplimit" }, { IEEE80211_WMMPARAMS_TXOPLIMIT, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_txoplimit" }, { IEEE80211_WMMPARAMS_ACM, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"acm" }, { IEEE80211_WMMPARAMS_ACM, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_acm" }, { IEEE80211_WMMPARAMS_NOACKPOLICY, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"noackpolicy" }, { IEEE80211_WMMPARAMS_NOACKPOLICY, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_noackpolicy" }, { IEEE80211_IOCTL_SETPARAM, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, 0, "setparam" }, /* * These depends on sub-ioctl support which added in version 12. */ { IEEE80211_IOCTL_GETPARAM, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "getparam" }, /* sub-ioctl handlers */ { IEEE80211_IOCTL_SETPARAM, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "" }, { IEEE80211_IOCTL_GETPARAM, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "" }, /* sub-ioctl definitions */ { IEEE80211_PARAM_AUTHMODE, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "authmode" }, { IEEE80211_PARAM_AUTHMODE, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_authmode" }, { IEEE80211_PARAM_PROTMODE, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "protmode" }, { IEEE80211_PARAM_PROTMODE, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_protmode" }, { IEEE80211_PARAM_MCASTCIPHER, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mcastcipher" }, { IEEE80211_PARAM_MCASTCIPHER, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_mcastcipher" }, { IEEE80211_PARAM_MCASTKEYLEN, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mcastkeylen" }, { IEEE80211_PARAM_MCASTKEYLEN, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_mcastkeylen" }, { IEEE80211_PARAM_UCASTCIPHERS, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ucastciphers" }, { IEEE80211_PARAM_UCASTCIPHERS, /* * NB: can't use "get_ucastciphers" due to iwpriv command names * must be = 16 set_handler(SIOCSIWTHRSPY, ieee80211_ioctl_setthrspy), set_handler(SIOCGIWTHRSPY, ieee80211_ioctl_getthrspy), #endif set_handler(SIOCSIWAP, ieee80211_ioctl_siwap), set_handler(SIOCGIWAP, ieee80211_ioctl_giwap), #ifdef SIOCSIWMLME set_handler(SIOCSIWMLME, ieee80211_ioctl_siwmlme), #endif set_handler(SIOCGIWAPLIST, ieee80211_ioctl_iwaplist), #ifdef SIOCGIWSCAN set_handler(SIOCSIWSCAN, ieee80211_ioctl_siwscan), set_handler(SIOCGIWSCAN, ieee80211_ioctl_giwscan), #endif /* SIOCGIWSCAN */ set_handler(SIOCSIWESSID, ieee80211_ioctl_siwessid), set_handler(SIOCGIWESSID, ieee80211_ioctl_giwessid), set_handler(SIOCSIWNICKN, ieee80211_ioctl_siwnickn), set_handler(SIOCGIWNICKN, ieee80211_ioctl_giwnickn), set_handler(SIOCSIWRATE, ieee80211_ioctl_siwrate), set_handler(SIOCGIWRATE, ieee80211_ioctl_giwrate), set_handler(SIOCSIWRTS, ieee80211_ioctl_siwrts), set_handler(SIOCGIWRTS, ieee80211_ioctl_giwrts), set_handler(SIOCSIWFRAG, ieee80211_ioctl_siwfrag), set_handler(SIOCGIWFRAG, ieee80211_ioctl_giwfrag), set_handler(SIOCSIWTXPOW, ieee80211_ioctl_siwtxpow), set_handler(SIOCGIWTXPOW, ieee80211_ioctl_giwtxpow), set_handler(SIOCSIWRETRY, ieee80211_ioctl_siwretry), set_handler(SIOCGIWRETRY, ieee80211_ioctl_giwretry), set_handler(SIOCSIWENCODE, ieee80211_ioctl_siwencode), set_handler(SIOCGIWENCODE, ieee80211_ioctl_giwencode), set_handler(SIOCSIWPOWER, ieee80211_ioctl_siwpower), set_handler(SIOCGIWPOWER, ieee80211_ioctl_giwpower), #if WIRELESS_EXT >= 18 set_handler(SIOCSIWGENIE, ieee80211_ioctl_siwgenie), set_handler(SIOCGIWGENIE, ieee80211_ioctl_giwgenie), set_handler(SIOCSIWAUTH, ieee80211_ioctl_siwauth), set_handler(SIOCGIWAUTH, ieee80211_ioctl_giwauth), set_handler(SIOCSIWENCODEEXT, ieee80211_ioctl_siwencodeext), set_handler(SIOCGIWENCODEEXT, ieee80211_ioctl_giwencodeext), #endif /* WIRELESS_EXT >= 18 */ }; #define set_priv(x,f) [x - SIOCIWFIRSTPRIV] = (iw_handler) f static const iw_handler ieee80211_priv_handlers[] = { set_priv(IEEE80211_IOCTL_SETPARAM, ieee80211_ioctl_setparam), set_priv(IEEE80211_IOCTL_GETPARAM, ieee80211_ioctl_getparam), set_priv(IEEE80211_IOCTL_SETMODE, ieee80211_ioctl_setmode), set_priv(IEEE80211_IOCTL_GETMODE, ieee80211_ioctl_getmode), set_priv(IEEE80211_IOCTL_SETWMMPARAMS, ieee80211_ioctl_setwmmparams), set_priv(IEEE80211_IOCTL_GETWMMPARAMS, ieee80211_ioctl_getwmmparams), set_priv(IEEE80211_IOCTL_SETCHANLIST, ieee80211_ioctl_setchanlist), set_priv(IEEE80211_IOCTL_GETCHANLIST, ieee80211_ioctl_getchanlist), set_priv(IEEE80211_IOCTL_CHANSWITCH, ieee80211_ioctl_chanswitch), set_priv(IEEE80211_IOCTL_RADAR, ieee80211_ioctl_radar), set_priv(IEEE80211_IOCTL_GET_APPIEBUF, ieee80211_ioctl_getappiebuf), set_priv(IEEE80211_IOCTL_SET_APPIEBUF, ieee80211_ioctl_setappiebuf), set_priv(IEEE80211_IOCTL_FILTERFRAME, ieee80211_ioctl_setfilter), set_priv(IEEE80211_IOCTL_GETCHANINFO, ieee80211_ioctl_getchaninfo), set_priv(IEEE80211_IOCTL_SETOPTIE, ieee80211_ioctl_setoptie), set_priv(IEEE80211_IOCTL_GETOPTIE, ieee80211_ioctl_getoptie), set_priv(IEEE80211_IOCTL_SETMLME, ieee80211_ioctl_setmlme), set_priv(IEEE80211_IOCTL_SETKEY, ieee80211_ioctl_setkey), set_priv(IEEE80211_IOCTL_DELKEY, ieee80211_ioctl_delkey), set_priv(IEEE80211_IOCTL_HALMAP, ieee80211_ioctl_hal_map), set_priv(IEEE80211_IOCTL_ADDMAC, ieee80211_ioctl_addmac), set_priv(IEEE80211_IOCTL_DELMAC, ieee80211_ioctl_delmac), set_priv(IEEE80211_IOCTL_WDSADDMAC, ieee80211_ioctl_wdsmac), set_priv(IEEE80211_IOCTL_WDSDELMAC, ieee80211_ioctl_wdsdelmac), set_priv(IEEE80211_IOCTL_KICKMAC, ieee80211_ioctl_kickmac), #ifdef ATH_REVERSE_ENGINEERING set_priv(IEEE80211_IOCTL_READREG, ieee80211_ioctl_readreg), set_priv(IEEE80211_IOCTL_WRITEREG, ieee80211_ioctl_writereg), #endif /* #ifdef ATH_REVERSE_ENGINEERING */ }; static struct iw_handler_def ieee80211_iw_handler_def = { .standard = (iw_handler *)ieee80211_handlers, .num_standard = ARRAY_SIZE(ieee80211_handlers), .private = (iw_handler *)ieee80211_priv_handlers, .num_private = ARRAY_SIZE(ieee80211_priv_handlers), .private_args = (struct iw_priv_args *)ieee80211_priv_args, .num_private_args = ARRAY_SIZE(ieee80211_priv_args), #if IW_HANDLER_VERSION >= 7 .get_wireless_stats = ieee80211_iw_getstats, #endif }; /* * Handle private ioctl requests. */ static int ieee80211_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct ieee80211vap *vap = dev->priv; switch (cmd) { case SIOCG80211STATS: return copy_to_user(ifr->ifr_data, &vap->iv_stats, sizeof (vap->iv_stats)) ? -EFAULT : 0; case SIOC80211IFDESTROY: if (!capable(CAP_NET_ADMIN)) return -EPERM; ieee80211_stop(vap->iv_dev); /* force state before cleanup */ vap->iv_ic->ic_vap_delete(vap); return 0; case IEEE80211_IOCTL_GETKEY: return ieee80211_ioctl_getkey(dev, (struct iwreq *)ifr); case IEEE80211_IOCTL_GETWPAIE: return ieee80211_ioctl_getwpaie(dev, (struct iwreq *)ifr); case IEEE80211_IOCTL_STA_STATS: return ieee80211_ioctl_getstastats(dev, (struct iwreq *)ifr); case IEEE80211_IOCTL_STA_INFO: return ieee80211_ioctl_getstainfo(dev, (struct iwreq *)ifr); case IEEE80211_IOCTL_SCAN_RESULTS: return ieee80211_ioctl_getscanresults(dev, (struct iwreq *)ifr); } return -EOPNOTSUPP; } /* * Create a virtual ap. This is public as it must be implemented * outside our control (e.g. in the driver). */ int ieee80211_ioctl_create_vap(struct ieee80211com *ic, struct ifreq *ifr, struct net_device *mdev) { struct ieee80211_clone_params cp; struct ieee80211vap *vap; char name[IFNAMSIZ]; if (!capable(CAP_NET_ADMIN)) return -EPERM; if (copy_from_user(&cp, ifr->ifr_data, sizeof(cp))) return -EFAULT; strncpy(name, cp.icp_name, sizeof(name)); vap = ieee80211_create_vap(ic, name, mdev, cp.icp_opmode, cp.icp_flags); if (vap == NULL) return -EIO; /* return final device name */ strncpy(ifr->ifr_name, vap->iv_dev->name, IFNAMSIZ); return 0; } EXPORT_SYMBOL(ieee80211_ioctl_create_vap); /* * Create a virtual ap. This is public as it must be implemented * outside our control (e.g. in the driver). * Must be called with rtnl_lock held */ struct ieee80211vap * ieee80211_create_vap(struct ieee80211com *ic, char *name, struct net_device *mdev, int opmode, int opflags) { return ic->ic_vap_create(ic, name, opmode, opflags, mdev); } EXPORT_SYMBOL(ieee80211_create_vap); void ieee80211_ioctl_vattach(struct ieee80211vap *vap) { struct net_device *dev = vap->iv_dev; dev->do_ioctl = ieee80211_ioctl; #if IW_HANDLER_VERSION < 7 dev->get_wireless_stats = ieee80211_iw_getstats; #endif dev->wireless_handlers = &ieee80211_iw_handler_def; } void ieee80211_ioctl_vdetach(struct ieee80211vap *vap) { }