freebsd-dev/sys/net80211/ieee80211_freebsd.c

339 lines
9.6 KiB
C
Raw Normal View History

/*-
* Copyright (c) 2003-2004 Sam Leffler, Errno Consulting
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* IEEE 802.11 support (FreeBSD-specific code)
*/
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/linker.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_media.h>
#include <net/ethernet.h>
#include <net/route.h>
#include <net80211/ieee80211_var.h>
SYSCTL_NODE(_net, OID_AUTO, wlan, CTLFLAG_RD, 0, "IEEE 80211 parameters");
#ifdef IEEE80211_DEBUG
int ieee80211_debug = 0;
SYSCTL_INT(_net_wlan, OID_AUTO, debug, CTLFLAG_RW, &ieee80211_debug,
0, "debugging printfs");
#endif
static int
ieee80211_sysctl_inact(SYSCTL_HANDLER_ARGS)
{
int inact = (*(int *)arg1) * IEEE80211_INACT_WAIT;
int error;
error = sysctl_handle_int(oidp, &inact, 0, req);
if (error || !req->newptr)
return error;
*(int *)arg1 = inact / IEEE80211_INACT_WAIT;
return 0;
}
static int
ieee80211_sysctl_parent(SYSCTL_HANDLER_ARGS)
{
struct ieee80211com *ic = arg1;
const char *name = ic->ic_ifp->if_xname;
return SYSCTL_OUT(req, name, strlen(name));
}
void
ieee80211_sysctl_attach(struct ieee80211com *ic)
{
struct sysctl_ctx_list *ctx;
struct sysctl_oid *oid;
char num[14]; /* sufficient for 32 bits */
MALLOC(ctx, struct sysctl_ctx_list *, sizeof(struct sysctl_ctx_list),
M_DEVBUF, M_NOWAIT | M_ZERO);
if (ctx == NULL) {
if_printf(ic->ic_ifp, "%s: cannot allocate sysctl context!\n",
__func__);
return;
}
sysctl_ctx_init(ctx);
snprintf(num, sizeof(num), "%u", ic->ic_vap);
oid = SYSCTL_ADD_NODE(ctx, &SYSCTL_NODE_CHILDREN(_net, wlan),
OID_AUTO, num, CTLFLAG_RD, NULL, "");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
"%parent", CTLFLAG_RD, ic, 0, ieee80211_sysctl_parent, "A",
"parent device");
#ifdef IEEE80211_DEBUG
ic->ic_debug = ieee80211_debug;
SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
"debug", CTLFLAG_RW, &ic->ic_debug, 0,
"control debugging printfs");
#endif
/* XXX inherit from tunables */
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
"inact", CTLTYPE_INT | CTLFLAG_RW, &ic->ic_inact_run, 0,
ieee80211_sysctl_inact, "I",
"station inactivity timeout (sec)");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
"inact_probe", CTLTYPE_INT | CTLFLAG_RW, &ic->ic_inact_probe, 0,
ieee80211_sysctl_inact, "I",
"station inactivity probe timeout (sec)");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
"inact_auth", CTLTYPE_INT | CTLFLAG_RW, &ic->ic_inact_auth, 0,
ieee80211_sysctl_inact, "I",
"station authentication timeout (sec)");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
"inact_init", CTLTYPE_INT | CTLFLAG_RW, &ic->ic_inact_init, 0,
ieee80211_sysctl_inact, "I",
"station initial state timeout (sec)");
ic->ic_sysctl = ctx;
}
void
ieee80211_sysctl_detach(struct ieee80211com *ic)
{
if (ic->ic_sysctl != NULL) {
sysctl_ctx_free(ic->ic_sysctl);
ic->ic_sysctl = NULL;
}
}
int
ieee80211_node_dectestref(struct ieee80211_node *ni)
{
/* XXX need equivalent of atomic_dec_and_test */
atomic_subtract_int(&ni->ni_refcnt, 1);
return atomic_cmpset_int(&ni->ni_refcnt, 0, 1);
}
/*
* Allocate and setup a management frame of the specified
* size. We return the mbuf and a pointer to the start
* of the contiguous data area that's been reserved based
* on the packet length. The data area is forced to 32-bit
* alignment and the buffer length to a multiple of 4 bytes.
* This is done mainly so beacon frames (that require this)
* can use this interface too.
*/
struct mbuf *
ieee80211_getmgtframe(u_int8_t **frm, u_int pktlen)
{
struct mbuf *m;
u_int len;
/*
* NB: we know the mbuf routines will align the data area
* so we don't need to do anything special.
*/
/* XXX 4-address frame? */
len = roundup(sizeof(struct ieee80211_frame) + pktlen, 4);
KASSERT(len <= MCLBYTES, ("802.11 mgt frame too large: %u", len));
if (len < MINCLSIZE) {
m = m_gethdr(M_NOWAIT, MT_HEADER);
/*
* Align the data in case additional headers are added.
* This should only happen when a WEP header is added
* which only happens for shared key authentication mgt
* frames which all fit in MHLEN.
*/
if (m != NULL)
MH_ALIGN(m, len);
} else
m = m_getcl(M_NOWAIT, MT_HEADER, M_PKTHDR);
if (m != NULL) {
m->m_data += sizeof(struct ieee80211_frame);
*frm = m->m_data;
}
return m;
}
#include <sys/libkern.h>
void
get_random_bytes(void *p, size_t n)
{
u_int8_t *dp = p;
while (n > 0) {
u_int32_t v = arc4random();
size_t nb = n > sizeof(u_int32_t) ? sizeof(u_int32_t) : n;
bcopy(&v, dp, n > sizeof(u_int32_t) ? sizeof(u_int32_t) : n);
dp += sizeof(u_int32_t), n -= nb;
}
}
void
ieee80211_notify_node_join(struct ieee80211com *ic, struct ieee80211_node *ni, int newassoc)
{
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211_join_event iev;
if (ni == ic->ic_bss) {
memset(&iev, 0, sizeof(iev));
IEEE80211_ADDR_COPY(iev.iev_addr, ni->ni_bssid);
rt_ieee80211msg(ifp, newassoc ?
RTM_IEEE80211_ASSOC : RTM_IEEE80211_REASSOC,
&iev, sizeof(iev));
if_link_state_change(ifp, LINK_STATE_UP);
} else if (newassoc) {
/* fire off wireless event only for new station */
memset(&iev, 0, sizeof(iev));
IEEE80211_ADDR_COPY(iev.iev_addr, ni->ni_macaddr);
rt_ieee80211msg(ifp, RTM_IEEE80211_JOIN, &iev, sizeof(iev));
}
}
void
ieee80211_notify_node_leave(struct ieee80211com *ic, struct ieee80211_node *ni)
{
struct ifnet *ifp = ic->ic_ifp;
struct ieee80211_leave_event iev;
if (ni == ic->ic_bss) {
rt_ieee80211msg(ifp, RTM_IEEE80211_DISASSOC, NULL, 0);
if_link_state_change(ifp, LINK_STATE_DOWN);
} else {
/* fire off wireless event station leaving */
memset(&iev, 0, sizeof(iev));
IEEE80211_ADDR_COPY(iev.iev_addr, ni->ni_macaddr);
rt_ieee80211msg(ifp, RTM_IEEE80211_LEAVE, &iev, sizeof(iev));
}
}
void
ieee80211_notify_scan_done(struct ieee80211com *ic)
{
struct ifnet *ifp = ic->ic_ifp;
IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN,
"%s: notify scan done\n", ic->ic_ifp->if_xname);
/* dispatch wireless event indicating scan completed */
rt_ieee80211msg(ifp, RTM_IEEE80211_SCAN, NULL, 0);
}
void
ieee80211_notify_replay_failure(struct ieee80211com *ic,
const struct ieee80211_frame *wh, const struct ieee80211_key *k,
u_int64_t rsc)
{
struct ifnet *ifp = ic->ic_ifp;
IEEE80211_DPRINTF(ic, IEEE80211_MSG_CRYPTO,
"[%s] %s replay detected <rsc %ju, csc %ju>\n",
ether_sprintf(wh->i_addr2), k->wk_cipher->ic_name,
(intmax_t) rsc, (intmax_t) k->wk_keyrsc);
if (ifp != NULL) { /* NB: for cipher test modules */
struct ieee80211_replay_event iev;
IEEE80211_ADDR_COPY(iev.iev_dst, wh->i_addr1);
IEEE80211_ADDR_COPY(iev.iev_src, wh->i_addr2);
iev.iev_cipher = k->wk_cipher->ic_cipher;
iev.iev_keyix = k->wk_keyix;
iev.iev_keyrsc = k->wk_keyrsc;
iev.iev_rsc = rsc;
rt_ieee80211msg(ifp, RTM_IEEE80211_REPLAY, &iev, sizeof(iev));
}
}
void
ieee80211_notify_michael_failure(struct ieee80211com *ic,
const struct ieee80211_frame *wh, u_int keyix)
{
struct ifnet *ifp = ic->ic_ifp;
IEEE80211_DPRINTF(ic, IEEE80211_MSG_CRYPTO,
"[%s] Michael MIC verification failed <keyidx %d>\n",
ether_sprintf(wh->i_addr2), keyix);
ic->ic_stats.is_rx_tkipmic++;
if (ifp != NULL) { /* NB: for cipher test modules */
struct ieee80211_michael_event iev;
IEEE80211_ADDR_COPY(iev.iev_dst, wh->i_addr1);
IEEE80211_ADDR_COPY(iev.iev_src, wh->i_addr2);
iev.iev_cipher = IEEE80211_CIPHER_TKIP;
iev.iev_keyix = keyix;
rt_ieee80211msg(ifp, RTM_IEEE80211_MICHAEL, &iev, sizeof(iev));
}
}
void
ieee80211_load_module(const char *modname)
{
struct thread *td = curthread;
if (suser(td) == 0 && securelevel_gt(td->td_ucred, 0) == 0) {
mtx_lock(&Giant);
(void) linker_load_module(modname, NULL, NULL, NULL, NULL);
mtx_unlock(&Giant);
}
}
/*
* Module glue.
*
* NB: the module name is "wlan" for compatibility with NetBSD.
*/
static int
wlan_modevent(module_t mod, int type, void *unused)
{
switch (type) {
case MOD_LOAD:
if (bootverbose)
printf("wlan: <802.11 Link Layer>\n");
return 0;
case MOD_UNLOAD:
return 0;
}
return EINVAL;
}
static moduledata_t wlan_mod = {
"wlan",
wlan_modevent,
0
};
DECLARE_MODULE(wlan, wlan_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
MODULE_VERSION(wlan, 1);
MODULE_DEPEND(wlan, ether, 1, 1, 1);