/*- * 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: ieee80211_wireless.c,v 1.27 2005/10/17 17:25:41 br1 Exp $ */ #ifndef EXPORT_SYMTAB #define EXPORT_SYMTAB #endif #ifdef CONFIG_NET_WIRELESS /* * Wireless extensions support for 802.11 common code. */ #include #include #include #include #include #include /* XXX for ARPHRD_ETHER */ #include #include #include "if_media.h" #include #define IS_UP(_dev) \ (((_dev)->flags & (IFF_RUNNING|IFF_UP)) == (IFF_RUNNING|IFF_UP)) #define IS_UP_AUTO(_ic) \ (IS_UP((_ic)->ic_dev) && (_ic)->ic_roaming == IEEE80211_ROAMING_AUTO) static u_int encode_ie(void *, size_t, const u_int8_t *, size_t, const char *, size_t); static void set_quality(struct iw_quality *, u_int); struct read_ap_args { int i; int mode; struct iw_event *iwe; char *start; char *current_ev; }; static void read_ap_result(void *arg, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct read_ap_args *sa = arg; struct iw_event *iwe = sa->iwe; char *end_buf = sa->start + IW_SCAN_MAX_DATA; char *current_val; int j; #if WIRELESS_EXT > 14 char buf[64*2 + 30]; #endif /* * Translate data to WE format. */ if (sa->current_ev >= end_buf) { return; } if ((sa->mode != 0) ^ (ni->ni_wpa_ie != NULL)) return; memset(iwe, 0, sizeof(iwe)); iwe->cmd = SIOCGIWAP; iwe->u.ap_addr.sa_family = ARPHRD_ETHER; if (ic->ic_opmode == IEEE80211_M_HOSTAP) IEEE80211_ADDR_COPY(iwe->u.ap_addr.sa_data, ni->ni_macaddr); else IEEE80211_ADDR_COPY(iwe->u.ap_addr.sa_data, ni->ni_bssid); sa->current_ev = iwe_stream_add_event(sa->current_ev, end_buf, iwe, IW_EV_ADDR_LEN); memset(iwe, 0, sizeof(iwe)); iwe->cmd = SIOCGIWESSID; iwe->u.data.flags = 1; if (ic->ic_opmode == IEEE80211_M_HOSTAP) { iwe->u.data.length = ic->ic_des_esslen; sa->current_ev = iwe_stream_add_point(sa->current_ev, end_buf, iwe, ic->ic_des_essid); } else { iwe->u.data.length = ni->ni_esslen; sa->current_ev = iwe_stream_add_point(sa->current_ev, end_buf, iwe, ni->ni_essid); } if (ni->ni_capinfo & (IEEE80211_CAPINFO_ESS|IEEE80211_CAPINFO_IBSS)) { memset(iwe, 0, sizeof(iwe)); iwe->cmd = SIOCGIWMODE; iwe->u.mode = ni->ni_capinfo & IEEE80211_CAPINFO_ESS ? IW_MODE_MASTER : IW_MODE_ADHOC; sa->current_ev = iwe_stream_add_event(sa->current_ev, end_buf, iwe, IW_EV_UINT_LEN); } memset(iwe, 0, sizeof(iwe)); iwe->cmd = SIOCGIWFREQ; iwe->u.freq.m = ni->ni_chan->ic_freq * 100000; iwe->u.freq.e = 1; sa->current_ev = iwe_stream_add_event(sa->current_ev, end_buf, iwe, IW_EV_FREQ_LEN); memset(iwe, 0, sizeof(iwe)); iwe->cmd = IWEVQUAL; set_quality(&iwe->u.qual, (*ic->ic_node_getrssi)(ni)); sa->current_ev = iwe_stream_add_event(sa->current_ev, end_buf, iwe, IW_EV_QUAL_LEN); memset(iwe, 0, sizeof(iwe)); iwe->cmd = SIOCGIWENCODE; if (ni->ni_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; sa->current_ev = iwe_stream_add_point(sa->current_ev, end_buf, iwe, ""); memset(iwe, 0, sizeof(iwe)); iwe->cmd = SIOCGIWRATE; current_val = sa->current_ev + IW_EV_LCP_LEN; for (j = 0; j < ni->ni_rates.rs_nrates; j++) { if (ni->ni_rates.rs_rates[j]) { iwe->u.bitrate.value = ((ni->ni_rates.rs_rates[j] & IEEE80211_RATE_VAL) / 2) * 1000000; current_val = iwe_stream_add_value(sa->current_ev, current_val, end_buf, iwe, IW_EV_PARAM_LEN); } } /* remove fixed header if no rates were added */ if ((u_int)(current_val - sa->current_ev) > IW_EV_LCP_LEN) sa->current_ev = current_val; #if WIRELESS_EXT > 14 memset(iwe, 0, sizeof(iwe)); iwe->cmd = IWEVCUSTOM; snprintf(buf, sizeof(buf), "bcn_int=%d", ni->ni_intval); iwe->u.data.length = strlen(buf); sa->current_ev = iwe_stream_add_point(sa->current_ev, end_buf, iwe, buf); if (ni->ni_wpa_ie != NULL) { static const char rsn_leader[] = "rsn_ie="; static const char wpa_leader[] = "wpa_ie="; memset(iwe, 0, sizeof(iwe)); iwe->cmd = IWEVCUSTOM; if (ni->ni_wpa_ie[0] == IEEE80211_ELEMID_RSN) iwe->u.data.length = encode_ie(buf, sizeof(buf), ni->ni_wpa_ie, ni->ni_wpa_ie[1]+2, rsn_leader, sizeof(rsn_leader)-1); else iwe->u.data.length = encode_ie(buf, sizeof(buf), ni->ni_wpa_ie, ni->ni_wpa_ie[1]+2, wpa_leader, sizeof(wpa_leader)-1); if (iwe->u.data.length != 0) sa->current_ev = iwe_stream_add_point(sa->current_ev, end_buf, iwe, buf); } #endif /* WIRELESS_EXT > 14 */ return; } /* * Units are in db above the noise floor. That means the * rssi values reported in the tx/rx descriptors in the * driver are the SNR expressed in db. * * If you assume that the noise floor is -95, which is an * excellent assumption 99.5 % of the time, then you can * derive the absolute signal level (i.e. -95 + rssi). * There are some other slight factors to take into account * depending on whether the rssi measurement is from 11b, * 11g, or 11a. These differences are at most 2db and * can be documented. * * NB: various calculations are based on the orinoco/wavelan * drivers for compatibility */ static void set_quality(struct iw_quality *iq, u_int rssi) { iq->qual = rssi; /* NB: max is 94 because noise is hardcoded to 161 */ if (iq->qual > 94) iq->qual = 94; iq->noise = 161; /* -95dBm */ iq->level = iq->noise + iq->qual; iq->updated = 7; } void ieee80211_iw_getstats(struct ieee80211com *ic, struct iw_statistics *is) { #define NZ(x) ((x) ? (x) : 1) switch (ic->ic_opmode) { case IEEE80211_M_STA: /* use stats from associated ap */ if (ic->ic_bss && ic->ic_state == IEEE80211_S_RUN) set_quality(&is->qual, (*ic->ic_node_getrssi)(ic->ic_bss)); else set_quality(&is->qual, 0); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: case IEEE80211_M_HOSTAP: { struct ieee80211_node_table *nt = &ic->ic_sta; struct ieee80211_node* ni; u_int32_t rssi_samples = 0; u_int32_t rssi_total = 0; /* average stats from all nodes */ TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { rssi_samples++; rssi_total += (*ic->ic_node_getrssi)(ni); } set_quality(&is->qual, rssi_total / NZ(rssi_samples)); break; } case IEEE80211_M_MONITOR: default: /* no stats */ set_quality(&is->qual, 0); break; } is->status = ic->ic_state; is->discard.nwid = ic->ic_stats.is_rx_wrongbss + ic->ic_stats.is_rx_ssidmismatch; is->discard.code = ic->ic_stats.is_rx_wepfail + ic->ic_stats.is_rx_decryptcrc; is->discard.fragment = 0; is->discard.retries = 0; is->discard.misc = 0; is->miss.beacon = 0; #undef NZ } EXPORT_SYMBOL(ieee80211_iw_getstats); int ieee80211_ioctl_giwname(struct ieee80211com *ic, struct iw_request_info *info, char *name, char *extra) { /* XXX should use media status but IFM_AUTO case gets tricky */ switch (ic->ic_curmode) { case IEEE80211_MODE_11A: strncpy(name, "IEEE 802.11a", IFNAMSIZ); break; case IEEE80211_MODE_11B: strncpy(name, "IEEE 802.11b", IFNAMSIZ); break; case IEEE80211_MODE_11G: strncpy(name, "IEEE 802.11g", IFNAMSIZ); break; case IEEE80211_MODE_TURBO_A: strncpy(name, "Turbo-A", IFNAMSIZ); break; case IEEE80211_MODE_TURBO_G: strncpy(name, "Turbo-G", IFNAMSIZ); break; default: strncpy(name, "IEEE 802.11", IFNAMSIZ); break; } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwname); /* * 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 ieee80211com *ic, const struct iw_point* erq, int *kix) { int kid; kid = erq->flags & IW_ENCODE_INDEX; if (kid < 1 || kid > IEEE80211_WEP_NKID) { kid = ic->ic_def_txkey; if (kid == IEEE80211_KEYIX_NONE) kid = 0; } else --kid; if (0 <= kid && kid < IEEE80211_WEP_NKID) { *kix = kid; return 0; } else return EINVAL; } int ieee80211_ioctl_siwencode(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *erq, char *keybuf) { int kid, error = 0; int wepchange = 0; struct ieee80211_key *k; /* * set key * * New version of iwconfig set the IW_ENCODE_NOKEY flag * when no key is given, but older versions don't, * so we have to check the length too. */ if (erq->length > 0 && !(erq->flags & IW_ENCODE_NOKEY)) { /* * set key contents, set the default transmit key * and enable crypto. */ error = getiwkeyix(ic, erq, &kid); if (error) return -error; if (erq->length > IEEE80211_KEYBUF_SIZE) return -EINVAL; /* set key contents */ k = &ic->ic_nw_keys[kid]; ieee80211_key_update_begin(ic); k->wk_keyix = kid; /* NB: force fixed key id */ if (ieee80211_crypto_newkey(ic, IEEE80211_CIPHER_WEP, IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV, k)) { k->wk_keylen = erq->length; /* NB: preserve flags set by newkey */ k->wk_flags |= IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV; memcpy(k->wk_key, keybuf, erq->length); memset(k->wk_key + erq->length, 0, IEEE80211_KEYBUF_SIZE - erq->length); if (!ieee80211_crypto_setkey(ic, k, ic->ic_myaddr)) error = -EINVAL; } else { error = -EINVAL; } ieee80211_key_update_end(ic); if (error == 0) { /* set default key & enable privacy */ ic->ic_def_txkey = kid; wepchange = (ic->ic_flags & IEEE80211_F_PRIVACY) == 0; ic->ic_flags |= IEEE80211_F_PRIVACY; ic->ic_flags |= IEEE80211_F_DROPUNENC; } } /* * set key index only */ else if ( (erq->flags & IW_ENCODE_INDEX) > 0) { /* * verify the new key has a non-zero length * and change the default transmit key. */ error = getiwkeyix(ic, erq, &kid); if (error) return -error; if (ic->ic_nw_keys[kid].wk_keylen == 0) return -EINVAL; ic->ic_def_txkey = kid; } /* disable crypto & make sure we allow unencrypted packets again */ if (erq->flags & IW_ENCODE_DISABLED) { wepchange = (ic->ic_flags & IEEE80211_F_PRIVACY) != 0; ic->ic_flags &= ~IEEE80211_F_PRIVACY; ic->ic_flags &= ~IEEE80211_F_DROPUNENC; } /* allow unencrypted packets */ if (erq->flags & IW_ENCODE_OPEN) { ic->ic_flags &= ~IEEE80211_F_DROPUNENC; } /* dont allow unencrypted packets & make sure privacy is enabled */ if (erq->flags & IW_ENCODE_RESTRICTED) { wepchange = (ic->ic_flags & IEEE80211_F_PRIVACY) == 0; ic->ic_flags |= IEEE80211_F_PRIVACY; ic->ic_flags |= IEEE80211_F_DROPUNENC; } if (error == 0 && IS_UP(ic->ic_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 && ic->ic_roaming == IEEE80211_ROAMING_AUTO) error = -(*ic->ic_init)(ic->ic_dev); } return error; } EXPORT_SYMBOL(ieee80211_ioctl_siwencode); int ieee80211_ioctl_giwencode(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *erq, char *key) { struct ieee80211_key *k; int error, kid; if (ic->ic_flags & IEEE80211_F_PRIVACY) { error = getiwkeyix(ic, erq, &kid); if (error != 0) return -error; k = &ic->ic_nw_keys[kid]; /* XXX no way to return cipher/key type */ erq->flags = kid + 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 (ic->ic_flags & IEEE80211_F_DROPUNENC) erq->flags |= IW_ENCODE_RESTRICTED; else erq->flags |= IW_ENCODE_OPEN; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwencode); #ifndef ifr_media #define ifr_media ifr_ifru.ifru_ivalue #endif int ieee80211_ioctl_siwrate(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ifreq ifr; int rate; if (!ic->ic_media.ifm_cur) return -EINVAL; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media &~ IFM_TMASK; if (rrq->fixed) { /* XXX fudge checking rates */ rate = ieee80211_rate2media(ic, 2 * rrq->value / 1000000, ic->ic_curmode); if (rate == IFM_AUTO) /* NB: unknown rate */ return -EINVAL; } else rate = IFM_AUTO; ifr.ifr_media |= IFM_SUBTYPE(rate); return -ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); } EXPORT_SYMBOL(ieee80211_ioctl_siwrate); int ieee80211_ioctl_giwrate(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { struct ifmediareq imr; int rate; memset(&imr, 0, sizeof(imr)); (*ic->ic_media.ifm_status)(ic->ic_dev, &imr); rrq->fixed = IFM_SUBTYPE(ic->ic_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; } EXPORT_SYMBOL(ieee80211_ioctl_giwrate); int ieee80211_ioctl_siwsens(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *sens, char *extra) { return 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwsens); int ieee80211_ioctl_giwsens(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *sens, char *extra) { sens->value = 0; sens->fixed = 1; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwsens); int ieee80211_ioctl_siwrts(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rts, char *extra) { 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 != ic->ic_rtsthreshold) { ic->ic_rtsthreshold = val; if (IS_UP(ic->ic_dev)) return -(*ic->ic_reset)(ic->ic_dev); } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwrts); int ieee80211_ioctl_giwrts(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rts, char *extra) { rts->value = ic->ic_rtsthreshold; rts->disabled = (rts->value == IEEE80211_RTS_MAX); rts->fixed = 1; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwrts); int ieee80211_ioctl_siwfrag(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rts, char *extra) { u16 val; if (rts->disabled) val = __constant_cpu_to_le16(2346); else if (rts->value < 256 || rts->value > 2346) return -EINVAL; else val = __cpu_to_le16(rts->value & ~0x1); /* even numbers only */ if (val != ic->ic_fragthreshold) { ic->ic_fragthreshold = val; if (IS_UP(ic->ic_dev)) return -(*ic->ic_reset)(ic->ic_dev); } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwfrag); int ieee80211_ioctl_giwfrag(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rts, char *extra) { rts->value = ic->ic_fragthreshold; rts->disabled = (rts->value == 2346); rts->fixed = 1; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwfrag); int ieee80211_ioctl_siwap(struct ieee80211com *ic, struct iw_request_info *info, struct sockaddr *ap_addr, char *extra) { static const u_int8_t zero_bssid[IEEE80211_ADDR_LEN]; /* NB: should not be set when in HOSTAP mode */ if (ic->ic_opmode != IEEE80211_M_STA && ic->ic_opmode != IEEE80211_M_AHDEMO && ic->ic_opmode != IEEE80211_M_IBSS) return -EINVAL; IEEE80211_ADDR_COPY(ic->ic_des_bssid, &ap_addr->sa_data); /* looks like a zero address disables */ if (IEEE80211_ADDR_EQ(ic->ic_des_bssid, zero_bssid)) ic->ic_flags &= ~IEEE80211_F_DESBSSID; else ic->ic_flags |= IEEE80211_F_DESBSSID; return IS_UP_AUTO(ic) ? -(*ic->ic_init)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwap); int ieee80211_ioctl_giwap(struct ieee80211com *ic, struct iw_request_info *info, struct sockaddr *ap_addr, char *extra) { if (ic->ic_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(&ap_addr->sa_data, ic->ic_des_bssid); else IEEE80211_ADDR_COPY(&ap_addr->sa_data, ic->ic_bss->ni_bssid); ap_addr->sa_family = ARPHRD_ETHER; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwap); int ieee80211_ioctl_siwnickn(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *nickname) { if (data->length > IEEE80211_NWID_LEN) return -EINVAL; memset(ic->ic_nickname, 0, IEEE80211_NWID_LEN); memcpy(ic->ic_nickname, nickname, data->length); ic->ic_nicknamelen = data->length; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwnickn); int ieee80211_ioctl_giwnickn(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *nickname) { if (data->length > ic->ic_nicknamelen + 1) data->length = ic->ic_nicknamelen + 1; if (data->length > 0) { memcpy(nickname, ic->ic_nickname, data->length-1); nickname[data->length-1] = '\0'; } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwnickn); static struct ieee80211_channel * getcurchan(struct ieee80211com *ic) { switch (ic->ic_state) { case IEEE80211_S_INIT: case IEEE80211_S_SCAN: return ic->ic_des_chan; default: return ic->ic_ibss_chan; } } 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; } int ieee80211_ioctl_siwfreq(struct ieee80211com *ic, struct iw_request_info *info, struct iw_freq *freq, char *extra) { struct ieee80211_channel *c; int i; if (freq->e > 1) return -EINVAL; if (freq->e == 1) i = ieee80211_mhz2ieee(freq->m / 100000, 0); else i = freq->m; if (i != 0) { if (i > IEEE80211_CHAN_MAX || isclr(ic->ic_chan_active, i)) return -EINVAL; c = &ic->ic_channels[i]; if (c == getcurchan(ic)) { /* no change, just return */ ic->ic_des_chan = c; /* XXX */ return 0; } ic->ic_des_chan = c; if (c != IEEE80211_CHAN_ANYC) ic->ic_ibss_chan = c; } else { /* * Intepret channel 0 to mean "no desired channel"; * otherwise there's no way to undo fixing the desired * channel. */ if (ic->ic_des_chan == IEEE80211_CHAN_ANYC) return 0; ic->ic_des_chan = IEEE80211_CHAN_ANYC; } if (ic->ic_opmode == IEEE80211_M_MONITOR) return IS_UP(ic->ic_dev) ? -(*ic->ic_reset)(ic->ic_dev) : 0; else return IS_UP_AUTO(ic) ? -(*ic->ic_init)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwfreq); int ieee80211_ioctl_giwfreq(struct ieee80211com *ic, struct iw_request_info *info, struct iw_freq *freq, char *extra) { if (!ic->ic_ibss_chan) return -EINVAL; freq->m = ic->ic_ibss_chan->ic_freq * 100000; freq->e = 1; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwfreq); int ieee80211_ioctl_siwessid(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *ssid) { if (data->flags == 0) { /* ANY */ memset(ic->ic_des_essid, 0, sizeof(ic->ic_des_essid)); ic->ic_des_esslen = 0; } else { if (data->length > sizeof(ic->ic_des_essid)) data->length = sizeof(ic->ic_des_essid); memcpy(ic->ic_des_essid, ssid, data->length); ic->ic_des_esslen = data->length; /* * 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). */ if (ic->ic_des_esslen > 0 && ic->ic_des_essid[ic->ic_des_esslen-1] == '\0') ic->ic_des_esslen--; } return IS_UP_AUTO(ic) ? -(*ic->ic_init)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwessid); int ieee80211_ioctl_giwessid(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *essid) { data->flags = 1; /* active */ if (ic->ic_opmode == IEEE80211_M_HOSTAP) { if (data->length > ic->ic_des_esslen) data->length = ic->ic_des_esslen; memcpy(essid, ic->ic_des_essid, data->length); } else { if (ic->ic_des_esslen == 0) { if (data->length > ic->ic_bss->ni_esslen) data->length = ic->ic_bss->ni_esslen; memcpy(essid, ic->ic_bss->ni_essid, data->length); } else { if (data->length > ic->ic_des_esslen) data->length = ic->ic_des_esslen; memcpy(essid, ic->ic_des_essid, data->length); } } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwessid); int ieee80211_ioctl_giwrange(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211_node *ni = ic->ic_bss; struct iw_range *range = (struct iw_range *) extra; struct ieee80211_rateset *rs; int i, r; data->length = sizeof(struct iw_range); memset(range, 0, sizeof(struct iw_range)); /* TODO: could fill num_txpower and txpower array with * something; however, there are 128 different values.. */ range->txpower_capa = IW_TXPOW_DBM; if (ic->ic_opmode == IEEE80211_M_STA || ic->ic_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 = 13; range->retry_capa = IW_RETRY_LIMIT; range->retry_flags = IW_RETRY_LIMIT; range->min_retry = 0; range->max_retry = 255; range->num_channels = IEEE80211_CHAN_MAX; /* XXX */ range->num_frequency = 0; for (i = 0; i <= IEEE80211_CHAN_MAX; i++) if (isset(ic->ic_chan_active, i)) { range->freq[range->num_frequency].i = i; 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; } /* Max quality is max field value minus noise floor */ range->max_qual.qual = 0xff - 161; /* * In order to use dBm measurements, 'level' must be lower * than any possible measurement (see iw_print_stats() in * wireless tools). It's unclear how this is meant to be * done, but setting zero in these values forces dBm and * the actual numbers are not used. */ range->max_qual.level = 0; range->max_qual.noise = 0; range->sensitivity = 3; 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; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwrange); int ieee80211_ioctl_siwmode(struct ieee80211com *ic, struct iw_request_info *info, __u32 *mode, char *extra) { struct ifreq ifr; if (!ic->ic_media.ifm_cur) return -EINVAL; memset(&ifr, 0, sizeof(ifr)); /* NB: remove any fixed-rate at the same time */ /* also remove pseudo_ibss (IFM_FLAG0) flag */ ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media &~ (IFM_OMASK | IFM_TMASK | IFM_FLAG0); switch (*mode) { case IW_MODE_INFRA: /* NB: this is the default */ ic->ic_des_chan = IEEE80211_CHAN_ANYC; break; case IW_MODE_ADHOC: ifr.ifr_media |= IFM_IEEE80211_ADHOC; break; case IW_MODE_MASTER: ifr.ifr_media |= IFM_IEEE80211_HOSTAP; break; #if WIRELESS_EXT >= 15 case IW_MODE_MONITOR: ifr.ifr_media |= IFM_IEEE80211_MONITOR; break; #endif default: return -EINVAL; } if (ic->ic_curmode == IEEE80211_MODE_TURBO_G || ic->ic_curmode == IEEE80211_MODE_TURBO_A) { ifr.ifr_media |= IFM_IEEE80211_TURBO; } return -ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); } EXPORT_SYMBOL(ieee80211_ioctl_siwmode); int ieee80211_ioctl_giwmode(struct ieee80211com *ic, struct iw_request_info *info, __u32 *mode, char *extra) { struct ifmediareq imr; memset(&imr, 0, sizeof(imr)); (*ic->ic_media.ifm_status)(ic->ic_dev, &imr); if (imr.ifm_active & IFM_IEEE80211_HOSTAP) *mode = IW_MODE_MASTER; #if WIRELESS_EXT >= 15 else if (imr.ifm_active & IFM_IEEE80211_MONITOR) *mode = IW_MODE_MONITOR; #endif else if (imr.ifm_active & IFM_IEEE80211_ADHOC) *mode = IW_MODE_ADHOC; else *mode = IW_MODE_INFRA; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwmode); int ieee80211_ioctl_siwpower(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *wrq, char *extra) { if (wrq->disabled) { if (ic->ic_flags & IEEE80211_F_PMGTON) { ic->ic_flags &= ~IEEE80211_F_PMGTON; goto done; } return 0; } if ((ic->ic_caps & IEEE80211_C_PMGT) == 0) return -EOPNOTSUPP; switch (wrq->flags & IW_POWER_MODE) { case IW_POWER_UNICAST_R: case IW_POWER_ALL_R: case IW_POWER_ON: ic->ic_flags |= IEEE80211_F_PMGTON; break; default: return -EINVAL; } if (wrq->flags & IW_POWER_TIMEOUT) { ic->ic_holdover = wrq->value / 1024; ic->ic_flags |= IEEE80211_F_PMGTON; } if (wrq->flags & IW_POWER_PERIOD) { ic->ic_lintval = wrq->value / 1024; ic->ic_flags |= IEEE80211_F_PMGTON; } done: return IS_UP(ic->ic_dev) ? -(*ic->ic_reset)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwpower); int ieee80211_ioctl_giwpower(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { 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 = ic->ic_holdover * 1024; break; case IW_POWER_PERIOD: rrq->flags = IW_POWER_PERIOD; rrq->value = ic->ic_lintval * 1024; break; } rrq->flags |= IW_POWER_ALL_R; } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwpower); int ieee80211_ioctl_siwretry(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { if (rrq->disabled) { if (ic->ic_flags & IEEE80211_F_SWRETRY) { ic->ic_flags &= ~IEEE80211_F_SWRETRY; goto done; } return 0; } if ((ic->ic_caps & IEEE80211_C_SWRETRY) == 0) return -EOPNOTSUPP; if (rrq->flags == IW_RETRY_LIMIT) { if (rrq->value >= 0) { ic->ic_txmin = rrq->value; ic->ic_txmax = rrq->value; /* XXX */ ic->ic_txlifetime = 0; /* XXX */ ic->ic_flags |= IEEE80211_F_SWRETRY; } else { ic->ic_flags &= ~IEEE80211_F_SWRETRY; } return 0; } done: return IS_UP(ic->ic_dev) ? -(*ic->ic_reset)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwretry); int ieee80211_ioctl_giwretry(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { rrq->disabled = (ic->ic_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 = ic->ic_txlifetime * 1024; 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 = ic->ic_txmin; break; case IW_RETRY_MAX: rrq->flags |= IW_RETRY_MAX; rrq->value = ic->ic_txmax; break; } break; } } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwretry); int ieee80211_ioctl_siwtxpow(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { int fixed, disabled; fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED); disabled = (fixed && ic->ic_bss->ni_txpower == 0); if (rrq->fixed) { if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0) return -EOPNOTSUPP; if (!(IEEE80211_TXPOWER_MIN < rrq->value && rrq->value < IEEE80211_TXPOWER_MAX)) return -EINVAL; ic->ic_txpowlimit = rrq->value; ic->ic_flags |= IEEE80211_F_TXPOW_FIXED; } else { if (!fixed) /* no change */ return 0; ic->ic_flags &= ~IEEE80211_F_TXPOW_FIXED; } return IS_UP(ic->ic_dev) ? -(*ic->ic_reset)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwtxpow); int ieee80211_ioctl_giwtxpow(struct ieee80211com *ic, struct iw_request_info *info, struct iw_param *rrq, char *extra) { rrq->value = ic->ic_txpowlimit; if (!(IEEE80211_TXPOWER_MIN < rrq->value && rrq->value < IEEE80211_TXPOWER_MAX)) rrq->value = IEEE80211_TXPOWER_MAX; rrq->fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED) != 0; rrq->disabled = (rrq->fixed && rrq->value == 0); rrq->flags = IW_TXPOW_MWATT; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwtxpow); /* is obsolete iwlist ap */ int ieee80211_ioctl_iwaplist(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *extra) { struct ieee80211_node_table *nt; struct ieee80211_node *ni; struct sockaddr addr[IW_MAX_AP]; struct iw_quality qual[IW_MAX_AP]; int i; i = 0; if (ic->ic_opmode == IEEE80211_M_STA) nt = &ic->ic_scan; else nt = &ic->ic_sta; /* * TODO: use ieee80211_iterate_nodes(&ic->ic_scan,func,&args) * also create a func like bsd does: wi_read_ap_result() */ IEEE80211_NODE_LOCK(nt); TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_IBSS) { if (ni == ic->ic_bss) /* don't include BSS node */ continue; IEEE80211_ADDR_COPY(addr[i].sa_data, ni->ni_macaddr); } else IEEE80211_ADDR_COPY(addr[i].sa_data, ni->ni_bssid); addr[i].sa_family = ARPHRD_ETHER; set_quality(&qual[i], (*ic->ic_node_getrssi)(ni)); if (++i >= IW_MAX_AP) break; } IEEE80211_NODE_UNLOCK(nt); data->length = i; memcpy(extra, &addr, i*sizeof(addr[0])); data->flags = 1; /* signal quality present (sort of) */ memcpy(extra + i*sizeof(addr[0]), &qual, i*sizeof(qual[i])); return 0; } EXPORT_SYMBOL(ieee80211_ioctl_iwaplist); #ifdef SIOCGIWSCAN int ieee80211_ioctl_siwscan(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *extra) { u_char *chanlist = ic->ic_chan_active; //u_char *chanlist = ic->ic_chan_avail; /* TODO: check */ int i; if (ic->ic_opmode == IEEE80211_M_HOSTAP) return -EINVAL; /* * 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(ic->ic_dev)) return -ENETDOWN; if (ic->ic_state == IEEE80211_S_SCAN && (ic->ic_flags & (IEEE80211_F_SCAN|IEEE80211_F_ASCAN))) { return -EINPROGRESS; } if (ic->ic_ibss_chan == NULL || isclr(chanlist, ieee80211_chan2ieee(ic, ic->ic_ibss_chan))) { for (i = 0; i <= IEEE80211_CHAN_MAX; i++) if (isset(chanlist, i)) { ic->ic_ibss_chan = &ic->ic_channels[i]; goto found; } return -EINVAL; /* no active channels */ found: ; } if (ic->ic_bss->ni_chan == IEEE80211_CHAN_ANYC || isclr(chanlist, ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan))) ic->ic_bss->ni_chan = ic->ic_ibss_chan; memcpy(ic->ic_chan_active, chanlist, sizeof(ic->ic_chan_active)); /* * We force the state to INIT before calling ieee80211_new_state * to get ieee80211_begin_scan called. We really want to scan w/o * altering the current state but that's not possible right now. */ /* XXX handle proberequest case */ if (ic->ic_state != IEEE80211_S_INIT) ieee80211_new_state(ic, IEEE80211_S_INIT, -1); ieee80211_new_state(ic, IEEE80211_S_SCAN, 1); return 0; } EXPORT_SYMBOL(ieee80211_ioctl_siwscan); #if WIRELESS_EXT > 14 /* * 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; u_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]); return (i == ielen ? p - (u_int8_t *)buf : 0); } #endif /* WIRELESS_EXT > 14 */ int ieee80211_ioctl_giwscan(struct ieee80211com *ic, struct iw_request_info *info, struct iw_point *data, char *extra) { char *current_ev = extra; struct iw_event iwe; struct read_ap_args args; /* XXX use generation number and always return current results */ if ((ic->ic_flags & (IEEE80211_F_SCAN|IEEE80211_F_ASCAN)) && !(ic->ic_flags & IEEE80211_F_SSCAN)) { /* * Still scanning, indicate the caller should try again. */ return -EAGAIN; } /* * Do two passes to insure 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. */ args.i = 0; args.iwe = &iwe; args.start = extra; args.current_ev = current_ev; args.mode = ic->ic_flags & IEEE80211_F_WPA; ieee80211_iterate_nodes(&ic->ic_scan, read_ap_result, &args); args.mode = args.mode ? 0 : IEEE80211_F_WPA; ieee80211_iterate_nodes(&ic->ic_scan, read_ap_result, &args); data->length = args.current_ev - extra; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_giwscan); #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; } int ieee80211_ioctl_setparam(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct ieee80211_rsnparms *rsn = &ic->ic_bss->ni_rsn; int *i = (int *) extra; int param = i[0]; /* parameter id is 1st */ int value = i[1]; /* NB: most values are TYPE_INT */ struct ifreq ifr; int retv = 0; int j, caps; const struct ieee80211_authenticator *auth; const struct ieee80211_aclator *acl; switch (param) { case IEEE80211_PARAM_TURBO: if (!ic->ic_media.ifm_cur) return -EINVAL; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media; if (value) ifr.ifr_media |= IFM_IEEE80211_TURBO; else ifr.ifr_media &= ~IFM_IEEE80211_TURBO; retv = ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); break; case IEEE80211_PARAM_MODE: if (!ic->ic_media.ifm_cur) return -EINVAL; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media &~ IFM_MMASK; ifr.ifr_media |= IFM_MAKEMODE(value); retv = ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); break; 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; break; default: return -EINVAL; } switch (value) { case IEEE80211_AUTH_WPA: /* WPA w/ 802.1x */ ic->ic_flags |= IEEE80211_F_PRIVACY; value = IEEE80211_AUTH_8021X; break; case IEEE80211_AUTH_OPEN: /* open */ ic->ic_flags &= ~(IEEE80211_F_WPA|IEEE80211_F_PRIVACY); break; case IEEE80211_AUTH_SHARED: /* shared-key */ case IEEE80211_AUTH_8021X: /* 802.1x */ ic->ic_flags &= ~IEEE80211_F_WPA; /* both require a key so mark the PRIVACY capability */ ic->ic_flags |= IEEE80211_F_PRIVACY; break; case IEEE80211_AUTH_AUTO: /* auto */ ic->ic_flags &= ~IEEE80211_F_WPA; /* XXX PRIVACY handling? */ /* XXX what's the right way to do this? */ break; } /* NB: authenticator attach/detach happens on state change */ ic->ic_bss->ni_authmode = value; /* XXX mixed/mode/usage? */ ic->ic_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_curmode == IEEE80211_MODE_11G || ic->ic_curmode == IEEE80211_MODE_TURBO_G) retv = ENETRESET; break; case IEEE80211_PARAM_MCASTCIPHER: /* XXX s/w implementations */ if ((ic->ic_caps & cipher2cap(value)) == 0) return -EINVAL; rsn->rsn_mcastcipher = value; if (ic->ic_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_MCASTKEYLEN: if (!(0 < value && value < IEEE80211_KEYBUF_SIZE)) return -EINVAL; /* XXX no way to verify driver capability */ rsn->rsn_mcastkeylen = value; if (ic->ic_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_UCASTCIPHERS: /* * Convert cipher set to equivalent capabilities. * NB: this logic intentionally ignores unknown and * unsupported ciphers so folks can specify 0xff or * similar and get all available ciphers. */ caps = 0; for (j = 1; j < 32; j++) /* NB: skip WEP */ if (value & (1<ic_caps; /* restrict to supported ciphers */ /* XXX verify ciphers ok for unicast use? */ /* XXX disallow if running as it'll have no effect */ rsn->rsn_ucastcipherset = caps; if (ic->ic_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_UCASTCIPHER: if ((rsn->rsn_ucastcipherset & cipher2cap(value)) == 0) return -EINVAL; rsn->rsn_ucastcipher = value; break; case IEEE80211_PARAM_UCASTKEYLEN: if (!(0 < value && 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 (ic->ic_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_RSNCAPS: /* XXX check */ rsn->rsn_caps = value; if (ic->ic_flags & IEEE80211_F_WPA) retv = ENETRESET; break; case IEEE80211_PARAM_WPA: if (value > 3) return -EINVAL; /* XXX verify ciphers available */ ic->ic_flags &= ~IEEE80211_F_WPA; switch (value) { case 1: ic->ic_flags |= IEEE80211_F_WPA1; break; case 2: ic->ic_flags |= IEEE80211_F_WPA2; break; case 3: ic->ic_flags |= IEEE80211_F_WPA1 | IEEE80211_F_WPA2; break; } retv = ENETRESET; /* XXX? */ break; case IEEE80211_PARAM_ROAMING: if (!(IEEE80211_ROAMING_DEVICE <= value && value <= IEEE80211_ROAMING_MANUAL)) return -EINVAL; ic->ic_roaming = value; break; case IEEE80211_PARAM_PRIVACY: if (value) { /* XXX check for key state? */ ic->ic_flags |= IEEE80211_F_PRIVACY; } else ic->ic_flags &= ~IEEE80211_F_PRIVACY; break; case IEEE80211_PARAM_DROPUNENCRYPTED: if (value) ic->ic_flags |= IEEE80211_F_DROPUNENC; else ic->ic_flags &= ~IEEE80211_F_DROPUNENC; break; case IEEE80211_PARAM_COUNTERMEASURES: if (value) { if ((ic->ic_flags & IEEE80211_F_WPA) == 0) return -EINVAL; ic->ic_flags |= IEEE80211_F_COUNTERM; } else ic->ic_flags &= ~IEEE80211_F_COUNTERM; break; case IEEE80211_PARAM_DRIVER_CAPS: ic->ic_caps = value; /* NB: for testing */ break; case IEEE80211_PARAM_MACCMD: acl = ic->ic_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(ic)) return -EINVAL; ic->ic_acl = acl; } acl->iac_setpolicy(ic, value); break; case IEEE80211_MACCMD_FLUSH: if (acl != NULL) acl->iac_flush(ic); /* NB: silently ignore when not in use */ break; case IEEE80211_MACCMD_DETACH: if (acl != NULL) { ic->ic_acl = NULL; acl->iac_detach(ic); } break; } break; case IEEE80211_PARAM_WME: if (ic->ic_opmode != IEEE80211_M_STA) return -EINVAL; if (value) ic->ic_flags |= IEEE80211_F_WME; else ic->ic_flags &= ~IEEE80211_F_WME; break; case IEEE80211_PARAM_HIDESSID: if (value) ic->ic_flags |= IEEE80211_F_HIDESSID; else ic->ic_flags &= ~IEEE80211_F_HIDESSID; retv = ENETRESET; break; case IEEE80211_PARAM_APBRIDGE: if (value == 0) ic->ic_flags |= IEEE80211_F_NOBRIDGE; else ic->ic_flags &= ~IEEE80211_F_NOBRIDGE; break; case IEEE80211_PARAM_INACT: ic->ic_inact_run = value / IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_AUTH: ic->ic_inact_auth = value / IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_INIT: ic->ic_inact_init = value / IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_IBSS: if (!ic->ic_media.ifm_cur) return -EINVAL; memset(&ifr, 0, sizeof(ifr)); /* NB: remove any fixed-rate at the same time */ ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media &~ (IFM_OMASK | IFM_TMASK); if (value==0) { // IFM_FLAG0 signals adhoc demo mode ifr.ifr_media |= (IFM_IEEE80211_ADHOC|IFM_FLAG0); } else { ifr.ifr_media |= IFM_IEEE80211_ADHOC; ifr.ifr_media &= ~IFM_FLAG0; } retv = ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA); break; case IEEE80211_PARAM_PUREG: if (value) ic->ic_flags |= IEEE80211_F_PUREG; else ic->ic_flags &= ~IEEE80211_F_PUREG; /* NB: reset only if we're operating on an 11g channel */ if (ic->ic_curmode == IEEE80211_MODE_11G) retv = ENETRESET; break; case IEEE80211_PARAM_WDSONLY: ic->ic_wdsonly = value; break; case IEEE80211_PARAM_RESET: ic->ic_init(ic->ic_dev); break; default: retv = EOPNOTSUPP; break; } if (retv == ENETRESET) retv = IS_UP_AUTO(ic) ? (*ic->ic_init)(ic->ic_dev) : 0; return -retv; } EXPORT_SYMBOL(ieee80211_ioctl_setparam); int ieee80211_ioctl_getparam(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct ieee80211_rsnparms *rsn = &ic->ic_bss->ni_rsn; struct ifmediareq imr; int *param = (int *) extra; u_int m; switch (param[0]) { case IEEE80211_PARAM_TURBO: (*ic->ic_media.ifm_status)(ic->ic_dev, &imr); param[0] = (imr.ifm_active & IFM_IEEE80211_TURBO) != 0; break; case IEEE80211_PARAM_MODE: (*ic->ic_media.ifm_status)(ic->ic_dev, &imr); switch (IFM_MODE(imr.ifm_active)) { case IFM_IEEE80211_11A: param[0] = 1; break; case IFM_IEEE80211_11B: param[0] = 2; break; case IFM_IEEE80211_11G: param[0] = 3; break; case IFM_IEEE80211_FH: param[0] = 4; break; case IFM_AUTO: param[0] = 0; break; default: return -EINVAL; } break; case IEEE80211_PARAM_AUTHMODE: if (ic->ic_flags & IEEE80211_F_WPA) param[0] = IEEE80211_AUTH_WPA; else param[0] = ic->ic_bss->ni_authmode; break; case IEEE80211_PARAM_PROTMODE: param[0] = ic->ic_protmode; 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] = 0; for (m = 0x1; m != 0; m <<= 1) if (rsn->rsn_ucastcipherset & m) param[0] |= 1<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 (ic->ic_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] = (ic->ic_flags & IEEE80211_F_PRIVACY) != 0; break; case IEEE80211_PARAM_DROPUNENCRYPTED: param[0] = (ic->ic_flags & IEEE80211_F_DROPUNENC) != 0; break; case IEEE80211_PARAM_COUNTERMEASURES: param[0] = (ic->ic_flags & IEEE80211_F_COUNTERM) != 0; break; case IEEE80211_PARAM_DRIVER_CAPS: param[0] = ic->ic_caps; break; case IEEE80211_PARAM_WME: param[0] = (ic->ic_flags & IEEE80211_F_WME) != 0; break; case IEEE80211_PARAM_HIDESSID: param[0] = (ic->ic_flags & IEEE80211_F_HIDESSID) != 0; break; case IEEE80211_PARAM_APBRIDGE: param[0] = (ic->ic_flags & IEEE80211_F_NOBRIDGE) == 0; break; case IEEE80211_PARAM_INACT: param[0] = ic->ic_inact_run * IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_AUTH: param[0] = ic->ic_inact_auth * IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_INACT_INIT: param[0] = ic->ic_inact_init * IEEE80211_INACT_WAIT; break; case IEEE80211_PARAM_IBSS: param[0] = (ic->ic_opmode == IEEE80211_M_IBSS); break; case IEEE80211_PARAM_PUREG: param[0] = (ic->ic_flags & IEEE80211_F_PUREG) != 0; break; case IEEE80211_PARAM_WDSONLY: param[0] = ic->ic_wdsonly; break; default: return -EOPNOTSUPP; } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_getparam); int ieee80211_ioctl_setoptie(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { union iwreq_data *u = w; 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 (ic->ic_opmode != IEEE80211_M_STA) return -EINVAL; /* NB: data.length is validated by the wireless extensions code */ MALLOC(ie, void *, u->data.length, M_DEVBUF, M_WAITOK); if (ie == NULL) return -ENOMEM; memcpy(ie, extra, u->data.length); /* XXX sanity check data? */ if (ic->ic_opt_ie != NULL) FREE(ic->ic_opt_ie, M_DEVBUF); ic->ic_opt_ie = ie; ic->ic_opt_ie_len = u->data.length; return 0; } EXPORT_SYMBOL(ieee80211_ioctl_setoptie); int ieee80211_ioctl_getoptie(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { union iwreq_data *u = w; if (ic->ic_opt_ie == NULL) return -EINVAL; if (u->data.length < ic->ic_opt_ie_len) return -EINVAL; u->data.length = ic->ic_opt_ie_len; memcpy(extra, ic->ic_opt_ie, u->data.length); return 0; } EXPORT_SYMBOL(ieee80211_ioctl_getoptie); int ieee80211_ioctl_setkey(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct ieee80211req_key *ik = (struct ieee80211req_key *)extra; struct ieee80211_node *ni; struct ieee80211_key *wk; u_int16_t kid; int error; /* NB: cipher support is verified by ieee80211_crypt_newkey */ /* NB: this also checks ik->ik_keylen > sizeof(wk->wk_key) */ if (ik->ik_keylen > sizeof(ik->ik_keydata)) return -E2BIG; kid = ik->ik_keyix; if (kid == IEEE80211_KEYIX_NONE) { /* XXX unicast keys currently must be tx/rx */ if (ik->ik_flags != (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV)) return -EINVAL; if (ic->ic_opmode == IEEE80211_M_STA) { ni = ic->ic_bss; if (!IEEE80211_ADDR_EQ(ik->ik_macaddr, ni->ni_bssid)) return -EADDRNOTAVAIL; } else ni = ieee80211_find_node(&ic->ic_sta, ik->ik_macaddr); if (ni == NULL) return -ENOENT; wk = &ni->ni_ucastkey; } else { if (kid >= IEEE80211_WEP_NKID) return -EINVAL; wk = &ic->ic_nw_keys[kid]; ni = NULL; ik->ik_flags |= IEEE80211_KEY_GROUP; } error = 0; ieee80211_key_update_begin(ic); if (ieee80211_crypto_newkey(ic, ik->ik_type, ik->ik_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; wk->wk_keyrsc = ik->ik_keyrsc; wk->wk_keytsc = 0; /* new key, reset */ wk->wk_flags |= ik->ik_flags & (IEEE80211_KEY_XMIT|IEEE80211_KEY_RECV); memset(wk->wk_key, 0, sizeof(wk->wk_key)); memcpy(wk->wk_key, ik->ik_keydata, ik->ik_keylen); if (!ieee80211_crypto_setkey(ic, wk, ni != NULL ? ni->ni_macaddr : ik->ik_macaddr)) error = -EIO; else if ((ik->ik_flags & IEEE80211_KEY_DEFAULT)) ic->ic_def_txkey = kid; } else error = -ENXIO; ieee80211_key_update_end(ic); if (ni != NULL) ieee80211_free_node(ni); return error; } EXPORT_SYMBOL(ieee80211_ioctl_setkey); static int ieee80211_ioctl_getkey(struct ieee80211com *ic, struct iwreq *iwr) { struct ieee80211_node *ni; struct ieee80211req_key ik; struct ieee80211_key *wk; const struct ieee80211_cipher *cip; u_int kid; if (iwr->u.data.length != sizeof(ik)) return -EINVAL; if (copy_from_user(&ik, iwr->u.data.pointer, sizeof(ik))) return -EFAULT; kid = ik.ik_keyix; if (kid == IEEE80211_KEYIX_NONE) { ni = ieee80211_find_node(&ic->ic_sta, ik.ik_macaddr); if (ni == NULL) return -EINVAL; /* XXX */ wk = &ni->ni_ucastkey; } else { if (kid >= IEEE80211_WEP_NKID) return -EINVAL; wk = &ic->ic_nw_keys[kid]; IEEE80211_ADDR_COPY(&ik.ik_macaddr, ic->ic_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 == ic->ic_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; 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_free_node(ni); return (copy_to_user(iwr->u.data.pointer, &ik, sizeof(ik)) ? -EFAULT : 0); } int ieee80211_ioctl_delkey(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct ieee80211req_del_key *dk = (struct ieee80211req_del_key *)extra; int kid; kid = dk->idk_keyix; /* XXX u_int8_t -> u_int16_t */ if (dk->idk_keyix == (u_int8_t) IEEE80211_KEYIX_NONE) { struct ieee80211_node *ni = ieee80211_find_node(&ic->ic_sta, dk->idk_macaddr); if (ni == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: node not found\n", __func__); return -ENOENT; } /* XXX error return */ ieee80211_crypto_delkey(ic, &ni->ni_ucastkey); ieee80211_free_node(ni); } else { if (kid >= IEEE80211_WEP_NKID) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: key id %d >= %d\n", __func__, kid, IEEE80211_WEP_NKID); return -EINVAL; } /* XXX error return */ ieee80211_crypto_delkey(ic, &ic->ic_nw_keys[kid]); } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_delkey); static void domlme(void *arg, struct ieee80211_node *ni) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211req_mlme *mlme = arg; if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ic, ni, mlme->im_op == IEEE80211_MLME_DEAUTH ? IEEE80211_FC0_SUBTYPE_DEAUTH : IEEE80211_FC0_SUBTYPE_DISASSOC, mlme->im_reason); } ieee80211_node_leave(ic, ni); } int ieee80211_ioctl_setmlme(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct ieee80211req_mlme *mlme = (struct ieee80211req_mlme *)extra; struct ieee80211_node *ni; switch (mlme->im_op) { case IEEE80211_MLME_ASSOC: IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: assoc\n", __func__); if (ic->ic_opmode != IEEE80211_M_STA) return -EINVAL; /* XXX must be in S_SCAN state? */ if (mlme->im_ssid_len != 0) { /* * Desired ssid specified; must match both bssid and * ssid to distinguish ap advertising multiple ssid's. */ ni = ieee80211_find_node_with_ssid(&ic->ic_scan, mlme->im_macaddr, mlme->im_ssid_len, mlme->im_ssid); } else { /* * Normal case; just match bssid. */ ni = ieee80211_find_node(&ic->ic_scan, mlme->im_macaddr); } if (ni == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: node not found\n", __func__); return -ENOENT; } if (!ieee80211_sta_join(ic, ni)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: join failed\n", __func__); ieee80211_free_node(ni); return -EINVAL; } break; case IEEE80211_MLME_DISASSOC: case IEEE80211_MLME_DEAUTH: IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: %s\n", __func__, (mlme->im_op == IEEE80211_MLME_DISASSOC ? "disassoc" : "deauth")); switch (ic->ic_opmode) { case IEEE80211_M_STA: /* XXX not quite right */ ieee80211_new_state(ic, 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, ic->ic_dev->broadcast)) { if ((ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr)) == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: node not found\n", __func__); return -ENOENT; } domlme(mlme, ni); ieee80211_free_node(ni); } else { ieee80211_iterate_nodes(&ic->ic_sta, domlme, mlme); } break; default: return -EINVAL; } break; case IEEE80211_MLME_AUTHORIZE: case IEEE80211_MLME_UNAUTHORIZE: IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: %sauthorize\n", __func__, (mlme->im_op == IEEE80211_MLME_UNAUTHORIZE ? "un" : "")); if (ic->ic_opmode != IEEE80211_M_HOSTAP) return -EINVAL; ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr); if (ni == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: node not found\n", __func__); return -ENOENT; } if (mlme->im_op == IEEE80211_MLME_AUTHORIZE) ieee80211_node_authorize(ni); else ieee80211_node_unauthorize(ni); ieee80211_free_node(ni); break; case IEEE80211_MLME_CLEAR_STATS: IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: clear stats\n", __func__); if (ic->ic_opmode != IEEE80211_M_HOSTAP) return -EINVAL; ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr); if (ni == NULL) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_MLME, "%s: node not found\n", __func__); return -ENOENT; } /* clear statistics */ memset(&ni->ni_stats, 0, sizeof(struct ieee80211_nodestats)); ieee80211_free_node(ni); break; default: return -EINVAL; } return 0; } EXPORT_SYMBOL(ieee80211_ioctl_setmlme); int ieee80211_ioctl_addmac(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct sockaddr *sa = (struct sockaddr *)extra; const struct ieee80211_aclator *acl = ic->ic_acl; if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(ic)) return -EINVAL; ic->ic_acl = acl; } acl->iac_add(ic, sa->sa_data); return 0; } EXPORT_SYMBOL(ieee80211_ioctl_addmac); int ieee80211_ioctl_delmac(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct sockaddr *sa = (struct sockaddr *)extra; const struct ieee80211_aclator *acl = ic->ic_acl; if (acl == NULL) { acl = ieee80211_aclator_get("mac"); if (acl == NULL || !acl->iac_attach(ic)) return -EINVAL; ic->ic_acl = acl; } acl->iac_remove(ic, sa->sa_data); return 0; } EXPORT_SYMBOL(ieee80211_ioctl_delmac); int ieee80211_ioctl_chanlist(struct ieee80211com *ic, struct iw_request_info *info, void *w, char *extra) { struct ieee80211req_chanlist *list = (struct ieee80211req_chanlist *)extra; u_char chanlist[roundup(IEEE80211_CHAN_MAX, NBBY)]; int i, j; 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; 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); } if (ic->ic_ibss_chan == NULL || isclr(chanlist, ieee80211_chan2ieee(ic, ic->ic_ibss_chan))) { for (i = 0; i <= IEEE80211_CHAN_MAX; i++) if (isset(chanlist, i)) { ic->ic_ibss_chan = &ic->ic_channels[i]; goto found; } return EINVAL; /* no active channels */ found: ; } memcpy(ic->ic_chan_active, chanlist, sizeof(ic->ic_chan_active)); if (ic->ic_bss->ni_chan == IEEE80211_CHAN_ANYC || isclr(chanlist, ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan))) ic->ic_bss->ni_chan = ic->ic_ibss_chan; return IS_UP_AUTO(ic) ? -(*ic->ic_init)(ic->ic_dev) : 0; } EXPORT_SYMBOL(ieee80211_ioctl_chanlist); static int ieee80211_ioctl_getwpaie(struct ieee80211com *ic, struct iwreq *iwr) { 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) return -EINVAL; /* XXX */ 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 > (int)sizeof(wpaie.wpa_ie)) ielen = sizeof(wpaie.wpa_ie); memcpy(wpaie.wpa_ie, ni->ni_wpa_ie, ielen); } ieee80211_free_node(ni); return (copy_to_user(iwr->u.data.pointer, &wpaie, sizeof(wpaie)) ? -EFAULT : 0); } static int ieee80211_ioctl_getstastats(struct ieee80211com *ic, struct iwreq *iwr) { 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 -EINVAL; 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) return -EINVAL; /* XXX */ /* NB: copy out only the statistics */ error = copy_to_user((u_int8_t *) iwr->u.data.pointer + off, &ni->ni_stats, iwr->u.data.length - off); ieee80211_free_node(ni); return (error ? -EFAULT : 0); } #define IW_PRIV_TYPE_OPTIE IW_PRIV_TYPE_BYTE | IEEE80211_MAX_OPT_IE #define IW_PRIV_TYPE_KEY \ IW_PRIV_TYPE_BYTE | sizeof(struct ieee80211req_key) #define IW_PRIV_TYPE_DELKEY \ IW_PRIV_TYPE_BYTE | sizeof(struct ieee80211req_del_key) #define IW_PRIV_TYPE_MLME \ IW_PRIV_TYPE_BYTE | sizeof(struct ieee80211req_mlme) #define IW_PRIV_TYPE_CHANLIST \ IW_PRIV_TYPE_BYTE | sizeof(struct ieee80211req_chanlist) 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_CHANLIST, IW_PRIV_TYPE_CHANLIST | IW_PRIV_SIZE_FIXED, 0,"chanlist" }, { IEEE80211_IOCTL_WDSADD, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"wdsadd" }, { IEEE80211_IOCTL_WDSDEL, IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"wdsdel" }, #if WIRELESS_EXT >= 12 { 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_TURBO, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "turbo" }, { IEEE80211_PARAM_TURBO, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_turbo" }, { IEEE80211_PARAM_MODE, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mode" }, { IEEE80211_PARAM_MODE, 0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_mode" }, { 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" 'cuz iwpriv command names * must be = 12 */ }; void ieee80211_ioctl_iwsetup(struct iw_handler_def *def) { #define N(a) (sizeof(a)/sizeof(a[0])) def->private_args = (struct iw_priv_args *) ieee80211_priv_args; def->num_private_args = N(ieee80211_priv_args); #undef N } EXPORT_SYMBOL(ieee80211_ioctl_iwsetup); /* * Handle private ioctl requests. */ int ieee80211_ioctl(struct ieee80211com *ic, struct ifreq *ifr, int cmd) { switch (cmd) { case SIOCG80211STATS: return copy_to_user(ifr->ifr_data, &ic->ic_stats, sizeof (ic->ic_stats)) ? -EFAULT : 0; case IEEE80211_IOCTL_GETKEY: return ieee80211_ioctl_getkey(ic, (struct iwreq *) ifr); case IEEE80211_IOCTL_GETWPAIE: return ieee80211_ioctl_getwpaie(ic, (struct iwreq *) ifr); case IEEE80211_IOCTL_GETSTASTATS: return ieee80211_ioctl_getstastats(ic, (struct iwreq *) ifr); } return -EOPNOTSUPP; } EXPORT_SYMBOL(ieee80211_ioctl); #endif /* CONFIG_NET_WIRELESS */