/* $OpenBSD: if_wi_hostap.c,v 1.36 2006/07/01 20:22:22 reyk Exp $ */ /* * Copyright (c) 2002 * Thomas Skibo . 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Thomas Skibo. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Thomas Skibo AND CONTRIBUTORS ``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 Thomas Skibo OR HIS DRINKING PALS * 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. * */ /* This is experimental Host AP software for Prism 2 802.11b interfaces. * * Much of this is based upon the "Linux Host AP driver Host AP driver * for Intersil Prism2" by Jouni Malinen or . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 /* * take_hword() * * Used for parsing management frames. The pkt pointer and length * variables are updated after the value is removed. */ 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; } /* take_tlv() * * Parse out TLV element from a packet, check for underflow of packet * or overflow of buffer, update pkt/len. */ 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); } /* put_hword() * Put half-word element into management frames. */ static __inline void put_hword(caddr_t *ppkt, u_int16_t s) { * (u_int16_t *) *ppkt = htole16(s); *ppkt += sizeof(u_int16_t); } /* put_tlv() * Put TLV elements into management frames. */ 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; } /* wihap_init() * * Initialize host AP data structures. Called even if port type is * not AP. Caller MUST raise to splnet(). */ 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); } /* wihap_sta_disassoc() * * Send a disassociation frame to a specified station. */ 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)); /* Send disassoc packet. */ 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)); } /* wihap_sta_deauth() * * Send a deauthentication message to a specified station. */ 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)); /* Send deauth packet. */ 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)); } /* wihap_shutdown() * * Disassociate all stations and free up data structures. */ 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(); /* Disable wihap inactivity timer. */ timeout_del(&whi->tmo); /* Delete all stations from the list. */ for (sta = TAILQ_FIRST(&whi->sta_list); sta != TAILQ_END(&whi->sta_list); 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); FREE(sta, M_DEVBUF); } TAILQ_INIT(&whi->sta_list); /* Broadcast disassoc and deauth to all the stations. */ 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); } /* sta_hash_func() * Hash function for finding stations from ethernet address. */ static __inline int sta_hash_func(u_int8_t addr[]) { return ((addr[3] + addr[4] + addr[5]) % WI_STA_HASH_SIZE); } /* addr_cmp(): Maybe this is a faster way to compare addresses? */ 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)); } /* wihap_sta_movetail(): move sta to the tail of the station list in whi */ 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 != TAILQ_END(&whi->sta_list) && (sta->flags & WI_SIFLAGS_DEAD); i--, sta = next) { next = TAILQ_NEXT(sta, list); if (timeout_pending(&sta->tmo)) { /* Became alive again, move to end of list. */ 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)); /* Disassoc station. */ wihap_sta_disassoc(sc, sta->addr, IEEE80211_REASON_ASSOC_EXPIRE); sta->flags &= ~WI_SIFLAGS_ASSOC; /* * Move to end of the list and reset station timeout. * We do this to make sure we don't get deauthed * until inactivity_time seconds have passed. */ wihap_sta_movetail(whi, sta); timeout_add(&sta->tmo, hz * 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)); /* Deauthenticate station. */ wihap_sta_deauth(sc, sta->addr, IEEE80211_REASON_AUTH_EXPIRE); sta->flags &= ~WI_SIFLAGS_AUTHEN; /* Delete the station if it's not permanent. */ if (sta->flags & WI_SIFLAGS_PERM) wihap_sta_movetail(whi, sta); else wihap_sta_delete(sta); } } /* Restart the timeout if there are still dead stations left. */ sta = TAILQ_FIRST(&whi->sta_list); if (sta != NULL && (sta->flags & WI_SIFLAGS_DEAD)) timeout_add(&whi->tmo, 1); /* still work left, requeue */ 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(); /* Mark sta as dead and move it to the head of the list. */ TAILQ_REMOVE(&whi->sta_list, sta, list); sta->flags |= WI_SIFLAGS_DEAD; TAILQ_INSERT_HEAD(&whi->sta_list, sta, list); /* Add wihap timeout if we have not already done so. */ if (!timeout_pending(&whi->tmo)) timeout_add(&whi->tmo, hz / 10); splx(s); } /* wihap_sta_delete() * Delete a single station and free up its data structure. * Caller must raise to splnet(). */ 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); FREE(sta, M_DEVBUF); whi->n_stations--; } /* wihap_sta_alloc() * * Create a new station data structure and put it in the list * and hash table. */ 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); /* Allocate structure. */ MALLOC(sta, struct wihap_sta_info *, sizeof(struct wihap_sta_info), M_DEVBUF, M_NOWAIT); if (sta == NULL) return (NULL); bzero(sta, sizeof(struct wihap_sta_info)); /* Allocate an ASID. */ 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; /* Insert in list and hash list. */ 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(&sta->tmo, hz * whi->inactivity_time); return (sta); } /* wihap_sta_find() * * Find station structure given address. */ 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); } /* wihap_auth_req() * * Handle incoming authentication request. */ 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; } /* Break open packet. */ 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); /* Ignore vendor private tlv (if any). */ (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; } /* Find or create station info. */ sta = wihap_sta_find(whi, rxfrm->wi_addr2); if (sta == NULL) { /* Are we allowing new stations? */ if (whi->apflags & WIHAPFL_MAC_FILT) { status = IEEE80211_STATUS_OTHER; /* XXX */ goto fail; } /* Check for too many stations. */ 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"); /* Create new station. */ s = splnet(); sta = wihap_sta_alloc(sc, rxfrm->wi_addr2); splx(s); if (sta == NULL) { /* Out of memory! */ status = IEEE80211_STATUS_TOOMANY; goto fail; } } timeout_add(&sta->tmo, hz * whi->inactivity_time); /* Note: it's okay to leave the station info structure around * if the authen fails. It'll be timed out eventually. */ 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: /* Create a challenge frame. */ if (!sta->challenge) { MALLOC(sta->challenge, u_int32_t *, 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); sta->challenge = NULL; challenge_len = 0; break; default: status = IEEE80211_STATUS_SEQUENCE; goto fail; } /* switch (seq) */ 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; } /* switch (algo) */ 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); /* Send response. */ 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)); } /* wihap_assoc_req() * * Handle incoming association and reassociation requests. */ 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; /* Pull out request parameters. */ 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; /* Eat the MAC address of the current AP */ 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 SSID doesn't match, simply drop. */ 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; } /* Is this station authenticated yet? */ 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; } /* Check supported rates against ours. */ 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; } /* Check capinfo. * Check for ESS, not IBSS. * Check WEP/PRIVACY flags match. * Refuse stations requesting to be put on CF-polling list. */ 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; } /* Use ASID is allocated by whi_sta_alloc(). */ 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(&sta->tmo, hz * 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); /* Send response. */ 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)); } /* wihap_deauth_req() * * Handle deauthentication requests. Delete the station. */ 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); } /* wihap_disassoc_req() * * Handle disassociation requests. Just reset the assoc flag. * We'll free up the station resources when we get a deauth * request or when it times out. */ 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)) { /* * If station is not authenticated, send deauthentication * frame. */ wihap_sta_deauth(sc, rxfrm->wi_addr2, IEEE80211_REASON_NOT_AUTHED); return; } else sta->flags &= ~WI_SIFLAGS_ASSOC; } /* wihap_debug_frame_type() * * Print out frame type. Used in early debugging. */ 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)); } } /* * wihap_mgmt_input: * * Called for each management frame received in host ap mode. * wihap_mgmt_input() is expected to free the mbuf. */ 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)) { /* any of the following will mess w/ the station list */ 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); } /* wihap_sta_is_assoc() * * Determine if a station is assoc'ed. Update its activity * counter as a side-effect. */ 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)) { /* Keep it active. */ timeout_add(&sta->tmo, hz * whi->inactivity_time); return (1); } return (0); } /* wihap_check_tx() * * Determine if a station is assoc'ed, get its tx rate, and update * its activity. */ 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; /* XXX: multicast rate? */ return (1); } s = splsoftclock(); sta = wihap_sta_find(whi, addr); if (sta != NULL && (sta->flags & WI_SIFLAGS_ASSOC)) { /* Keep it active. */ timeout_add(&sta->tmo, hz * whi->inactivity_time); *txrate = txratetable[sta->tx_curr_rate]; splx(s); return (1); } splx(s); return (0); } /* * wihap_data_input() * * Handle all data input on interface when in Host AP mode. * Some packets are destined for this machine, others are * repeated to other stations. * * If wihap_data_input() returns a non-zero, it has processed * the packet and will free the mbuf. */ 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; /* * TODS flag must be set. However, Lucent cards set NULLFUNC but * not TODS when probing an AP to see if it is alive after it has * been down for a while. We accept these probe packets and send a * disassoc packet later on if the station is not already associated. */ 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); } /* Check BSSID. (Is this necessary?) */ 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(); /* Find source station. */ sta = wihap_sta_find(whi, rxfrm->wi_addr2); /* Source station must be associated. */ 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(&sta->tmo, hz * whi->inactivity_time); sta->sig_info = letoh16(rxfrm->wi_q_info); splx(s); /* Repeat this packet to BSS? */ mcast = (rxfrm->wi_addr3[0] & 0x01) != 0; if (mcast || wihap_sta_is_assoc(whi, rxfrm->wi_addr3)) { /* If it's multicast, make a copy. */ if (mcast) { m = m_copym(m, 0, M_COPYALL, M_DONTWAIT); if (m == NULL) return (0); m->m_flags |= M_MCAST; /* XXX */ } /* Queue up for repeating. */ if (IF_QFULL(&ifp->if_snd)) { IF_DROP(&ifp->if_snd); m_freem(m); } else { ifp->if_obytes += m->m_pkthdr.len; if (m->m_flags & M_MCAST) ifp->if_omcasts++; IF_ENQUEUE(&ifp->if_snd, m); if ((ifp->if_flags & IFF_OACTIVE) == 0) (*ifp->if_start)(ifp); } return (!mcast); } return (0); } /* wihap_ioctl() * * Handle Host AP specific ioctls. Called from wi_ioctl(). */ 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, 0))) 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 { /* Disassociate station. */ if (sta->flags & WI_SIFLAGS_ASSOC) wihap_sta_disassoc(sc, sta->addr, IEEE80211_REASON_ASSOC_LEAVE); /* Deauth station. */ 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, 0))) 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(&sta->tmo, hz * whi->inactivity_time); splx(s); break; case SIOCHOSTAP_SFLAGS: if ((error = suser(p, 0))) 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