#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/timeout.h>
#include <sys/ucred.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <net/if.h>
#include <net/if_media.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_ioctl.h>
#include <dev/ic/if_wireg.h>
#include <dev/ic/if_wi_ieee.h>
#include <dev/ic/if_wivar.h>
void wihap_timeout(void *v);
void wihap_sta_timeout(void *v);
struct wihap_sta_info *wihap_sta_alloc(struct wi_softc *sc, u_int8_t *addr);
void wihap_sta_delete(struct wihap_sta_info *sta);
struct wihap_sta_info *wihap_sta_find(struct wihap_info *whi, u_int8_t *addr);
int wihap_sta_is_assoc(struct wihap_info *whi, u_int8_t addr[]);
void wihap_auth_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len);
void wihap_sta_deauth(struct wi_softc *sc, u_int8_t sta_addr[],
u_int16_t reason);
void wihap_deauth_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len);
void wihap_assoc_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len);
void wihap_sta_disassoc(struct wi_softc *sc, u_int8_t sta_addr[],
u_int16_t reason);
void wihap_disassoc_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len);
#ifndef SMALL_KERNEL
static __inline u_int16_t
take_hword(caddr_t *ppkt, int *plen)
{
u_int16_t s = letoh16(* (u_int16_t *) *ppkt);
*ppkt += sizeof(u_int16_t);
*plen -= sizeof(u_int16_t);
return s;
}
static int
take_tlv(caddr_t *ppkt, int *plen, int id_expect, void *dst, int maxlen)
{
u_int8_t id, len;
if (*plen < 2)
return -1;
id = ((u_int8_t *)*ppkt)[0];
len = ((u_int8_t *)*ppkt)[1];
if (id != id_expect || *plen < len+2 || maxlen < len)
return -1;
bcopy(*ppkt + 2, dst, len);
*plen -= 2 + len;
*ppkt += 2 + len;
return (len);
}
static __inline void
put_hword(caddr_t *ppkt, u_int16_t s)
{
* (u_int16_t *) *ppkt = htole16(s);
*ppkt += sizeof(u_int16_t);
}
static void
put_tlv(caddr_t *ppkt, u_int8_t id, void *src, u_int8_t len)
{
(*ppkt)[0] = id;
(*ppkt)[1] = len;
bcopy(src, (*ppkt) + 2, len);
*ppkt += 2 + len;
}
static int
put_rates(caddr_t *ppkt, u_int16_t rates)
{
u_int8_t ratebuf[8];
int len = 0;
if (rates & WI_SUPPRATES_1M)
ratebuf[len++] = 0x82;
if (rates & WI_SUPPRATES_2M)
ratebuf[len++] = 0x84;
if (rates & WI_SUPPRATES_5M)
ratebuf[len++] = 0x8b;
if (rates & WI_SUPPRATES_11M)
ratebuf[len++] = 0x96;
put_tlv(ppkt, IEEE80211_ELEMID_RATES, ratebuf, len);
return len;
}
void
wihap_init(struct wi_softc *sc)
{
int i;
struct wihap_info *whi = &sc->wi_hostap_info;
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_init: sc=%p whi=%p\n", sc, whi);
bzero(whi, sizeof(struct wihap_info));
if (sc->wi_ptype != WI_PORTTYPE_HOSTAP)
return;
whi->apflags = WIHAPFL_ACTIVE;
TAILQ_INIT(&whi->sta_list);
for (i = 0; i < WI_STA_HASH_SIZE; i++)
LIST_INIT(&whi->sta_hash[i]);
whi->inactivity_time = WIHAP_DFLT_INACTIVITY_TIME;
timeout_set(&whi->tmo, wihap_timeout, sc);
}
void
wihap_sta_disassoc(struct wi_softc *sc, u_int8_t sta_addr[], u_int16_t reason)
{
struct wi_80211_hdr *resp_hdr;
caddr_t pkt;
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("Sending disassoc to sta %s\n", ether_sprintf(sta_addr));
resp_hdr = (struct wi_80211_hdr *)sc->wi_txbuf;
bzero(resp_hdr, sizeof(struct wi_80211_hdr));
resp_hdr->frame_ctl = WI_FTYPE_MGMT | WI_STYPE_MGMT_DISAS;
pkt = (caddr_t)&sc->wi_txbuf + sizeof(struct wi_80211_hdr);
bcopy(sta_addr, resp_hdr->addr1, ETHER_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr2, IEEE80211_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr3, IEEE80211_ADDR_LEN);
put_hword(&pkt, reason);
wi_mgmt_xmit(sc, (caddr_t)&sc->wi_txbuf,
2 + sizeof(struct wi_80211_hdr));
}
void
wihap_sta_deauth(struct wi_softc *sc, u_int8_t sta_addr[], u_int16_t reason)
{
struct wi_80211_hdr *resp_hdr;
caddr_t pkt;
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("Sending deauth to sta %s\n", ether_sprintf(sta_addr));
resp_hdr = (struct wi_80211_hdr *)sc->wi_txbuf;
bzero(resp_hdr, sizeof(struct wi_80211_hdr));
resp_hdr->frame_ctl = htole16(WI_FTYPE_MGMT | WI_STYPE_MGMT_DEAUTH);
pkt = (caddr_t)&sc->wi_txbuf + sizeof(struct wi_80211_hdr);
bcopy(sta_addr, resp_hdr->addr1, ETHER_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr2, IEEE80211_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr3, IEEE80211_ADDR_LEN);
put_hword(&pkt, reason);
wi_mgmt_xmit(sc, (caddr_t)&sc->wi_txbuf,
2 + sizeof(struct wi_80211_hdr));
}
void
wihap_shutdown(struct wi_softc *sc)
{
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta, *next;
int i, s;
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_shutdown: sc=%p whi=%p\n", sc, whi);
if (!(whi->apflags & WIHAPFL_ACTIVE))
return;
whi->apflags = 0;
s = splnet();
timeout_del(&whi->tmo);
for (sta = TAILQ_FIRST(&whi->sta_list); sta != NULL; sta = next) {
timeout_del(&sta->tmo);
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_shutdown: free(sta=%p)\n", sta);
next = TAILQ_NEXT(sta, list);
if (sta->challenge)
free(sta->challenge, M_TEMP, 0);
free(sta, M_DEVBUF, 0);
}
TAILQ_INIT(&whi->sta_list);
if (sc->wi_flags & WI_FLAGS_ATTACHED) {
for (i = 0; i < 5; i++) {
wihap_sta_disassoc(sc, etherbroadcastaddr,
IEEE80211_REASON_ASSOC_LEAVE);
wihap_sta_deauth(sc, etherbroadcastaddr,
IEEE80211_REASON_AUTH_LEAVE);
DELAY(50);
}
}
splx(s);
}
static __inline int
sta_hash_func(u_int8_t addr[])
{
return ((addr[3] + addr[4] + addr[5]) % WI_STA_HASH_SIZE);
}
static __inline int
addr_cmp(u_int8_t a[], u_int8_t b[])
{
return (*(u_int16_t *)(a + 4) == *(u_int16_t *)(b + 4) &&
*(u_int16_t *)(a + 2) == *(u_int16_t *)(b + 2) &&
*(u_int16_t *)(a ) == *(u_int16_t *)(b));
}
static __inline void
wihap_sta_movetail(struct wihap_info *whi, struct wihap_sta_info *sta)
{
TAILQ_REMOVE(&whi->sta_list, sta, list);
sta->flags &= ~WI_SIFLAGS_DEAD;
TAILQ_INSERT_TAIL(&whi->sta_list, sta, list);
}
void
wihap_timeout(void *v)
{
struct wi_softc *sc = v;
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta, *next;
int i, s;
s = splnet();
for (i = 10, sta = TAILQ_FIRST(&whi->sta_list);
i != 0 && sta != NULL && (sta->flags & WI_SIFLAGS_DEAD);
i--, sta = next) {
next = TAILQ_NEXT(sta, list);
if (timeout_pending(&sta->tmo)) {
wihap_sta_movetail(whi, sta);
} else if (sta->flags & WI_SIFLAGS_ASSOC) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_timeout: disassoc due to inactivity: %s\n",
ether_sprintf(sta->addr));
wihap_sta_disassoc(sc, sta->addr,
IEEE80211_REASON_ASSOC_EXPIRE);
sta->flags &= ~WI_SIFLAGS_ASSOC;
wihap_sta_movetail(whi, sta);
timeout_add_sec(&sta->tmo, whi->inactivity_time);
} else if (sta->flags & WI_SIFLAGS_AUTHEN) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_timeout: deauth due to inactivity: %s\n",
ether_sprintf(sta->addr));
wihap_sta_deauth(sc, sta->addr,
IEEE80211_REASON_AUTH_EXPIRE);
sta->flags &= ~WI_SIFLAGS_AUTHEN;
if (sta->flags & WI_SIFLAGS_PERM)
wihap_sta_movetail(whi, sta);
else
wihap_sta_delete(sta);
}
}
sta = TAILQ_FIRST(&whi->sta_list);
if (sta != NULL && (sta->flags & WI_SIFLAGS_DEAD))
timeout_add(&whi->tmo, 1);
splx(s);
}
void
wihap_sta_timeout(void *v)
{
struct wihap_sta_info *sta = v;
struct wi_softc *sc = sta->sc;
struct wihap_info *whi = &sc->wi_hostap_info;
int s;
s = splnet();
TAILQ_REMOVE(&whi->sta_list, sta, list);
sta->flags |= WI_SIFLAGS_DEAD;
TAILQ_INSERT_HEAD(&whi->sta_list, sta, list);
if (!timeout_pending(&whi->tmo))
timeout_add(&whi->tmo, hz / 10);
splx(s);
}
void
wihap_sta_delete(struct wihap_sta_info *sta)
{
struct wi_softc *sc = sta->sc;
struct wihap_info *whi = &sc->wi_hostap_info;
int i = sta->asid - 0xc001;
timeout_del(&sta->tmo);
whi->asid_inuse_mask[i >> 4] &= ~(1UL << (i & 0xf));
TAILQ_REMOVE(&whi->sta_list, sta, list);
LIST_REMOVE(sta, hash);
if (sta->challenge)
free(sta->challenge, M_TEMP, 0);
free(sta, M_DEVBUF, 0);
whi->n_stations--;
}
struct wihap_sta_info *
wihap_sta_alloc(struct wi_softc *sc, u_int8_t *addr)
{
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
int i, hash = sta_hash_func(addr);
sta = malloc(sizeof(*sta), M_DEVBUF, M_NOWAIT | M_ZERO);
if (sta == NULL)
return (NULL);
i=hash<<4;
while (whi->asid_inuse_mask[i >> 4] & (1UL << (i & 0xf)))
i = (i == (WI_STA_HASH_SIZE << 4) - 1) ? 0 : (i + 1);
whi->asid_inuse_mask[i >> 4] |= (1UL << (i & 0xf));
sta->asid = 0xc001 + i;
TAILQ_INSERT_TAIL(&whi->sta_list, sta, list);
LIST_INSERT_HEAD(&whi->sta_hash[hash], sta, hash);
sta->sc = sc;
whi->n_stations++;
bcopy(addr, &sta->addr, ETHER_ADDR_LEN);
timeout_set(&sta->tmo, wihap_sta_timeout, sta);
timeout_add_sec(&sta->tmo, whi->inactivity_time);
return (sta);
}
struct wihap_sta_info *
wihap_sta_find(struct wihap_info *whi, u_int8_t *addr)
{
int i;
struct wihap_sta_info *sta;
i = sta_hash_func(addr);
LIST_FOREACH(sta, &whi->sta_hash[i], hash)
if (addr_cmp(addr, sta->addr))
return sta;
return (NULL);
}
static __inline int
wihap_check_rates(struct wihap_sta_info *sta, u_int8_t rates[], int rates_len)
{
struct wi_softc *sc = sta->sc;
int i;
sta->rates = 0;
sta->tx_max_rate = 0;
for (i = 0; i < rates_len; i++)
switch (rates[i] & 0x7f) {
case 0x02:
sta->rates |= WI_SUPPRATES_1M;
break;
case 0x04:
sta->rates |= WI_SUPPRATES_2M;
if (sta->tx_max_rate < 1)
sta->tx_max_rate = 1;
break;
case 0x0b:
sta->rates |= WI_SUPPRATES_5M;
if (sta->tx_max_rate < 2)
sta->tx_max_rate = 2;
break;
case 0x16:
sta->rates |= WI_SUPPRATES_11M;
sta->tx_max_rate = 3;
break;
}
sta->rates &= sc->wi_supprates;
sta->tx_curr_rate = sta->tx_max_rate;
return (sta->rates == 0 ? -1 : 0);
}
void
wihap_auth_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len)
{
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
int i, s;
u_int16_t algo;
u_int16_t seq;
u_int16_t status;
int challenge_len;
u_int32_t challenge[32];
struct wi_80211_hdr *resp_hdr;
if (len < 6) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_auth_req: station %s short request\n",
ether_sprintf(rxfrm->wi_addr2));
return;
}
algo = take_hword(&pkt, &len);
seq = take_hword(&pkt, &len);
status = take_hword(&pkt, &len);
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_auth_req: station %s algo=0x%x seq=0x%x\n",
ether_sprintf(rxfrm->wi_addr2), algo, seq);
(void)take_tlv(&pkt, &len, IEEE80211_ELEMID_VENDOR, challenge,
sizeof(challenge));
challenge_len = 0;
if (len > 0 && (challenge_len = take_tlv(&pkt, &len,
IEEE80211_ELEMID_CHALLENGE, challenge, sizeof(challenge))) < 0) {
status = IEEE80211_STATUS_CHALLENGE;
goto fail;
}
sta = wihap_sta_find(whi, rxfrm->wi_addr2);
if (sta == NULL) {
if (whi->apflags & WIHAPFL_MAC_FILT) {
status = IEEE80211_STATUS_OTHER;
goto fail;
}
if (whi->n_stations >= WIHAP_MAX_STATIONS) {
status = IEEE80211_STATUS_TOOMANY;
goto fail;
}
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_auth_req: new station\n");
s = splnet();
sta = wihap_sta_alloc(sc, rxfrm->wi_addr2);
splx(s);
if (sta == NULL) {
status = IEEE80211_STATUS_TOOMANY;
goto fail;
}
}
timeout_add_sec(&sta->tmo, whi->inactivity_time);
switch (algo) {
case IEEE80211_AUTH_ALG_OPEN:
if (sc->wi_authtype != IEEE80211_AUTH_OPEN) {
status = IEEE80211_STATUS_ALG;
goto fail;
}
if (seq != 1) {
status = IEEE80211_STATUS_SEQUENCE;
goto fail;
}
challenge_len = 0;
sta->flags |= WI_SIFLAGS_AUTHEN;
break;
case IEEE80211_AUTH_ALG_SHARED:
if (sc->wi_authtype != IEEE80211_AUTH_SHARED) {
status = IEEE80211_STATUS_ALG;
goto fail;
}
switch (seq) {
case 1:
if (!sta->challenge) {
sta->challenge = malloc(128, M_TEMP, M_NOWAIT);
if (!sta->challenge)
return;
}
for (i = 0; i < 32; i++)
challenge[i] = sta->challenge[i] =
arc4random();
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("\tchallenge: 0x%x 0x%x ...\n",
challenge[0], challenge[1]);
challenge_len = 128;
break;
case 3:
if (challenge_len != 128 || !sta->challenge ||
!(letoh16(rxfrm->wi_frame_ctl) & WI_FCTL_WEP)) {
status = IEEE80211_STATUS_CHALLENGE;
goto fail;
}
for (i=0; i<32; i++)
if (sta->challenge[i] != challenge[i]) {
status = IEEE80211_STATUS_CHALLENGE;
goto fail;
}
sta->flags |= WI_SIFLAGS_AUTHEN;
free(sta->challenge, M_TEMP, 0);
sta->challenge = NULL;
challenge_len = 0;
break;
default:
status = IEEE80211_STATUS_SEQUENCE;
goto fail;
}
break;
default:
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_auth_req: algorithm unsupported: 0x%x\n",
algo);
status = IEEE80211_STATUS_ALG;
goto fail;
}
status = IEEE80211_STATUS_SUCCESS;
fail:
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_auth_req: returns status=0x%x\n", status);
resp_hdr = (struct wi_80211_hdr *)&sc->wi_txbuf;
bzero(resp_hdr, sizeof(struct wi_80211_hdr));
resp_hdr->frame_ctl = htole16(WI_FTYPE_MGMT | WI_STYPE_MGMT_AUTH);
bcopy(rxfrm->wi_addr2, resp_hdr->addr1, ETHER_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr2, IEEE80211_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr3, IEEE80211_ADDR_LEN);
pkt = (caddr_t)&sc->wi_txbuf + sizeof(struct wi_80211_hdr);
put_hword(&pkt, algo);
put_hword(&pkt, seq + 1);
put_hword(&pkt, status);
if (challenge_len > 0)
put_tlv(&pkt, IEEE80211_ELEMID_CHALLENGE,
challenge, challenge_len);
wi_mgmt_xmit(sc, (caddr_t)&sc->wi_txbuf,
6 + sizeof(struct wi_80211_hdr) +
(challenge_len > 0 ? challenge_len + 2 : 0));
}
void
wihap_assoc_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len)
{
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
struct wi_80211_hdr *resp_hdr;
u_int16_t capinfo;
u_int16_t lstintvl;
u_int8_t rates[12];
int ssid_len, rates_len;
struct ieee80211_nwid ssid;
u_int16_t status;
u_int16_t asid = 0;
if (len < 8)
return;
capinfo = take_hword(&pkt, &len);
lstintvl = take_hword(&pkt, &len);
if ((rxfrm->wi_frame_ctl & htole16(WI_FCTL_STYPE)) ==
htole16(WI_STYPE_MGMT_REASREQ)) {
if (len < 6)
return;
take_hword(&pkt, &len);
take_hword(&pkt, &len);
take_hword(&pkt, &len);
}
if ((ssid_len = take_tlv(&pkt, &len, IEEE80211_ELEMID_SSID,
ssid.i_nwid, sizeof(ssid))) < 0)
return;
ssid.i_len = ssid_len;
if ((rates_len = take_tlv(&pkt, &len, IEEE80211_ELEMID_RATES,
rates, sizeof(rates))) < 0)
return;
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: from station %s\n",
ether_sprintf(rxfrm->wi_addr2));
if (sc->wi_net_name.i_len != ssid.i_len ||
memcmp(sc->wi_net_name.i_nwid, ssid.i_nwid, ssid.i_len)) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: bad ssid: '%.*s' != '%.*s'\n",
ssid.i_len, ssid.i_nwid, sc->wi_net_name.i_len,
sc->wi_net_name.i_nwid);
return;
}
sta = wihap_sta_find(whi, rxfrm->wi_addr2);
if (sta == NULL || !(sta->flags & WI_SIFLAGS_AUTHEN)) {
wihap_sta_deauth(sc, rxfrm->wi_addr2,
IEEE80211_REASON_NOT_AUTHED);
return;
}
if (wihap_check_rates(sta, rates, rates_len) < 0) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: rates mismatch.\n");
status = IEEE80211_STATUS_BASIC_RATE;
goto fail;
}
sta->capinfo = capinfo;
status = IEEE80211_STATUS_CAPINFO;
if ((capinfo & (IEEE80211_CAPINFO_ESS | IEEE80211_CAPINFO_IBSS)) !=
IEEE80211_CAPINFO_ESS) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: capinfo: not ESS: "
"capinfo=0x%x\n", capinfo);
goto fail;
}
if ((sc->wi_use_wep && !(capinfo & IEEE80211_CAPINFO_PRIVACY)) ||
(!sc->wi_use_wep && (capinfo & IEEE80211_CAPINFO_PRIVACY))) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: WEP flag mismatch: "
"capinfo=0x%x\n", capinfo);
goto fail;
}
if ((capinfo & (IEEE80211_CAPINFO_CF_POLLABLE |
IEEE80211_CAPINFO_CF_POLLREQ)) == IEEE80211_CAPINFO_CF_POLLABLE) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: polling not supported: "
"capinfo=0x%x\n", capinfo);
goto fail;
}
asid = sta->asid;
if (sta->flags & WI_SIFLAGS_ASSOC) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: already assoc'ed?\n");
}
sta->flags |= WI_SIFLAGS_ASSOC;
timeout_add_sec(&sta->tmo, whi->inactivity_time);
status = IEEE80211_STATUS_SUCCESS;
fail:
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_assoc_req: returns status=0x%x\n", status);
resp_hdr = (struct wi_80211_hdr *)&sc->wi_txbuf;
bzero(resp_hdr, sizeof(struct wi_80211_hdr));
resp_hdr->frame_ctl = htole16(WI_FTYPE_MGMT | WI_STYPE_MGMT_ASRESP);
pkt = (caddr_t)&sc->wi_txbuf + sizeof(struct wi_80211_hdr);
bcopy(rxfrm->wi_addr2, resp_hdr->addr1, ETHER_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr2, IEEE80211_ADDR_LEN);
bcopy(sc->sc_ic.ic_myaddr, resp_hdr->addr3, IEEE80211_ADDR_LEN);
put_hword(&pkt, capinfo);
put_hword(&pkt, status);
put_hword(&pkt, asid);
rates_len = put_rates(&pkt, sc->wi_supprates);
wi_mgmt_xmit(sc, (caddr_t)&sc->wi_txbuf,
8 + rates_len + sizeof(struct wi_80211_hdr));
}
void
wihap_deauth_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len)
{
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
u_int16_t reason;
if (len<2)
return;
reason = take_hword(&pkt, &len);
sta = wihap_sta_find(whi, rxfrm->wi_addr2);
if (sta == NULL) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_deauth_req: unknown station: %s\n",
ether_sprintf(rxfrm->wi_addr2));
}
else
wihap_sta_delete(sta);
}
void
wihap_disassoc_req(struct wi_softc *sc, struct wi_frame *rxfrm,
caddr_t pkt, int len)
{
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
u_int16_t reason;
if (len < 2)
return;
reason = take_hword(&pkt, &len);
sta = wihap_sta_find(whi, rxfrm->wi_addr2);
if (sta == NULL) {
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
printf("wihap_disassoc_req: unknown station: %s\n",
ether_sprintf(rxfrm->wi_addr2));
}
else if (!(sta->flags & WI_SIFLAGS_AUTHEN)) {
wihap_sta_deauth(sc, rxfrm->wi_addr2,
IEEE80211_REASON_NOT_AUTHED);
return;
}
else
sta->flags &= ~WI_SIFLAGS_ASSOC;
}
static __inline void
wihap_debug_frame_type(struct wi_frame *rxfrm)
{
printf("wihap_mgmt_input: len=%d ", letoh16(rxfrm->wi_dat_len));
if ((rxfrm->wi_frame_ctl & htole16(WI_FCTL_FTYPE)) ==
htole16(WI_FTYPE_MGMT)) {
printf("MGMT: ");
switch (letoh16(rxfrm->wi_frame_ctl) & WI_FCTL_STYPE) {
case WI_STYPE_MGMT_ASREQ:
printf("assoc req: \n");
break;
case WI_STYPE_MGMT_ASRESP:
printf("assoc resp: \n");
break;
case WI_STYPE_MGMT_REASREQ:
printf("reassoc req: \n");
break;
case WI_STYPE_MGMT_REASRESP:
printf("reassoc resp: \n");
break;
case WI_STYPE_MGMT_PROBEREQ:
printf("probe req: \n");
break;
case WI_STYPE_MGMT_PROBERESP:
printf("probe resp: \n");
break;
case WI_STYPE_MGMT_BEACON:
printf("beacon: \n");
break;
case WI_STYPE_MGMT_ATIM:
printf("ann traf ind \n");
break;
case WI_STYPE_MGMT_DISAS:
printf("disassociation: \n");
break;
case WI_STYPE_MGMT_AUTH:
printf("auth: \n");
break;
case WI_STYPE_MGMT_DEAUTH:
printf("deauth: \n");
break;
default:
printf("unknown (stype=0x%x)\n",
letoh16(rxfrm->wi_frame_ctl) & WI_FCTL_STYPE);
}
}
else {
printf("ftype=0x%x (ctl=0x%x)\n",
letoh16(rxfrm->wi_frame_ctl) & WI_FCTL_FTYPE,
letoh16(rxfrm->wi_frame_ctl));
}
}
void
wihap_mgmt_input(struct wi_softc *sc, struct wi_frame *rxfrm, struct mbuf *m)
{
caddr_t pkt;
int s, len;
if (sc->sc_ic.ic_if.if_flags & IFF_DEBUG)
wihap_debug_frame_type(rxfrm);
pkt = mtod(m, caddr_t) + WI_802_11_OFFSET_RAW;
len = m->m_len - WI_802_11_OFFSET_RAW;
if ((rxfrm->wi_frame_ctl & htole16(WI_FCTL_FTYPE)) ==
htole16(WI_FTYPE_MGMT)) {
s = splsoftclock();
switch (letoh16(rxfrm->wi_frame_ctl) & WI_FCTL_STYPE) {
case WI_STYPE_MGMT_ASREQ:
wihap_assoc_req(sc, rxfrm, pkt, len);
break;
case WI_STYPE_MGMT_ASRESP:
break;
case WI_STYPE_MGMT_REASREQ:
wihap_assoc_req(sc, rxfrm, pkt, len);
break;
case WI_STYPE_MGMT_REASRESP:
break;
case WI_STYPE_MGMT_PROBEREQ:
break;
case WI_STYPE_MGMT_PROBERESP:
break;
case WI_STYPE_MGMT_BEACON:
break;
case WI_STYPE_MGMT_ATIM:
break;
case WI_STYPE_MGMT_DISAS:
wihap_disassoc_req(sc, rxfrm, pkt, len);
break;
case WI_STYPE_MGMT_AUTH:
wihap_auth_req(sc, rxfrm, pkt, len);
break;
case WI_STYPE_MGMT_DEAUTH:
wihap_deauth_req(sc, rxfrm, pkt, len);
break;
}
splx(s);
}
m_freem(m);
}
int
wihap_sta_is_assoc(struct wihap_info *whi, u_int8_t addr[])
{
struct wihap_sta_info *sta;
sta = wihap_sta_find(whi, addr);
if (sta != NULL && (sta->flags & WI_SIFLAGS_ASSOC)) {
timeout_add_sec(&sta->tmo, whi->inactivity_time);
return (1);
}
return (0);
}
int
wihap_check_tx(struct wihap_info *whi, u_int8_t addr[], u_int8_t *txrate)
{
struct wihap_sta_info *sta;
static u_int8_t txratetable[] = { 10, 20, 55, 110 };
int s;
if (addr[0] & 0x01) {
*txrate = 0;
return (1);
}
s = splsoftclock();
sta = wihap_sta_find(whi, addr);
if (sta != NULL && (sta->flags & WI_SIFLAGS_ASSOC)) {
timeout_add_sec(&sta->tmo, whi->inactivity_time);
*txrate = txratetable[sta->tx_curr_rate];
splx(s);
return (1);
}
splx(s);
return (0);
}
int
wihap_data_input(struct wi_softc *sc, struct wi_frame *rxfrm, struct mbuf *m)
{
struct ifnet *ifp = &sc->sc_ic.ic_if;
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
int mcast, s;
u_int16_t fctl;
fctl = letoh16(rxfrm->wi_frame_ctl);
if (!(fctl & WI_FCTL_TODS) && !(fctl & WI_STYPE_NULLFUNC)) {
if (ifp->if_flags & IFF_DEBUG)
printf("wihap_data_input: no TODS src=%s, fctl=0x%x\n",
ether_sprintf(rxfrm->wi_addr2), fctl);
m_freem(m);
return (1);
}
if (!addr_cmp(rxfrm->wi_addr1, sc->sc_ic.ic_myaddr)) {
if (ifp->if_flags & IFF_DEBUG)
printf("wihap_data_input: incorrect bss: %s\n",
ether_sprintf(rxfrm->wi_addr1));
m_freem(m);
return (1);
}
s = splsoftclock();
sta = wihap_sta_find(whi, rxfrm->wi_addr2);
if (sta == NULL || !(sta->flags & WI_SIFLAGS_ASSOC)) {
if (ifp->if_flags & IFF_DEBUG)
printf("wihap_data_input: dropping unassoc src %s\n",
ether_sprintf(rxfrm->wi_addr2));
wihap_sta_disassoc(sc, rxfrm->wi_addr2,
IEEE80211_REASON_ASSOC_LEAVE);
splx(s);
m_freem(m);
return (1);
}
timeout_add_sec(&sta->tmo, whi->inactivity_time);
sta->sig_info = letoh16(rxfrm->wi_q_info);
splx(s);
mcast = (rxfrm->wi_addr3[0] & 0x01) != 0;
if (mcast || wihap_sta_is_assoc(whi, rxfrm->wi_addr3)) {
if (mcast) {
m = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
if (m == NULL)
return (0);
m->m_flags |= M_MCAST;
}
if_enqueue(ifp, m);
return (!mcast);
}
return (0);
}
int
wihap_ioctl(struct wi_softc *sc, u_long command, caddr_t data)
{
struct proc *p = curproc;
struct ifreq *ifr = (struct ifreq *) data;
struct wihap_info *whi = &sc->wi_hostap_info;
struct wihap_sta_info *sta;
struct hostap_getall reqall;
struct hostap_sta reqsta;
struct hostap_sta stabuf;
int s, error = 0, n, flag;
struct ieee80211_nodereq nr;
struct ieee80211_nodereq_all *na;
if (!(sc->sc_ic.ic_if.if_flags & IFF_RUNNING))
return ENODEV;
switch (command) {
case SIOCHOSTAP_DEL:
if ((error = suser(p)))
break;
if ((error = copyin(ifr->ifr_data, &reqsta, sizeof(reqsta))))
break;
s = splnet();
sta = wihap_sta_find(whi, reqsta.addr);
if (sta == NULL)
error = ENOENT;
else {
if (sta->flags & WI_SIFLAGS_ASSOC)
wihap_sta_disassoc(sc, sta->addr,
IEEE80211_REASON_ASSOC_LEAVE);
if (sta->flags & WI_SIFLAGS_AUTHEN)
wihap_sta_deauth(sc, sta->addr,
IEEE80211_REASON_AUTH_LEAVE);
wihap_sta_delete(sta);
}
splx(s);
break;
case SIOCHOSTAP_GET:
if ((error = copyin(ifr->ifr_data, &reqsta, sizeof(reqsta))))
break;
s = splnet();
sta = wihap_sta_find(whi, reqsta.addr);
if (sta == NULL)
error = ENOENT;
else {
reqsta.flags = sta->flags;
reqsta.asid = sta->asid;
reqsta.capinfo = sta->capinfo;
reqsta.sig_info = sta->sig_info;
reqsta.rates = sta->rates;
error = copyout(&reqsta, ifr->ifr_data,
sizeof(reqsta));
}
splx(s);
break;
case SIOCHOSTAP_ADD:
if ((error = suser(p)))
break;
if ((error = copyin(ifr->ifr_data, &reqsta, sizeof(reqsta))))
break;
s = splnet();
sta = wihap_sta_find(whi, reqsta.addr);
if (sta != NULL) {
error = EEXIST;
splx(s);
break;
}
if (whi->n_stations >= WIHAP_MAX_STATIONS) {
error = ENOSPC;
splx(s);
break;
}
sta = wihap_sta_alloc(sc, reqsta.addr);
sta->flags = reqsta.flags;
timeout_add_sec(&sta->tmo, whi->inactivity_time);
splx(s);
break;
case SIOCHOSTAP_SFLAGS:
if ((error = suser(p)))
break;
if ((error = copyin(ifr->ifr_data, &flag, sizeof(int))))
break;
whi->apflags = (whi->apflags & WIHAPFL_CANTCHANGE) |
(flag & ~WIHAPFL_CANTCHANGE);
break;
case SIOCHOSTAP_GFLAGS:
flag = (int) whi->apflags;
error = copyout(&flag, ifr->ifr_data, sizeof(int));
break;
case SIOCHOSTAP_GETALL:
if ((error = copyin(ifr->ifr_data, &reqall, sizeof(reqall))))
break;
reqall.nstations = whi->n_stations;
n = 0;
s = splnet();
sta = TAILQ_FIRST(&whi->sta_list);
while (sta && reqall.size >= n+sizeof(struct hostap_sta)) {
bcopy(sta->addr, stabuf.addr, ETHER_ADDR_LEN);
stabuf.asid = sta->asid;
stabuf.flags = sta->flags;
stabuf.capinfo = sta->capinfo;
stabuf.sig_info = sta->sig_info;
stabuf.rates = sta->rates;
error = copyout(&stabuf, (caddr_t) reqall.addr + n,
sizeof(struct hostap_sta));
if (error)
break;
sta = TAILQ_NEXT(sta, list);
n += sizeof(struct hostap_sta);
}
splx(s);
if (!error)
error = copyout(&reqall, ifr->ifr_data,
sizeof(reqall));
break;
case SIOCG80211ALLNODES:
na = (struct ieee80211_nodereq_all *)data;
na->na_nodes = n = 0;
s = splnet();
sta = TAILQ_FIRST(&whi->sta_list);
while (sta && na->na_size >=
n + sizeof(struct ieee80211_nodereq)) {
bzero(&nr, sizeof(nr));
IEEE80211_ADDR_COPY(nr.nr_macaddr, sta->addr);
IEEE80211_ADDR_COPY(nr.nr_bssid,
&sc->sc_ic.ic_myaddr);
nr.nr_channel = sc->wi_channel;
nr.nr_chan_flags = IEEE80211_CHAN_B;
nr.nr_associd = sta->asid;
nr.nr_rssi = sta->sig_info >> 8;
nr.nr_max_rssi = 0;
nr.nr_capinfo = sta->capinfo;
nr.nr_nrates = 0;
if (sta->rates & WI_SUPPRATES_1M)
nr.nr_rates[nr.nr_nrates++] = 2;
if (sta->rates & WI_SUPPRATES_2M)
nr.nr_rates[nr.nr_nrates++] = 4;
if (sta->rates & WI_SUPPRATES_5M)
nr.nr_rates[nr.nr_nrates++] = 11;
if (sta->rates & WI_SUPPRATES_11M)
nr.nr_rates[nr.nr_nrates++] = 22;
error = copyout(&nr, (caddr_t)na->na_node + n,
sizeof(struct ieee80211_nodereq));
if (error)
break;
n += sizeof(struct ieee80211_nodereq);
na->na_nodes++;
sta = TAILQ_NEXT(sta, list);
}
splx(s);
break;
default:
printf("wihap_ioctl: i shouldn't get other ioctls!\n");
error = EINVAL;
}
return (error);
}
#else
void
wihap_init(struct wi_softc *sc)
{
return;
}
void
wihap_shutdown(struct wi_softc *sc)
{
return;
}
void
wihap_mgmt_input(struct wi_softc *sc, struct wi_frame *rxfrm, struct mbuf *m)
{
return;
}
int
wihap_data_input(struct wi_softc *sc, struct wi_frame *rxfrm, struct mbuf *m)
{
return (0);
}
int
wihap_ioctl(struct wi_softc *sc, u_long command, caddr_t data)
{
return (EINVAL);
}
int
wihap_check_tx(struct wihap_info *whi, u_int8_t addr[], u_int8_t *txrate)
{
return (0);
}
#endif