diff options
author | sam <sam@FreeBSD.org> | 2003-06-23 16:55:01 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2003-06-23 16:55:01 +0000 |
commit | 505adc686a0029ab4aa4877118f251a1894a2603 (patch) | |
tree | 66ac01a331d111073b96a9870f6a5b21af78a8e1 /sys/net80211/ieee80211_node.c | |
parent | 93890f9f0a6aff4f62adc5d1fe890af26121c947 (diff) | |
download | FreeBSD-src-505adc686a0029ab4aa4877118f251a1894a2603.zip FreeBSD-src-505adc686a0029ab4aa4877118f251a1894a2603.tar.gz |
new 802.11 layer:
o code reorg (relative to old netbsd-derived code) for future growth
o drivers now specify available channels and rates and 802.11 layer handles
almost all ifmedia actions
o multi-mode support for 11a/b/g devices
o 11g protocol additions (incomplete)
o new element id additions (for other than 11g)
o node/station table redone for proper locking and to eliminate driver
incestuousness
o split device flags and capabilities to reduce confusion and provide room
for expansion
o incomplete power management infrastructure (need to revisit)
o incomplete hooks for software retry
o more...
Diffstat (limited to 'sys/net80211/ieee80211_node.c')
-rw-r--r-- | sys/net80211/ieee80211_node.c | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c new file mode 100644 index 0000000..5ed7fd1 --- /dev/null +++ b/sys/net80211/ieee80211_node.c @@ -0,0 +1,557 @@ +/*- + * Copyright (c) 2002, 2003 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_inet.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/endian.h> +#include <sys/errno.h> +#include <sys/bus.h> +#include <sys/proc.h> +#include <sys/sysctl.h> + +#include <machine/atomic.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_llc.h> + +#include <net80211/ieee80211_var.h> + +#include <net/bpf.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/if_ether.h> +#endif + +static struct ieee80211_node *ieee80211_node_alloc(struct ieee80211com *); +static void ieee80211_node_free(struct ieee80211com *, struct ieee80211_node *); +static void ieee80211_node_copy(struct ieee80211com *, + struct ieee80211_node *, const struct ieee80211_node *); +static void ieee80211_setup_node(struct ieee80211com *ic, + struct ieee80211_node *ni, u_int8_t *macaddr); +static void _ieee80211_free_node(struct ieee80211com *, + struct ieee80211_node *); + +void +ieee80211_node_attach(struct ifnet *ifp) +{ + struct ieee80211com *ic = (void *)ifp; + + /* XXX need unit */ + mtx_init(&ic->ic_nodelock, ifp->if_name, "802.11 node table", MTX_DEF); + TAILQ_INIT(&ic->ic_node); + ic->ic_node_alloc = ieee80211_node_alloc; + ic->ic_node_free = ieee80211_node_free; + ic->ic_node_copy = ieee80211_node_copy; + ic->ic_bss = (*ic->ic_node_alloc)(ic); + /* XXX KASSERT != NULL? */ +} + +void +ieee80211_node_detach(struct ifnet *ifp) +{ + struct ieee80211com *ic = (void *)ifp; + + if (ic->ic_bss != NULL) + (*ic->ic_node_free)(ic, ic->ic_bss); + ieee80211_free_allnodes(ic); + mtx_destroy(&ic->ic_nodelock); +} + +/* + * AP scanning support. + */ + +/* + * Initialize the active channel set based on the set + * of available channels and the current PHY mode. + */ +void +ieee80211_reset_scan(struct ifnet *ifp) +{ + struct ieee80211com *ic = (void *)ifp; + + memcpy(ic->ic_chan_scan, ic->ic_chan_active, + sizeof(ic->ic_chan_active)); +} + +/* + * Begin an active scan. + */ +void +ieee80211_begin_scan(struct ifnet *ifp, struct ieee80211_node *ni) +{ + struct ieee80211com *ic = (void *)ifp; + + if (ifp->if_flags & IFF_DEBUG) + if_printf(ifp, "begin %s scan\n", + ic->ic_opmode != IEEE80211_M_HOSTAP ? + "active" : "passive"); + + ieee80211_reset_scan(ifp); + /* + * Flush any previously seen AP's. Note that this + * assumes we don't act as both an AP and a station, + * otherwise we'll potentially flush state of stations + * associated with us. + */ + ieee80211_free_allnodes(ic); + + clrbit(ic->ic_chan_scan, ieee80211_chan2ieee(ic, ni->ni_chan)); + if (ic->ic_opmode != IEEE80211_M_HOSTAP) { + ic->ic_flags |= IEEE80211_F_ASCAN; + IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_PROBE_REQ, 0); + } +} + +/* + * Switch to the next channel marked for scanning. + */ +void +ieee80211_next_scan(struct ifnet *ifp) +{ + struct ieee80211com *ic = (void *)ifp; + struct ieee80211_channel *chan; + + chan = ic->ic_bss->ni_chan; + for (;;) { + if (++chan > &ic->ic_channels[IEEE80211_CHAN_MAX]) + chan = &ic->ic_channels[0]; + if (isset(ic->ic_chan_scan, ieee80211_chan2ieee(ic, chan))) { + /* + * Honor channels marked passive-only + * during an active scan. + */ + if ((ic->ic_flags & IEEE80211_F_ASCAN) == 0 || + (chan->ic_flags & IEEE80211_CHAN_PASSIVE) == 0) + break; + } + if (chan == ic->ic_bss->ni_chan) { + ieee80211_end_scan(ifp); + return; + } + } + clrbit(ic->ic_chan_scan, ieee80211_chan2ieee(ic, chan)); + IEEE80211_DPRINTF(("ieee80211_next_scan: chan %d->%d\n", + ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan), + ieee80211_chan2ieee(ic, chan))); + ic->ic_bss->ni_chan = chan; + ieee80211_new_state(ifp, IEEE80211_S_SCAN, -1); +} + +void +ieee80211_create_ibss(struct ieee80211com* ic, struct ieee80211_channel *chan) +{ + struct ieee80211_node *ni; + struct ifnet *ifp = &ic->ic_if; + + ni = ic->ic_bss; + if (ifp->if_flags & IFF_DEBUG) + if_printf(ifp, "creating ibss\n"); + ic->ic_flags |= IEEE80211_F_SIBSS; + ni->ni_chan = chan; + ni->ni_rates = ic->ic_sup_rates[ieee80211_chan2mode(ic, ni->ni_chan)]; + IEEE80211_ADDR_COPY(ni->ni_macaddr, ic->ic_myaddr); + IEEE80211_ADDR_COPY(ni->ni_bssid, ic->ic_myaddr); + if (ic->ic_opmode == IEEE80211_M_IBSS) + ni->ni_bssid[0] |= 0x02; /* local bit for IBSS */ + ni->ni_esslen = ic->ic_des_esslen; + memcpy(ni->ni_essid, ic->ic_des_essid, ni->ni_esslen); + ni->ni_rssi = 0; + ni->ni_rstamp = 0; + ni->ni_rantenna = 0; + memset(ni->ni_tstamp, 0, sizeof(ni->ni_tstamp)); + ni->ni_intval = ic->ic_lintval; + ni->ni_capinfo = IEEE80211_CAPINFO_IBSS; + if (ic->ic_flags & IEEE80211_F_WEPON) + ni->ni_capinfo |= IEEE80211_CAPINFO_PRIVACY; + if (ic->ic_phytype == IEEE80211_T_FH) { + ni->ni_fhdwell = 200; /* XXX */ + ni->ni_fhindex = 1; + } + ieee80211_new_state(ifp, IEEE80211_S_RUN, -1); +} + +/* + * Complete a scan of potential channels. + */ +void +ieee80211_end_scan(struct ifnet *ifp) +{ + struct ieee80211com *ic = (void *)ifp; + struct ieee80211_node *ni, *nextbs, *selbs; + u_int8_t rate; + int i, fail; + + ic->ic_flags &= ~IEEE80211_F_ASCAN; + ni = TAILQ_FIRST(&ic->ic_node); + + if (ic->ic_opmode == IEEE80211_M_HOSTAP) { + /* XXX off stack? */ + u_char occupied[roundup(IEEE80211_CHAN_MAX, NBBY)]; + /* + * The passive scan to look for existing AP's completed, + * select a channel to camp on. Identify the channels + * that already have one or more AP's and try to locate + * an unnoccupied one. If that fails, pick a random + * channel from the active set. + */ + for (; ni != NULL; ni = nextbs) { + ieee80211_ref_node(ni); + nextbs = TAILQ_NEXT(ni, ni_list); + setbit(occupied, ieee80211_chan2ieee(ic, ni->ni_chan)); + ieee80211_free_node(ic, ni); + } + for (i = 0; i < IEEE80211_CHAN_MAX; i++) + if (isset(ic->ic_chan_active, i) && isclr(occupied, i)) + break; + if (i == IEEE80211_CHAN_MAX) { + fail = arc4random() & 3; /* random 0-3 */ + for (i = 0; i < IEEE80211_CHAN_MAX; i++) + if (isset(ic->ic_chan_active, i) && fail-- == 0) + break; + } + ieee80211_create_ibss(ic, &ic->ic_channels[i]); + return; + } + if (ni == NULL) { + IEEE80211_DPRINTF(("%s: no scan candidate\n", __func__)); + notfound: + if (ic->ic_opmode == IEEE80211_M_IBSS && + (ic->ic_flags & IEEE80211_F_IBSSON) && + ic->ic_des_esslen != 0) { + ieee80211_create_ibss(ic, ic->ic_ibss_chan); + return; + } + /* + * Reset the list of channels to scan and start again. + */ + ieee80211_reset_scan(ifp); + ieee80211_next_scan(ifp); + return; + } + selbs = NULL; + if (ifp->if_flags & IFF_DEBUG) + if_printf(ifp, "\tmacaddr bssid chan rssi rate ant flag wep essid\n"); + for (; ni != NULL; ni = nextbs) { + ieee80211_ref_node(ni); + nextbs = TAILQ_NEXT(ni, ni_list); + if (ni->ni_fails) { + /* + * The configuration of the access points may change + * during my scan. So delete the entry for the AP + * and retry to associate if there is another beacon. + */ + if (ni->ni_fails++ > 2) + ieee80211_free_node(ic, ni); + continue; + } + fail = 0; + if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, ni->ni_chan))) + fail |= 0x01; + if (ic->ic_des_chan != IEEE80211_CHAN_ANYC && + ni->ni_chan != ic->ic_des_chan) + fail |= 0x01; + if (ic->ic_opmode == IEEE80211_M_IBSS) { + if ((ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) == 0) + fail |= 0x02; + } else { + if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) + fail |= 0x02; + } + if (ic->ic_flags & IEEE80211_F_WEPON) { + if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) + fail |= 0x04; + } else { + if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) + fail |= 0x04; + } + rate = ieee80211_fix_rate(ic, ni, IEEE80211_F_DONEGO); + if (rate & IEEE80211_RATE_BASIC) + fail |= 0x08; + if (ic->ic_des_esslen != 0 && + (ni->ni_esslen != ic->ic_des_esslen || + memcmp(ni->ni_essid, ic->ic_des_essid, + ic->ic_des_esslen != 0))) + fail |= 0x10; + if ((ic->ic_flags & IEEE80211_F_DESBSSID) && + !IEEE80211_ADDR_EQ(ic->ic_des_bssid, ni->ni_bssid)) + fail |= 0x20; + if (ifp->if_flags & IFF_DEBUG) { + printf(" %c %s", fail ? '-' : '+', + ether_sprintf(ni->ni_macaddr)); + printf(" %s%c", ether_sprintf(ni->ni_bssid), + fail & 0x20 ? '!' : ' '); + printf(" %3d%c", ieee80211_chan2ieee(ic, ni->ni_chan), + fail & 0x01 ? '!' : ' '); + printf(" %+4d", ni->ni_rssi); + printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2, + fail & 0x08 ? '!' : ' '); + printf(" %3d", ni->ni_rantenna); + printf(" %4s%c", + (ni->ni_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" : + (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" : + "????", + fail & 0x02 ? '!' : ' '); + printf(" %3s%c ", + (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) ? + "wep" : "no", + fail & 0x04 ? '!' : ' '); + ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); + printf("%s\n", fail & 0x10 ? "!" : ""); + } + if (!fail) { + if (selbs == NULL) + selbs = ni; + else if (ni->ni_rssi > selbs->ni_rssi) { + ieee80211_unref_node(&selbs); + selbs = ni; + } else + ieee80211_unref_node(&ni); + } else { + ieee80211_unref_node(&ni); + } + } + if (selbs == NULL) + goto notfound; + (*ic->ic_node_copy)(ic, ic->ic_bss, selbs); + if (ic->ic_opmode == IEEE80211_M_IBSS) { + ieee80211_fix_rate(ic, ic->ic_bss, IEEE80211_F_DOFRATE | + IEEE80211_F_DONEGO | IEEE80211_F_DODEL); + if (ic->ic_bss->ni_rates.rs_nrates == 0) { + selbs->ni_fails++; + ieee80211_unref_node(&selbs); + goto notfound; + } + ieee80211_unref_node(&selbs); + ieee80211_new_state(ifp, IEEE80211_S_RUN, -1); + } else { + ieee80211_unref_node(&selbs); + ieee80211_new_state(ifp, IEEE80211_S_AUTH, -1); + } +} + +static struct ieee80211_node * +ieee80211_node_alloc(struct ieee80211com *ic) +{ + return malloc(sizeof(struct ieee80211_node), M_DEVBUF, + M_NOWAIT | M_ZERO); +} + +static void +ieee80211_node_free(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + free(ni, M_DEVBUF); +} + +static void +ieee80211_node_copy(struct ieee80211com *ic, + struct ieee80211_node *dst, const struct ieee80211_node *src) +{ + *dst = *src; +} + +static void +ieee80211_setup_node(struct ieee80211com *ic, + struct ieee80211_node *ni, u_int8_t *macaddr) +{ + int hash; + + IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); + hash = IEEE80211_NODE_HASH(macaddr); + ni->ni_refcnt = 1; /* mark referenced */ + mtx_lock(&ic->ic_nodelock); + TAILQ_INSERT_TAIL(&ic->ic_node, ni, ni_list); + LIST_INSERT_HEAD(&ic->ic_hash[hash], ni, ni_hash); + /* + * Note we don't enable the inactive timer when acting + * as a station. Nodes created in this mode represent + * AP's identified while scanning. If we time them out + * then several things happen: we can't return the data + * to users to show the list of AP's we encountered, and + * more importantly, we'll incorrectly deauthenticate + * ourself because the inactivity timer will kick us off. + */ + if (ic->ic_opmode != IEEE80211_M_STA) + ic->ic_inact_timer = IEEE80211_INACT_WAIT; + mtx_unlock(&ic->ic_nodelock); +} + +struct ieee80211_node * +ieee80211_alloc_node(struct ieee80211com *ic, u_int8_t *macaddr) +{ + struct ieee80211_node *ni = (*ic->ic_node_alloc)(ic); + if (ni != NULL) + ieee80211_setup_node(ic, ni, macaddr); + return ni; +} + +struct ieee80211_node * +ieee80211_dup_bss(struct ieee80211com *ic, u_int8_t *macaddr) +{ + struct ieee80211_node *ni = (*ic->ic_node_alloc)(ic); + if (ni != NULL) { + memcpy(ni, ic->ic_bss, sizeof(struct ieee80211_node)); + ieee80211_setup_node(ic, ni, macaddr); + } + return ni; +} + +struct ieee80211_node * +ieee80211_find_node(struct ieee80211com *ic, u_int8_t *macaddr) +{ + struct ieee80211_node *ni; + int hash; + + hash = IEEE80211_NODE_HASH(macaddr); + mtx_lock(&ic->ic_nodelock); + LIST_FOREACH(ni, &ic->ic_hash[hash], ni_hash) { + if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { + atomic_add_int(&ni->ni_refcnt, 1); /* mark referenced */ + break; + } + } + mtx_unlock(&ic->ic_nodelock); + return ni; +} + +/* + * Like find but search based on the channel too. + */ +struct ieee80211_node * +ieee80211_lookup_node(struct ieee80211com *ic, + u_int8_t *macaddr, struct ieee80211_channel *chan) +{ + struct ieee80211_node *ni; + int hash; + + hash = IEEE80211_NODE_HASH(macaddr); + mtx_lock(&ic->ic_nodelock); + LIST_FOREACH(ni, &ic->ic_hash[hash], ni_hash) { + if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr) && ni->ni_chan == chan) { + atomic_add_int(&ni->ni_refcnt, 1);/* mark referenced */ + break; + } + } + mtx_unlock(&ic->ic_nodelock); + return ni; +} + +static void +_ieee80211_free_node(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + TAILQ_REMOVE(&ic->ic_node, ni, ni_list); + LIST_REMOVE(ni, ni_hash); + if (TAILQ_EMPTY(&ic->ic_node)) + ic->ic_inact_timer = 0; + (*ic->ic_node_free)(ic, ni); +} + +void +ieee80211_free_node(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + /* XXX need equivalent of atomic_dec_and_test */ + atomic_subtract_int(&ni->ni_refcnt, 1); + if (atomic_cmpset_int(&ni->ni_refcnt, 0, 1)) { + mtx_lock(&ic->ic_nodelock); + _ieee80211_free_node(ic, ni); + mtx_unlock(&ic->ic_nodelock); + } +} + +void +ieee80211_free_allnodes(struct ieee80211com *ic) +{ + struct ieee80211_node *ni; + + mtx_lock(&ic->ic_nodelock); + while ((ni = TAILQ_FIRST(&ic->ic_node)) != NULL) + _ieee80211_free_node(ic, ni); + mtx_unlock(&ic->ic_nodelock); +} + +void +ieee80211_timeout_nodes(struct ieee80211com *ic) +{ + struct ieee80211_node *ni, *nextbs; + + mtx_lock(&ic->ic_nodelock); + for (ni = TAILQ_FIRST(&ic->ic_node); ni != NULL;) { + if (++ni->ni_inact <= IEEE80211_INACT_MAX) { + ni = TAILQ_NEXT(ni, ni_list); + continue; + } + /* NB: don't honor reference count */ + IEEE80211_DPRINTF(("station %s timed out " + "due to inactivity (%u secs)\n", + ether_sprintf(ni->ni_macaddr), + ni->ni_inact)); + nextbs = TAILQ_NEXT(ni, ni_list); + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_AUTH_EXPIRE); + _ieee80211_free_node(ic, ni); + ni = nextbs; + } + if (!TAILQ_EMPTY(&ic->ic_node)) + ic->ic_inact_timer = IEEE80211_INACT_WAIT; + mtx_unlock(&ic->ic_nodelock); +} + +void +ieee80211_iterate_nodes(struct ieee80211com *ic, ieee80211_iter_func *f, void *arg) +{ + struct ieee80211_node *ni; + + mtx_lock(&ic->ic_nodelock); + TAILQ_FOREACH(ni, &ic->ic_node, ni_list) + (*f)(arg, ni); + mtx_unlock(&ic->ic_nodelock); +} |