diff --git a/sbin/ifconfig/ifieee80211.c b/sbin/ifconfig/ifieee80211.c index 42ef6d731662..bde079deb4c6 100644 --- a/sbin/ifconfig/ifieee80211.c +++ b/sbin/ifconfig/ifieee80211.c @@ -1,6078 +1,6094 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright 2001 The Aerospace Corporation. 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 Aerospace Corporation may not be used to endorse or * promote products derived from this software. * * THIS SOFTWARE IS PROVIDED BY THE AEROSPACE CORPORATION ``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 AEROSPACE CORPORATION 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. */ /*- * Copyright (c) 1997, 1998, 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, * NASA Ames Research Center. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION OR CONTRIBUTORS * 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 #include #include #include #include #include #include #include #include #include #include +#define WANT_NET80211 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* NB: for offsetof */ #include #include #include "ifconfig.h" #include #include #ifndef IEEE80211_FIXED_RATE_NONE #define IEEE80211_FIXED_RATE_NONE 0xff #endif /* XXX need these publicly defined or similar */ #ifndef IEEE80211_NODE_AUTH #define IEEE80211_NODE_AUTH 0x000001 /* authorized for data */ #define IEEE80211_NODE_QOS 0x000002 /* QoS enabled */ #define IEEE80211_NODE_ERP 0x000004 /* ERP enabled */ #define IEEE80211_NODE_PWR_MGT 0x000010 /* power save mode enabled */ #define IEEE80211_NODE_AREF 0x000020 /* authentication ref held */ #define IEEE80211_NODE_HT 0x000040 /* HT enabled */ #define IEEE80211_NODE_HTCOMPAT 0x000080 /* HT setup w/ vendor OUI's */ #define IEEE80211_NODE_WPS 0x000100 /* WPS association */ #define IEEE80211_NODE_TSN 0x000200 /* TSN association */ #define IEEE80211_NODE_AMPDU_RX 0x000400 /* AMPDU rx enabled */ #define IEEE80211_NODE_AMPDU_TX 0x000800 /* AMPDU tx enabled */ #define IEEE80211_NODE_MIMO_PS 0x001000 /* MIMO power save enabled */ #define IEEE80211_NODE_MIMO_RTS 0x002000 /* send RTS in MIMO PS */ #define IEEE80211_NODE_RIFS 0x004000 /* RIFS enabled */ #define IEEE80211_NODE_SGI20 0x008000 /* Short GI in HT20 enabled */ #define IEEE80211_NODE_SGI40 0x010000 /* Short GI in HT40 enabled */ #define IEEE80211_NODE_ASSOCID 0x020000 /* xmit requires associd */ #define IEEE80211_NODE_AMSDU_RX 0x040000 /* AMSDU rx enabled */ #define IEEE80211_NODE_AMSDU_TX 0x080000 /* AMSDU tx enabled */ #define IEEE80211_NODE_VHT 0x100000 /* VHT enabled */ #define IEEE80211_NODE_LDPC 0x200000 /* LDPC enabled */ #define IEEE80211_NODE_UAPSD 0x400000 /* UAPSD enabled */ #endif /* XXX should also figure out where to put these for k/u-space sharing. */ #ifndef IEEE80211_FVHT_VHT #define IEEE80211_FVHT_VHT 0x000000001 /* CONF: VHT supported */ #define IEEE80211_FVHT_USEVHT40 0x000000002 /* CONF: Use VHT40 */ #define IEEE80211_FVHT_USEVHT80 0x000000004 /* CONF: Use VHT80 */ #define IEEE80211_FVHT_USEVHT160 0x000000008 /* CONF: Use VHT160 */ #define IEEE80211_FVHT_USEVHT80P80 0x000000010 /* CONF: Use VHT 80+80 */ #endif /* Helper macros unified. */ #ifndef _IEEE80211_MASKSHIFT #define _IEEE80211_MASKSHIFT(_v, _f) (((_v) & _f) >> _f##_S) #endif #ifndef _IEEE80211_SHIFTMASK #define _IEEE80211_SHIFTMASK(_v, _f) (((_v) << _f##_S) & _f) #endif #define MAXCHAN 1536 /* max 1.5K channels */ #define MAXCOL 78 static int col; static char spacer; static void LINE_INIT(char c); static void LINE_BREAK(void); static void LINE_CHECK(const char *fmt, ...); static const char *modename[IEEE80211_MODE_MAX] = { [IEEE80211_MODE_AUTO] = "auto", [IEEE80211_MODE_11A] = "11a", [IEEE80211_MODE_11B] = "11b", [IEEE80211_MODE_11G] = "11g", [IEEE80211_MODE_FH] = "fh", [IEEE80211_MODE_TURBO_A] = "turboA", [IEEE80211_MODE_TURBO_G] = "turboG", [IEEE80211_MODE_STURBO_A] = "sturbo", [IEEE80211_MODE_11NA] = "11na", [IEEE80211_MODE_11NG] = "11ng", [IEEE80211_MODE_HALF] = "half", [IEEE80211_MODE_QUARTER] = "quarter", [IEEE80211_MODE_VHT_2GHZ] = "11acg", [IEEE80211_MODE_VHT_5GHZ] = "11ac", }; static void set80211(if_ctx *ctx, int type, int val, int len, void *data); static int get80211(if_ctx *ctx, int type, void *data, int len); static int get80211len(if_ctx *ctx, int type, void *data, int len, int *plen); static int get80211val(if_ctx *ctx, int type, int *val); static const char *get_string(const char *val, const char *sep, u_int8_t *buf, int *lenp); static void print_string(const u_int8_t *buf, int len); static void print_regdomain(const struct ieee80211_regdomain *, int); static void print_channels(if_ctx *, const struct ieee80211req_chaninfo *, int allchans, int verbose); static void regdomain_makechannels(if_ctx *, struct ieee80211_regdomain_req *, const struct ieee80211_devcaps_req *); static const char *mesh_linkstate_string(uint8_t state); static struct ieee80211req_chaninfo *chaninfo; static struct ieee80211_regdomain regdomain; static int gotregdomain = 0; static struct ieee80211_roamparams_req roamparams; static int gotroam = 0; static struct ieee80211_txparams_req txparams; static int gottxparams = 0; static struct ieee80211_channel curchan; static int gotcurchan = 0; static struct ifmediareq *global_ifmr; static int htconf = 0; static int gothtconf = 0; static void gethtconf(if_ctx *ctx) { if (gothtconf) return; if (get80211val(ctx, IEEE80211_IOC_HTCONF, &htconf) < 0) warn("unable to get HT configuration information"); gothtconf = 1; } /* VHT */ static int vhtconf = 0; static int gotvhtconf = 0; static void getvhtconf(if_ctx *ctx) { if (gotvhtconf) return; if (get80211val(ctx, IEEE80211_IOC_VHTCONF, &vhtconf) < 0) warn("unable to get VHT configuration information"); gotvhtconf = 1; } /* * Collect channel info from the kernel. We use this (mostly) * to handle mapping between frequency and IEEE channel number. */ static void getchaninfo(if_ctx *ctx) { if (chaninfo != NULL) return; chaninfo = malloc(IEEE80211_CHANINFO_SIZE(MAXCHAN)); if (chaninfo == NULL) errx(1, "no space for channel list"); if (get80211(ctx, IEEE80211_IOC_CHANINFO, chaninfo, IEEE80211_CHANINFO_SIZE(MAXCHAN)) < 0) err(1, "unable to get channel information"); global_ifmr = ifmedia_getstate(ctx); gethtconf(ctx); getvhtconf(ctx); } static struct regdata * getregdata(void) { static struct regdata *rdp = NULL; if (rdp == NULL) { rdp = lib80211_alloc_regdata(); if (rdp == NULL) errx(-1, "missing or corrupted regdomain database"); } return rdp; } /* * Given the channel at index i with attributes from, * check if there is a channel with attributes to in * the channel table. With suitable attributes this * allows the caller to look for promotion; e.g. from * 11b > 11g. */ static int canpromote(unsigned int i, uint32_t from, uint32_t to) { const struct ieee80211_channel *fc = &chaninfo->ic_chans[i]; u_int j; if ((fc->ic_flags & from) != from) return i; /* NB: quick check exploiting ordering of chans w/ same frequency */ if (i+1 < chaninfo->ic_nchans && chaninfo->ic_chans[i+1].ic_freq == fc->ic_freq && (chaninfo->ic_chans[i+1].ic_flags & to) == to) return i+1; /* brute force search in case channel list is not ordered */ for (j = 0; j < chaninfo->ic_nchans; j++) { const struct ieee80211_channel *tc = &chaninfo->ic_chans[j]; if (j != i && tc->ic_freq == fc->ic_freq && (tc->ic_flags & to) == to) return j; } return i; } /* * Handle channel promotion. When a channel is specified with * only a frequency we want to promote it to the ``best'' channel * available. The channel list has separate entries for 11b, 11g, * 11a, and 11n[ga] channels so specifying a frequency w/o any * attributes requires we upgrade, e.g. from 11b -> 11g. This * gets complicated when the channel is specified on the same * command line with a media request that constrains the available * channe list (e.g. mode 11a); we want to honor that to avoid * confusing behaviour. */ /* * XXX VHT */ static int promote(unsigned int i) { /* * Query the current mode of the interface in case it's * constrained (e.g. to 11a). We must do this carefully * as there may be a pending ifmedia request in which case * asking the kernel will give us the wrong answer. This * is an unfortunate side-effect of the way ifconfig is * structure for modularity (yech). * * NB: ifmr is actually setup in getchaninfo (above); we * assume it's called coincident with to this call so * we have a ``current setting''; otherwise we must pass * the socket descriptor down to here so we can make * the ifmedia_getstate call ourselves. */ int chanmode = global_ifmr != NULL ? IFM_MODE(global_ifmr->ifm_current) : IFM_AUTO; /* when ambiguous promote to ``best'' */ /* NB: we abitrarily pick HT40+ over HT40- */ if (chanmode != IFM_IEEE80211_11B) i = canpromote(i, IEEE80211_CHAN_B, IEEE80211_CHAN_G); if (chanmode != IFM_IEEE80211_11G && (htconf & 1)) { i = canpromote(i, IEEE80211_CHAN_G, IEEE80211_CHAN_G | IEEE80211_CHAN_HT20); if (htconf & 2) { i = canpromote(i, IEEE80211_CHAN_G, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D); i = canpromote(i, IEEE80211_CHAN_G, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U); } } if (chanmode != IFM_IEEE80211_11A && (htconf & 1)) { i = canpromote(i, IEEE80211_CHAN_A, IEEE80211_CHAN_A | IEEE80211_CHAN_HT20); if (htconf & 2) { i = canpromote(i, IEEE80211_CHAN_A, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D); i = canpromote(i, IEEE80211_CHAN_A, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U); } } return i; } static void mapfreq(struct ieee80211_channel *chan, uint16_t freq, unsigned int flags) { u_int i; for (i = 0; i < chaninfo->ic_nchans; i++) { const struct ieee80211_channel *c = &chaninfo->ic_chans[i]; if (c->ic_freq == freq && (c->ic_flags & flags) == flags) { if (flags == 0) { /* when ambiguous promote to ``best'' */ c = &chaninfo->ic_chans[promote(i)]; } *chan = *c; return; } } errx(1, "unknown/undefined frequency %u/0x%x", freq, flags); } static void mapchan(struct ieee80211_channel *chan, uint8_t ieee, unsigned int flags) { u_int i; for (i = 0; i < chaninfo->ic_nchans; i++) { const struct ieee80211_channel *c = &chaninfo->ic_chans[i]; if (c->ic_ieee == ieee && (c->ic_flags & flags) == flags) { if (flags == 0) { /* when ambiguous promote to ``best'' */ c = &chaninfo->ic_chans[promote(i)]; } *chan = *c; return; } } errx(1, "unknown/undefined channel number %d flags 0x%x", ieee, flags); } static const struct ieee80211_channel * getcurchan(if_ctx *ctx) { if (gotcurchan) return &curchan; if (get80211(ctx, IEEE80211_IOC_CURCHAN, &curchan, sizeof(curchan)) < 0) { int val; /* fall back to legacy ioctl */ if (get80211val(ctx, IEEE80211_IOC_CHANNEL, &val) < 0) err(-1, "cannot figure out current channel"); getchaninfo(ctx); mapchan(&curchan, val, 0); } gotcurchan = 1; return &curchan; } static enum ieee80211_phymode chan2mode(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_VHTA(c)) return IEEE80211_MODE_VHT_5GHZ; if (IEEE80211_IS_CHAN_VHTG(c)) return IEEE80211_MODE_VHT_2GHZ; if (IEEE80211_IS_CHAN_HTA(c)) return IEEE80211_MODE_11NA; if (IEEE80211_IS_CHAN_HTG(c)) return IEEE80211_MODE_11NG; if (IEEE80211_IS_CHAN_108A(c)) return IEEE80211_MODE_TURBO_A; if (IEEE80211_IS_CHAN_108G(c)) return IEEE80211_MODE_TURBO_G; if (IEEE80211_IS_CHAN_ST(c)) return IEEE80211_MODE_STURBO_A; if (IEEE80211_IS_CHAN_FHSS(c)) return IEEE80211_MODE_FH; if (IEEE80211_IS_CHAN_HALF(c)) return IEEE80211_MODE_HALF; if (IEEE80211_IS_CHAN_QUARTER(c)) return IEEE80211_MODE_QUARTER; if (IEEE80211_IS_CHAN_A(c)) return IEEE80211_MODE_11A; if (IEEE80211_IS_CHAN_ANYG(c)) return IEEE80211_MODE_11G; if (IEEE80211_IS_CHAN_B(c)) return IEEE80211_MODE_11B; return IEEE80211_MODE_AUTO; } static void getroam(if_ctx *ctx) { if (gotroam) return; if (get80211(ctx, IEEE80211_IOC_ROAM, &roamparams, sizeof(roamparams)) < 0) err(1, "unable to get roaming parameters"); gotroam = 1; } static void setroam_cb(if_ctx *ctx, void *arg) { struct ieee80211_roamparams_req *roam = arg; set80211(ctx, IEEE80211_IOC_ROAM, 0, sizeof(*roam), roam); } static void gettxparams(if_ctx *ctx) { if (gottxparams) return; if (get80211(ctx, IEEE80211_IOC_TXPARAMS, &txparams, sizeof(txparams)) < 0) err(1, "unable to get transmit parameters"); gottxparams = 1; } static void settxparams_cb(if_ctx *ctx, void *arg) { struct ieee80211_txparams_req *txp = arg; set80211(ctx, IEEE80211_IOC_TXPARAMS, 0, sizeof(*txp), txp); } static void getregdomain(if_ctx *ctx) { if (gotregdomain) return; if (get80211(ctx, IEEE80211_IOC_REGDOMAIN, ®domain, sizeof(regdomain)) < 0) err(1, "unable to get regulatory domain info"); gotregdomain = 1; } static void getdevcaps(if_ctx *ctx, struct ieee80211_devcaps_req *dc) { if (get80211(ctx, IEEE80211_IOC_DEVCAPS, dc, IEEE80211_DEVCAPS_SPACE(dc)) < 0) err(1, "unable to get device capabilities"); } static void setregdomain_cb(if_ctx *ctx, void *arg) { struct ieee80211_regdomain_req *req; struct ieee80211_regdomain *rd = arg; struct ieee80211_devcaps_req *dc; struct regdata *rdp = getregdata(); if (rd->country != NO_COUNTRY) { const struct country *cc; /* * Check current country seting to make sure it's * compatible with the new regdomain. If not, then * override it with any default country for this * SKU. If we cannot arrange a match, then abort. */ cc = lib80211_country_findbycc(rdp, rd->country); if (cc == NULL) errx(1, "unknown ISO country code %d", rd->country); if (cc->rd->sku != rd->regdomain) { const struct regdomain *rp; /* * Check if country is incompatible with regdomain. * To enable multiple regdomains for a country code * we permit a mismatch between the regdomain and * the country's associated regdomain when the * regdomain is setup w/o a default country. For * example, US is bound to the FCC regdomain but * we allow US to be combined with FCC3 because FCC3 * has not default country. This allows bogus * combinations like FCC3+DK which are resolved when * constructing the channel list by deferring to the * regdomain to construct the channel list. */ rp = lib80211_regdomain_findbysku(rdp, rd->regdomain); if (rp == NULL) errx(1, "country %s (%s) is not usable with " "regdomain %d", cc->isoname, cc->name, rd->regdomain); else if (rp->cc != NULL && rp->cc != cc) errx(1, "country %s (%s) is not usable with " "regdomain %s", cc->isoname, cc->name, rp->name); } } /* * Fetch the device capabilities and calculate the * full set of netbands for which we request a new * channel list be constructed. Once that's done we * push the regdomain info + channel list to the kernel. */ dc = malloc(IEEE80211_DEVCAPS_SIZE(MAXCHAN)); if (dc == NULL) errx(1, "no space for device capabilities"); dc->dc_chaninfo.ic_nchans = MAXCHAN; getdevcaps(ctx, dc); #if 0 if (verbose) { printf("drivercaps: 0x%x\n", dc->dc_drivercaps); printf("cryptocaps: 0x%x\n", dc->dc_cryptocaps); printf("htcaps : 0x%x\n", dc->dc_htcaps); printf("vhtcaps : 0x%x\n", dc->dc_vhtcaps); #if 0 memcpy(chaninfo, &dc->dc_chaninfo, IEEE80211_CHANINFO_SPACE(&dc->dc_chaninfo)); print_channels(s, &dc->dc_chaninfo, 1/*allchans*/, 1/*verbose*/); #endif } #endif req = malloc(IEEE80211_REGDOMAIN_SIZE(dc->dc_chaninfo.ic_nchans)); if (req == NULL) errx(1, "no space for regdomain request"); req->rd = *rd; regdomain_makechannels(ctx, req, dc); if (ctx->args->verbose) { LINE_INIT(':'); print_regdomain(rd, 1/*verbose*/); LINE_BREAK(); /* blech, reallocate channel list for new data */ if (chaninfo != NULL) free(chaninfo); chaninfo = malloc(IEEE80211_CHANINFO_SPACE(&req->chaninfo)); if (chaninfo == NULL) errx(1, "no space for channel list"); memcpy(chaninfo, &req->chaninfo, IEEE80211_CHANINFO_SPACE(&req->chaninfo)); print_channels(ctx, &req->chaninfo, 1/*allchans*/, 1/*verbose*/); } if (req->chaninfo.ic_nchans == 0) errx(1, "no channels calculated"); set80211(ctx, IEEE80211_IOC_REGDOMAIN, 0, IEEE80211_REGDOMAIN_SPACE(req), req); free(req); free(dc); } static int ieee80211_mhz2ieee(int freq, int flags) { struct ieee80211_channel chan; mapfreq(&chan, freq, flags); return chan.ic_ieee; } static int isanyarg(const char *arg) { return (strncmp(arg, "-", 1) == 0 || strncasecmp(arg, "any", 3) == 0 || strncasecmp(arg, "off", 3) == 0); } static void set80211ssid(if_ctx *ctx, const char *val, int dummy __unused) { int ssid; int len; u_int8_t data[IEEE80211_NWID_LEN]; ssid = 0; len = strlen(val); if (len > 2 && isdigit((int)val[0]) && val[1] == ':') { ssid = atoi(val)-1; val += 2; } bzero(data, sizeof(data)); len = sizeof(data); if (get_string(val, NULL, data, &len) == NULL) exit(1); set80211(ctx, IEEE80211_IOC_SSID, ssid, len, data); } static void set80211meshid(if_ctx *ctx, const char *val, int dummy __unused) { int len; u_int8_t data[IEEE80211_NWID_LEN]; memset(data, 0, sizeof(data)); len = sizeof(data); if (get_string(val, NULL, data, &len) == NULL) exit(1); set80211(ctx, IEEE80211_IOC_MESH_ID, 0, len, data); } static void set80211stationname(if_ctx *ctx, const char *val, int dummy __unused) { int len; u_int8_t data[33]; bzero(data, sizeof(data)); len = sizeof(data); get_string(val, NULL, data, &len); set80211(ctx, IEEE80211_IOC_STATIONNAME, 0, len, data); } /* * Parse a channel specification for attributes/flags. * The syntax is: * freq/xx channel width (5,10,20,40,40+,40-) * freq:mode channel mode (a,b,g,h,n,t,s,d) * * These can be combined in either order; e.g. 2437:ng/40. * Modes are case insensitive. * * The result is not validated here; it's assumed to be * checked against the channel table fetched from the kernel. */ static unsigned int getchannelflags(const char *val, int freq) { #define _CHAN_HT 0x80000000 const char *cp; int flags; int is_vht = 0; flags = 0; cp = strchr(val, ':'); if (cp != NULL) { for (cp++; isalpha((int) *cp); cp++) { /* accept mixed case */ int c = *cp; if (isupper(c)) c = tolower(c); switch (c) { case 'a': /* 802.11a */ flags |= IEEE80211_CHAN_A; break; case 'b': /* 802.11b */ flags |= IEEE80211_CHAN_B; break; case 'g': /* 802.11g */ flags |= IEEE80211_CHAN_G; break; case 'v': /* vht: 802.11ac */ is_vht = 1; /* Fallthrough */ case 'h': /* ht = 802.11n */ case 'n': /* 802.11n */ flags |= _CHAN_HT; /* NB: private */ break; case 'd': /* dt = Atheros Dynamic Turbo */ flags |= IEEE80211_CHAN_TURBO; break; case 't': /* ht, dt, st, t */ /* dt and unadorned t specify Dynamic Turbo */ if ((flags & (IEEE80211_CHAN_STURBO|_CHAN_HT)) == 0) flags |= IEEE80211_CHAN_TURBO; break; case 's': /* st = Atheros Static Turbo */ flags |= IEEE80211_CHAN_STURBO; break; default: errx(-1, "%s: Invalid channel attribute %c\n", val, *cp); } } } cp = strchr(val, '/'); if (cp != NULL) { char *ep; u_long cw = strtoul(cp+1, &ep, 10); switch (cw) { case 5: flags |= IEEE80211_CHAN_QUARTER; break; case 10: flags |= IEEE80211_CHAN_HALF; break; case 20: /* NB: this may be removed below */ flags |= IEEE80211_CHAN_HT20; break; case 40: case 80: case 160: /* Handle the 80/160 VHT flag */ if (cw == 80) flags |= IEEE80211_CHAN_VHT80; else if (cw == 160) flags |= IEEE80211_CHAN_VHT160; /* Fallthrough */ if (ep != NULL && *ep == '+') flags |= IEEE80211_CHAN_HT40U; else if (ep != NULL && *ep == '-') flags |= IEEE80211_CHAN_HT40D; break; default: errx(-1, "%s: Invalid channel width\n", val); } } /* * Cleanup specifications. */ if ((flags & _CHAN_HT) == 0) { /* * If user specified freq/20 or freq/40 quietly remove * HT cw attributes depending on channel use. To give * an explicit 20/40 width for an HT channel you must * indicate it is an HT channel since all HT channels * are also usable for legacy operation; e.g. freq:n/40. */ flags &= ~IEEE80211_CHAN_HT; flags &= ~IEEE80211_CHAN_VHT; } else { /* * Remove private indicator that this is an HT channel * and if no explicit channel width has been given * provide the default settings. */ flags &= ~_CHAN_HT; if ((flags & IEEE80211_CHAN_HT) == 0) { struct ieee80211_channel chan; /* * Consult the channel list to see if we can use * HT40+ or HT40- (if both the map routines choose). */ if (freq > 255) mapfreq(&chan, freq, 0); else mapchan(&chan, freq, 0); flags |= (chan.ic_flags & IEEE80211_CHAN_HT); } /* * If VHT is enabled, then also set the VHT flag and the * relevant channel up/down. */ if (is_vht && (flags & IEEE80211_CHAN_HT)) { /* * XXX yes, maybe we should just have VHT, and reuse * HT20/HT40U/HT40D */ if (flags & IEEE80211_CHAN_VHT80) ; else if (flags & IEEE80211_CHAN_HT20) flags |= IEEE80211_CHAN_VHT20; else if (flags & IEEE80211_CHAN_HT40U) flags |= IEEE80211_CHAN_VHT40U; else if (flags & IEEE80211_CHAN_HT40D) flags |= IEEE80211_CHAN_VHT40D; } } return flags; #undef _CHAN_HT } static void getchannel(if_ctx *ctx, struct ieee80211_channel *chan, const char *val) { unsigned int v, flags; char *eptr; memset(chan, 0, sizeof(*chan)); if (isanyarg(val)) { chan->ic_freq = IEEE80211_CHAN_ANY; return; } getchaninfo(ctx); errno = 0; v = strtol(val, &eptr, 10); if (val[0] == '\0' || val == eptr || errno == ERANGE || /* channel may be suffixed with nothing, :flag, or /width */ (eptr[0] != '\0' && eptr[0] != ':' && eptr[0] != '/')) errx(1, "invalid channel specification%s", errno == ERANGE ? " (out of range)" : ""); flags = getchannelflags(val, v); if (v > 255) { /* treat as frequency */ mapfreq(chan, v, flags); } else { mapchan(chan, v, flags); } } static void set80211channel(if_ctx *ctx, const char *val, int dummy __unused) { struct ieee80211_channel chan; getchannel(ctx, &chan, val); set80211(ctx, IEEE80211_IOC_CURCHAN, 0, sizeof(chan), &chan); } static void set80211chanswitch(if_ctx *ctx, const char *val, int dummy __unused) { struct ieee80211_chanswitch_req csr; getchannel(ctx, &csr.csa_chan, val); csr.csa_mode = 1; csr.csa_count = 5; set80211(ctx, IEEE80211_IOC_CHANSWITCH, 0, sizeof(csr), &csr); } static void set80211authmode(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "none") == 0) { mode = IEEE80211_AUTH_NONE; } else if (strcasecmp(val, "open") == 0) { mode = IEEE80211_AUTH_OPEN; } else if (strcasecmp(val, "shared") == 0) { mode = IEEE80211_AUTH_SHARED; } else if (strcasecmp(val, "8021x") == 0) { mode = IEEE80211_AUTH_8021X; } else if (strcasecmp(val, "wpa") == 0) { mode = IEEE80211_AUTH_WPA; } else { errx(1, "unknown authmode"); } set80211(ctx, IEEE80211_IOC_AUTHMODE, mode, 0, NULL); } static void set80211powersavemode(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_POWERSAVE_OFF; } else if (strcasecmp(val, "on") == 0) { mode = IEEE80211_POWERSAVE_ON; } else if (strcasecmp(val, "cam") == 0) { mode = IEEE80211_POWERSAVE_CAM; } else if (strcasecmp(val, "psp") == 0) { mode = IEEE80211_POWERSAVE_PSP; } else if (strcasecmp(val, "psp-cam") == 0) { mode = IEEE80211_POWERSAVE_PSP_CAM; } else { errx(1, "unknown powersavemode"); } set80211(ctx, IEEE80211_IOC_POWERSAVE, mode, 0, NULL); } static void set80211powersave(if_ctx *ctx, const char *val __unused, int d) { if (d == 0) set80211(ctx, IEEE80211_IOC_POWERSAVE, IEEE80211_POWERSAVE_OFF, 0, NULL); else set80211(ctx, IEEE80211_IOC_POWERSAVE, IEEE80211_POWERSAVE_ON, 0, NULL); } static void set80211powersavesleep(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_POWERSAVESLEEP, atoi(val), 0, NULL); } static void set80211wepmode(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_WEP_OFF; } else if (strcasecmp(val, "on") == 0) { mode = IEEE80211_WEP_ON; } else if (strcasecmp(val, "mixed") == 0) { mode = IEEE80211_WEP_MIXED; } else { errx(1, "unknown wep mode"); } set80211(ctx, IEEE80211_IOC_WEP, mode, 0, NULL); } static void set80211wep(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_WEP, d, 0, NULL); } static int isundefarg(const char *arg) { return (strcmp(arg, "-") == 0 || strncasecmp(arg, "undef", 5) == 0); } static void set80211weptxkey(if_ctx *ctx, const char *val, int dummy __unused) { if (isundefarg(val)) set80211(ctx, IEEE80211_IOC_WEPTXKEY, IEEE80211_KEYIX_NONE, 0, NULL); else set80211(ctx, IEEE80211_IOC_WEPTXKEY, atoi(val)-1, 0, NULL); } static void set80211wepkey(if_ctx *ctx, const char *val, int dummy __unused) { int key = 0; int len; u_int8_t data[IEEE80211_KEYBUF_SIZE]; if (isdigit((int)val[0]) && val[1] == ':') { key = atoi(val)-1; val += 2; } bzero(data, sizeof(data)); len = sizeof(data); get_string(val, NULL, data, &len); set80211(ctx, IEEE80211_IOC_WEPKEY, key, len, data); } /* * This function is purely a NetBSD compatibility interface. The NetBSD * interface is too inflexible, but it's there so we'll support it since * it's not all that hard. */ static void set80211nwkey(if_ctx *ctx, const char *val, int dummy __unused) { int txkey; int i, len; u_int8_t data[IEEE80211_KEYBUF_SIZE]; set80211(ctx, IEEE80211_IOC_WEP, IEEE80211_WEP_ON, 0, NULL); if (isdigit((int)val[0]) && val[1] == ':') { txkey = val[0]-'0'-1; val += 2; for (i = 0; i < 4; i++) { bzero(data, sizeof(data)); len = sizeof(data); val = get_string(val, ",", data, &len); if (val == NULL) exit(1); set80211(ctx, IEEE80211_IOC_WEPKEY, i, len, data); } } else { bzero(data, sizeof(data)); len = sizeof(data); get_string(val, NULL, data, &len); txkey = 0; set80211(ctx, IEEE80211_IOC_WEPKEY, 0, len, data); bzero(data, sizeof(data)); for (i = 1; i < 4; i++) set80211(ctx, IEEE80211_IOC_WEPKEY, i, 0, data); } set80211(ctx, IEEE80211_IOC_WEPTXKEY, txkey, 0, NULL); } static void set80211rtsthreshold(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_RTSTHRESHOLD, isundefarg(val) ? IEEE80211_RTS_MAX : atoi(val), 0, NULL); } static void set80211protmode(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_PROTMODE_OFF; } else if (strcasecmp(val, "cts") == 0) { mode = IEEE80211_PROTMODE_CTS; } else if (strncasecmp(val, "rtscts", 3) == 0) { mode = IEEE80211_PROTMODE_RTSCTS; } else { errx(1, "unknown protection mode"); } set80211(ctx, IEEE80211_IOC_PROTMODE, mode, 0, NULL); } static void set80211htprotmode(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "off") == 0) { mode = IEEE80211_PROTMODE_OFF; } else if (strncasecmp(val, "rts", 3) == 0) { mode = IEEE80211_PROTMODE_RTSCTS; } else { errx(1, "unknown protection mode"); } set80211(ctx, IEEE80211_IOC_HTPROTMODE, mode, 0, NULL); } static void set80211txpower(if_ctx *ctx, const char *val, int dummy __unused) { double v = atof(val); int txpow; txpow = (int) (2*v); if (txpow != 2*v) errx(-1, "invalid tx power (must be .5 dBm units)"); set80211(ctx, IEEE80211_IOC_TXPOWER, txpow, 0, NULL); } #define IEEE80211_ROAMING_DEVICE 0 #define IEEE80211_ROAMING_AUTO 1 #define IEEE80211_ROAMING_MANUAL 2 static void set80211roaming(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "device") == 0) { mode = IEEE80211_ROAMING_DEVICE; } else if (strcasecmp(val, "auto") == 0) { mode = IEEE80211_ROAMING_AUTO; } else if (strcasecmp(val, "manual") == 0) { mode = IEEE80211_ROAMING_MANUAL; } else { errx(1, "unknown roaming mode"); } set80211(ctx, IEEE80211_IOC_ROAMING, mode, 0, NULL); } static void set80211wme(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_WME, d, 0, NULL); } static void set80211hidessid(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_HIDESSID, d, 0, NULL); } static void set80211apbridge(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_APBRIDGE, d, 0, NULL); } static void set80211fastframes(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_FF, d, 0, NULL); } static void set80211dturbo(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_TURBOP, d, 0, NULL); } static void set80211chanlist(if_ctx *ctx, const char *val, int dummy __unused) { struct ieee80211req_chanlist chanlist; char *temp, *cp, *tp; temp = malloc(strlen(val) + 1); if (temp == NULL) errx(1, "malloc failed"); strcpy(temp, val); memset(&chanlist, 0, sizeof(chanlist)); cp = temp; for (;;) { int first, last, f, c; tp = strchr(cp, ','); if (tp != NULL) *tp++ = '\0'; switch (sscanf(cp, "%u-%u", &first, &last)) { case 1: if (first > IEEE80211_CHAN_MAX) errx(-1, "channel %u out of range, max %u", first, IEEE80211_CHAN_MAX); setbit(chanlist.ic_channels, first); break; case 2: if (first > IEEE80211_CHAN_MAX) errx(-1, "channel %u out of range, max %u", first, IEEE80211_CHAN_MAX); if (last > IEEE80211_CHAN_MAX) errx(-1, "channel %u out of range, max %u", last, IEEE80211_CHAN_MAX); if (first > last) errx(-1, "void channel range, %u > %u", first, last); for (f = first; f <= last; f++) setbit(chanlist.ic_channels, f); break; } if (tp == NULL) break; c = *tp; while (isspace(c)) tp++; if (!isdigit(c)) break; cp = tp; } set80211(ctx, IEEE80211_IOC_CHANLIST, 0, sizeof(chanlist), &chanlist); free(temp); } static void set80211bssid(if_ctx *ctx, const char *val, int dummy __unused) { if (!isanyarg(val)) { char *temp; struct sockaddr_dl sdl; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); set80211(ctx, IEEE80211_IOC_BSSID, 0, IEEE80211_ADDR_LEN, LLADDR(&sdl)); } else { uint8_t zerobssid[IEEE80211_ADDR_LEN]; memset(zerobssid, 0, sizeof(zerobssid)); set80211(ctx, IEEE80211_IOC_BSSID, 0, IEEE80211_ADDR_LEN, zerobssid); } } static int getac(const char *ac) { if (strcasecmp(ac, "ac_be") == 0 || strcasecmp(ac, "be") == 0) return WME_AC_BE; if (strcasecmp(ac, "ac_bk") == 0 || strcasecmp(ac, "bk") == 0) return WME_AC_BK; if (strcasecmp(ac, "ac_vi") == 0 || strcasecmp(ac, "vi") == 0) return WME_AC_VI; if (strcasecmp(ac, "ac_vo") == 0 || strcasecmp(ac, "vo") == 0) return WME_AC_VO; errx(1, "unknown wme access class %s", ac); } static void set80211cwmin(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_CWMIN, atoi(val), getac(ac), NULL); } static void set80211cwmax(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_CWMAX, atoi(val), getac(ac), NULL); } static void set80211aifs(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_AIFS, atoi(val), getac(ac), NULL); } static void set80211txoplimit(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_TXOPLIMIT, atoi(val), getac(ac), NULL); } static void set80211acm(if_ctx *ctx, const char *ac, int dummy __unused) { set80211(ctx, IEEE80211_IOC_WME_ACM, 1, getac(ac), NULL); } static void set80211noacm(if_ctx *ctx, const char *ac, int dummy __unused) { set80211(ctx, IEEE80211_IOC_WME_ACM, 0, getac(ac), NULL); } static void set80211ackpolicy(if_ctx *ctx, const char *ac, int dummy __unused) { set80211(ctx, IEEE80211_IOC_WME_ACKPOLICY, 1, getac(ac), NULL); } static void set80211noackpolicy(if_ctx *ctx, const char *ac, int dummy __unused) { set80211(ctx, IEEE80211_IOC_WME_ACKPOLICY, 0, getac(ac), NULL); } static void set80211bsscwmin(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_CWMIN, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static void set80211bsscwmax(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_CWMAX, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static void set80211bssaifs(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_AIFS, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static void set80211bsstxoplimit(if_ctx *ctx, const char *ac, const char *val) { set80211(ctx, IEEE80211_IOC_WME_TXOPLIMIT, atoi(val), getac(ac)|IEEE80211_WMEPARAM_BSS, NULL); } static void set80211dtimperiod(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_DTIM_PERIOD, atoi(val), 0, NULL); } static void set80211bintval(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_BEACON_INTERVAL, atoi(val), 0, NULL); } static void set80211macmac(if_ctx *ctx, int op, const char *val) { char *temp; struct sockaddr_dl sdl; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); set80211(ctx, op, 0, IEEE80211_ADDR_LEN, LLADDR(&sdl)); } static void set80211addmac(if_ctx *ctx, const char *val, int dummy __unused) { set80211macmac(ctx, IEEE80211_IOC_ADDMAC, val); } static void set80211delmac(if_ctx *ctx, const char *val, int dummy __unused) { set80211macmac(ctx, IEEE80211_IOC_DELMAC, val); } static void set80211kickmac(if_ctx *ctx, const char *val, int dummy __unused) { char *temp; struct sockaddr_dl sdl; struct ieee80211req_mlme mlme; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); memset(&mlme, 0, sizeof(mlme)); mlme.im_op = IEEE80211_MLME_DEAUTH; mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE; memcpy(mlme.im_macaddr, LLADDR(&sdl), IEEE80211_ADDR_LEN); set80211(ctx, IEEE80211_IOC_MLME, 0, sizeof(mlme), &mlme); } static void set80211maccmd(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_MACCMD, d, 0, NULL); } static void set80211meshrtmac(if_ctx *ctx, int req, const char *val) { char *temp; struct sockaddr_dl sdl; temp = malloc(strlen(val) + 2); /* ':' and '\0' */ if (temp == NULL) errx(1, "malloc failed"); temp[0] = ':'; strcpy(temp + 1, val); sdl.sdl_len = sizeof(sdl); link_addr(temp, &sdl); free(temp); if (sdl.sdl_alen != IEEE80211_ADDR_LEN) errx(1, "malformed link-level address"); set80211(ctx, IEEE80211_IOC_MESH_RTCMD, req, IEEE80211_ADDR_LEN, LLADDR(&sdl)); } static void set80211addmeshrt(if_ctx *ctx, const char *val, int dummy __unused) { set80211meshrtmac(ctx, IEEE80211_MESH_RTCMD_ADD, val); } static void set80211delmeshrt(if_ctx *ctx, const char *val, int dummy __unused) { set80211meshrtmac(ctx, IEEE80211_MESH_RTCMD_DELETE, val); } static void set80211meshrtcmd(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_MESH_RTCMD, d, 0, NULL); } static void set80211hwmprootmode(if_ctx *ctx, const char *val, int dummy __unused) { int mode; if (strcasecmp(val, "normal") == 0) mode = IEEE80211_HWMP_ROOTMODE_NORMAL; else if (strcasecmp(val, "proactive") == 0) mode = IEEE80211_HWMP_ROOTMODE_PROACTIVE; else if (strcasecmp(val, "rann") == 0) mode = IEEE80211_HWMP_ROOTMODE_RANN; else mode = IEEE80211_HWMP_ROOTMODE_DISABLED; set80211(ctx, IEEE80211_IOC_HWMP_ROOTMODE, mode, 0, NULL); } static void set80211hwmpmaxhops(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_HWMP_MAXHOPS, atoi(val), 0, NULL); } static void set80211pureg(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_PUREG, d, 0, NULL); } static void set80211quiet(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_QUIET, d, 0, NULL); } static void set80211quietperiod(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_QUIET_PERIOD, atoi(val), 0, NULL); } static void set80211quietcount(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_QUIET_COUNT, atoi(val), 0, NULL); } static void set80211quietduration(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_QUIET_DUR, atoi(val), 0, NULL); } static void set80211quietoffset(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_QUIET_OFFSET, atoi(val), 0, NULL); } static void set80211bgscan(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_BGSCAN, d, 0, NULL); } static void set80211bgscanidle(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_BGSCAN_IDLE, atoi(val), 0, NULL); } static void set80211bgscanintvl(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_BGSCAN_INTERVAL, atoi(val), 0, NULL); } static void set80211scanvalid(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_SCANVALID, atoi(val), 0, NULL); } /* * Parse an optional trailing specification of which netbands * to apply a parameter to. This is basically the same syntax * as used for channels but you can concatenate to specify * multiple. For example: * 14:abg apply to 11a, 11b, and 11g * 6:ht apply to 11na and 11ng * We don't make a big effort to catch silly things; this is * really a convenience mechanism. */ static int getmodeflags(const char *val) { const char *cp; int flags; flags = 0; cp = strchr(val, ':'); if (cp != NULL) { for (cp++; isalpha((int) *cp); cp++) { /* accept mixed case */ int c = *cp; if (isupper(c)) c = tolower(c); switch (c) { case 'a': /* 802.11a */ flags |= IEEE80211_CHAN_A; break; case 'b': /* 802.11b */ flags |= IEEE80211_CHAN_B; break; case 'g': /* 802.11g */ flags |= IEEE80211_CHAN_G; break; case 'n': /* 802.11n */ flags |= IEEE80211_CHAN_HT; break; case 'd': /* dt = Atheros Dynamic Turbo */ flags |= IEEE80211_CHAN_TURBO; break; case 't': /* ht, dt, st, t */ /* dt and unadorned t specify Dynamic Turbo */ if ((flags & (IEEE80211_CHAN_STURBO|IEEE80211_CHAN_HT)) == 0) flags |= IEEE80211_CHAN_TURBO; break; case 's': /* st = Atheros Static Turbo */ flags |= IEEE80211_CHAN_STURBO; break; case 'h': /* 1/2-width channels */ flags |= IEEE80211_CHAN_HALF; break; case 'q': /* 1/4-width channels */ flags |= IEEE80211_CHAN_QUARTER; break; case 'v': /* XXX set HT too? */ flags |= IEEE80211_CHAN_VHT; break; default: errx(-1, "%s: Invalid mode attribute %c\n", val, *cp); } } } return flags; } #define _APPLY(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ if ((_flags & (IEEE80211_CHAN_5GHZ|IEEE80211_CHAN_2GHZ)) == 0) {\ _base.params[IEEE80211_MODE_11NA]._param = _v; \ _base.params[IEEE80211_MODE_11NG]._param = _v; \ } else if (_flags & IEEE80211_CHAN_5GHZ) \ _base.params[IEEE80211_MODE_11NA]._param = _v; \ else \ _base.params[IEEE80211_MODE_11NG]._param = _v; \ } \ if (_flags & IEEE80211_CHAN_TURBO) { \ if ((_flags & (IEEE80211_CHAN_5GHZ|IEEE80211_CHAN_2GHZ)) == 0) {\ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \ } else if (_flags & IEEE80211_CHAN_5GHZ) \ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \ else \ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \ } \ if (_flags & IEEE80211_CHAN_STURBO) \ _base.params[IEEE80211_MODE_STURBO_A]._param = _v; \ if ((_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) \ _base.params[IEEE80211_MODE_11A]._param = _v; \ if ((_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) \ _base.params[IEEE80211_MODE_11G]._param = _v; \ if ((_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) \ _base.params[IEEE80211_MODE_11B]._param = _v; \ if (_flags & IEEE80211_CHAN_HALF) \ _base.params[IEEE80211_MODE_HALF]._param = _v; \ if (_flags & IEEE80211_CHAN_QUARTER) \ _base.params[IEEE80211_MODE_QUARTER]._param = _v; \ } while (0) #define _APPLY1(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ if (_flags & IEEE80211_CHAN_5GHZ) \ _base.params[IEEE80211_MODE_11NA]._param = _v; \ else \ _base.params[IEEE80211_MODE_11NG]._param = _v; \ } else if ((_flags & IEEE80211_CHAN_108A) == IEEE80211_CHAN_108A) \ _base.params[IEEE80211_MODE_TURBO_A]._param = _v; \ else if ((_flags & IEEE80211_CHAN_108G) == IEEE80211_CHAN_108G) \ _base.params[IEEE80211_MODE_TURBO_G]._param = _v; \ else if ((_flags & IEEE80211_CHAN_ST) == IEEE80211_CHAN_ST) \ _base.params[IEEE80211_MODE_STURBO_A]._param = _v; \ else if (_flags & IEEE80211_CHAN_HALF) \ _base.params[IEEE80211_MODE_HALF]._param = _v; \ else if (_flags & IEEE80211_CHAN_QUARTER) \ _base.params[IEEE80211_MODE_QUARTER]._param = _v; \ else if ((_flags & IEEE80211_CHAN_A) == IEEE80211_CHAN_A) \ _base.params[IEEE80211_MODE_11A]._param = _v; \ else if ((_flags & IEEE80211_CHAN_G) == IEEE80211_CHAN_G) \ _base.params[IEEE80211_MODE_11G]._param = _v; \ else if ((_flags & IEEE80211_CHAN_B) == IEEE80211_CHAN_B) \ _base.params[IEEE80211_MODE_11B]._param = _v; \ } while (0) #define _APPLY_RATE(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ (_v) = (_v / 2) | IEEE80211_RATE_MCS; \ } \ _APPLY(_flags, _base, _param, _v); \ } while (0) #define _APPLY_RATE1(_flags, _base, _param, _v) do { \ if (_flags & IEEE80211_CHAN_HT) { \ (_v) = (_v / 2) | IEEE80211_RATE_MCS; \ } \ _APPLY1(_flags, _base, _param, _v); \ } while (0) static void set80211roamrssi(if_ctx *ctx, const char *val, int dummy __unused) { double v = atof(val); int rssi, flags; rssi = (int) (2*v); if (rssi != 2*v) errx(-1, "invalid rssi (must be .5 dBm units)"); flags = getmodeflags(val); getroam(ctx); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY1(flags, roamparams, rssi, rssi); } else _APPLY(flags, roamparams, rssi, rssi); callback_register(setroam_cb, &roamparams); } static int getrate(const char *val, const char *tag) { double v = atof(val); int rate; rate = (int) (2*v); if (rate != 2*v) errx(-1, "invalid %s rate (must be .5 Mb/s units)", tag); return rate; /* NB: returns 2x the specified value */ } static void set80211roamrate(if_ctx *ctx, const char *val, int dummy __unused) { int rate, flags; rate = getrate(val, "roam"); flags = getmodeflags(val); getroam(ctx); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY_RATE1(flags, roamparams, rate, rate); } else _APPLY_RATE(flags, roamparams, rate, rate); callback_register(setroam_cb, &roamparams); } static void set80211mcastrate(if_ctx *ctx, const char *val, int dummy __unused) { int rate, flags; rate = getrate(val, "mcast"); flags = getmodeflags(val); gettxparams(ctx); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY_RATE1(flags, txparams, mcastrate, rate); } else _APPLY_RATE(flags, txparams, mcastrate, rate); callback_register(settxparams_cb, &txparams); } static void set80211mgtrate(if_ctx *ctx, const char *val, int dummy __unused) { int rate, flags; rate = getrate(val, "mgmt"); flags = getmodeflags(val); gettxparams(ctx); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY_RATE1(flags, txparams, mgmtrate, rate); } else _APPLY_RATE(flags, txparams, mgmtrate, rate); callback_register(settxparams_cb, &txparams); } static void set80211ucastrate(if_ctx *ctx, const char *val, int dummy __unused) { int flags; gettxparams(ctx); flags = getmodeflags(val); if (isanyarg(val)) { if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY1(flags, txparams, ucastrate, IEEE80211_FIXED_RATE_NONE); } else _APPLY(flags, txparams, ucastrate, IEEE80211_FIXED_RATE_NONE); } else { int rate = getrate(val, "ucast"); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY_RATE1(flags, txparams, ucastrate, rate); } else _APPLY_RATE(flags, txparams, ucastrate, rate); } callback_register(settxparams_cb, &txparams); } static void set80211maxretry(if_ctx *ctx, const char *val, int dummy __unused) { int v = atoi(val), flags; flags = getmodeflags(val); gettxparams(ctx); if (flags == 0) { /* NB: no flags => current channel */ flags = getcurchan(ctx)->ic_flags; _APPLY1(flags, txparams, maxretry, v); } else _APPLY(flags, txparams, maxretry, v); callback_register(settxparams_cb, &txparams); } #undef _APPLY_RATE #undef _APPLY static void set80211fragthreshold(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_FRAGTHRESHOLD, isundefarg(val) ? IEEE80211_FRAG_MAX : atoi(val), 0, NULL); } static void set80211bmissthreshold(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_BMISSTHRESHOLD, isundefarg(val) ? IEEE80211_HWBMISS_MAX : atoi(val), 0, NULL); } static void set80211burst(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_BURST, d, 0, NULL); } static void set80211doth(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_DOTH, d, 0, NULL); } static void set80211dfs(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_DFS, d, 0, NULL); } static void set80211shortgi(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_SHORTGI, d ? (IEEE80211_HTCAP_SHORTGI20 | IEEE80211_HTCAP_SHORTGI40) : 0, 0, NULL); } /* XXX 11ac density/size is different */ static void set80211ampdu(if_ctx *ctx, const char *val __unused, int d) { int ampdu; if (get80211val(ctx, IEEE80211_IOC_AMPDU, &du) < 0) errx(-1, "cannot set AMPDU setting"); if (d < 0) { d = -d; ampdu &= ~d; } else ampdu |= d; set80211(ctx, IEEE80211_IOC_AMPDU, ampdu, 0, NULL); } static void set80211stbc(if_ctx *ctx, const char *val __unused, int d) { int stbc; if (get80211val(ctx, IEEE80211_IOC_STBC, &stbc) < 0) errx(-1, "cannot set STBC setting"); if (d < 0) { d = -d; stbc &= ~d; } else stbc |= d; set80211(ctx, IEEE80211_IOC_STBC, stbc, 0, NULL); } static void set80211ldpc(if_ctx *ctx, const char *val __unused, int d) { int ldpc; if (get80211val(ctx, IEEE80211_IOC_LDPC, &ldpc) < 0) errx(-1, "cannot set LDPC setting"); if (d < 0) { d = -d; ldpc &= ~d; } else ldpc |= d; set80211(ctx, IEEE80211_IOC_LDPC, ldpc, 0, NULL); } static void set80211uapsd(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_UAPSD, d, 0, NULL); } static void set80211ampdulimit(if_ctx *ctx, const char *val, int dummy __unused) { int v; switch (atoi(val)) { case 8: case 8*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_8K; break; case 16: case 16*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_16K; break; case 32: case 32*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_32K; break; case 64: case 64*1024: v = IEEE80211_HTCAP_MAXRXAMPDU_64K; break; default: errx(-1, "invalid A-MPDU limit %s", val); } set80211(ctx, IEEE80211_IOC_AMPDU_LIMIT, v, 0, NULL); } /* XXX 11ac density/size is different */ static void set80211ampdudensity(if_ctx *ctx, const char *val, int dummy __unused) { int v; if (isanyarg(val) || strcasecmp(val, "na") == 0) v = IEEE80211_HTCAP_MPDUDENSITY_NA; else switch ((int)(atof(val)*4)) { case 0: v = IEEE80211_HTCAP_MPDUDENSITY_NA; break; case 1: v = IEEE80211_HTCAP_MPDUDENSITY_025; break; case 2: v = IEEE80211_HTCAP_MPDUDENSITY_05; break; case 4: v = IEEE80211_HTCAP_MPDUDENSITY_1; break; case 8: v = IEEE80211_HTCAP_MPDUDENSITY_2; break; case 16: v = IEEE80211_HTCAP_MPDUDENSITY_4; break; case 32: v = IEEE80211_HTCAP_MPDUDENSITY_8; break; case 64: v = IEEE80211_HTCAP_MPDUDENSITY_16; break; default: errx(-1, "invalid A-MPDU density %s", val); } set80211(ctx, IEEE80211_IOC_AMPDU_DENSITY, v, 0, NULL); } static void set80211amsdu(if_ctx *ctx, const char *val __unused, int d) { int amsdu; if (get80211val(ctx, IEEE80211_IOC_AMSDU, &amsdu) < 0) err(-1, "cannot get AMSDU setting"); if (d < 0) { d = -d; amsdu &= ~d; } else amsdu |= d; set80211(ctx, IEEE80211_IOC_AMSDU, amsdu, 0, NULL); } static void set80211amsdulimit(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_AMSDU_LIMIT, atoi(val), 0, NULL); } static void set80211puren(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_PUREN, d, 0, NULL); } static void set80211htcompat(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_HTCOMPAT, d, 0, NULL); } static void set80211htconf(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_HTCONF, d, 0, NULL); htconf = d; } static void set80211dwds(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_DWDS, d, 0, NULL); } static void set80211inact(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_INACTIVITY, d, 0, NULL); } static void set80211tsn(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_TSN, d, 0, NULL); } static void set80211dotd(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_DOTD, d, 0, NULL); } static void set80211smps(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_SMPS, d, 0, NULL); } static void set80211rifs(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_RIFS, d, 0, NULL); } static void set80211vhtconf(if_ctx *ctx, const char *val __unused, int d) { if (get80211val(ctx, IEEE80211_IOC_VHTCONF, &vhtconf) < 0) errx(-1, "cannot set VHT setting"); printf("%s: vhtconf=0x%08x, d=%d\n", __func__, vhtconf, d); if (d < 0) { d = -d; vhtconf &= ~d; } else vhtconf |= d; printf("%s: vhtconf is now 0x%08x\n", __func__, vhtconf); set80211(ctx, IEEE80211_IOC_VHTCONF, vhtconf, 0, NULL); } static void set80211tdmaslot(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_TDMA_SLOT, atoi(val), 0, NULL); } static void set80211tdmaslotcnt(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_TDMA_SLOTCNT, atoi(val), 0, NULL); } static void set80211tdmaslotlen(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_TDMA_SLOTLEN, atoi(val), 0, NULL); } static void set80211tdmabintval(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_TDMA_BINTERVAL, atoi(val), 0, NULL); } static void set80211meshttl(if_ctx *ctx, const char *val, int dummy __unused) { set80211(ctx, IEEE80211_IOC_MESH_TTL, atoi(val), 0, NULL); } static void set80211meshforward(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_MESH_FWRD, d, 0, NULL); } static void set80211meshgate(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_MESH_GATE, d, 0, NULL); } static void set80211meshpeering(if_ctx *ctx, const char *val __unused, int d) { set80211(ctx, IEEE80211_IOC_MESH_AP, d, 0, NULL); } static void set80211meshmetric(if_ctx *ctx, const char *val, int dummy __unused) { char v[12]; memcpy(v, val, sizeof(v)); set80211(ctx, IEEE80211_IOC_MESH_PR_METRIC, 0, 0, v); } static void set80211meshpath(if_ctx *ctx, const char *val, int dummy __unused) { char v[12]; memcpy(v, val, sizeof(v)); set80211(ctx, IEEE80211_IOC_MESH_PR_PATH, 0, 0, v); } static int regdomain_sort(const void *a, const void *b) { #define CHAN_ALL \ (IEEE80211_CHAN_ALLTURBO|IEEE80211_CHAN_HALF|IEEE80211_CHAN_QUARTER) const struct ieee80211_channel *ca = a; const struct ieee80211_channel *cb = b; return ca->ic_freq == cb->ic_freq ? (int)(ca->ic_flags & CHAN_ALL) - (int)(cb->ic_flags & CHAN_ALL) : ca->ic_freq - cb->ic_freq; #undef CHAN_ALL } static const struct ieee80211_channel * chanlookup(const struct ieee80211_channel chans[], int nchans, int freq, uint32_t flags) { int i; flags &= IEEE80211_CHAN_ALLTURBO; for (i = 0; i < nchans; i++) { const struct ieee80211_channel *c = &chans[i]; if (c->ic_freq == freq && (c->ic_flags & IEEE80211_CHAN_ALLTURBO) == flags) return c; } return NULL; } static int chanfind(const struct ieee80211_channel chans[], int nchans, uint32_t flags) { for (int i = 0; i < nchans; i++) { const struct ieee80211_channel *c = &chans[i]; if ((c->ic_flags & flags) == flags) return 1; } return 0; } /* * Check channel compatibility. */ static int checkchan(const struct ieee80211req_chaninfo *avail, int freq, uint32_t flags) { flags &= ~REQ_FLAGS; /* * Check if exact channel is in the calibration table; * everything below is to deal with channels that we * want to include but that are not explicitly listed. */ if (chanlookup(avail->ic_chans, avail->ic_nchans, freq, flags) != NULL) return 1; if (flags & IEEE80211_CHAN_GSM) { /* * XXX GSM frequency mapping is handled in the kernel * so we cannot find them in the calibration table; * just accept the channel and the kernel will reject * the channel list if it's wrong. */ return 1; } /* * If this is a 1/2 or 1/4 width channel allow it if a full * width channel is present for this frequency, and the device * supports fractional channels on this band. This is a hack * that avoids bloating the calibration table; it may be better * by per-band attributes though (we are effectively calculating * this attribute by scanning the channel list ourself). */ if ((flags & (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)) == 0) return 0; if (chanlookup(avail->ic_chans, avail->ic_nchans, freq, flags &~ (IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)) == NULL) return 0; if (flags & IEEE80211_CHAN_HALF) { return chanfind(avail->ic_chans, avail->ic_nchans, IEEE80211_CHAN_HALF | (flags & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ))); } else { return chanfind(avail->ic_chans, avail->ic_nchans, IEEE80211_CHAN_QUARTER | (flags & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ))); } } static void regdomain_addchans(if_ctx *ctx, struct ieee80211req_chaninfo *ci, const netband_head *bands, const struct ieee80211_regdomain *reg, uint32_t chanFlags, const struct ieee80211req_chaninfo *avail) { const struct netband *nb; const struct freqband *b; struct ieee80211_channel *c, *prev; int freq, hi_adj, lo_adj, channelSep; uint32_t flags; const int verbose = ctx->args->verbose; hi_adj = (chanFlags & IEEE80211_CHAN_HT40U) ? -20 : 0; lo_adj = (chanFlags & IEEE80211_CHAN_HT40D) ? 20 : 0; channelSep = (chanFlags & IEEE80211_CHAN_2GHZ) ? 0 : 40; LIST_FOREACH(nb, bands, next) { b = nb->band; if (verbose) { printf("%s:", __func__); printb(" chanFlags", chanFlags, IEEE80211_CHAN_BITS); printb(" bandFlags", nb->flags | b->flags, IEEE80211_CHAN_BITS); putchar('\n'); } prev = NULL; for (freq = b->freqStart + lo_adj; freq <= b->freqEnd + hi_adj; freq += b->chanSep) { /* * Construct flags for the new channel. We take * the attributes from the band descriptions except * for HT40 which is enabled generically (i.e. +/- * extension channel) in the band description and * then constrained according by channel separation. */ flags = nb->flags | b->flags; /* * VHT first - HT is a subset. */ if (flags & IEEE80211_CHAN_VHT) { if ((chanFlags & IEEE80211_CHAN_VHT20) && (flags & IEEE80211_CHAN_VHT20) == 0) { if (verbose) printf("%u: skip, not a " "VHT20 channel\n", freq); continue; } if ((chanFlags & IEEE80211_CHAN_VHT40) && (flags & IEEE80211_CHAN_VHT40) == 0) { if (verbose) printf("%u: skip, not a " "VHT40 channel\n", freq); continue; } if ((chanFlags & IEEE80211_CHAN_VHT80) && (flags & IEEE80211_CHAN_VHT80) == 0) { if (verbose) printf("%u: skip, not a " "VHT80 channel\n", freq); continue; } if ((chanFlags & IEEE80211_CHAN_VHT160) && (flags & IEEE80211_CHAN_VHT160) == 0) { if (verbose) printf("%u: skip, not a " "VHT160 channel\n", freq); continue; } if ((chanFlags & IEEE80211_CHAN_VHT80P80) && (flags & IEEE80211_CHAN_VHT80P80) == 0) { if (verbose) printf("%u: skip, not a " "VHT80+80 channel\n", freq); continue; } flags &= ~IEEE80211_CHAN_VHT; flags |= chanFlags & IEEE80211_CHAN_VHT; } /* Now, constrain HT */ if (flags & IEEE80211_CHAN_HT) { /* * HT channels are generated specially; we're * called to add HT20, HT40+, and HT40- chan's * so we need to expand only band specs for * the HT channel type being added. */ if ((chanFlags & IEEE80211_CHAN_HT20) && (flags & IEEE80211_CHAN_HT20) == 0) { if (verbose) printf("%u: skip, not an " "HT20 channel\n", freq); continue; } if ((chanFlags & IEEE80211_CHAN_HT40) && (flags & IEEE80211_CHAN_HT40) == 0) { if (verbose) printf("%u: skip, not an " "HT40 channel\n", freq); continue; } /* NB: HT attribute comes from caller */ flags &= ~IEEE80211_CHAN_HT; flags |= chanFlags & IEEE80211_CHAN_HT; } /* * Check if device can operate on this frequency. */ if (!checkchan(avail, freq, flags)) { if (verbose) { printf("%u: skip, ", freq); printb("flags", flags, IEEE80211_CHAN_BITS); printf(" not available\n"); } continue; } if ((flags & REQ_ECM) && !reg->ecm) { if (verbose) printf("%u: skip, ECM channel\n", freq); continue; } if ((flags & REQ_INDOOR) && reg->location == 'O') { if (verbose) printf("%u: skip, indoor channel\n", freq); continue; } if ((flags & REQ_OUTDOOR) && reg->location == 'I') { if (verbose) printf("%u: skip, outdoor channel\n", freq); continue; } if ((flags & IEEE80211_CHAN_HT40) && prev != NULL && (freq - prev->ic_freq) < channelSep) { if (verbose) printf("%u: skip, only %u channel " "separation, need %d\n", freq, freq - prev->ic_freq, channelSep); continue; } if (ci->ic_nchans == IEEE80211_CHAN_MAX) { if (verbose) printf("%u: skip, channel table full\n", freq); break; } c = &ci->ic_chans[ci->ic_nchans++]; memset(c, 0, sizeof(*c)); c->ic_freq = freq; c->ic_flags = flags; if (c->ic_flags & IEEE80211_CHAN_DFS) c->ic_maxregpower = nb->maxPowerDFS; else c->ic_maxregpower = nb->maxPower; if (verbose) { printf("[%3d] add freq %u ", ci->ic_nchans-1, c->ic_freq); printb("flags", c->ic_flags, IEEE80211_CHAN_BITS); printf(" power %u\n", c->ic_maxregpower); } /* NB: kernel fills in other fields */ prev = c; } } } static void regdomain_makechannels( if_ctx *ctx, struct ieee80211_regdomain_req *req, const struct ieee80211_devcaps_req *dc) { struct regdata *rdp = getregdata(); const struct country *cc; const struct ieee80211_regdomain *reg = &req->rd; struct ieee80211req_chaninfo *ci = &req->chaninfo; const struct regdomain *rd; /* * Locate construction table for new channel list. We treat * the regdomain/SKU as definitive so a country can be in * multiple with different properties (e.g. US in FCC+FCC3). * If no regdomain is specified then we fallback on the country * code to find the associated regdomain since countries always * belong to at least one regdomain. */ if (reg->regdomain == 0) { cc = lib80211_country_findbycc(rdp, reg->country); if (cc == NULL) errx(1, "internal error, country %d not found", reg->country); rd = cc->rd; } else rd = lib80211_regdomain_findbysku(rdp, reg->regdomain); if (rd == NULL) errx(1, "internal error, regdomain %d not found", reg->regdomain); if (rd->sku != SKU_DEBUG) { /* * regdomain_addchans incrememnts the channel count for * each channel it adds so initialize ic_nchans to zero. * Note that we know we have enough space to hold all possible * channels because the devcaps list size was used to * allocate our request. */ ci->ic_nchans = 0; if (!LIST_EMPTY(&rd->bands_11b)) regdomain_addchans(ctx, ci, &rd->bands_11b, reg, IEEE80211_CHAN_B, &dc->dc_chaninfo); if (!LIST_EMPTY(&rd->bands_11g)) regdomain_addchans(ctx, ci, &rd->bands_11g, reg, IEEE80211_CHAN_G, &dc->dc_chaninfo); if (!LIST_EMPTY(&rd->bands_11a)) regdomain_addchans(ctx, ci, &rd->bands_11a, reg, IEEE80211_CHAN_A, &dc->dc_chaninfo); if (!LIST_EMPTY(&rd->bands_11na) && dc->dc_htcaps != 0) { regdomain_addchans(ctx, ci, &rd->bands_11na, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT20, &dc->dc_chaninfo); if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) { regdomain_addchans(ctx, ci, &rd->bands_11na, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U, &dc->dc_chaninfo); regdomain_addchans(ctx, ci, &rd->bands_11na, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D, &dc->dc_chaninfo); } } if (!LIST_EMPTY(&rd->bands_11ac) && dc->dc_vhtcaps != 0) { regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT20 | IEEE80211_CHAN_VHT20, &dc->dc_chaninfo); /* VHT40 is a function of HT40.. */ if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) { regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT40U, &dc->dc_chaninfo); regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT40D, &dc->dc_chaninfo); } /* VHT80 is mandatory (and so should be VHT40 above). */ if (1) { regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80, &dc->dc_chaninfo); regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80, &dc->dc_chaninfo); } /* VHT160 */ if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160MHZ( dc->dc_vhtcaps)) { regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT160, &dc->dc_chaninfo); regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT160, &dc->dc_chaninfo); } /* VHT80P80 */ if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ( dc->dc_vhtcaps)) { regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U | IEEE80211_CHAN_VHT80P80, &dc->dc_chaninfo); regdomain_addchans(ctx, ci, &rd->bands_11ac, reg, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D | IEEE80211_CHAN_VHT80P80, &dc->dc_chaninfo); } } if (!LIST_EMPTY(&rd->bands_11ng) && dc->dc_htcaps != 0) { regdomain_addchans(ctx, ci, &rd->bands_11ng, reg, IEEE80211_CHAN_G | IEEE80211_CHAN_HT20, &dc->dc_chaninfo); if (dc->dc_htcaps & IEEE80211_HTCAP_CHWIDTH40) { regdomain_addchans(ctx, ci, &rd->bands_11ng, reg, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U, &dc->dc_chaninfo); regdomain_addchans(ctx, ci, &rd->bands_11ng, reg, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D, &dc->dc_chaninfo); } } qsort(ci->ic_chans, ci->ic_nchans, sizeof(ci->ic_chans[0]), regdomain_sort); } else memcpy(ci, &dc->dc_chaninfo, IEEE80211_CHANINFO_SPACE(&dc->dc_chaninfo)); } static void list_countries(void) { struct regdata *rdp = getregdata(); const struct country *cp; const struct regdomain *dp; int i; i = 0; printf("\nCountry codes:\n"); LIST_FOREACH(cp, &rdp->countries, next) { printf("%2s %-15.15s%s", cp->isoname, cp->name, ((i+1)%4) == 0 ? "\n" : " "); i++; } i = 0; printf("\nRegulatory domains:\n"); LIST_FOREACH(dp, &rdp->domains, next) { printf("%-15.15s%s", dp->name, ((i+1)%4) == 0 ? "\n" : " "); i++; } printf("\n"); } static void defaultcountry(const struct regdomain *rd) { struct regdata *rdp = getregdata(); const struct country *cc; cc = lib80211_country_findbycc(rdp, rd->cc->code); if (cc == NULL) errx(1, "internal error, ISO country code %d not " "defined for regdomain %s", rd->cc->code, rd->name); regdomain.country = cc->code; regdomain.isocc[0] = cc->isoname[0]; regdomain.isocc[1] = cc->isoname[1]; } static void set80211regdomain(if_ctx *ctx, const char *val, int dummy __unused) { struct regdata *rdp = getregdata(); const struct regdomain *rd; rd = lib80211_regdomain_findbyname(rdp, val); if (rd == NULL) { char *eptr; long sku = strtol(val, &eptr, 0); if (eptr != val) rd = lib80211_regdomain_findbysku(rdp, sku); if (eptr == val || rd == NULL) errx(1, "unknown regdomain %s", val); } getregdomain(ctx); regdomain.regdomain = rd->sku; if (regdomain.country == 0 && rd->cc != NULL) { /* * No country code setup and there's a default * one for this regdomain fill it in. */ defaultcountry(rd); } callback_register(setregdomain_cb, ®domain); } static void set80211country(if_ctx *ctx, const char *val, int dummy __unused) { struct regdata *rdp = getregdata(); const struct country *cc; cc = lib80211_country_findbyname(rdp, val); if (cc == NULL) { char *eptr; long code = strtol(val, &eptr, 0); if (eptr != val) cc = lib80211_country_findbycc(rdp, code); if (eptr == val || cc == NULL) errx(1, "unknown ISO country code %s", val); } getregdomain(ctx); regdomain.regdomain = cc->rd->sku; regdomain.country = cc->code; regdomain.isocc[0] = cc->isoname[0]; regdomain.isocc[1] = cc->isoname[1]; callback_register(setregdomain_cb, ®domain); } static void set80211location(if_ctx *ctx, const char *val __unused, int d) { getregdomain(ctx); regdomain.location = d; callback_register(setregdomain_cb, ®domain); } static void set80211ecm(if_ctx *ctx, const char *val __unused, int d) { getregdomain(ctx); regdomain.ecm = d; callback_register(setregdomain_cb, ®domain); } static void LINE_INIT(char c) { spacer = c; if (c == '\t') col = 8; else col = 1; } static void LINE_BREAK(void) { if (spacer != '\t') { printf("\n"); spacer = '\t'; } col = 8; /* 8-col tab */ } static void LINE_CHECK(const char *fmt, ...) { char buf[80]; va_list ap; int n; va_start(ap, fmt); n = vsnprintf(buf+1, sizeof(buf)-1, fmt, ap); va_end(ap); col += 1+n; if (col > MAXCOL) { LINE_BREAK(); col += n; } buf[0] = spacer; printf("%s", buf); spacer = ' '; } static int getmaxrate(const uint8_t rates[15], uint8_t nrates) { int i, maxrate = -1; for (i = 0; i < nrates; i++) { int rate = rates[i] & IEEE80211_RATE_VAL; if (rate > maxrate) maxrate = rate; } return maxrate / 2; } static const char * getcaps(int capinfo) { static char capstring[32]; char *cp = capstring; if (capinfo & IEEE80211_CAPINFO_ESS) *cp++ = 'E'; if (capinfo & IEEE80211_CAPINFO_IBSS) *cp++ = 'I'; if (capinfo & IEEE80211_CAPINFO_CF_POLLABLE) *cp++ = 'c'; if (capinfo & IEEE80211_CAPINFO_CF_POLLREQ) *cp++ = 'C'; if (capinfo & IEEE80211_CAPINFO_PRIVACY) *cp++ = 'P'; if (capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) *cp++ = 'S'; if (capinfo & IEEE80211_CAPINFO_PBCC) *cp++ = 'B'; if (capinfo & IEEE80211_CAPINFO_CHNL_AGILITY) *cp++ = 'A'; if (capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) *cp++ = 's'; if (capinfo & IEEE80211_CAPINFO_RSN) *cp++ = 'R'; if (capinfo & IEEE80211_CAPINFO_DSSSOFDM) *cp++ = 'D'; *cp = '\0'; return capstring; } static const char * getflags(int flags) { static char flagstring[32]; char *cp = flagstring; if (flags & IEEE80211_NODE_AUTH) *cp++ = 'A'; if (flags & IEEE80211_NODE_QOS) *cp++ = 'Q'; if (flags & IEEE80211_NODE_ERP) *cp++ = 'E'; if (flags & IEEE80211_NODE_PWR_MGT) *cp++ = 'P'; if (flags & IEEE80211_NODE_HT) { *cp++ = 'H'; if (flags & IEEE80211_NODE_HTCOMPAT) *cp++ = '+'; } if (flags & IEEE80211_NODE_VHT) *cp++ = 'V'; if (flags & IEEE80211_NODE_WPS) *cp++ = 'W'; if (flags & IEEE80211_NODE_TSN) *cp++ = 'N'; if (flags & IEEE80211_NODE_AMPDU_TX) *cp++ = 'T'; if (flags & IEEE80211_NODE_AMPDU_RX) *cp++ = 'R'; if (flags & IEEE80211_NODE_MIMO_PS) { *cp++ = 'M'; if (flags & IEEE80211_NODE_MIMO_RTS) *cp++ = '+'; } if (flags & IEEE80211_NODE_RIFS) *cp++ = 'I'; if (flags & IEEE80211_NODE_SGI40) { *cp++ = 'S'; if (flags & IEEE80211_NODE_SGI20) *cp++ = '+'; } else if (flags & IEEE80211_NODE_SGI20) *cp++ = 's'; if (flags & IEEE80211_NODE_AMSDU_TX) *cp++ = 't'; if (flags & IEEE80211_NODE_AMSDU_RX) *cp++ = 'r'; if (flags & IEEE80211_NODE_UAPSD) *cp++ = 'U'; if (flags & IEEE80211_NODE_LDPC) *cp++ = 'L'; *cp = '\0'; return flagstring; } static void printie(if_ctx *ctx, const char* tag, const uint8_t *ie, size_t ielen, unsigned int maxlen) { printf("%s", tag); if (ctx->args->verbose) { maxlen -= strlen(tag)+2; if (2*ielen > maxlen) maxlen--; printf("<"); for (; ielen > 0; ie++, ielen--) { if (maxlen-- <= 0) break; printf("%02x", *ie); } if (ielen != 0) printf("-"); printf(">"); } } #define LE_READ_2(p) \ ((u_int16_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8))) #define LE_READ_4(p) \ ((u_int32_t) \ ((((const u_int8_t *)(p))[0] ) | \ (((const u_int8_t *)(p))[1] << 8) | \ (((const u_int8_t *)(p))[2] << 16) | \ (((const u_int8_t *)(p))[3] << 24))) /* * NB: The decoding routines assume a properly formatted ie * which should be safe as the kernel only retains them * if they parse ok. */ static void printwmeparam(if_ctx *ctx, const char *tag, const u_int8_t *ie) { static const char *acnames[] = { "BE", "BK", "VO", "VI" }; const struct ieee80211_wme_param *wme = (const struct ieee80211_wme_param *) ie; int i; printf("%s", tag); if (!ctx->args->verbose) return; printf("param_qosInfo); ie += offsetof(struct ieee80211_wme_param, params_acParams); for (i = 0; i < WME_NUM_AC; i++) { const struct ieee80211_wme_acparams *ac = &wme->params_acParams[i]; printf(" %s[%saifsn %u cwmin %u cwmax %u txop %u]", acnames[i], _IEEE80211_MASKSHIFT(ac->acp_aci_aifsn, WME_PARAM_ACM) ? "acm " : "", _IEEE80211_MASKSHIFT(ac->acp_aci_aifsn, WME_PARAM_AIFSN), _IEEE80211_MASKSHIFT(ac->acp_logcwminmax, WME_PARAM_LOGCWMIN), _IEEE80211_MASKSHIFT(ac->acp_logcwminmax, WME_PARAM_LOGCWMAX), LE_READ_2(&ac->acp_txop)); } printf(">"); } static void printwmeinfo(if_ctx *ctx, const char *tag, const u_int8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_wme_info *wme = (const struct ieee80211_wme_info *) ie; printf("", wme->wme_version, wme->wme_info); } } static void printvhtcap(if_ctx *ctx, const char *tag, const u_int8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { - const struct ieee80211_ie_vhtcap *vhtcap = - (const struct ieee80211_ie_vhtcap *) ie; - uint32_t vhtcap_info = LE_READ_4(&vhtcap->vht_cap_info); + const struct ieee80211_vht_cap *vhtcap; + uint32_t vhtcap_info; + /* Check that the the right size. */ + if (ie[1] != sizeof(*vhtcap)) { + printf(""); + return; + } + /* Skip Element ID and Length. */ + vhtcap = (const struct ieee80211_vht_cap *)(ie + 2); + + vhtcap_info = LE_READ_4(&vhtcap->vht_cap_info); printf("supp_mcs.rx_mcs_map)); printf(" rx_highest %d", LE_READ_2(&vhtcap->supp_mcs.rx_highest) & 0x1fff); printf(" tx_mcs_map 0x%x", LE_READ_2(&vhtcap->supp_mcs.tx_mcs_map)); printf(" tx_highest %d", LE_READ_2(&vhtcap->supp_mcs.tx_highest) & 0x1fff); printf(">"); } } static void printvhtinfo(if_ctx *ctx, const char *tag, const u_int8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { - const struct ieee80211_ie_vht_operation *vhtinfo = - (const struct ieee80211_ie_vht_operation *) ie; + const struct ieee80211_vht_operation *vhtinfo; + + /* Check that the the right size. */ + if (ie[1] != sizeof(*vhtinfo)) { + printf(""); + return; + } + /* Skip Element ID and Length. */ + vhtinfo = (const struct ieee80211_vht_operation *)(ie + 2); - printf("", + printf("", vhtinfo->chan_width, - vhtinfo->center_freq_seg1_idx, - vhtinfo->center_freq_seg2_idx, + vhtinfo->center_freq_seq0_idx, + vhtinfo->center_freq_seq1_idx, LE_READ_2(&vhtinfo->basic_mcs_set)); } } static void printvhtpwrenv(if_ctx *ctx, const char *tag, const u_int8_t *ie, size_t ielen) { printf("%s", tag); static const char *txpwrmap[] = { "20", "40", "80", "160", }; if (ctx->args->verbose) { const struct ieee80211_ie_vht_txpwrenv *vhtpwr = (const struct ieee80211_ie_vht_txpwrenv *) ie; size_t i, n; const char *sep = ""; /* Get count; trim at ielen */ n = (vhtpwr->tx_info & IEEE80211_VHT_TXPWRENV_INFO_COUNT_MASK) + 1; /* Trim at ielen */ if (n + 3 > ielen) n = ielen - 3; printf("tx_info); for (i = 0; i < n; i++) { printf("%s%s:%.2f", sep, txpwrmap[i], ((float) ((int8_t) ie[i+3])) / 2.0); sep = " "; } printf("]>"); } } static void printhtcap(if_ctx *ctx, const char *tag, const u_int8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_ie_htcap *htcap = (const struct ieee80211_ie_htcap *) ie; const char *sep; int i, j; printf("hc_cap), htcap->hc_param); printf(" mcsset["); sep = ""; for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) if (isset(htcap->hc_mcsset, i)) { for (j = i+1; j < IEEE80211_HTRATE_MAXSIZE; j++) if (isclr(htcap->hc_mcsset, j)) break; j--; if (i == j) printf("%s%u", sep, i); else printf("%s%u-%u", sep, i, j); i += j-i; sep = ","; } printf("] extcap 0x%x txbf 0x%x antenna 0x%x>", LE_READ_2(&htcap->hc_extcap), LE_READ_4(&htcap->hc_txbf), htcap->hc_antenna); } } static void printhtinfo(if_ctx *ctx, const char *tag, const u_int8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_ie_htinfo *htinfo = (const struct ieee80211_ie_htinfo *) ie; const char *sep; int i, j; printf("hi_ctrlchannel, htinfo->hi_byte1, htinfo->hi_byte2, htinfo->hi_byte3, LE_READ_2(&htinfo->hi_byte45)); printf(" basicmcs["); sep = ""; for (i = 0; i < IEEE80211_HTRATE_MAXSIZE; i++) if (isset(htinfo->hi_basicmcsset, i)) { for (j = i+1; j < IEEE80211_HTRATE_MAXSIZE; j++) if (isclr(htinfo->hi_basicmcsset, j)) break; j--; if (i == j) printf("%s%u", sep, i); else printf("%s%u-%u", sep, i, j); i += j-i; sep = ","; } printf("]>"); } } static void printathie(if_ctx *ctx, const char *tag, const u_int8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_ath_ie *ath = (const struct ieee80211_ath_ie *)ie; printf("<"); if (ath->ath_capability & ATHEROS_CAP_TURBO_PRIME) printf("DTURBO,"); if (ath->ath_capability & ATHEROS_CAP_COMPRESSION) printf("COMP,"); if (ath->ath_capability & ATHEROS_CAP_FAST_FRAME) printf("FF,"); if (ath->ath_capability & ATHEROS_CAP_XR) printf("XR,"); if (ath->ath_capability & ATHEROS_CAP_AR) printf("AR,"); if (ath->ath_capability & ATHEROS_CAP_BURST) printf("BURST,"); if (ath->ath_capability & ATHEROS_CAP_WME) printf("WME,"); if (ath->ath_capability & ATHEROS_CAP_BOOST) printf("BOOST,"); printf("0x%x>", LE_READ_2(ath->ath_defkeyix)); } } static void printmeshconf(if_ctx *ctx, const char *tag, const uint8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_meshconf_ie *mconf = (const struct ieee80211_meshconf_ie *)ie; printf("conf_pselid == IEEE80211_MESHCONF_PATH_HWMP) printf("HWMP"); else printf("UNKNOWN"); printf(" LINK:"); if (mconf->conf_pmetid == IEEE80211_MESHCONF_METRIC_AIRTIME) printf("AIRTIME"); else printf("UNKNOWN"); printf(" CONGESTION:"); if (mconf->conf_ccid == IEEE80211_MESHCONF_CC_DISABLED) printf("DISABLED"); else printf("UNKNOWN"); printf(" SYNC:"); if (mconf->conf_syncid == IEEE80211_MESHCONF_SYNC_NEIGHOFF) printf("NEIGHOFF"); else printf("UNKNOWN"); printf(" AUTH:"); if (mconf->conf_authid == IEEE80211_MESHCONF_AUTH_DISABLED) printf("DISABLED"); else printf("UNKNOWN"); printf(" FORM:0x%x CAPS:0x%x>", mconf->conf_form, mconf->conf_cap); } } static void printbssload(if_ctx *ctx, const char *tag, const uint8_t *ie) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_bss_load_ie *bssload = (const struct ieee80211_bss_load_ie *) ie; printf("", LE_READ_2(&bssload->sta_count), bssload->chan_load, bssload->aac); } } static void printapchanrep(if_ctx *ctx, const char *tag, const u_int8_t *ie, size_t ielen) { printf("%s", tag); if (ctx->args->verbose) { const struct ieee80211_ap_chan_report_ie *ap = (const struct ieee80211_ap_chan_report_ie *) ie; const char *sep = ""; printf("i_class); for (size_t i = 3; i < ielen; i++) { printf("%s%u", sep, ie[i]); sep = ","; } printf("]>"); } } static const char * wpa_cipher(const u_int8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_CSE_NULL): return "NONE"; case WPA_SEL(WPA_CSE_WEP40): return "WEP40"; case WPA_SEL(WPA_CSE_WEP104): return "WEP104"; case WPA_SEL(WPA_CSE_TKIP): return "TKIP"; case WPA_SEL(WPA_CSE_CCMP): return "AES-CCMP"; } return "?"; /* NB: so 1<< is discarded */ #undef WPA_SEL } static const char * wpa_keymgmt(const u_int8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case WPA_SEL(WPA_ASE_8021X_UNSPEC): return "8021X-UNSPEC"; case WPA_SEL(WPA_ASE_8021X_PSK): return "8021X-PSK"; case WPA_SEL(WPA_ASE_NONE): return "NONE"; } return "?"; #undef WPA_SEL } static void printwpaie(if_ctx *ctx, const char *tag, const u_int8_t *ie) { u_int8_t len = ie[1]; printf("%s", tag); if (ctx->args->verbose) { const char *sep; int n; ie += 6, len -= 4; /* NB: len is payload only */ printf(" 0; n--) { printf("%s%s", sep, wpa_cipher(ie)); ie += 4, len -= 4; sep = "+"; } /* key management algorithms */ n = LE_READ_2(ie); ie += 2, len -= 2; sep = " km:"; for (; n > 0; n--) { printf("%s%s", sep, wpa_keymgmt(ie)); ie += 4, len -= 4; sep = "+"; } if (len > 2) /* optional capabilities */ printf(", caps 0x%x", LE_READ_2(ie)); printf(">"); } } static const char * rsn_cipher(const u_int8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_CSE_NULL): return "NONE"; case RSN_SEL(RSN_CSE_WEP40): return "WEP40"; case RSN_SEL(RSN_CSE_WEP104): return "WEP104"; case RSN_SEL(RSN_CSE_TKIP): return "TKIP"; case RSN_SEL(RSN_CSE_CCMP): return "AES-CCMP"; case RSN_SEL(RSN_CSE_WRAP): return "AES-OCB"; } return "?"; #undef WPA_SEL } static const char * rsn_keymgmt(const u_int8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) u_int32_t w = LE_READ_4(sel); switch (w) { case RSN_SEL(RSN_ASE_8021X_UNSPEC): return "8021X-UNSPEC"; case RSN_SEL(RSN_ASE_8021X_PSK): return "8021X-PSK"; case RSN_SEL(RSN_ASE_NONE): return "NONE"; } return "?"; #undef RSN_SEL } static void printrsnie(if_ctx *ctx, const char *tag, const u_int8_t *ie, size_t ielen) { printf("%s", tag); if (ctx->args->verbose) { const char *sep; int n; ie += 2, ielen -= 2; printf(" 0; n--) { printf("%s%s", sep, rsn_cipher(ie)); ie += 4, ielen -= 4; sep = "+"; } /* key management algorithms */ n = LE_READ_2(ie); ie += 2, ielen -= 2; sep = " km:"; for (; n > 0; n--) { printf("%s%s", sep, rsn_keymgmt(ie)); ie += 4, ielen -= 4; sep = "+"; } if (ielen > 2) /* optional capabilities */ printf(", caps 0x%x", LE_READ_2(ie)); /* XXXPMKID */ printf(">"); } } #define BE_READ_2(p) \ ((u_int16_t) \ ((((const u_int8_t *)(p))[1] ) | \ (((const u_int8_t *)(p))[0] << 8))) static void printwpsie(if_ctx *ctx, const char *tag, const u_int8_t *ie) { u_int8_t len = ie[1]; printf("%s", tag); if (ctx->args->verbose) { static const char *dev_pass_id[] = { "D", /* Default (PIN) */ "U", /* User-specified */ "M", /* Machine-specified */ "K", /* Rekey */ "P", /* PushButton */ "R" /* Registrar-specified */ }; int n; int f; ie +=6, len -= 4; /* NB: len is payload only */ /* WPS IE in Beacon and Probe Resp frames have different fields */ printf("<"); while (len) { uint16_t tlv_type = BE_READ_2(ie); uint16_t tlv_len = BE_READ_2(ie + 2); uint16_t cfg_mthd; /* some devices broadcast invalid WPS frames */ if (tlv_len > len) { printf("bad frame length tlv_type=0x%02x " "tlv_len=%d len=%d", tlv_type, tlv_len, len); break; } ie += 4, len -= 4; switch (tlv_type) { case IEEE80211_WPS_ATTR_VERSION: printf("v:%d.%d", *ie >> 4, *ie & 0xf); break; case IEEE80211_WPS_ATTR_AP_SETUP_LOCKED: printf(" ap_setup:%s", *ie ? "locked" : "unlocked"); break; case IEEE80211_WPS_ATTR_CONFIG_METHODS: case IEEE80211_WPS_ATTR_SELECTED_REGISTRAR_CONFIG_METHODS: if (tlv_type == IEEE80211_WPS_ATTR_SELECTED_REGISTRAR_CONFIG_METHODS) printf(" sel_reg_cfg_mthd:"); else printf(" cfg_mthd:" ); cfg_mthd = BE_READ_2(ie); f = 0; for (n = 15; n >= 0; n--) { if (f) { printf(","); f = 0; } switch (cfg_mthd & (1 << n)) { case 0: break; case IEEE80211_WPS_CONFIG_USBA: printf("usba"); f++; break; case IEEE80211_WPS_CONFIG_ETHERNET: printf("ethernet"); f++; break; case IEEE80211_WPS_CONFIG_LABEL: printf("label"); f++; break; case IEEE80211_WPS_CONFIG_DISPLAY: if (!(cfg_mthd & (IEEE80211_WPS_CONFIG_VIRT_DISPLAY | IEEE80211_WPS_CONFIG_PHY_DISPLAY))) { printf("display"); f++; } break; case IEEE80211_WPS_CONFIG_EXT_NFC_TOKEN: printf("ext_nfc_tokenk"); f++; break; case IEEE80211_WPS_CONFIG_INT_NFC_TOKEN: printf("int_nfc_token"); f++; break; case IEEE80211_WPS_CONFIG_NFC_INTERFACE: printf("nfc_interface"); f++; break; case IEEE80211_WPS_CONFIG_PUSHBUTTON: if (!(cfg_mthd & (IEEE80211_WPS_CONFIG_VIRT_PUSHBUTTON | IEEE80211_WPS_CONFIG_PHY_PUSHBUTTON))) { printf("push_button"); f++; } break; case IEEE80211_WPS_CONFIG_KEYPAD: printf("keypad"); f++; break; case IEEE80211_WPS_CONFIG_VIRT_PUSHBUTTON: printf("virtual_push_button"); f++; break; case IEEE80211_WPS_CONFIG_PHY_PUSHBUTTON: printf("physical_push_button"); f++; break; case IEEE80211_WPS_CONFIG_P2PS: printf("p2ps"); f++; break; case IEEE80211_WPS_CONFIG_VIRT_DISPLAY: printf("virtual_display"); f++; break; case IEEE80211_WPS_CONFIG_PHY_DISPLAY: printf("physical_display"); f++; break; default: printf("unknown_wps_config<%04x>", cfg_mthd & (1 << n)); f++; break; } } break; case IEEE80211_WPS_ATTR_DEV_NAME: printf(" device_name:<%.*s>", tlv_len, ie); break; case IEEE80211_WPS_ATTR_DEV_PASSWORD_ID: n = LE_READ_2(ie); if (n < (int)nitems(dev_pass_id)) printf(" dpi:%s", dev_pass_id[n]); break; case IEEE80211_WPS_ATTR_MANUFACTURER: printf(" manufacturer:<%.*s>", tlv_len, ie); break; case IEEE80211_WPS_ATTR_MODEL_NAME: printf(" model_name:<%.*s>", tlv_len, ie); break; case IEEE80211_WPS_ATTR_MODEL_NUMBER: printf(" model_number:<%.*s>", tlv_len, ie); break; case IEEE80211_WPS_ATTR_PRIMARY_DEV_TYPE: printf(" prim_dev:"); for (n = 0; n < tlv_len; n++) printf("%02x", ie[n]); break; case IEEE80211_WPS_ATTR_RF_BANDS: printf(" rf:"); f = 0; for (n = 7; n >= 0; n--) { if (f) { printf(","); f = 0; } switch (*ie & (1 << n)) { case 0: break; case IEEE80211_WPS_RF_BAND_24GHZ: printf("2.4Ghz"); f++; break; case IEEE80211_WPS_RF_BAND_50GHZ: printf("5Ghz"); f++; break; case IEEE80211_WPS_RF_BAND_600GHZ: printf("60Ghz"); f++; break; default: printf("unknown<%02x>", *ie & (1 << n)); f++; break; } } break; case IEEE80211_WPS_ATTR_RESPONSE_TYPE: printf(" resp_type:0x%02x", *ie); break; case IEEE80211_WPS_ATTR_SELECTED_REGISTRAR: printf(" sel:%s", *ie ? "T" : "F"); break; case IEEE80211_WPS_ATTR_SERIAL_NUMBER: printf(" serial_number:<%.*s>", tlv_len, ie); break; case IEEE80211_WPS_ATTR_UUID_E: printf(" uuid-e:"); for (n = 0; n < (tlv_len - 1); n++) printf("%02x-", ie[n]); printf("%02x", ie[n]); break; case IEEE80211_WPS_ATTR_VENDOR_EXT: printf(" vendor:"); for (n = 0; n < tlv_len; n++) printf("%02x", ie[n]); break; case IEEE80211_WPS_ATTR_WPS_STATE: switch (*ie) { case IEEE80211_WPS_STATE_NOT_CONFIGURED: printf(" state:N"); break; case IEEE80211_WPS_STATE_CONFIGURED: printf(" state:C"); break; default: printf(" state:B<%02x>", *ie); break; } break; default: printf(" unknown_wps_attr:0x%x", tlv_type); break; } ie += tlv_len, len -= tlv_len; } printf(">"); } } static void printtdmaie(if_ctx *ctx, const char *tag, const u_int8_t *ie, size_t ielen) { printf("%s", tag); if (ctx->args->verbose && ielen >= sizeof(struct ieee80211_tdma_param)) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *) ie; /* XXX tstamp */ printf("", tdma->tdma_version, tdma->tdma_slot, tdma->tdma_slotcnt, LE_READ_2(&tdma->tdma_slotlen), tdma->tdma_bintval, tdma->tdma_inuse[0]); } } /* * Copy the ssid string contents into buf, truncating to fit. If the * ssid is entirely printable then just copy intact. Otherwise convert * to hexadecimal. If the result is truncated then replace the last * three characters with "...". */ static int copy_essid(char buf[], size_t bufsize, const u_int8_t *essid, size_t essid_len) { const u_int8_t *p; size_t maxlen; u_int i; if (essid_len > bufsize) maxlen = bufsize; else maxlen = essid_len; /* determine printable or not */ for (i = 0, p = essid; i < maxlen; i++, p++) { if (*p < ' ' || *p > 0x7e) break; } if (i != maxlen) { /* not printable, print as hex */ if (bufsize < 3) return 0; strlcpy(buf, "0x", bufsize); bufsize -= 2; p = essid; for (i = 0; i < maxlen && bufsize >= 2; i++) { sprintf(&buf[2+2*i], "%02x", p[i]); bufsize -= 2; } if (i != essid_len) memcpy(&buf[2+2*i-3], "...", 3); } else { /* printable, truncate as needed */ memcpy(buf, essid, maxlen); if (maxlen != essid_len) memcpy(&buf[maxlen-3], "...", 3); } return maxlen; } static void printssid(const char *tag, const u_int8_t *ie, int maxlen) { char ssid[2*IEEE80211_NWID_LEN+1]; printf("%s<%.*s>", tag, copy_essid(ssid, maxlen, ie+2, ie[1]), ssid); } static void printrates(const char *tag, const u_int8_t *ie, size_t ielen) { const char *sep; printf("%s", tag); sep = "<"; for (size_t i = 2; i < ielen; i++) { printf("%s%s%d", sep, ie[i] & IEEE80211_RATE_BASIC ? "B" : "", ie[i] & IEEE80211_RATE_VAL); sep = ","; } printf(">"); } static void printcountry(const char *tag, const u_int8_t *ie) { const struct ieee80211_country_ie *cie = (const struct ieee80211_country_ie *) ie; int i, nbands, schan, nchan; printf("%s<%c%c%c", tag, cie->cc[0], cie->cc[1], cie->cc[2]); nbands = (cie->len - 3) / sizeof(cie->band[0]); for (i = 0; i < nbands; i++) { schan = cie->band[i].schan; nchan = cie->band[i].nchan; if (nchan != 1) printf(" %u-%u,%u", schan, schan + nchan-1, cie->band[i].maxtxpwr); else printf(" %u,%u", schan, cie->band[i].maxtxpwr); } printf(">"); } static __inline int iswpaoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((WPA_OUI_TYPE<<24)|WPA_OUI); } static __inline int iswmeinfo(const u_int8_t *frm) { return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && frm[6] == WME_INFO_OUI_SUBTYPE; } static __inline int iswmeparam(const u_int8_t *frm) { return frm[1] > 5 && LE_READ_4(frm+2) == ((WME_OUI_TYPE<<24)|WME_OUI) && frm[6] == WME_PARAM_OUI_SUBTYPE; } static __inline int isatherosoui(const u_int8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((ATH_OUI_TYPE<<24)|ATH_OUI); } static __inline int istdmaoui(const uint8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((TDMA_OUI_TYPE<<24)|TDMA_OUI); } static __inline int iswpsoui(const uint8_t *frm) { return frm[1] > 3 && LE_READ_4(frm+2) == ((WPS_OUI_TYPE<<24)|WPA_OUI); } static const char * iename(int elemid) { static char iename_buf[64]; switch (elemid) { case IEEE80211_ELEMID_FHPARMS: return " FHPARMS"; case IEEE80211_ELEMID_CFPARMS: return " CFPARMS"; case IEEE80211_ELEMID_TIM: return " TIM"; case IEEE80211_ELEMID_IBSSPARMS:return " IBSSPARMS"; case IEEE80211_ELEMID_BSSLOAD: return " BSSLOAD"; case IEEE80211_ELEMID_CHALLENGE:return " CHALLENGE"; case IEEE80211_ELEMID_PWRCNSTR: return " PWRCNSTR"; case IEEE80211_ELEMID_PWRCAP: return " PWRCAP"; case IEEE80211_ELEMID_TPCREQ: return " TPCREQ"; case IEEE80211_ELEMID_TPCREP: return " TPCREP"; case IEEE80211_ELEMID_SUPPCHAN: return " SUPPCHAN"; case IEEE80211_ELEMID_CSA: return " CSA"; case IEEE80211_ELEMID_MEASREQ: return " MEASREQ"; case IEEE80211_ELEMID_MEASREP: return " MEASREP"; case IEEE80211_ELEMID_QUIET: return " QUIET"; case IEEE80211_ELEMID_IBSSDFS: return " IBSSDFS"; case IEEE80211_ELEMID_RESERVED_47: return " RESERVED_47"; case IEEE80211_ELEMID_MOBILITY_DOMAIN: return " MOBILITY_DOMAIN"; case IEEE80211_ELEMID_RRM_ENACAPS: return " RRM_ENCAPS"; case IEEE80211_ELEMID_OVERLAP_BSS_SCAN_PARAM: return " OVERLAP_BSS"; case IEEE80211_ELEMID_TPC: return " TPC"; case IEEE80211_ELEMID_CCKM: return " CCKM"; case IEEE80211_ELEMID_EXTCAP: return " EXTCAP"; } snprintf(iename_buf, sizeof(iename_buf), " UNKNOWN_ELEMID_%d", elemid); return (const char *) iename_buf; } static void printies(if_ctx *ctx, const u_int8_t *vp, int ielen, unsigned int maxcols) { const int verbose = ctx->args->verbose; while (ielen > 0) { switch (vp[0]) { case IEEE80211_ELEMID_SSID: if (verbose) printssid(" SSID", vp, maxcols); break; case IEEE80211_ELEMID_RATES: case IEEE80211_ELEMID_XRATES: if (verbose) printrates(vp[0] == IEEE80211_ELEMID_RATES ? " RATES" : " XRATES", vp, 2+vp[1]); break; case IEEE80211_ELEMID_DSPARMS: if (verbose) printf(" DSPARMS<%u>", vp[2]); break; case IEEE80211_ELEMID_COUNTRY: if (verbose) printcountry(" COUNTRY", vp); break; case IEEE80211_ELEMID_ERP: if (verbose) printf(" ERP<0x%x>", vp[2]); break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(vp)) printwpaie(ctx, " WPA", vp); else if (iswmeinfo(vp)) printwmeinfo(ctx, " WME", vp); else if (iswmeparam(vp)) printwmeparam(ctx, " WME", vp); else if (isatherosoui(vp)) printathie(ctx, " ATH", vp); else if (iswpsoui(vp)) printwpsie(ctx, " WPS", vp); else if (istdmaoui(vp)) printtdmaie(ctx, " TDMA", vp, 2+vp[1]); else if (verbose) printie(ctx, " VEN", vp, 2+vp[1], maxcols); break; case IEEE80211_ELEMID_RSN: printrsnie(ctx, " RSN", vp, 2+vp[1]); break; case IEEE80211_ELEMID_HTCAP: printhtcap(ctx, " HTCAP", vp); break; case IEEE80211_ELEMID_HTINFO: if (verbose) printhtinfo(ctx, " HTINFO", vp); break; case IEEE80211_ELEMID_MESHID: if (verbose) printssid(" MESHID", vp, maxcols); break; case IEEE80211_ELEMID_MESHCONF: printmeshconf(ctx, " MESHCONF", vp); break; case IEEE80211_ELEMID_VHT_CAP: printvhtcap(ctx, " VHTCAP", vp); break; case IEEE80211_ELEMID_VHT_OPMODE: printvhtinfo(ctx, " VHTOPMODE", vp); break; case IEEE80211_ELEMID_VHT_PWR_ENV: printvhtpwrenv(ctx, " VHTPWRENV", vp, 2+vp[1]); break; case IEEE80211_ELEMID_BSSLOAD: printbssload(ctx, " BSSLOAD", vp); break; case IEEE80211_ELEMID_APCHANREP: printapchanrep(ctx, " APCHANREP", vp, 2+vp[1]); break; default: if (verbose) printie(ctx, iename(vp[0]), vp, 2+vp[1], maxcols); break; } ielen -= 2+vp[1]; vp += 2+vp[1]; } } static void printmimo(const struct ieee80211_mimo_info *mi) { int i; int r = 0; for (i = 0; i < IEEE80211_MAX_CHAINS; i++) { if (mi->ch[i].rssi[0] != 0) { r = 1; break; } } /* NB: don't muddy display unless there's something to show */ if (r == 0) return; /* XXX TODO: ignore EVM; secondary channels for now */ printf(" (rssi %.1f:%.1f:%.1f:%.1f nf %d:%d:%d:%d)", mi->ch[0].rssi[0] / 2.0, mi->ch[1].rssi[0] / 2.0, mi->ch[2].rssi[0] / 2.0, mi->ch[3].rssi[0] / 2.0, mi->ch[0].noise[0], mi->ch[1].noise[0], mi->ch[2].noise[0], mi->ch[3].noise[0]); } static void printbssidname(const struct ether_addr *n) { char name[MAXHOSTNAMELEN + 1]; if (ether_ntohost(name, n) != 0) return; printf(" (%s)", name); } static void list_scan(if_ctx *ctx) { uint8_t buf[24*1024]; char ssid[IEEE80211_NWID_LEN+1]; const uint8_t *cp; int len, idlen; if (get80211len(ctx, IEEE80211_IOC_SCAN_RESULTS, buf, sizeof(buf), &len) < 0) errx(1, "unable to get scan results"); if (len < (int)sizeof(struct ieee80211req_scan_result)) return; getchaninfo(ctx); printf("%-*.*s %-17.17s %4s %4s %-7s %3s %4s\n" , IEEE80211_NWID_LEN, IEEE80211_NWID_LEN, "SSID/MESH ID" , "BSSID" , "CHAN" , "RATE" , " S:N" , "INT" , "CAPS" ); cp = buf; do { const struct ieee80211req_scan_result *sr; const uint8_t *vp, *idp; sr = (const struct ieee80211req_scan_result *)(const void *) cp; vp = cp + sr->isr_ie_off; if (sr->isr_meshid_len) { idp = vp + sr->isr_ssid_len; idlen = sr->isr_meshid_len; } else { idp = vp; idlen = sr->isr_ssid_len; } printf("%-*.*s %s %3d %3dM %4d:%-4d %4d %-4.4s" , IEEE80211_NWID_LEN , copy_essid(ssid, IEEE80211_NWID_LEN, idp, idlen) , ssid , ether_ntoa((const struct ether_addr *) sr->isr_bssid) , ieee80211_mhz2ieee(sr->isr_freq, sr->isr_flags) , getmaxrate(sr->isr_rates, sr->isr_nrates) , (sr->isr_rssi/2)+sr->isr_noise, sr->isr_noise , sr->isr_intval , getcaps(sr->isr_capinfo) ); printies(ctx, vp + sr->isr_ssid_len + sr->isr_meshid_len, sr->isr_ie_len, 24); printbssidname((const struct ether_addr *)sr->isr_bssid); printf("\n"); cp += sr->isr_len, len -= sr->isr_len; } while (len >= (int)sizeof(struct ieee80211req_scan_result)); } static void scan_and_wait(if_ctx *ctx) { struct ieee80211_scan_req sr; struct ieee80211req ireq; int sroute; sroute = socket(PF_ROUTE, SOCK_RAW, 0); if (sroute < 0) { perror("socket(PF_ROUTE,SOCK_RAW)"); return; } memset(&ireq, 0, sizeof(ireq)); strlcpy(ireq.i_name, ctx->ifname, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_SCAN_REQ; memset(&sr, 0, sizeof(sr)); sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE | IEEE80211_IOC_SCAN_BGSCAN | IEEE80211_IOC_SCAN_NOPICK | IEEE80211_IOC_SCAN_ONCE; sr.sr_duration = IEEE80211_IOC_SCAN_FOREVER; sr.sr_nssid = 0; ireq.i_data = &sr; ireq.i_len = sizeof(sr); /* * NB: only root can trigger a scan so ignore errors. Also ignore * possible errors from net80211, even if no new scan could be * started there might still be a valid scan cache. */ if (ioctl_ctx(ctx, SIOCS80211, &ireq) == 0) { char buf[2048]; struct if_announcemsghdr *ifan; struct rt_msghdr *rtm; do { if (read(sroute, buf, sizeof(buf)) < 0) { perror("read(PF_ROUTE)"); break; } rtm = (struct rt_msghdr *)(void *)buf; if (rtm->rtm_version != RTM_VERSION) break; ifan = (struct if_announcemsghdr *) rtm; } while (rtm->rtm_type != RTM_IEEE80211 || ifan->ifan_what != RTM_IEEE80211_SCAN); } close(sroute); } static void set80211scan(if_ctx *ctx, const char *val __unused, int dummy __unused) { scan_and_wait(ctx); list_scan(ctx); } static enum ieee80211_opmode get80211opmode(if_ctx *ctx); static int gettxseq(const struct ieee80211req_sta_info *si) { int i, txseq; if ((si->isi_state & IEEE80211_NODE_QOS) == 0) return si->isi_txseqs[0]; /* XXX not right but usually what folks want */ txseq = 0; for (i = 0; i < IEEE80211_TID_SIZE; i++) if (si->isi_txseqs[i] > txseq) txseq = si->isi_txseqs[i]; return txseq; } static int getrxseq(const struct ieee80211req_sta_info *si) { int rxseq; if ((si->isi_state & IEEE80211_NODE_QOS) == 0) return si->isi_rxseqs[0]; /* XXX not right but usually what folks want */ rxseq = 0; for (unsigned int i = 0; i < IEEE80211_TID_SIZE; i++) if (si->isi_rxseqs[i] > rxseq) rxseq = si->isi_rxseqs[i]; return rxseq; } static void list_stations(if_ctx *ctx) { union { struct ieee80211req_sta_req req; uint8_t buf[24*1024]; } u; enum ieee80211_opmode opmode = get80211opmode(ctx); const uint8_t *cp; int len; /* broadcast address =>'s get all stations */ (void) memset(u.req.is_u.macaddr, 0xff, IEEE80211_ADDR_LEN); if (opmode == IEEE80211_M_STA) { /* * Get information about the associated AP. */ (void) get80211(ctx, IEEE80211_IOC_BSSID, u.req.is_u.macaddr, IEEE80211_ADDR_LEN); } if (get80211len(ctx, IEEE80211_IOC_STA_INFO, &u, sizeof(u), &len) < 0) errx(1, "unable to get station information"); if (len < (int)sizeof(struct ieee80211req_sta_info)) return; getchaninfo(ctx); if (opmode == IEEE80211_M_MBSS) printf("%-17.17s %4s %5s %5s %7s %4s %4s %4s %6s %6s\n" , "ADDR" , "CHAN" , "LOCAL" , "PEER" , "STATE" , "RATE" , "RSSI" , "IDLE" , "TXSEQ" , "RXSEQ" ); else printf("%-17.17s %4s %4s %4s %4s %4s %6s %6s %4s %-12s\n" , "ADDR" , "AID" , "CHAN" , "RATE" , "RSSI" , "IDLE" , "TXSEQ" , "RXSEQ" , "CAPS" , "FLAG" ); cp = (const uint8_t *) u.req.info; do { const struct ieee80211req_sta_info *si; si = (const struct ieee80211req_sta_info *)(const void *)cp; if (si->isi_len < sizeof(*si)) break; if (opmode == IEEE80211_M_MBSS) printf("%s %4d %5x %5x %7.7s %3dM %4.1f %4d %6d %6d" , ether_ntoa((const struct ether_addr*) si->isi_macaddr) , ieee80211_mhz2ieee(si->isi_freq, si->isi_flags) , si->isi_localid , si->isi_peerid , mesh_linkstate_string(si->isi_peerstate) , si->isi_txmbps/2 , si->isi_rssi/2. , si->isi_inact , gettxseq(si) , getrxseq(si) ); else printf("%s %4u %4d %3dM %4.1f %4d %6d %6d %-4.4s %-12.12s" , ether_ntoa((const struct ether_addr*) si->isi_macaddr) , IEEE80211_AID(si->isi_associd) , ieee80211_mhz2ieee(si->isi_freq, si->isi_flags) , si->isi_txmbps/2 , si->isi_rssi/2. , si->isi_inact , gettxseq(si) , getrxseq(si) , getcaps(si->isi_capinfo) , getflags(si->isi_state) ); printies(ctx, cp + si->isi_ie_off, si->isi_ie_len, 24); printmimo(&si->isi_mimo); printf("\n"); cp += si->isi_len, len -= si->isi_len; } while (len >= (int)sizeof(struct ieee80211req_sta_info)); } static const char * mesh_linkstate_string(uint8_t state) { static const char *state_names[] = { [0] = "IDLE", [1] = "OPEN-TX", [2] = "OPEN-RX", [3] = "CONF-RX", [4] = "ESTAB", [5] = "HOLDING", }; if (state >= nitems(state_names)) { static char buf[10]; snprintf(buf, sizeof(buf), "#%u", state); return buf; } else return state_names[state]; } static const char * get_chaninfo(const struct ieee80211_channel *c, int precise, char buf[], size_t bsize) { buf[0] = '\0'; if (IEEE80211_IS_CHAN_FHSS(c)) strlcat(buf, " FHSS", bsize); if (IEEE80211_IS_CHAN_A(c)) strlcat(buf, " 11a", bsize); else if (IEEE80211_IS_CHAN_ANYG(c)) strlcat(buf, " 11g", bsize); else if (IEEE80211_IS_CHAN_B(c)) strlcat(buf, " 11b", bsize); if (IEEE80211_IS_CHAN_HALF(c)) strlcat(buf, "/10MHz", bsize); if (IEEE80211_IS_CHAN_QUARTER(c)) strlcat(buf, "/5MHz", bsize); if (IEEE80211_IS_CHAN_TURBO(c)) strlcat(buf, " Turbo", bsize); if (precise) { if (IEEE80211_IS_CHAN_VHT80P80(c)) strlcat(buf, " vht/80p80", bsize); else if (IEEE80211_IS_CHAN_VHT160(c)) strlcat(buf, " vht/160", bsize); else if (IEEE80211_IS_CHAN_VHT80(c) && IEEE80211_IS_CHAN_HT40D(c)) strlcat(buf, " vht/80-", bsize); else if (IEEE80211_IS_CHAN_VHT80(c) && IEEE80211_IS_CHAN_HT40U(c)) strlcat(buf, " vht/80+", bsize); else if (IEEE80211_IS_CHAN_VHT80(c)) strlcat(buf, " vht/80", bsize); else if (IEEE80211_IS_CHAN_VHT40D(c)) strlcat(buf, " vht/40-", bsize); else if (IEEE80211_IS_CHAN_VHT40U(c)) strlcat(buf, " vht/40+", bsize); else if (IEEE80211_IS_CHAN_VHT20(c)) strlcat(buf, " vht/20", bsize); else if (IEEE80211_IS_CHAN_HT20(c)) strlcat(buf, " ht/20", bsize); else if (IEEE80211_IS_CHAN_HT40D(c)) strlcat(buf, " ht/40-", bsize); else if (IEEE80211_IS_CHAN_HT40U(c)) strlcat(buf, " ht/40+", bsize); } else { if (IEEE80211_IS_CHAN_VHT(c)) strlcat(buf, " vht", bsize); else if (IEEE80211_IS_CHAN_HT(c)) strlcat(buf, " ht", bsize); } return buf; } static void print_chaninfo(const struct ieee80211_channel *c, int verb) { char buf[14]; if (verb) printf("Channel %3u : %u%c%c%c%c%c MHz%-14.14s", ieee80211_mhz2ieee(c->ic_freq, c->ic_flags), c->ic_freq, IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ', IEEE80211_IS_CHAN_DFS(c) ? 'D' : ' ', IEEE80211_IS_CHAN_RADAR(c) ? 'R' : ' ', IEEE80211_IS_CHAN_CWINT(c) ? 'I' : ' ', IEEE80211_IS_CHAN_CACDONE(c) ? 'C' : ' ', get_chaninfo(c, verb, buf, sizeof(buf))); else printf("Channel %3u : %u%c MHz%-14.14s", ieee80211_mhz2ieee(c->ic_freq, c->ic_flags), c->ic_freq, IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ', get_chaninfo(c, verb, buf, sizeof(buf))); } static int chanpref(const struct ieee80211_channel *c) { if (IEEE80211_IS_CHAN_VHT80P80(c)) return 90; if (IEEE80211_IS_CHAN_VHT160(c)) return 80; if (IEEE80211_IS_CHAN_VHT80(c)) return 70; if (IEEE80211_IS_CHAN_VHT40(c)) return 60; if (IEEE80211_IS_CHAN_VHT20(c)) return 50; if (IEEE80211_IS_CHAN_HT40(c)) return 40; if (IEEE80211_IS_CHAN_HT20(c)) return 30; if (IEEE80211_IS_CHAN_HALF(c)) return 10; if (IEEE80211_IS_CHAN_QUARTER(c)) return 5; if (IEEE80211_IS_CHAN_TURBO(c)) return 25; if (IEEE80211_IS_CHAN_A(c)) return 20; if (IEEE80211_IS_CHAN_G(c)) return 20; if (IEEE80211_IS_CHAN_B(c)) return 15; if (IEEE80211_IS_CHAN_PUREG(c)) return 15; return 0; } static void print_channels(if_ctx *ctx, const struct ieee80211req_chaninfo *chans, int allchans, int verb) { struct ieee80211req_chaninfo *achans; uint8_t reported[IEEE80211_CHAN_BYTES]; const struct ieee80211_channel *c; unsigned int i, half; achans = malloc(IEEE80211_CHANINFO_SPACE(chans)); if (achans == NULL) errx(1, "no space for active channel list"); achans->ic_nchans = 0; memset(reported, 0, sizeof(reported)); if (!allchans) { struct ieee80211req_chanlist active; if (get80211(ctx, IEEE80211_IOC_CHANLIST, &active, sizeof(active)) < 0) errx(1, "unable to get active channel list"); for (i = 0; i < chans->ic_nchans; i++) { c = &chans->ic_chans[i]; if (!isset(active.ic_channels, c->ic_ieee)) continue; /* * Suppress compatible duplicates unless * verbose. The kernel gives us it's * complete channel list which has separate * entries for 11g/11b and 11a/turbo. */ if (isset(reported, c->ic_ieee) && !verb) { /* XXX we assume duplicates are adjacent */ achans->ic_chans[achans->ic_nchans-1] = *c; } else { achans->ic_chans[achans->ic_nchans++] = *c; setbit(reported, c->ic_ieee); } } } else { for (i = 0; i < chans->ic_nchans; i++) { c = &chans->ic_chans[i]; /* suppress duplicates as above */ if (isset(reported, c->ic_ieee) && !verb) { /* XXX we assume duplicates are adjacent */ struct ieee80211_channel *a = &achans->ic_chans[achans->ic_nchans-1]; if (chanpref(c) > chanpref(a)) *a = *c; } else { achans->ic_chans[achans->ic_nchans++] = *c; setbit(reported, c->ic_ieee); } } } half = achans->ic_nchans / 2; if (achans->ic_nchans % 2) half++; for (i = 0; i < achans->ic_nchans / 2; i++) { print_chaninfo(&achans->ic_chans[i], verb); print_chaninfo(&achans->ic_chans[half+i], verb); printf("\n"); } if (achans->ic_nchans % 2) { print_chaninfo(&achans->ic_chans[i], verb); printf("\n"); } free(achans); } static void list_channels(if_ctx *ctx, int allchans) { getchaninfo(ctx); print_channels(ctx, chaninfo, allchans, ctx->args->verbose); } static void print_txpow(const struct ieee80211_channel *c) { printf("Channel %3u : %u MHz %3.1f reg %2d ", c->ic_ieee, c->ic_freq, c->ic_maxpower/2., c->ic_maxregpower); } static void print_txpow_verbose(const struct ieee80211_channel *c) { print_chaninfo(c, 1); printf("min %4.1f dBm max %3.1f dBm reg %2d dBm", c->ic_minpower/2., c->ic_maxpower/2., c->ic_maxregpower); /* indicate where regulatory cap limits power use */ if (c->ic_maxpower > 2*c->ic_maxregpower) printf(" <"); } static void list_txpow(if_ctx *ctx) { struct ieee80211req_chaninfo *achans; uint8_t reported[IEEE80211_CHAN_BYTES]; struct ieee80211_channel *c, *prev; unsigned int i, half; getchaninfo(ctx); achans = malloc(IEEE80211_CHANINFO_SPACE(chaninfo)); if (achans == NULL) errx(1, "no space for active channel list"); achans->ic_nchans = 0; memset(reported, 0, sizeof(reported)); for (i = 0; i < chaninfo->ic_nchans; i++) { c = &chaninfo->ic_chans[i]; /* suppress duplicates as above */ if (isset(reported, c->ic_ieee) && !ctx->args->verbose) { /* XXX we assume duplicates are adjacent */ assert(achans->ic_nchans > 0); prev = &achans->ic_chans[achans->ic_nchans-1]; /* display highest power on channel */ if (c->ic_maxpower > prev->ic_maxpower) *prev = *c; } else { achans->ic_chans[achans->ic_nchans++] = *c; setbit(reported, c->ic_ieee); } } if (!ctx->args->verbose) { half = achans->ic_nchans / 2; if (achans->ic_nchans % 2) half++; for (i = 0; i < achans->ic_nchans / 2; i++) { print_txpow(&achans->ic_chans[i]); print_txpow(&achans->ic_chans[half+i]); printf("\n"); } if (achans->ic_nchans % 2) { print_txpow(&achans->ic_chans[i]); printf("\n"); } } else { for (i = 0; i < achans->ic_nchans; i++) { print_txpow_verbose(&achans->ic_chans[i]); printf("\n"); } } free(achans); } static void list_keys(int s __unused) { } static void list_capabilities(if_ctx *ctx) { struct ieee80211_devcaps_req *dc; const int verbose = ctx->args->verbose; if (verbose) dc = malloc(IEEE80211_DEVCAPS_SIZE(MAXCHAN)); else dc = malloc(IEEE80211_DEVCAPS_SIZE(1)); if (dc == NULL) errx(1, "no space for device capabilities"); dc->dc_chaninfo.ic_nchans = verbose ? MAXCHAN : 1; getdevcaps(ctx, dc); printb("drivercaps", dc->dc_drivercaps, IEEE80211_C_BITS); if (dc->dc_cryptocaps != 0 || verbose) { putchar('\n'); printb("cryptocaps", dc->dc_cryptocaps, IEEE80211_CRYPTO_BITS); } if (dc->dc_htcaps != 0 || verbose) { putchar('\n'); printb("htcaps", dc->dc_htcaps, IEEE80211_HTCAP_BITS); } if (dc->dc_vhtcaps != 0 || verbose) { putchar('\n'); printb("vhtcaps", dc->dc_vhtcaps, IEEE80211_VHTCAP_BITS); } putchar('\n'); if (verbose) { chaninfo = &dc->dc_chaninfo; /* XXX */ print_channels(ctx, &dc->dc_chaninfo, 1/*allchans*/, verbose); } free(dc); } static int get80211wme(if_ctx *ctx, int param, int ac, int *val) { struct ieee80211req ireq = {}; strlcpy(ireq.i_name, ctx->ifname, sizeof(ireq.i_name)); ireq.i_type = param; ireq.i_len = ac; if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) { warn("cannot get WME parameter %d, ac %d%s", param, ac & IEEE80211_WMEPARAM_VAL, ac & IEEE80211_WMEPARAM_BSS ? " (BSS)" : ""); return -1; } *val = ireq.i_val; return 0; } static void list_wme_aci(if_ctx *ctx, const char *tag, int ac) { int val; printf("\t%s", tag); /* show WME BSS parameters */ if (get80211wme(ctx, IEEE80211_IOC_WME_CWMIN, ac, &val) != -1) printf(" cwmin %2u", val); if (get80211wme(ctx, IEEE80211_IOC_WME_CWMAX, ac, &val) != -1) printf(" cwmax %2u", val); if (get80211wme(ctx, IEEE80211_IOC_WME_AIFS, ac, &val) != -1) printf(" aifs %2u", val); if (get80211wme(ctx, IEEE80211_IOC_WME_TXOPLIMIT, ac, &val) != -1) printf(" txopLimit %3u", val); if (get80211wme(ctx, IEEE80211_IOC_WME_ACM, ac, &val) != -1) { if (val) printf(" acm"); else if (ctx->args->verbose) printf(" -acm"); } /* !BSS only */ if ((ac & IEEE80211_WMEPARAM_BSS) == 0) { if (get80211wme(ctx, IEEE80211_IOC_WME_ACKPOLICY, ac, &val) != -1) { if (!val) printf(" -ack"); else if (ctx->args->verbose) printf(" ack"); } } printf("\n"); } static void list_wme(if_ctx *ctx) { static const char *acnames[] = { "AC_BE", "AC_BK", "AC_VI", "AC_VO" }; int ac; if (ctx->args->verbose) { /* display both BSS and local settings */ for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++) { again: if (ac & IEEE80211_WMEPARAM_BSS) list_wme_aci(ctx, " ", ac); else list_wme_aci(ctx, acnames[ac], ac); if ((ac & IEEE80211_WMEPARAM_BSS) == 0) { ac |= IEEE80211_WMEPARAM_BSS; goto again; } else ac &= ~IEEE80211_WMEPARAM_BSS; } } else { /* display only channel settings */ for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++) list_wme_aci(ctx, acnames[ac], ac); } } static void list_roam(if_ctx *ctx) { const struct ieee80211_roamparam *rp; int mode; getroam(ctx); for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) { rp = &roamparams.params[mode]; if (rp->rssi == 0 && rp->rate == 0) continue; if (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG || mode == IEEE80211_MODE_VHT_2GHZ || mode == IEEE80211_MODE_VHT_5GHZ) { if (rp->rssi & 1) LINE_CHECK("roam:%-7.7s rssi %2u.5dBm MCS %2u ", modename[mode], rp->rssi/2, rp->rate &~ IEEE80211_RATE_MCS); else LINE_CHECK("roam:%-7.7s rssi %4udBm MCS %2u ", modename[mode], rp->rssi/2, rp->rate &~ IEEE80211_RATE_MCS); } else { if (rp->rssi & 1) LINE_CHECK("roam:%-7.7s rssi %2u.5dBm rate %2u Mb/s", modename[mode], rp->rssi/2, rp->rate/2); else LINE_CHECK("roam:%-7.7s rssi %4udBm rate %2u Mb/s", modename[mode], rp->rssi/2, rp->rate/2); } } } /* XXX TODO: rate-to-string method... */ static const char* get_mcs_mbs_rate_str(uint8_t rate) { return (rate & IEEE80211_RATE_MCS) ? "MCS " : "Mb/s"; } static uint8_t get_rate_value(uint8_t rate) { if (rate & IEEE80211_RATE_MCS) return (rate &~ IEEE80211_RATE_MCS); return (rate / 2); } static void list_txparams(if_ctx *ctx) { const struct ieee80211_txparam *tp; int mode; gettxparams(ctx); for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) { tp = &txparams.params[mode]; if (tp->mgmtrate == 0 && tp->mcastrate == 0) continue; if (mode == IEEE80211_MODE_11NA || mode == IEEE80211_MODE_11NG || mode == IEEE80211_MODE_VHT_2GHZ || mode == IEEE80211_MODE_VHT_5GHZ) { if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) LINE_CHECK("%-7.7s ucast NONE mgmt %2u %s " "mcast %2u %s maxretry %u", modename[mode], get_rate_value(tp->mgmtrate), get_mcs_mbs_rate_str(tp->mgmtrate), get_rate_value(tp->mcastrate), get_mcs_mbs_rate_str(tp->mcastrate), tp->maxretry); else LINE_CHECK("%-7.7s ucast %2u MCS mgmt %2u %s " "mcast %2u %s maxretry %u", modename[mode], tp->ucastrate &~ IEEE80211_RATE_MCS, get_rate_value(tp->mgmtrate), get_mcs_mbs_rate_str(tp->mgmtrate), get_rate_value(tp->mcastrate), get_mcs_mbs_rate_str(tp->mcastrate), tp->maxretry); } else { if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) LINE_CHECK("%-7.7s ucast NONE mgmt %2u Mb/s " "mcast %2u Mb/s maxretry %u", modename[mode], tp->mgmtrate/2, tp->mcastrate/2, tp->maxretry); else LINE_CHECK("%-7.7s ucast %2u Mb/s mgmt %2u Mb/s " "mcast %2u Mb/s maxretry %u", modename[mode], tp->ucastrate/2, tp->mgmtrate/2, tp->mcastrate/2, tp->maxretry); } } } static void printpolicy(int policy) { switch (policy) { case IEEE80211_MACCMD_POLICY_OPEN: printf("policy: open\n"); break; case IEEE80211_MACCMD_POLICY_ALLOW: printf("policy: allow\n"); break; case IEEE80211_MACCMD_POLICY_DENY: printf("policy: deny\n"); break; case IEEE80211_MACCMD_POLICY_RADIUS: printf("policy: radius\n"); break; default: printf("policy: unknown (%u)\n", policy); break; } } static void list_mac(if_ctx *ctx) { struct ieee80211req ireq = {}; struct ieee80211req_maclist *acllist; int i, nacls, policy, len; uint8_t *data; char c; strlcpy(ireq.i_name, ctx->ifname, sizeof(ireq.i_name)); /* XXX ?? */ ireq.i_type = IEEE80211_IOC_MACCMD; ireq.i_val = IEEE80211_MACCMD_POLICY; if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) { if (errno == EINVAL) { printf("No acl policy loaded\n"); return; } err(1, "unable to get mac policy"); } policy = ireq.i_val; if (policy == IEEE80211_MACCMD_POLICY_OPEN) { c = '*'; } else if (policy == IEEE80211_MACCMD_POLICY_ALLOW) { c = '+'; } else if (policy == IEEE80211_MACCMD_POLICY_DENY) { c = '-'; } else if (policy == IEEE80211_MACCMD_POLICY_RADIUS) { c = 'r'; /* NB: should never have entries */ } else { printf("policy: unknown (%u)\n", policy); c = '?'; } if (ctx->args->verbose || c == '?') printpolicy(policy); ireq.i_val = IEEE80211_MACCMD_LIST; ireq.i_len = 0; if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) err(1, "unable to get mac acl list size"); if (ireq.i_len == 0) { /* NB: no acls */ if (!(ctx->args->verbose || c == '?')) printpolicy(policy); return; } len = ireq.i_len; data = malloc(len); if (data == NULL) err(1, "out of memory for acl list"); ireq.i_data = data; if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) err(1, "unable to get mac acl list"); nacls = len / sizeof(*acllist); acllist = (struct ieee80211req_maclist *) data; for (i = 0; i < nacls; i++) printf("%c%s\n", c, ether_ntoa( (const struct ether_addr *) acllist[i].ml_macaddr)); free(data); } static void print_regdomain(const struct ieee80211_regdomain *reg, int verb) { if ((reg->regdomain != 0 && reg->regdomain != reg->country) || verb) { const struct regdomain *rd = lib80211_regdomain_findbysku(getregdata(), reg->regdomain); if (rd == NULL) LINE_CHECK("regdomain %d", reg->regdomain); else LINE_CHECK("regdomain %s", rd->name); } if (reg->country != 0 || verb) { const struct country *cc = lib80211_country_findbycc(getregdata(), reg->country); if (cc == NULL) LINE_CHECK("country %d", reg->country); else LINE_CHECK("country %s", cc->isoname); } if (reg->location == 'I') LINE_CHECK("indoor"); else if (reg->location == 'O') LINE_CHECK("outdoor"); else if (verb) LINE_CHECK("anywhere"); if (reg->ecm) LINE_CHECK("ecm"); else if (verb) LINE_CHECK("-ecm"); } static void list_regdomain(if_ctx *ctx, int channelsalso) { getregdomain(ctx); if (channelsalso) { getchaninfo(ctx); spacer = ':'; print_regdomain(®domain, 1); LINE_BREAK(); print_channels(ctx, chaninfo, 1/*allchans*/, 1/*verbose*/); } else print_regdomain(®domain, ctx->args->verbose); } static void list_mesh(if_ctx *ctx) { struct ieee80211req ireq = {}; struct ieee80211req_mesh_route routes[128]; struct ieee80211req_mesh_route *rt; strlcpy(ireq.i_name, ctx->ifname, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_MESH_RTCMD; ireq.i_val = IEEE80211_MESH_RTCMD_LIST; ireq.i_data = &routes; ireq.i_len = sizeof(routes); if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) err(1, "unable to get the Mesh routing table"); printf("%-17.17s %-17.17s %4s %4s %4s %6s %s\n" , "DEST" , "NEXT HOP" , "HOPS" , "METRIC" , "LIFETIME" , "MSEQ" , "FLAGS"); for (unsigned int i = 0; i < ireq.i_len / sizeof(*rt); i++) { rt = &routes[i]; printf("%s ", ether_ntoa((const struct ether_addr *)rt->imr_dest)); printf("%s %4u %4u %6u %6u %c%c\n", ether_ntoa((const struct ether_addr *)rt->imr_nexthop), rt->imr_nhops, rt->imr_metric, rt->imr_lifetime, rt->imr_lastmseq, (rt->imr_flags & IEEE80211_MESHRT_FLAGS_DISCOVER) ? 'D' : (rt->imr_flags & IEEE80211_MESHRT_FLAGS_VALID) ? 'V' : '!', (rt->imr_flags & IEEE80211_MESHRT_FLAGS_PROXY) ? 'P' : (rt->imr_flags & IEEE80211_MESHRT_FLAGS_GATE) ? 'G' :' '); } } static void set80211list(if_ctx *ctx, const char *arg, int dummy __unused) { int s = ctx->io_s; #define iseq(a,b) (strncasecmp(a,b,sizeof(b)-1) == 0) LINE_INIT('\t'); if (iseq(arg, "sta")) list_stations(ctx); else if (iseq(arg, "scan") || iseq(arg, "ap")) list_scan(ctx); else if (iseq(arg, "chan") || iseq(arg, "freq")) list_channels(ctx, 1); else if (iseq(arg, "active")) list_channels(ctx, 0); else if (iseq(arg, "keys")) list_keys(s); else if (iseq(arg, "caps")) list_capabilities(ctx); else if (iseq(arg, "wme") || iseq(arg, "wmm")) list_wme(ctx); else if (iseq(arg, "mac")) list_mac(ctx); else if (iseq(arg, "txpow")) list_txpow(ctx); else if (iseq(arg, "roam")) list_roam(ctx); else if (iseq(arg, "txparam") || iseq(arg, "txparm")) list_txparams(ctx); else if (iseq(arg, "regdomain")) list_regdomain(ctx, 1); else if (iseq(arg, "countries")) list_countries(); else if (iseq(arg, "mesh")) list_mesh(ctx); else errx(1, "Don't know how to list %s for %s", arg, ctx->ifname); LINE_BREAK(); #undef iseq } static enum ieee80211_opmode get80211opmode(if_ctx *ctx) { struct ifmediareq ifmr = {}; strlcpy(ifmr.ifm_name, ctx->ifname, sizeof(ifmr.ifm_name)); if (ioctl_ctx(ctx, SIOCGIFMEDIA, (caddr_t)&ifmr) >= 0) { if (ifmr.ifm_current & IFM_IEEE80211_ADHOC) { if (ifmr.ifm_current & IFM_FLAG0) return IEEE80211_M_AHDEMO; else return IEEE80211_M_IBSS; } if (ifmr.ifm_current & IFM_IEEE80211_HOSTAP) return IEEE80211_M_HOSTAP; if (ifmr.ifm_current & IFM_IEEE80211_IBSS) return IEEE80211_M_IBSS; if (ifmr.ifm_current & IFM_IEEE80211_MONITOR) return IEEE80211_M_MONITOR; if (ifmr.ifm_current & IFM_IEEE80211_MBSS) return IEEE80211_M_MBSS; } return IEEE80211_M_STA; } #if 0 static void printcipher(int s, struct ieee80211req *ireq, int keylenop) { switch (ireq->i_val) { case IEEE80211_CIPHER_WEP: ireq->i_type = keylenop; if (ioctl(s, SIOCG80211, ireq) != -1) printf("WEP-%s", ireq->i_len <= 5 ? "40" : ireq->i_len <= 13 ? "104" : "128"); else printf("WEP"); break; case IEEE80211_CIPHER_TKIP: printf("TKIP"); break; case IEEE80211_CIPHER_AES_OCB: printf("AES-OCB"); break; case IEEE80211_CIPHER_AES_CCM: printf("AES-CCM"); break; case IEEE80211_CIPHER_CKIP: printf("CKIP"); break; case IEEE80211_CIPHER_NONE: printf("NONE"); break; default: printf("UNKNOWN (0x%x)", ireq->i_val); break; } } #endif static void printkey(if_ctx *ctx, const struct ieee80211req_key *ik) { static const uint8_t zerodata[IEEE80211_KEYBUF_SIZE]; u_int keylen = ik->ik_keylen; int printcontents; const int verbose = ctx->args->verbose; const bool printkeys = ctx->args->printkeys; printcontents = printkeys && (memcmp(ik->ik_keydata, zerodata, keylen) != 0 || verbose); if (printcontents) LINE_BREAK(); switch (ik->ik_type) { case IEEE80211_CIPHER_WEP: /* compatibility */ LINE_CHECK("wepkey %u:%s", ik->ik_keyix+1, keylen <= 5 ? "40-bit" : keylen <= 13 ? "104-bit" : "128-bit"); break; case IEEE80211_CIPHER_TKIP: if (keylen > 128/8) keylen -= 128/8; /* ignore MIC for now */ LINE_CHECK("TKIP %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_AES_OCB: LINE_CHECK("AES-OCB %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_AES_CCM: LINE_CHECK("AES-CCM %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_CKIP: LINE_CHECK("CKIP %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; case IEEE80211_CIPHER_NONE: LINE_CHECK("NULL %u:%u-bit", ik->ik_keyix+1, 8*keylen); break; default: LINE_CHECK("UNKNOWN (0x%x) %u:%u-bit", ik->ik_type, ik->ik_keyix+1, 8*keylen); break; } if (printcontents) { u_int i; printf(" <"); for (i = 0; i < keylen; i++) printf("%02x", ik->ik_keydata[i]); printf(">"); if (ik->ik_type != IEEE80211_CIPHER_WEP && (ik->ik_keyrsc != 0 || verbose)) printf(" rsc %ju", (uintmax_t)ik->ik_keyrsc); if (ik->ik_type != IEEE80211_CIPHER_WEP && (ik->ik_keytsc != 0 || verbose)) printf(" tsc %ju", (uintmax_t)ik->ik_keytsc); if (ik->ik_flags != 0 && verbose) { const char *sep = " "; if (ik->ik_flags & IEEE80211_KEY_XMIT) printf("%stx", sep), sep = "+"; if (ik->ik_flags & IEEE80211_KEY_RECV) printf("%srx", sep), sep = "+"; if (ik->ik_flags & IEEE80211_KEY_DEFAULT) printf("%sdef", sep), sep = "+"; } LINE_BREAK(); } } static void printrate(const char *tag, int v, int defrate, int defmcs) { if ((v & IEEE80211_RATE_MCS) == 0) { if (v != defrate) { if (v & 1) LINE_CHECK("%s %d.5", tag, v/2); else LINE_CHECK("%s %d", tag, v/2); } } else { if (v != defmcs) LINE_CHECK("%s %d", tag, v &~ 0x80); } } static int getid(if_ctx *ctx, int ix, void *data, size_t len, int *plen, int mesh) { struct ieee80211req ireq = {}; strlcpy(ireq.i_name, ctx->ifname, sizeof(ireq.i_name)); ireq.i_type = (!mesh) ? IEEE80211_IOC_SSID : IEEE80211_IOC_MESH_ID; ireq.i_val = ix; ireq.i_data = data; ireq.i_len = len; if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) return -1; *plen = ireq.i_len; return 0; } static int getdevicename(if_ctx *ctx, void *data, size_t len, int *plen) { struct ieee80211req ireq = {}; strlcpy(ireq.i_name, ctx->ifname, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_IC_NAME; ireq.i_val = -1; ireq.i_data = data; ireq.i_len = len; if (ioctl_ctx(ctx, SIOCG80211, &ireq) < 0) return (-1); *plen = ireq.i_len; return (0); } static void ieee80211_status(if_ctx *ctx) { int s = ctx->io_s; static const uint8_t zerobssid[IEEE80211_ADDR_LEN]; enum ieee80211_opmode opmode = get80211opmode(ctx); int i, num, wpa, wme, bgscan, bgscaninterval, val, len, wepmode; uint8_t data[32]; const struct ieee80211_channel *c; const struct ieee80211_roamparam *rp; const struct ieee80211_txparam *tp; const int verbose = ctx->args->verbose; if (getid(ctx, -1, data, sizeof(data), &len, 0) < 0) { /* If we can't get the SSID, this isn't an 802.11 device. */ return; } /* * Invalidate cached state so printing status for multiple * if's doesn't reuse the first interfaces' cached state. */ gotcurchan = 0; gotroam = 0; gottxparams = 0; gothtconf = 0; gotregdomain = 0; printf("\t"); if (opmode == IEEE80211_M_MBSS) { printf("meshid "); getid(ctx, 0, data, sizeof(data), &len, 1); print_string(data, len); } else { if (get80211val(ctx, IEEE80211_IOC_NUMSSIDS, &num) < 0) num = 0; printf("ssid "); if (num > 1) { for (i = 0; i < num; i++) { if (getid(ctx, i, data, sizeof(data), &len, 0) >= 0 && len > 0) { printf(" %d:", i + 1); print_string(data, len); } } } else print_string(data, len); } c = getcurchan(ctx); if (c->ic_freq != IEEE80211_CHAN_ANY) { char buf[14]; printf(" channel %d (%u MHz%s)", c->ic_ieee, c->ic_freq, get_chaninfo(c, 1, buf, sizeof(buf))); } else if (verbose) printf(" channel UNDEF"); if (get80211(ctx, IEEE80211_IOC_BSSID, data, IEEE80211_ADDR_LEN) >= 0 && (memcmp(data, zerobssid, sizeof(zerobssid)) != 0 || verbose)) { printf(" bssid %s", ether_ntoa((struct ether_addr *)data)); printbssidname((struct ether_addr *)data); } if (get80211len(ctx, IEEE80211_IOC_STATIONNAME, data, sizeof(data), &len) != -1) { printf("\n\tstationname "); print_string(data, len); } spacer = ' '; /* force first break */ LINE_BREAK(); list_regdomain(ctx, 0); wpa = 0; if (get80211val(ctx, IEEE80211_IOC_AUTHMODE, &val) != -1) { switch (val) { case IEEE80211_AUTH_NONE: LINE_CHECK("authmode NONE"); break; case IEEE80211_AUTH_OPEN: LINE_CHECK("authmode OPEN"); break; case IEEE80211_AUTH_SHARED: LINE_CHECK("authmode SHARED"); break; case IEEE80211_AUTH_8021X: LINE_CHECK("authmode 802.1x"); break; case IEEE80211_AUTH_WPA: if (get80211val(ctx, IEEE80211_IOC_WPA, &wpa) < 0) wpa = 1; /* default to WPA1 */ switch (wpa) { case 2: LINE_CHECK("authmode WPA2/802.11i"); break; case 3: LINE_CHECK("authmode WPA1+WPA2/802.11i"); break; default: LINE_CHECK("authmode WPA"); break; } break; case IEEE80211_AUTH_AUTO: LINE_CHECK("authmode AUTO"); break; default: LINE_CHECK("authmode UNKNOWN (0x%x)", val); break; } } if (wpa || verbose) { if (get80211val(ctx, IEEE80211_IOC_WPS, &val) != -1) { if (val) LINE_CHECK("wps"); else if (verbose) LINE_CHECK("-wps"); } if (get80211val(ctx, IEEE80211_IOC_TSN, &val) != -1) { if (val) LINE_CHECK("tsn"); else if (verbose) LINE_CHECK("-tsn"); } if (ioctl(s, IEEE80211_IOC_COUNTERMEASURES, &val) != -1) { if (val) LINE_CHECK("countermeasures"); else if (verbose) LINE_CHECK("-countermeasures"); } #if 0 /* XXX not interesting with WPA done in user space */ ireq.i_type = IEEE80211_IOC_KEYMGTALGS; if (ioctl(s, SIOCG80211, &ireq) != -1) { } ireq.i_type = IEEE80211_IOC_MCASTCIPHER; if (ioctl(s, SIOCG80211, &ireq) != -1) { LINE_CHECK("mcastcipher "); printcipher(s, &ireq, IEEE80211_IOC_MCASTKEYLEN); spacer = ' '; } ireq.i_type = IEEE80211_IOC_UCASTCIPHER; if (ioctl(s, SIOCG80211, &ireq) != -1) { LINE_CHECK("ucastcipher "); printcipher(s, &ireq, IEEE80211_IOC_UCASTKEYLEN); } if (wpa & 2) { ireq.i_type = IEEE80211_IOC_RSNCAPS; if (ioctl(s, SIOCG80211, &ireq) != -1) { LINE_CHECK("RSN caps 0x%x", ireq.i_val); spacer = ' '; } } ireq.i_type = IEEE80211_IOC_UCASTCIPHERS; if (ioctl(s, SIOCG80211, &ireq) != -1) { } #endif } if (get80211val(ctx, IEEE80211_IOC_WEP, &wepmode) != -1 && wepmode != IEEE80211_WEP_NOSUP) { switch (wepmode) { case IEEE80211_WEP_OFF: LINE_CHECK("privacy OFF"); break; case IEEE80211_WEP_ON: LINE_CHECK("privacy ON"); break; case IEEE80211_WEP_MIXED: LINE_CHECK("privacy MIXED"); break; default: LINE_CHECK("privacy UNKNOWN (0x%x)", wepmode); break; } /* * If we get here then we've got WEP support so we need * to print WEP status. */ if (get80211val(ctx, IEEE80211_IOC_WEPTXKEY, &val) < 0) { warn("WEP support, but no tx key!"); goto end; } if (val != -1) LINE_CHECK("deftxkey %d", val+1); else if (wepmode != IEEE80211_WEP_OFF || verbose) LINE_CHECK("deftxkey UNDEF"); if (get80211val(ctx, IEEE80211_IOC_NUMWEPKEYS, &num) < 0) { warn("WEP support, but no NUMWEPKEYS support!"); goto end; } for (i = 0; i < num; i++) { struct ieee80211req_key ik; memset(&ik, 0, sizeof(ik)); ik.ik_keyix = i; if (get80211(ctx, IEEE80211_IOC_WPAKEY, &ik, sizeof(ik)) < 0) { warn("WEP support, but can get keys!"); goto end; } if (ik.ik_keylen != 0) { if (verbose) LINE_BREAK(); printkey(ctx, &ik); } } if (i > 0 && verbose) LINE_BREAK(); end: ; } if (get80211val(ctx, IEEE80211_IOC_POWERSAVE, &val) != -1 && val != IEEE80211_POWERSAVE_NOSUP ) { if (val != IEEE80211_POWERSAVE_OFF || verbose) { switch (val) { case IEEE80211_POWERSAVE_OFF: LINE_CHECK("powersavemode OFF"); break; case IEEE80211_POWERSAVE_CAM: LINE_CHECK("powersavemode CAM"); break; case IEEE80211_POWERSAVE_PSP: LINE_CHECK("powersavemode PSP"); break; case IEEE80211_POWERSAVE_PSP_CAM: LINE_CHECK("powersavemode PSP-CAM"); break; } if (get80211val(ctx, IEEE80211_IOC_POWERSAVESLEEP, &val) != -1) LINE_CHECK("powersavesleep %d", val); } } if (get80211val(ctx, IEEE80211_IOC_TXPOWER, &val) != -1) { if (val & 1) LINE_CHECK("txpower %d.5", val/2); else LINE_CHECK("txpower %d", val/2); } if (verbose) { if (get80211val(ctx, IEEE80211_IOC_TXPOWMAX, &val) != -1) LINE_CHECK("txpowmax %.1f", val/2.); } if (get80211val(ctx, IEEE80211_IOC_DOTD, &val) != -1) { if (val) LINE_CHECK("dotd"); else if (verbose) LINE_CHECK("-dotd"); } if (get80211val(ctx, IEEE80211_IOC_RTSTHRESHOLD, &val) != -1) { if (val != IEEE80211_RTS_MAX || verbose) LINE_CHECK("rtsthreshold %d", val); } if (get80211val(ctx, IEEE80211_IOC_FRAGTHRESHOLD, &val) != -1) { if (val != IEEE80211_FRAG_MAX || verbose) LINE_CHECK("fragthreshold %d", val); } if (opmode == IEEE80211_M_STA || verbose) { if (get80211val(ctx, IEEE80211_IOC_BMISSTHRESHOLD, &val) != -1) { if (val != IEEE80211_HWBMISS_MAX || verbose) LINE_CHECK("bmiss %d", val); } } if (!verbose) { gettxparams(ctx); tp = &txparams.params[chan2mode(c)]; printrate("ucastrate", tp->ucastrate, IEEE80211_FIXED_RATE_NONE, IEEE80211_FIXED_RATE_NONE); printrate("mcastrate", tp->mcastrate, 2*1, IEEE80211_RATE_MCS|0); printrate("mgmtrate", tp->mgmtrate, 2*1, IEEE80211_RATE_MCS|0); if (tp->maxretry != 6) /* XXX */ LINE_CHECK("maxretry %d", tp->maxretry); } else { LINE_BREAK(); list_txparams(ctx); } bgscaninterval = -1; (void) get80211val(ctx, IEEE80211_IOC_BGSCAN_INTERVAL, &bgscaninterval); if (get80211val(ctx, IEEE80211_IOC_SCANVALID, &val) != -1) { if (val != bgscaninterval || verbose) LINE_CHECK("scanvalid %u", val); } bgscan = 0; if (get80211val(ctx, IEEE80211_IOC_BGSCAN, &bgscan) != -1) { if (bgscan) LINE_CHECK("bgscan"); else if (verbose) LINE_CHECK("-bgscan"); } if (bgscan || verbose) { if (bgscaninterval != -1) LINE_CHECK("bgscanintvl %u", bgscaninterval); if (get80211val(ctx, IEEE80211_IOC_BGSCAN_IDLE, &val) != -1) LINE_CHECK("bgscanidle %u", val); if (!verbose) { getroam(ctx); rp = &roamparams.params[chan2mode(c)]; if (rp->rssi & 1) LINE_CHECK("roam:rssi %u.5", rp->rssi/2); else LINE_CHECK("roam:rssi %u", rp->rssi/2); LINE_CHECK("roam:rate %s%u", (rp->rate & IEEE80211_RATE_MCS) ? "MCS " : "", get_rate_value(rp->rate)); } else { LINE_BREAK(); list_roam(ctx); LINE_BREAK(); } } if (IEEE80211_IS_CHAN_ANYG(c) || verbose) { if (get80211val(ctx, IEEE80211_IOC_PUREG, &val) != -1) { if (val) LINE_CHECK("pureg"); else if (verbose) LINE_CHECK("-pureg"); } if (get80211val(ctx, IEEE80211_IOC_PROTMODE, &val) != -1) { switch (val) { case IEEE80211_PROTMODE_OFF: LINE_CHECK("protmode OFF"); break; case IEEE80211_PROTMODE_CTS: LINE_CHECK("protmode CTS"); break; case IEEE80211_PROTMODE_RTSCTS: LINE_CHECK("protmode RTSCTS"); break; default: LINE_CHECK("protmode UNKNOWN (0x%x)", val); break; } } } if (IEEE80211_IS_CHAN_HT(c) || verbose) { gethtconf(ctx); switch (htconf & 3) { case 0: case 2: LINE_CHECK("-ht"); break; case 1: LINE_CHECK("ht20"); break; case 3: if (verbose) LINE_CHECK("ht"); break; } if (get80211val(ctx, IEEE80211_IOC_HTCOMPAT, &val) != -1) { if (!val) LINE_CHECK("-htcompat"); else if (verbose) LINE_CHECK("htcompat"); } if (get80211val(ctx, IEEE80211_IOC_AMPDU, &val) != -1) { switch (val) { case 0: LINE_CHECK("-ampdu"); break; case 1: LINE_CHECK("ampdutx -ampdurx"); break; case 2: LINE_CHECK("-ampdutx ampdurx"); break; case 3: if (verbose) LINE_CHECK("ampdu"); break; } } /* XXX 11ac density/size is different */ if (get80211val(ctx, IEEE80211_IOC_AMPDU_LIMIT, &val) != -1) { switch (val) { case IEEE80211_HTCAP_MAXRXAMPDU_8K: LINE_CHECK("ampdulimit 8k"); break; case IEEE80211_HTCAP_MAXRXAMPDU_16K: LINE_CHECK("ampdulimit 16k"); break; case IEEE80211_HTCAP_MAXRXAMPDU_32K: LINE_CHECK("ampdulimit 32k"); break; case IEEE80211_HTCAP_MAXRXAMPDU_64K: LINE_CHECK("ampdulimit 64k"); break; } } /* XXX 11ac density/size is different */ if (get80211val(ctx, IEEE80211_IOC_AMPDU_DENSITY, &val) != -1) { switch (val) { case IEEE80211_HTCAP_MPDUDENSITY_NA: if (verbose) LINE_CHECK("ampdudensity NA"); break; case IEEE80211_HTCAP_MPDUDENSITY_025: LINE_CHECK("ampdudensity .25"); break; case IEEE80211_HTCAP_MPDUDENSITY_05: LINE_CHECK("ampdudensity .5"); break; case IEEE80211_HTCAP_MPDUDENSITY_1: LINE_CHECK("ampdudensity 1"); break; case IEEE80211_HTCAP_MPDUDENSITY_2: LINE_CHECK("ampdudensity 2"); break; case IEEE80211_HTCAP_MPDUDENSITY_4: LINE_CHECK("ampdudensity 4"); break; case IEEE80211_HTCAP_MPDUDENSITY_8: LINE_CHECK("ampdudensity 8"); break; case IEEE80211_HTCAP_MPDUDENSITY_16: LINE_CHECK("ampdudensity 16"); break; } } if (get80211val(ctx, IEEE80211_IOC_AMSDU, &val) != -1) { switch (val) { case 0: LINE_CHECK("-amsdu"); break; case 1: LINE_CHECK("amsdutx -amsdurx"); break; case 2: LINE_CHECK("-amsdutx amsdurx"); break; case 3: if (verbose) LINE_CHECK("amsdu"); break; } } /* XXX amsdu limit */ if (get80211val(ctx, IEEE80211_IOC_SHORTGI, &val) != -1) { if (val) LINE_CHECK("shortgi"); else if (verbose) LINE_CHECK("-shortgi"); } if (get80211val(ctx, IEEE80211_IOC_HTPROTMODE, &val) != -1) { if (val == IEEE80211_PROTMODE_OFF) LINE_CHECK("htprotmode OFF"); else if (val != IEEE80211_PROTMODE_RTSCTS) LINE_CHECK("htprotmode UNKNOWN (0x%x)", val); else if (verbose) LINE_CHECK("htprotmode RTSCTS"); } if (get80211val(ctx, IEEE80211_IOC_PUREN, &val) != -1) { if (val) LINE_CHECK("puren"); else if (verbose) LINE_CHECK("-puren"); } if (get80211val(ctx, IEEE80211_IOC_SMPS, &val) != -1) { if (val == IEEE80211_HTCAP_SMPS_DYNAMIC) LINE_CHECK("smpsdyn"); else if (val == IEEE80211_HTCAP_SMPS_ENA) LINE_CHECK("smps"); else if (verbose) LINE_CHECK("-smps"); } if (get80211val(ctx, IEEE80211_IOC_RIFS, &val) != -1) { if (val) LINE_CHECK("rifs"); else if (verbose) LINE_CHECK("-rifs"); } /* XXX VHT STBC? */ if (get80211val(ctx, IEEE80211_IOC_STBC, &val) != -1) { switch (val) { case 0: LINE_CHECK("-stbc"); break; case 1: LINE_CHECK("stbctx -stbcrx"); break; case 2: LINE_CHECK("-stbctx stbcrx"); break; case 3: if (verbose) LINE_CHECK("stbc"); break; } } if (get80211val(ctx, IEEE80211_IOC_LDPC, &val) != -1) { switch (val) { case 0: LINE_CHECK("-ldpc"); break; case 1: LINE_CHECK("ldpctx -ldpcrx"); break; case 2: LINE_CHECK("-ldpctx ldpcrx"); break; case 3: if (verbose) LINE_CHECK("ldpc"); break; } } if (get80211val(ctx, IEEE80211_IOC_UAPSD, &val) != -1) { switch (val) { case 0: LINE_CHECK("-uapsd"); break; case 1: LINE_CHECK("uapsd"); break; } } } if (IEEE80211_IS_CHAN_VHT(c) || verbose) { getvhtconf(ctx); if (vhtconf & IEEE80211_FVHT_VHT) LINE_CHECK("vht"); else LINE_CHECK("-vht"); if (vhtconf & IEEE80211_FVHT_USEVHT40) LINE_CHECK("vht40"); else LINE_CHECK("-vht40"); if (vhtconf & IEEE80211_FVHT_USEVHT80) LINE_CHECK("vht80"); else LINE_CHECK("-vht80"); if (vhtconf & IEEE80211_FVHT_USEVHT160) LINE_CHECK("vht160"); else LINE_CHECK("-vht160"); if (vhtconf & IEEE80211_FVHT_USEVHT80P80) LINE_CHECK("vht80p80"); else LINE_CHECK("-vht80p80"); } if (get80211val(ctx, IEEE80211_IOC_WME, &wme) != -1) { if (wme) LINE_CHECK("wme"); else if (verbose) LINE_CHECK("-wme"); } else wme = 0; if (get80211val(ctx, IEEE80211_IOC_BURST, &val) != -1) { if (val) LINE_CHECK("burst"); else if (verbose) LINE_CHECK("-burst"); } if (get80211val(ctx, IEEE80211_IOC_FF, &val) != -1) { if (val) LINE_CHECK("ff"); else if (verbose) LINE_CHECK("-ff"); } if (get80211val(ctx, IEEE80211_IOC_TURBOP, &val) != -1) { if (val) LINE_CHECK("dturbo"); else if (verbose) LINE_CHECK("-dturbo"); } if (get80211val(ctx, IEEE80211_IOC_DWDS, &val) != -1) { if (val) LINE_CHECK("dwds"); else if (verbose) LINE_CHECK("-dwds"); } if (opmode == IEEE80211_M_HOSTAP) { if (get80211val(ctx, IEEE80211_IOC_HIDESSID, &val) != -1) { if (val) LINE_CHECK("hidessid"); else if (verbose) LINE_CHECK("-hidessid"); } if (get80211val(ctx, IEEE80211_IOC_APBRIDGE, &val) != -1) { if (!val) LINE_CHECK("-apbridge"); else if (verbose) LINE_CHECK("apbridge"); } if (get80211val(ctx, IEEE80211_IOC_DTIM_PERIOD, &val) != -1) LINE_CHECK("dtimperiod %u", val); if (get80211val(ctx, IEEE80211_IOC_DOTH, &val) != -1) { if (!val) LINE_CHECK("-doth"); else if (verbose) LINE_CHECK("doth"); } if (get80211val(ctx, IEEE80211_IOC_DFS, &val) != -1) { if (!val) LINE_CHECK("-dfs"); else if (verbose) LINE_CHECK("dfs"); } if (get80211val(ctx, IEEE80211_IOC_INACTIVITY, &val) != -1) { if (!val) LINE_CHECK("-inact"); else if (verbose) LINE_CHECK("inact"); } } else { if (get80211val(ctx, IEEE80211_IOC_ROAMING, &val) != -1) { if (val != IEEE80211_ROAMING_AUTO || verbose) { switch (val) { case IEEE80211_ROAMING_DEVICE: LINE_CHECK("roaming DEVICE"); break; case IEEE80211_ROAMING_AUTO: LINE_CHECK("roaming AUTO"); break; case IEEE80211_ROAMING_MANUAL: LINE_CHECK("roaming MANUAL"); break; default: LINE_CHECK("roaming UNKNOWN (0x%x)", val); break; } } } } if (opmode == IEEE80211_M_AHDEMO) { if (get80211val(ctx, IEEE80211_IOC_TDMA_SLOT, &val) != -1) LINE_CHECK("tdmaslot %u", val); if (get80211val(ctx, IEEE80211_IOC_TDMA_SLOTCNT, &val) != -1) LINE_CHECK("tdmaslotcnt %u", val); if (get80211val(ctx, IEEE80211_IOC_TDMA_SLOTLEN, &val) != -1) LINE_CHECK("tdmaslotlen %u", val); if (get80211val(ctx, IEEE80211_IOC_TDMA_BINTERVAL, &val) != -1) LINE_CHECK("tdmabintval %u", val); } else if (get80211val(ctx, IEEE80211_IOC_BEACON_INTERVAL, &val) != -1) { /* XXX default define not visible */ if (val != 100 || verbose) LINE_CHECK("bintval %u", val); } if (wme && verbose) { LINE_BREAK(); list_wme(ctx); } if (opmode == IEEE80211_M_MBSS) { if (get80211val(ctx, IEEE80211_IOC_MESH_TTL, &val) != -1) { LINE_CHECK("meshttl %u", val); } if (get80211val(ctx, IEEE80211_IOC_MESH_AP, &val) != -1) { if (val) LINE_CHECK("meshpeering"); else LINE_CHECK("-meshpeering"); } if (get80211val(ctx, IEEE80211_IOC_MESH_FWRD, &val) != -1) { if (val) LINE_CHECK("meshforward"); else LINE_CHECK("-meshforward"); } if (get80211val(ctx, IEEE80211_IOC_MESH_GATE, &val) != -1) { if (val) LINE_CHECK("meshgate"); else LINE_CHECK("-meshgate"); } if (get80211len(ctx, IEEE80211_IOC_MESH_PR_METRIC, data, 12, &len) != -1) { data[len] = '\0'; LINE_CHECK("meshmetric %s", data); } if (get80211len(ctx, IEEE80211_IOC_MESH_PR_PATH, data, 12, &len) != -1) { data[len] = '\0'; LINE_CHECK("meshpath %s", data); } if (get80211val(ctx, IEEE80211_IOC_HWMP_ROOTMODE, &val) != -1) { switch (val) { case IEEE80211_HWMP_ROOTMODE_DISABLED: LINE_CHECK("hwmprootmode DISABLED"); break; case IEEE80211_HWMP_ROOTMODE_NORMAL: LINE_CHECK("hwmprootmode NORMAL"); break; case IEEE80211_HWMP_ROOTMODE_PROACTIVE: LINE_CHECK("hwmprootmode PROACTIVE"); break; case IEEE80211_HWMP_ROOTMODE_RANN: LINE_CHECK("hwmprootmode RANN"); break; default: LINE_CHECK("hwmprootmode UNKNOWN(%d)", val); break; } } if (get80211val(ctx, IEEE80211_IOC_HWMP_MAXHOPS, &val) != -1) { LINE_CHECK("hwmpmaxhops %u", val); } } LINE_BREAK(); if (getdevicename(ctx, data, sizeof(data), &len) < 0) return; LINE_CHECK("parent interface: %s", data); LINE_BREAK(); } static int get80211(if_ctx *ctx, int type, void *data, int len) { return (lib80211_get80211(ctx->io_s, ctx->ifname, type, data, len)); } static int get80211len(if_ctx *ctx, int type, void *data, int len, int *plen) { return (lib80211_get80211len(ctx->io_s, ctx->ifname, type, data, len, plen)); } static int get80211val(if_ctx *ctx, int type, int *val) { return (lib80211_get80211val(ctx->io_s, ctx->ifname, type, val)); } static void set80211(if_ctx *ctx, int type, int val, int len, void *data) { int ret; ret = lib80211_set80211(ctx->io_s, ctx->ifname, type, val, len, data); if (ret < 0) err(1, "SIOCS80211"); } static const char * get_string(const char *val, const char *sep, u_int8_t *buf, int *lenp) { int len; int hexstr; u_int8_t *p; len = *lenp; p = buf; hexstr = (val[0] == '0' && tolower((u_char)val[1]) == 'x'); if (hexstr) val += 2; for (;;) { if (*val == '\0') break; if (sep != NULL && strchr(sep, *val) != NULL) { val++; break; } if (hexstr) { if (!isxdigit((u_char)val[0])) { warnx("bad hexadecimal digits"); return NULL; } if (!isxdigit((u_char)val[1])) { warnx("odd count hexadecimal digits"); return NULL; } } if (p >= buf + len) { if (hexstr) warnx("hexadecimal digits too long"); else warnx("string too long"); return NULL; } if (hexstr) { #define tohex(x) (isdigit(x) ? (x) - '0' : tolower(x) - 'a' + 10) *p++ = (tohex((u_char)val[0]) << 4) | tohex((u_char)val[1]); #undef tohex val += 2; } else *p++ = *val++; } len = p - buf; /* The string "-" is treated as the empty string. */ if (!hexstr && len == 1 && buf[0] == '-') { len = 0; memset(buf, 0, *lenp); } else if (len < *lenp) memset(p, 0, *lenp - len); *lenp = len; return val; } static void print_string(const u_int8_t *buf, int len) { int i; int hasspc; int utf8; i = 0; hasspc = 0; setlocale(LC_CTYPE, ""); utf8 = strncmp("UTF-8", nl_langinfo(CODESET), 5) == 0; for (; i < len; i++) { if (!isprint(buf[i]) && buf[i] != '\0' && !utf8) break; if (isspace(buf[i])) hasspc++; } if (i == len || utf8) { if (hasspc || len == 0 || buf[0] == '\0') printf("\"%.*s\"", len, buf); else printf("%.*s", len, buf); } else { printf("0x"); for (i = 0; i < len; i++) printf("%02x", buf[i]); } } static void setdefregdomain(if_ctx *ctx) { struct regdata *rdp = getregdata(); const struct regdomain *rd; /* Check if regdomain/country was already set by a previous call. */ /* XXX is it possible? */ if (regdomain.regdomain != 0 || regdomain.country != CTRY_DEFAULT) return; getregdomain(ctx); /* Check if it was already set by the driver. */ if (regdomain.regdomain != 0 || regdomain.country != CTRY_DEFAULT) return; /* Set FCC/US as default. */ rd = lib80211_regdomain_findbysku(rdp, SKU_FCC); if (rd == NULL) errx(1, "FCC regdomain was not found"); regdomain.regdomain = rd->sku; if (rd->cc != NULL) defaultcountry(rd); /* Send changes to net80211. */ setregdomain_cb(ctx, ®domain); /* Cleanup (so it can be overridden by subsequent parameters). */ regdomain.regdomain = 0; regdomain.country = CTRY_DEFAULT; regdomain.isocc[0] = 0; regdomain.isocc[1] = 0; } /* * Virtual AP cloning support. */ static struct ieee80211_clone_params params = { .icp_opmode = IEEE80211_M_STA, /* default to station mode */ }; static void wlan_create(if_ctx *ctx, struct ifreq *ifr) { static const uint8_t zerobssid[IEEE80211_ADDR_LEN]; if (params.icp_parent[0] == '\0') errx(1, "must specify a parent device (wlandev) when creating " "a wlan device"); if (params.icp_opmode == IEEE80211_M_WDS && memcmp(params.icp_bssid, zerobssid, sizeof(zerobssid)) == 0) errx(1, "no bssid specified for WDS (use wlanbssid)"); ifr->ifr_data = (caddr_t) ¶ms; ifcreate_ioctl(ctx, ifr); setdefregdomain(ctx); } static void set80211clone_wlandev(if_ctx *ctx __unused, const char *arg, int dummy __unused) { strlcpy(params.icp_parent, arg, IFNAMSIZ); } static void set80211clone_wlanbssid(if_ctx *ctx __unused, const char *arg, int dummy __unused) { const struct ether_addr *ea; ea = ether_aton(arg); if (ea == NULL) errx(1, "%s: cannot parse bssid", arg); memcpy(params.icp_bssid, ea->octet, IEEE80211_ADDR_LEN); } static void set80211clone_wlanaddr(if_ctx *ctx __unused, const char *arg, int dummy __unused) { const struct ether_addr *ea; ea = ether_aton(arg); if (ea == NULL) errx(1, "%s: cannot parse address", arg); memcpy(params.icp_macaddr, ea->octet, IEEE80211_ADDR_LEN); params.icp_flags |= IEEE80211_CLONE_MACADDR; } static void set80211clone_wlanmode(if_ctx *ctx, const char *arg, int dummy __unused) { #define iseq(a,b) (strncasecmp(a,b,sizeof(b)-1) == 0) if (iseq(arg, "sta")) params.icp_opmode = IEEE80211_M_STA; else if (iseq(arg, "ahdemo") || iseq(arg, "adhoc-demo")) params.icp_opmode = IEEE80211_M_AHDEMO; else if (iseq(arg, "ibss") || iseq(arg, "adhoc")) params.icp_opmode = IEEE80211_M_IBSS; else if (iseq(arg, "ap") || iseq(arg, "host")) params.icp_opmode = IEEE80211_M_HOSTAP; else if (iseq(arg, "wds")) params.icp_opmode = IEEE80211_M_WDS; else if (iseq(arg, "monitor")) params.icp_opmode = IEEE80211_M_MONITOR; else if (iseq(arg, "tdma")) { params.icp_opmode = IEEE80211_M_AHDEMO; params.icp_flags |= IEEE80211_CLONE_TDMA; } else if (iseq(arg, "mesh") || iseq(arg, "mp")) /* mesh point */ params.icp_opmode = IEEE80211_M_MBSS; else errx(1, "Don't know to create %s for %s", arg, ctx->ifname); #undef iseq } static void set80211clone_beacons(if_ctx *ctx __unused, const char *val __unused, int d) { /* NB: inverted sense */ if (d) params.icp_flags &= ~IEEE80211_CLONE_NOBEACONS; else params.icp_flags |= IEEE80211_CLONE_NOBEACONS; } static void set80211clone_bssid(if_ctx *ctx __unused, const char *val __unused, int d) { if (d) params.icp_flags |= IEEE80211_CLONE_BSSID; else params.icp_flags &= ~IEEE80211_CLONE_BSSID; } static void set80211clone_wdslegacy(if_ctx *ctx __unused, const char *val __unused, int d) { if (d) params.icp_flags |= IEEE80211_CLONE_WDSLEGACY; else params.icp_flags &= ~IEEE80211_CLONE_WDSLEGACY; } static struct cmd ieee80211_cmds[] = { DEF_CMD_ARG("ssid", set80211ssid), DEF_CMD_ARG("nwid", set80211ssid), DEF_CMD_ARG("meshid", set80211meshid), DEF_CMD_ARG("stationname", set80211stationname), DEF_CMD_ARG("station", set80211stationname), /* BSD/OS */ DEF_CMD_ARG("channel", set80211channel), DEF_CMD_ARG("authmode", set80211authmode), DEF_CMD_ARG("powersavemode", set80211powersavemode), DEF_CMD("powersave", 1, set80211powersave), DEF_CMD("-powersave", 0, set80211powersave), DEF_CMD_ARG("powersavesleep", set80211powersavesleep), DEF_CMD_ARG("wepmode", set80211wepmode), DEF_CMD("wep", 1, set80211wep), DEF_CMD("-wep", 0, set80211wep), DEF_CMD_ARG("deftxkey", set80211weptxkey), DEF_CMD_ARG("weptxkey", set80211weptxkey), DEF_CMD_ARG("wepkey", set80211wepkey), DEF_CMD_ARG("nwkey", set80211nwkey), /* NetBSD */ DEF_CMD("-nwkey", 0, set80211wep), /* NetBSD */ DEF_CMD_ARG("rtsthreshold", set80211rtsthreshold), DEF_CMD_ARG("protmode", set80211protmode), DEF_CMD_ARG("txpower", set80211txpower), DEF_CMD_ARG("roaming", set80211roaming), DEF_CMD("wme", 1, set80211wme), DEF_CMD("-wme", 0, set80211wme), DEF_CMD("wmm", 1, set80211wme), DEF_CMD("-wmm", 0, set80211wme), DEF_CMD("hidessid", 1, set80211hidessid), DEF_CMD("-hidessid", 0, set80211hidessid), DEF_CMD("apbridge", 1, set80211apbridge), DEF_CMD("-apbridge", 0, set80211apbridge), DEF_CMD_ARG("chanlist", set80211chanlist), DEF_CMD_ARG("bssid", set80211bssid), DEF_CMD_ARG("ap", set80211bssid), DEF_CMD("scan", 0, set80211scan), DEF_CMD_ARG("list", set80211list), DEF_CMD_ARG2("cwmin", set80211cwmin), DEF_CMD_ARG2("cwmax", set80211cwmax), DEF_CMD_ARG2("aifs", set80211aifs), DEF_CMD_ARG2("txoplimit", set80211txoplimit), DEF_CMD_ARG("acm", set80211acm), DEF_CMD_ARG("-acm", set80211noacm), DEF_CMD_ARG("ack", set80211ackpolicy), DEF_CMD_ARG("-ack", set80211noackpolicy), DEF_CMD_ARG2("bss:cwmin", set80211bsscwmin), DEF_CMD_ARG2("bss:cwmax", set80211bsscwmax), DEF_CMD_ARG2("bss:aifs", set80211bssaifs), DEF_CMD_ARG2("bss:txoplimit", set80211bsstxoplimit), DEF_CMD_ARG("dtimperiod", set80211dtimperiod), DEF_CMD_ARG("bintval", set80211bintval), DEF_CMD("mac:open", IEEE80211_MACCMD_POLICY_OPEN, set80211maccmd), DEF_CMD("mac:allow", IEEE80211_MACCMD_POLICY_ALLOW, set80211maccmd), DEF_CMD("mac:deny", IEEE80211_MACCMD_POLICY_DENY, set80211maccmd), DEF_CMD("mac:radius", IEEE80211_MACCMD_POLICY_RADIUS, set80211maccmd), DEF_CMD("mac:flush", IEEE80211_MACCMD_FLUSH, set80211maccmd), DEF_CMD("mac:detach", IEEE80211_MACCMD_DETACH, set80211maccmd), DEF_CMD_ARG("mac:add", set80211addmac), DEF_CMD_ARG("mac:del", set80211delmac), DEF_CMD_ARG("mac:kick", set80211kickmac), DEF_CMD("pureg", 1, set80211pureg), DEF_CMD("-pureg", 0, set80211pureg), DEF_CMD("ff", 1, set80211fastframes), DEF_CMD("-ff", 0, set80211fastframes), DEF_CMD("dturbo", 1, set80211dturbo), DEF_CMD("-dturbo", 0, set80211dturbo), DEF_CMD("bgscan", 1, set80211bgscan), DEF_CMD("-bgscan", 0, set80211bgscan), DEF_CMD_ARG("bgscanidle", set80211bgscanidle), DEF_CMD_ARG("bgscanintvl", set80211bgscanintvl), DEF_CMD_ARG("scanvalid", set80211scanvalid), DEF_CMD("quiet", 1, set80211quiet), DEF_CMD("-quiet", 0, set80211quiet), DEF_CMD_ARG("quiet_count", set80211quietcount), DEF_CMD_ARG("quiet_period", set80211quietperiod), DEF_CMD_ARG("quiet_duration", set80211quietduration), DEF_CMD_ARG("quiet_offset", set80211quietoffset), DEF_CMD_ARG("roam:rssi", set80211roamrssi), DEF_CMD_ARG("roam:rate", set80211roamrate), DEF_CMD_ARG("mcastrate", set80211mcastrate), DEF_CMD_ARG("ucastrate", set80211ucastrate), DEF_CMD_ARG("mgtrate", set80211mgtrate), DEF_CMD_ARG("mgmtrate", set80211mgtrate), DEF_CMD_ARG("maxretry", set80211maxretry), DEF_CMD_ARG("fragthreshold", set80211fragthreshold), DEF_CMD("burst", 1, set80211burst), DEF_CMD("-burst", 0, set80211burst), DEF_CMD_ARG("bmiss", set80211bmissthreshold), DEF_CMD_ARG("bmissthreshold", set80211bmissthreshold), DEF_CMD("shortgi", 1, set80211shortgi), DEF_CMD("-shortgi", 0, set80211shortgi), DEF_CMD("ampdurx", 2, set80211ampdu), DEF_CMD("-ampdurx", -2, set80211ampdu), DEF_CMD("ampdutx", 1, set80211ampdu), DEF_CMD("-ampdutx", -1, set80211ampdu), DEF_CMD("ampdu", 3, set80211ampdu), /* NB: tx+rx */ DEF_CMD("-ampdu", -3, set80211ampdu), DEF_CMD_ARG("ampdulimit", set80211ampdulimit), DEF_CMD_ARG("ampdudensity", set80211ampdudensity), DEF_CMD("amsdurx", 2, set80211amsdu), DEF_CMD("-amsdurx", -2, set80211amsdu), DEF_CMD("amsdutx", 1, set80211amsdu), DEF_CMD("-amsdutx", -1, set80211amsdu), DEF_CMD("amsdu", 3, set80211amsdu), /* NB: tx+rx */ DEF_CMD("-amsdu", -3, set80211amsdu), DEF_CMD_ARG("amsdulimit", set80211amsdulimit), DEF_CMD("stbcrx", 2, set80211stbc), DEF_CMD("-stbcrx", -2, set80211stbc), DEF_CMD("stbctx", 1, set80211stbc), DEF_CMD("-stbctx", -1, set80211stbc), DEF_CMD("stbc", 3, set80211stbc), /* NB: tx+rx */ DEF_CMD("-stbc", -3, set80211stbc), DEF_CMD("ldpcrx", 2, set80211ldpc), DEF_CMD("-ldpcrx", -2, set80211ldpc), DEF_CMD("ldpctx", 1, set80211ldpc), DEF_CMD("-ldpctx", -1, set80211ldpc), DEF_CMD("ldpc", 3, set80211ldpc), /* NB: tx+rx */ DEF_CMD("-ldpc", -3, set80211ldpc), DEF_CMD("uapsd", 1, set80211uapsd), DEF_CMD("-uapsd", 0, set80211uapsd), DEF_CMD("puren", 1, set80211puren), DEF_CMD("-puren", 0, set80211puren), DEF_CMD("doth", 1, set80211doth), DEF_CMD("-doth", 0, set80211doth), DEF_CMD("dfs", 1, set80211dfs), DEF_CMD("-dfs", 0, set80211dfs), DEF_CMD("htcompat", 1, set80211htcompat), DEF_CMD("-htcompat", 0, set80211htcompat), DEF_CMD("dwds", 1, set80211dwds), DEF_CMD("-dwds", 0, set80211dwds), DEF_CMD("inact", 1, set80211inact), DEF_CMD("-inact", 0, set80211inact), DEF_CMD("tsn", 1, set80211tsn), DEF_CMD("-tsn", 0, set80211tsn), DEF_CMD_ARG("regdomain", set80211regdomain), DEF_CMD_ARG("country", set80211country), DEF_CMD("indoor", 'I', set80211location), DEF_CMD("-indoor", 'O', set80211location), DEF_CMD("outdoor", 'O', set80211location), DEF_CMD("-outdoor", 'I', set80211location), DEF_CMD("anywhere", ' ', set80211location), DEF_CMD("ecm", 1, set80211ecm), DEF_CMD("-ecm", 0, set80211ecm), DEF_CMD("dotd", 1, set80211dotd), DEF_CMD("-dotd", 0, set80211dotd), DEF_CMD_ARG("htprotmode", set80211htprotmode), DEF_CMD("ht20", 1, set80211htconf), DEF_CMD("-ht20", 0, set80211htconf), DEF_CMD("ht40", 3, set80211htconf), /* NB: 20+40 */ DEF_CMD("-ht40", 0, set80211htconf), DEF_CMD("ht", 3, set80211htconf), /* NB: 20+40 */ DEF_CMD("-ht", 0, set80211htconf), DEF_CMD("vht", IEEE80211_FVHT_VHT, set80211vhtconf), DEF_CMD("-vht", 0, set80211vhtconf), DEF_CMD("vht40", IEEE80211_FVHT_USEVHT40, set80211vhtconf), DEF_CMD("-vht40", -IEEE80211_FVHT_USEVHT40, set80211vhtconf), DEF_CMD("vht80", IEEE80211_FVHT_USEVHT80, set80211vhtconf), DEF_CMD("-vht80", -IEEE80211_FVHT_USEVHT80, set80211vhtconf), DEF_CMD("vht160", IEEE80211_FVHT_USEVHT160, set80211vhtconf), DEF_CMD("-vht160", -IEEE80211_FVHT_USEVHT160, set80211vhtconf), DEF_CMD("vht80p80", IEEE80211_FVHT_USEVHT80P80, set80211vhtconf), DEF_CMD("-vht80p80", -IEEE80211_FVHT_USEVHT80P80, set80211vhtconf), DEF_CMD("rifs", 1, set80211rifs), DEF_CMD("-rifs", 0, set80211rifs), DEF_CMD("smps", IEEE80211_HTCAP_SMPS_ENA, set80211smps), DEF_CMD("smpsdyn", IEEE80211_HTCAP_SMPS_DYNAMIC, set80211smps), DEF_CMD("-smps", IEEE80211_HTCAP_SMPS_OFF, set80211smps), /* XXX for testing */ DEF_CMD_ARG("chanswitch", set80211chanswitch), DEF_CMD_ARG("tdmaslot", set80211tdmaslot), DEF_CMD_ARG("tdmaslotcnt", set80211tdmaslotcnt), DEF_CMD_ARG("tdmaslotlen", set80211tdmaslotlen), DEF_CMD_ARG("tdmabintval", set80211tdmabintval), DEF_CMD_ARG("meshttl", set80211meshttl), DEF_CMD("meshforward", 1, set80211meshforward), DEF_CMD("-meshforward", 0, set80211meshforward), DEF_CMD("meshgate", 1, set80211meshgate), DEF_CMD("-meshgate", 0, set80211meshgate), DEF_CMD("meshpeering", 1, set80211meshpeering), DEF_CMD("-meshpeering", 0, set80211meshpeering), DEF_CMD_ARG("meshmetric", set80211meshmetric), DEF_CMD_ARG("meshpath", set80211meshpath), DEF_CMD("meshrt:flush", IEEE80211_MESH_RTCMD_FLUSH, set80211meshrtcmd), DEF_CMD_ARG("meshrt:add", set80211addmeshrt), DEF_CMD_ARG("meshrt:del", set80211delmeshrt), DEF_CMD_ARG("hwmprootmode", set80211hwmprootmode), DEF_CMD_ARG("hwmpmaxhops", set80211hwmpmaxhops), /* vap cloning support */ DEF_CLONE_CMD_ARG("wlanaddr", set80211clone_wlanaddr), DEF_CLONE_CMD_ARG("wlanbssid", set80211clone_wlanbssid), DEF_CLONE_CMD_ARG("wlandev", set80211clone_wlandev), DEF_CLONE_CMD_ARG("wlanmode", set80211clone_wlanmode), DEF_CLONE_CMD("beacons", 1, set80211clone_beacons), DEF_CLONE_CMD("-beacons", 0, set80211clone_beacons), DEF_CLONE_CMD("bssid", 1, set80211clone_bssid), DEF_CLONE_CMD("-bssid", 0, set80211clone_bssid), DEF_CLONE_CMD("wdslegacy", 1, set80211clone_wdslegacy), DEF_CLONE_CMD("-wdslegacy", 0, set80211clone_wdslegacy), }; static struct afswtch af_ieee80211 = { .af_name = "af_ieee80211", .af_af = AF_UNSPEC, .af_other_status = ieee80211_status, }; static __constructor void ieee80211_ctor(void) { for (size_t i = 0; i < nitems(ieee80211_cmds); i++) cmd_register(&ieee80211_cmds[i]); af_register(&af_ieee80211); clone_setdefcallback_prefix("wlan", wlan_create); } diff --git a/sys/net80211/ieee80211.h b/sys/net80211/ieee80211.h index b3fbd0a27b51..47e496bf42a1 100644 --- a/sys/net80211/ieee80211.h +++ b/sys/net80211/ieee80211.h @@ -1,1555 +1,1551 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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. * * 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. */ #ifndef _NET80211_IEEE80211_H_ #define _NET80211_IEEE80211_H_ /* * 802.11 protocol definitions. */ #define IEEE80211_ADDR_LEN 6 /* size of 802.11 address */ /* is 802.11 address multicast/broadcast? */ #define IEEE80211_IS_MULTICAST(_a) (*(_a) & 0x01) #ifdef _KERNEL extern const uint8_t ieee80211broadcastaddr[]; #endif typedef uint16_t ieee80211_seq; /* IEEE 802.11 PLCP header */ struct ieee80211_plcp_hdr { uint16_t i_sfd; uint8_t i_signal; uint8_t i_service; uint16_t i_length; uint16_t i_crc; } __packed; #define IEEE80211_PLCP_SFD 0xF3A0 #define IEEE80211_PLCP_SERVICE 0x00 #define IEEE80211_PLCP_SERVICE_LOCKED 0x04 #define IEEE80211_PLCL_SERVICE_PBCC 0x08 #define IEEE80211_PLCP_SERVICE_LENEXT5 0x20 #define IEEE80211_PLCP_SERVICE_LENEXT6 0x40 #define IEEE80211_PLCP_SERVICE_LENEXT7 0x80 /* * generic definitions for IEEE 802.11 frames */ struct ieee80211_frame { uint8_t i_fc[2]; uint8_t i_dur[2]; uint8_t i_addr1[IEEE80211_ADDR_LEN]; uint8_t i_addr2[IEEE80211_ADDR_LEN]; uint8_t i_addr3[IEEE80211_ADDR_LEN]; uint8_t i_seq[2]; /* possibly followed by addr4[IEEE80211_ADDR_LEN]; */ /* see below */ } __packed; struct ieee80211_qosframe { uint8_t i_fc[2]; uint8_t i_dur[2]; uint8_t i_addr1[IEEE80211_ADDR_LEN]; uint8_t i_addr2[IEEE80211_ADDR_LEN]; uint8_t i_addr3[IEEE80211_ADDR_LEN]; uint8_t i_seq[2]; uint8_t i_qos[2]; /* possibly followed by addr4[IEEE80211_ADDR_LEN]; */ /* see below */ } __packed; struct ieee80211_qoscntl { uint8_t i_qos[2]; }; struct ieee80211_frame_addr4 { uint8_t i_fc[2]; uint8_t i_dur[2]; uint8_t i_addr1[IEEE80211_ADDR_LEN]; uint8_t i_addr2[IEEE80211_ADDR_LEN]; uint8_t i_addr3[IEEE80211_ADDR_LEN]; uint8_t i_seq[2]; uint8_t i_addr4[IEEE80211_ADDR_LEN]; } __packed; struct ieee80211_qosframe_addr4 { uint8_t i_fc[2]; uint8_t i_dur[2]; uint8_t i_addr1[IEEE80211_ADDR_LEN]; uint8_t i_addr2[IEEE80211_ADDR_LEN]; uint8_t i_addr3[IEEE80211_ADDR_LEN]; uint8_t i_seq[2]; uint8_t i_addr4[IEEE80211_ADDR_LEN]; uint8_t i_qos[2]; } __packed; #define IEEE80211_FC0_VERSION_MASK 0x03 #define IEEE80211_FC0_VERSION_SHIFT 0 #define IEEE80211_FC0_VERSION_0 0x00 #define IEEE80211_FC0_TYPE_MASK 0x0c #define IEEE80211_FC0_TYPE_SHIFT 2 #define IEEE80211_FC0_TYPE_MGT 0x00 /* Management */ #define IEEE80211_FC0_TYPE_CTL 0x04 /* Control */ #define IEEE80211_FC0_TYPE_DATA 0x08 /* Data */ #define IEEE80211_FC0_TYPE_EXT 0x0c /* Extension */ #define IEEE80211_FC0_SUBTYPE_MASK 0xf0 #define IEEE80211_FC0_SUBTYPE_SHIFT 4 /* 802.11-2020 Table 9-1-Valid type and subtype combinations */ /* For type 00 Management (IEEE80211_FC0_TYPE_MGT) */ #define IEEE80211_FC0_SUBTYPE_ASSOC_REQ 0x00 /* Association Request */ #define IEEE80211_FC0_SUBTYPE_ASSOC_RESP 0x10 /* Association Response */ #define IEEE80211_FC0_SUBTYPE_REASSOC_REQ 0x20 /* Reassociation Request */ #define IEEE80211_FC0_SUBTYPE_REASSOC_RESP 0x30 /* Reassociation Response */ #define IEEE80211_FC0_SUBTYPE_PROBE_REQ 0x40 /* Probe Request */ #define IEEE80211_FC0_SUBTYPE_PROBE_RESP 0x50 /* Probe Response */ #define IEEE80211_FC0_SUBTYPE_TIMING_ADV 0x60 /* Timing Advertisement */ /* 0111 Reserved 0x70 */ #define IEEE80211_FC0_SUBTYPE_BEACON 0x80 /* Beacon */ #define IEEE80211_FC0_SUBTYPE_ATIM 0x90 /* ATIM */ #define IEEE80211_FC0_SUBTYPE_DISASSOC 0xa0 /* Disassociation */ #define IEEE80211_FC0_SUBTYPE_AUTH 0xb0 /* Authentication */ #define IEEE80211_FC0_SUBTYPE_DEAUTH 0xc0 /* Deauthentication */ #define IEEE80211_FC0_SUBTYPE_ACTION 0xd0 /* Action */ #define IEEE80211_FC0_SUBTYPE_ACTION_NOACK 0xe0 /* Action No Ack */ /* 1111 Reserved 0xf0 */ /* For type 01 Control (IEEE80211_FC0_TYPE_CTL) */ /* 0000-0001 Reserved 0x00-0x10 */ #define IEEE80211_FC0_SUBTYPE_TRIGGER 0x20 /* Trigger, 80211ax-2021 */ #define IEEE80211_FC0_SUBTYPE_TACK 0x30 /* TACK */ #define IEEE80211_FC0_SUBTYPE_BF_REPORT_POLL 0x40 /* Beamforming Report Poll */ #define IEEE80211_FC0_SUBTYPE_VHT_HE_NDP 0x50 /* VHT/HE NDP Announcement, 80211ac/ax-2013/2021 */ #define IEEE80211_FC0_SUBTYPE_CTL_EXT 0x60 /* Control Frame Extension */ #define IEEE80211_FC0_SUBTYPE_CONTROL_WRAP 0x70 /* Control Wrapper */ #define IEEE80211_FC0_SUBTYPE_BAR 0x80 /* Block Ack Request (BlockAckReq) */ #define IEEE80211_FC0_SUBTYPE_BA 0x90 /* Block Ack (BlockAck) */ #define IEEE80211_FC0_SUBTYPE_PS_POLL 0xa0 /* PS-Poll */ #define IEEE80211_FC0_SUBTYPE_RTS 0xb0 /* RTS */ #define IEEE80211_FC0_SUBTYPE_CTS 0xc0 /* CTS */ #define IEEE80211_FC0_SUBTYPE_ACK 0xd0 /* Ack */ #define IEEE80211_FC0_SUBTYPE_CF_END 0xe0 /* CF-End */ #define IEEE80211_FC0_SUBTYPE_CF_END_ACK 0xf0 /* 1111 Reserved - what was CF_END_ACK? */ /* For type 10 Data (IEEE80211_FC0_TYPE_DATA) */ #define IEEE80211_FC0_SUBTYPE_DATA 0x00 /* Data */ /* 0001-0011 Reserved 0x10-0x30 */ /* Were: CF_ACK, CF_POLL, CF_ACPL */ #define IEEE80211_FC0_SUBTYPE_NODATA 0x40 /* Null */ /* 0101-0111 Reserved 0x50-0x70 */ /* Were: CFACK, CFPOLL, CF_ACK_CF_ACK */ #define IEEE80211_FC0_SUBTYPE_QOS_DATA 0x80 /* QoS Data */ #define IEEE80211_FC0_SUBTYPE_QOS_DATA_CFACK 0x90 /* QoS Data +CF-Ack */ #define IEEE80211_FC0_SUBTYPE_QOS_DATA_CFPOLL 0xa0 /* QoS Data +CF-Poll */ #define IEEE80211_FC0_SUBTYPE_QOS_DATA_CFACKPOLL 0xb0 /* QoS Data +CF-Ack +CF-Poll */ #define IEEE80211_FC0_SUBTYPE_QOS_NULL 0xc0 /* QoS Null */ /* 1101 Reserved 0xd0 */ #define IEEE80211_FC0_SUBTYPE_QOS_CFPOLL 0xe0 /* QoS CF-Poll */ #define IEEE80211_FC0_SUBTYPE_QOS_CFACKPOLL 0xf0 /* QoS CF-Ack +CF-Poll */ /* For type 11 Extension (IEEE80211_FC0_TYPE_EXT) */ #define IEEE80211_FC0_SUBTYPE_DMG_BEACON 0x00 /* DMG Beacon */ #define IEEE80211_FC0_SUBTYPE_S1G_BEACON 0x10 /* S1G Beacon */ /* 0010-1111 Reserved 0x20-0xff */ /* 802.11-2020 Table 9-2-Control Frame Extension */ /* Reusing B11..B8, part of FC1 */ #define IEEE80211_CTL_EXT_SECTOR_ACK 0x00 /* Sector Ack, 80211ay-2021 */ #define IEEE80211_CTL_EXT_BA_SCHED 0x01 /* Block Ack Schedule, 80211ay-2021 */ #define IEEE80211_CTL_EXT_POLL 0x02 /* Poll */ #define IEEE80211_CTL_EXT_SPR 0x03 /* SPR */ #define IEEE80211_CTL_EXT_GRANT 0x04 /* Grant */ #define IEEE80211_CTL_EXT_DMG_CTS 0x05 /* DMG CTS */ #define IEEE80211_CTL_EXT_DMG_DTS 0x06 /* DMG DTS */ #define IEEE80211_CTL_EXT_GRANT_ACK 0x07 /* Grant Ack */ #define IEEE80211_CTL_EXT_SSW 0x08 /* SSW */ #define IEEE80211_CTL_EXT_SSW_FBACK 0x09 /* SSW-Feedback */ #define IEEE80211_CTL_EXT_SSW_ACK 0x0a /* SSW-Ack */ #define IEEE80211_CTL_EXT_TDD_BF 0x0b /* TDD Beamforming, 80211ay-2021 */ /* 1100-1111 Reserved 0xc-0xf */ #define IEEE80211_IS_MGMT(wh) \ (!! (((wh)->i_fc[0] & IEEE80211_FC0_TYPE_MASK) \ == IEEE80211_FC0_TYPE_MGT)) #define IEEE80211_IS_CTL(wh) \ (!! (((wh)->i_fc[0] & IEEE80211_FC0_TYPE_MASK) \ == IEEE80211_FC0_TYPE_CTL)) #define IEEE80211_IS_DATA(wh) \ (!! (((wh)->i_fc[0] & IEEE80211_FC0_TYPE_MASK) \ == IEEE80211_FC0_TYPE_DATA)) #define IEEE80211_IS_EXT(wh) \ (!! (((wh)->i_fc[0] & IEEE80211_FC0_TYPE_MASK) \ == IEEE80211_FC0_TYPE_EXT)) #define IEEE80211_FC0_QOSDATA \ (IEEE80211_FC0_TYPE_DATA|IEEE80211_FC0_SUBTYPE_QOS_DATA|IEEE80211_FC0_VERSION_0) #define IEEE80211_IS_QOSDATA(wh) \ ((wh)->i_fc[0] == IEEE80211_FC0_QOSDATA) #define IEEE80211_FC1_DIR_MASK 0x03 #define IEEE80211_FC1_DIR_NODS 0x00 /* STA->STA */ #define IEEE80211_FC1_DIR_TODS 0x01 /* STA->AP */ #define IEEE80211_FC1_DIR_FROMDS 0x02 /* AP ->STA */ #define IEEE80211_FC1_DIR_DSTODS 0x03 /* AP ->AP */ #define IEEE80211_IS_DSTODS(wh) \ (((wh)->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS) #define IEEE80211_FC1_MORE_FRAG 0x04 #define IEEE80211_FC1_RETRY 0x08 #define IEEE80211_FC1_PWR_MGT 0x10 #define IEEE80211_FC1_MORE_DATA 0x20 #define IEEE80211_FC1_PROTECTED 0x40 #define IEEE80211_FC1_ORDER 0x80 #define IEEE80211_IS_PROTECTED(wh) \ ((wh)->i_fc[1] & IEEE80211_FC1_PROTECTED) #define IEEE80211_HAS_SEQ(type, subtype) \ ((type) != IEEE80211_FC0_TYPE_CTL && \ !((type) == IEEE80211_FC0_TYPE_DATA && \ ((subtype) & IEEE80211_FC0_SUBTYPE_QOS_NULL) == \ IEEE80211_FC0_SUBTYPE_QOS_NULL)) #define IEEE80211_SEQ_FRAG_MASK 0x000f #define IEEE80211_SEQ_FRAG_SHIFT 0 #define IEEE80211_SEQ_SEQ_MASK 0xfff0 #define IEEE80211_SEQ_SEQ_SHIFT 4 #define IEEE80211_SEQ_RANGE 4096 #define IEEE80211_SEQ_ADD(seq, incr) \ (((seq) + (incr)) & (IEEE80211_SEQ_RANGE-1)) #define IEEE80211_SEQ_INC(seq) IEEE80211_SEQ_ADD(seq,1) #define IEEE80211_SEQ_SUB(a, b) \ (((a) + IEEE80211_SEQ_RANGE - (b)) & (IEEE80211_SEQ_RANGE-1)) #define IEEE80211_SEQ_BA_RANGE 2048 /* 2^11 */ #define IEEE80211_SEQ_BA_BEFORE(a, b) \ (IEEE80211_SEQ_SUB(b, a+1) < IEEE80211_SEQ_BA_RANGE-1) #define IEEE80211_NWID_LEN 32 #define IEEE80211_MESHID_LEN 32 #define IEEE80211_QOS_CTL_LEN 2 #define IEEE80211_QOS_TXOP 0x00ff /* bit 8 is reserved */ #define IEEE80211_QOS_AMSDU 0x80 #define IEEE80211_QOS_AMSDU_S 7 #define IEEE80211_QOS_ACKPOLICY 0x60 #define IEEE80211_QOS_ACKPOLICY_S 5 #define IEEE80211_QOS_ACKPOLICY_NOACK 0x20 /* No ACK required */ #define IEEE80211_QOS_ACKPOLICY_BA 0x60 /* Block ACK */ #define IEEE80211_QOS_EOSP 0x10 /* EndOfService Period*/ #define IEEE80211_QOS_EOSP_S 4 #define IEEE80211_QOS_TID 0x0f /* qos[1] byte used for all frames sent by mesh STAs in a mesh BSS */ #define IEEE80211_QOS_MC 0x01 /* Mesh control */ /* Mesh power save level*/ #define IEEE80211_QOS_MESH_PSL 0x02 /* Mesh Receiver Service Period Initiated */ #define IEEE80211_QOS_RSPI 0x04 /* bits 11 to 15 reserved */ /* does frame have QoS sequence control data */ #define IEEE80211_QOS_HAS_SEQ(wh) \ (((wh)->i_fc[0] & \ (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_QOS_DATA)) == \ (IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_QOS_DATA)) /* * WME/802.11e information element. */ struct ieee80211_wme_info { uint8_t wme_id; /* IEEE80211_ELEMID_VENDOR */ uint8_t wme_len; /* length in bytes */ uint8_t wme_oui[3]; /* 0x00, 0x50, 0xf2 */ uint8_t wme_type; /* OUI type */ uint8_t wme_subtype; /* OUI subtype */ uint8_t wme_version; /* spec revision */ uint8_t wme_info; /* QoS info */ } __packed; /* * WME/802.11e Tspec Element */ struct ieee80211_wme_tspec { uint8_t ts_id; uint8_t ts_len; uint8_t ts_oui[3]; uint8_t ts_oui_type; uint8_t ts_oui_subtype; uint8_t ts_version; uint8_t ts_tsinfo[3]; uint8_t ts_nom_msdu[2]; uint8_t ts_max_msdu[2]; uint8_t ts_min_svc[4]; uint8_t ts_max_svc[4]; uint8_t ts_inactv_intv[4]; uint8_t ts_susp_intv[4]; uint8_t ts_start_svc[4]; uint8_t ts_min_rate[4]; uint8_t ts_mean_rate[4]; uint8_t ts_max_burst[4]; uint8_t ts_min_phy[4]; uint8_t ts_peak_rate[4]; uint8_t ts_delay[4]; uint8_t ts_surplus[2]; uint8_t ts_medium_time[2]; } __packed; /* * WME AC parameter field */ struct ieee80211_wme_acparams { uint8_t acp_aci_aifsn; uint8_t acp_logcwminmax; uint16_t acp_txop; } __packed; #define WME_NUM_AC 4 /* 4 AC categories */ #define WME_NUM_TID 16 /* 16 tids */ #define WME_PARAM_ACI 0x60 /* Mask for ACI field */ #define WME_PARAM_ACI_S 5 /* Shift for ACI field */ #define WME_PARAM_ACM 0x10 /* Mask for ACM bit */ #define WME_PARAM_ACM_S 4 /* Shift for ACM bit */ #define WME_PARAM_AIFSN 0x0f /* Mask for aifsn field */ #define WME_PARAM_AIFSN_S 0 /* Shift for aifsn field */ #define WME_PARAM_LOGCWMIN 0x0f /* Mask for CwMin field (in log) */ #define WME_PARAM_LOGCWMIN_S 0 /* Shift for CwMin field */ #define WME_PARAM_LOGCWMAX 0xf0 /* Mask for CwMax field (in log) */ #define WME_PARAM_LOGCWMAX_S 4 /* Shift for CwMax field */ #define WME_AC_TO_TID(_ac) ( \ ((_ac) == WME_AC_VO) ? 6 : \ ((_ac) == WME_AC_VI) ? 5 : \ ((_ac) == WME_AC_BK) ? 1 : \ 0) #define TID_TO_WME_AC(_tid) ( \ ((_tid) == 0 || (_tid) == 3) ? WME_AC_BE : \ ((_tid) < 3) ? WME_AC_BK : \ ((_tid) < 6) ? WME_AC_VI : \ WME_AC_VO) /* * WME Parameter Element */ struct ieee80211_wme_param { uint8_t param_id; uint8_t param_len; uint8_t param_oui[3]; uint8_t param_oui_type; uint8_t param_oui_subtype; uint8_t param_version; uint8_t param_qosInfo; #define WME_QOSINFO_COUNT 0x0f /* Mask for param count field */ uint8_t param_reserved; struct ieee80211_wme_acparams params_acParams[WME_NUM_AC]; } __packed; /* * WME U-APSD qos info field defines */ #define WME_CAPINFO_UAPSD_EN 0x00000080 #define WME_CAPINFO_UAPSD_VO 0x00000001 #define WME_CAPINFO_UAPSD_VI 0x00000002 #define WME_CAPINFO_UAPSD_BK 0x00000004 #define WME_CAPINFO_UAPSD_BE 0x00000008 #define WME_CAPINFO_UAPSD_ACFLAGS_SHIFT 0 #define WME_CAPINFO_UAPSD_ACFLAGS_MASK 0xF #define WME_CAPINFO_UAPSD_MAXSP_SHIFT 5 #define WME_CAPINFO_UAPSD_MAXSP_MASK 0x3 #define WME_CAPINFO_IE_OFFSET 8 #define WME_UAPSD_MAXSP(_qosinfo) \ (((_qosinfo) >> WME_CAPINFO_UAPSD_MAXSP_SHIFT) & \ WME_CAPINFO_UAPSD_MAXSP_MASK) #define WME_UAPSD_AC_ENABLED(_ac, _qosinfo) \ ((1 << (3 - (_ac))) & ( \ ((_qosinfo) >> WME_CAPINFO_UAPSD_ACFLAGS_SHIFT) & \ WME_CAPINFO_UAPSD_ACFLAGS_MASK)) /* * Management Notification Frame */ struct ieee80211_mnf { uint8_t mnf_category; uint8_t mnf_action; uint8_t mnf_dialog; uint8_t mnf_status; } __packed; #define MNF_SETUP_REQ 0 #define MNF_SETUP_RESP 1 #define MNF_TEARDOWN 2 /* * 802.11n Management Action Frames */ /* generic frame format */ struct ieee80211_action { uint8_t ia_category; uint8_t ia_action; } __packed; /* 80211-2020 Table 9-51-Category values */ #define IEEE80211_ACTION_CAT_SM 0 /* 9.6.2 Spectrum Management */ #define IEEE80211_ACTION_CAT_QOS 1 /* 9.6.3 QoS */ /* Reserved 2 was IEEE80211_ACTION_CAT_DLS */ #define IEEE80211_ACTION_CAT_BA 3 /* 9.6.4 Block Ack */ #define IEEE80211_ACTION_CAT_PUBLIC 4 /* 9.6.7 Public */ #define IEEE80211_ACTION_CAT_RADIO_MEASUREMENT 5 /* 9.6.6 Radio Measurement */ #define IEEE80211_ACTION_CAT_FAST_BBS_TRANSITION 6 /* 9.6.8 Fast BSS Transition */ #define IEEE80211_ACTION_CAT_HT 7 /* 9.6.11 HT */ #define IEEE80211_ACTION_CAT_SA_QUERY 8 /* 9.6.9 SA Query */ #define IEEE80211_ACTION_CAT_PROTECTED_DUAL_OF_PUBLIC_ACTION 9 /* 9.6.10 Protected Dual of Public Action */ #define IEEE80211_ACTION_CAT_WNM 10 /* 9.6.13 WNM */ #define IEEE80211_ACTION_CAT_UNPROTECTED_WNM 11 /* 9.6.14 Unprotected WNM */ #define IEEE80211_ACTION_CAT_TDLS 12 /* 9.6.12 TDLS */ #define IEEE80211_ACTION_CAT_MESH 13 /* 9.6.16 Mesh */ #define IEEE80211_ACTION_CAT_MULTIHOP 14 /* 9.6.17 Multihop */ #define IEEE80211_ACTION_CAT_SELF_PROT 15 /* 9.6.15 Self-protected */ #define IEEE80211_ACTION_CAT_DMG 16 /* 9.6.19 DMG */ /* Reserved 17 (R)Wi-Fi Alliance */ #define IEEE80211_ACTION_CAT_FAST_SESSION_TRANSFER 18 /* 9.6.20 Fast Session Transfer */ #define IEEE80211_ACTION_CAT_ROBUST_AV_STREAMING 19 /* 9.6.18 Robust AV Streaming */ #define IEEE80211_ACTION_CAT_UNPROTECTED_DMG 20 /* 9.6.21 Unprotected DMG */ #define IEEE80211_ACTION_CAT_VHT 21 /* 9.6.22 VHT */ #define IEEE80211_ACTION_CAT_UNPROTECTED_S1G 22 /* 9.6.24 Unprotected S1G */ #define IEEE80211_ACTION_CAT_S1G 23 /* 9.6.25 S1G */ #define IEEE80211_ACTION_CAT_FLOW_CONTROL 24 /* 9.6.26 Flow Control */ #define IEEE80211_ACTION_CAT_CTL_RESP_MCS_NEG 25 /* 9.6.27 Control Response MCS Negotiation */ #define IEEE80211_ACTION_CAT_FILS 26 /* 9.6.23 FILS */ #define IEEE80211_ACTION_CAT_CDMG 27 /* 9.6.28 CDMG */ #define IEEE80211_ACTION_CAT_CMMG 28 /* 9.6.29 CMMG */ #define IEEE80211_ACTION_CAT_GLK 29 /* 9.6.30 GLK */ #define IEEE80211_ACTION_CAT_HE 30 /* 9.6.31 HE, 80211ax-2021 */ #define IEEE80211_ACTION_CAT_PROTECTED_HE 31 /* 9.6.32 Protected HE, 80211ax-2021 */ /* Reserved 32-125 */ #define IEEE80211_ACTION_CAT_VENDOR_SPECIFIC_PROTECTED 126 /* 9.6.5 Vendor-specific Protected */ #define IEEE80211_ACTION_CAT_VENDOR 127 /* 9.6.5 Vendor-specific */ /* Error 128-255 */ /* 80211-2020 Table 9-346-Spectrum Management Action field values */ enum ieee80211_action_sm { IEEE80211_ACTION_SM_SMREQ = 0, /* Spectrum Measurement Request */ IEEE80211_ACTION_SM_SMREP = 1, /* Spectrum Measurement Report */ IEEE80211_ACTION_SM_TPCREQ = 2, /* TPC Request */ IEEE80211_ACTION_SM_TPCREP = 3, /* TPC Report */ IEEE80211_ACTION_SM_CSA = 4, /* Channel Switch Announcement */ /* Reserved = 5-255 */ }; /* 80211-2020 Table 9-363-Radio Measurement Action field values */ enum ieee80211_action_radio_measurement { IEEE80211_ACTION_RADIO_MEASUREMENT_RMREQ = 0, /* Radio Measurement Request */ IEEE80211_ACTION_RADIO_MEASUREMENT_RMREP = 1, /* Radio Measurement Report */ IEEE80211_ACTION_RADIO_MEASUREMENT_LMREQ = 2, /* Link Measurement Request */ IEEE80211_ACTION_RADIO_MEASUREMENT_LMREP = 3, /* Link Measurement Report */ IEEE80211_ACTION_RADIO_MEASUREMENT_NRREQ = 4, /* Neighbor Report Request */ IEEE80211_ACTION_RADIO_MEASUREMENT_NRRESP = 5, /* Neighbor Report Response */ /* Reserved = 6-255 */ }; #define IEEE80211_ACTION_HT_TXCHWIDTH 0 /* recommended xmit chan width*/ #define IEEE80211_ACTION_HT_MIMOPWRSAVE 1 /* MIMO power save */ /* HT - recommended transmission channel width */ struct ieee80211_action_ht_txchwidth { struct ieee80211_action at_header; uint8_t at_chwidth; } __packed; #define IEEE80211_A_HT_TXCHWIDTH_20 0 #define IEEE80211_A_HT_TXCHWIDTH_2040 1 /* HT - MIMO Power Save (NB: D2.04) */ struct ieee80211_action_ht_mimopowersave { struct ieee80211_action am_header; uint8_t am_control; } __packed; #define IEEE80211_A_HT_MIMOPWRSAVE_ENA 0x01 /* PS enabled */ #define IEEE80211_A_HT_MIMOPWRSAVE_MODE 0x02 #define IEEE80211_A_HT_MIMOPWRSAVE_MODE_S 1 #define IEEE80211_A_HT_MIMOPWRSAVE_DYNAMIC 0x02 /* Dynamic Mode */ #define IEEE80211_A_HT_MIMOPWRSAVE_STATIC 0x00 /* no SM packets */ /* bits 2-7 reserved */ /* Block Ack actions */ #define IEEE80211_ACTION_BA_ADDBA_REQUEST 0 /* ADDBA request */ #define IEEE80211_ACTION_BA_ADDBA_RESPONSE 1 /* ADDBA response */ #define IEEE80211_ACTION_BA_DELBA 2 /* DELBA */ /* Block Ack Parameter Set */ #define IEEE80211_BAPS_BUFSIZ 0xffc0 /* buffer size */ #define IEEE80211_BAPS_BUFSIZ_S 6 #define IEEE80211_BAPS_TID 0x003c /* TID */ #define IEEE80211_BAPS_TID_S 2 #define IEEE80211_BAPS_POLICY 0x0002 /* block ack policy */ #define IEEE80211_BAPS_POLICY_S 1 #define IEEE80211_BAPS_AMSDU 0x0001 /* A-MSDU permitted */ #define IEEE80211_BAPS_AMSDU_S 0 #define IEEE80211_BAPS_POLICY_DELAYED (0<= \ IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_160MHZ) #define IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ(_vhtcaps) \ (_IEEE80211_MASKSHIFT(_vhtcaps, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK) == \ IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_160_80P80MHZ) #define IEEE80211_VHTCAP_RXLDPC 0x00000010 #define IEEE80211_VHTCAP_RXLDPC_S 4 #define IEEE80211_VHTCAP_SHORT_GI_80 0x00000020 #define IEEE80211_VHTCAP_SHORT_GI_80_S 5 #define IEEE80211_VHTCAP_SHORT_GI_160 0x00000040 #define IEEE80211_VHTCAP_SHORT_GI_160_S 6 #define IEEE80211_VHTCAP_TXSTBC 0x00000080 #define IEEE80211_VHTCAP_TXSTBC_S 7 #define IEEE80211_VHTCAP_RXSTBC_1 0x00000100 #define IEEE80211_VHTCAP_RXSTBC_2 0x00000200 #define IEEE80211_VHTCAP_RXSTBC_3 0x00000300 #define IEEE80211_VHTCAP_RXSTBC_4 0x00000400 #define IEEE80211_VHTCAP_RXSTBC_MASK 0x00000700 #define IEEE80211_VHTCAP_RXSTBC_MASK_S 8 #define IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE 0x00000800 #define IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE_S 11 #define IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE 0x00001000 #define IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE_S 12 #define IEEE80211_VHTCAP_BEAMFORMEE_STS_SHIFT 13 #define IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK \ (7 << IEEE80211_VHTCAP_BEAMFORMEE_STS_SHIFT) #define IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK_S 13 #define IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_SHIFT 16 #define IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK \ (7 << IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_SHIFT) #define IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK_S 16 #define IEEE80211_VHTCAP_MU_BEAMFORMER_CAPABLE 0x00080000 #define IEEE80211_VHTCAP_MU_BEAMFORMER_CAPABLE_S 19 #define IEEE80211_VHTCAP_MU_BEAMFORMEE_CAPABLE 0x00100000 #define IEEE80211_VHTCAP_MU_BEAMFORMEE_CAPABLE_S 20 #define IEEE80211_VHTCAP_VHT_TXOP_PS 0x00200000 #define IEEE80211_VHTCAP_VHT_TXOP_PS_S 21 #define IEEE80211_VHTCAP_HTC_VHT 0x00400000 #define IEEE80211_VHTCAP_HTC_VHT_S 22 #define IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT 23 #define IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK \ (7 << IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT) #define IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK_S 23 #define IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK 0x0c000000 #define IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB 0x08000000 #define IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB 0x0c000000 #define IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK_S 26 #define IEEE80211_VHTCAP_RX_ANTENNA_PATTERN 0x10000000 #define IEEE80211_VHTCAP_RX_ANTENNA_PATTERN_S 28 #define IEEE80211_VHTCAP_TX_ANTENNA_PATTERN 0x20000000 #define IEEE80211_VHTCAP_TX_ANTENNA_PATTERN_S 29 /* * XXX TODO: add the rest of the bits */ #define IEEE80211_VHTCAP_BITS \ "\20\1MPDU7991\2MPDU11454\3CHAN160\4CHAN8080\5RXLDPC\6SHORTGI80" \ "\7SHORTGI160\10RXSTBC1\11RXSTBC2\12RXSTBC3\13RXSTBC4\14BFERCAP" \ "\15BFEECAP\27VHT\37RXANTPTN\40TXANTPTN" /* * VHT Transmit Power Envelope element - 802.11ac-2013 8.4.2.164 * * This defines the maximum transmit power for various bandwidths. */ /* * Count is how many elements follow and what they're for: * * 0 - 20 MHz * 1 - 20+40 MHz * 2 - 20+40+80 MHz * 3 - 20+40+80+(160, 80+80) MHz */ #define IEEE80211_VHT_TXPWRENV_INFO_COUNT_SHIFT 0 #define IEEE80211_VHT_TXPWRENV_INFO_COUNT_MASK 0x07 /* * Unit is the tx power representation. It should be EIRP for now; * other values are reserved. */ #define IEEE80211_VHT_TXPWRENV_UNIT_MASK 0x38 #define IEEE80211_VHT_TXPWRENV_UNIT_SHIFT 3 /* This value is within the unit mask/shift above */ #define IEEE80211_VHT_TXPWRENV_UNIT_EIRP 0 struct ieee80211_ie_vht_txpwrenv { uint8_t ie; uint8_t len; uint8_t tx_info; int8_t tx_elem[0]; /* TX power elements, 1/2 dB, signed */ }; /* VHT action codes */ #define WLAN_ACTION_VHT_COMPRESSED_BF 0 #define WLAN_ACTION_VHT_GROUPID_MGMT 1 #define WLAN_ACTION_VHT_OPMODE_NOTIF 2 /* * Management information element payloads. */ enum { IEEE80211_ELEMID_SSID = 0, IEEE80211_ELEMID_RATES = 1, IEEE80211_ELEMID_FHPARMS = 2, IEEE80211_ELEMID_DSPARMS = 3, IEEE80211_ELEMID_CFPARMS = 4, IEEE80211_ELEMID_TIM = 5, IEEE80211_ELEMID_IBSSPARMS = 6, IEEE80211_ELEMID_COUNTRY = 7, IEEE80211_ELEMID_BSSLOAD = 11, IEEE80211_ELEMID_TSPEC = 13, IEEE80211_ELEMID_TCLAS = 14, IEEE80211_ELEMID_CHALLENGE = 16, /* 17-31 reserved for challenge text extension */ IEEE80211_ELEMID_PWRCNSTR = 32, IEEE80211_ELEMID_PWRCAP = 33, IEEE80211_ELEMID_TPCREQ = 34, IEEE80211_ELEMID_TPCREP = 35, IEEE80211_ELEMID_SUPPCHAN = 36, IEEE80211_ELEMID_CSA = 37, IEEE80211_ELEMID_MEASREQ = 38, IEEE80211_ELEMID_MEASREP = 39, IEEE80211_ELEMID_QUIET = 40, IEEE80211_ELEMID_IBSSDFS = 41, IEEE80211_ELEMID_ERP = 42, IEEE80211_ELEMID_HTCAP = 45, IEEE80211_ELEMID_QOS = 46, IEEE80211_ELEMID_RESERVED_47 = 47, IEEE80211_ELEMID_RSN = 48, IEEE80211_ELEMID_XRATES = 50, IEEE80211_ELEMID_APCHANREP = 51, IEEE80211_ELEMID_MOBILITY_DOMAIN = 54, IEEE80211_ELEMID_HTINFO = 61, IEEE80211_ELEMID_SECCHAN_OFFSET = 62, IEEE80211_ELEMID_RRM_ENACAPS = 70, IEEE80211_ELEMID_MULTIBSSID = 71, IEEE80211_ELEMID_COEX_2040 = 72, IEEE80211_ELEMID_INTOL_CHN_REPORT = 73, IEEE80211_ELEMID_OVERLAP_BSS_SCAN_PARAM = 74, IEEE80211_ELEMID_TSF_REQ = 91, IEEE80211_ELEMID_TSF_RESP = 92, IEEE80211_ELEMID_WNM_SLEEP_MODE = 93, IEEE80211_ELEMID_TIM_BCAST_REQ = 94, IEEE80211_ELEMID_TIM_BCAST_RESP = 95, IEEE80211_ELEMID_TPC = 150, IEEE80211_ELEMID_CCKM = 156, IEEE80211_ELEMID_VENDOR = 221, /* vendor private */ /* * 802.11s IEs * NB: On vanilla Linux still IEEE80211_ELEMID_MESHPEER = 55, * but they defined a new with id 117 called PEER_MGMT. * NB: complies with open80211 */ IEEE80211_ELEMID_MESHCONF = 113, IEEE80211_ELEMID_MESHID = 114, IEEE80211_ELEMID_MESHLINK = 115, IEEE80211_ELEMID_MESHCNGST = 116, IEEE80211_ELEMID_MESHPEER = 117, IEEE80211_ELEMID_MESHCSA = 118, IEEE80211_ELEMID_MESHTIM = 39, /* XXX: remove */ IEEE80211_ELEMID_MESHAWAKEW = 119, IEEE80211_ELEMID_MESHBEACONT = 120, /* 121-124 MMCAOP not implemented yet */ IEEE80211_ELEMID_MESHGANN = 125, IEEE80211_ELEMID_MESHRANN = 126, /* 127 Extended Capabilities */ IEEE80211_ELEMID_EXTCAP = 127, /* 128-129 reserved */ IEEE80211_ELEMID_MESHPREQ = 130, IEEE80211_ELEMID_MESHPREP = 131, IEEE80211_ELEMID_MESHPERR = 132, /* 133-136 reserved */ IEEE80211_ELEMID_MESHPXU = 137, IEEE80211_ELEMID_MESHPXUC = 138, IEEE80211_ELEMID_MESHAH = 60, /* XXX: remove */ /* 802.11ac-2013, Table 8-54-Element IDs */ IEEE80211_ELEMID_VHT_CAP = 191, IEEE80211_ELEMID_VHT_OPMODE = 192, IEEE80211_ELEMID_EXTENDED_BSS_LOAD = 193, IEEE80211_ELEMID_WIDE_BW_CHANNEL_SWITCH = 194, IEEE80211_ELEMID_VHT_PWR_ENV = 195, IEEE80211_ELEMID_CHANNEL_SWITCH_WRAPPER = 196, IEEE80211_ELEMID_AID = 197, IEEE80211_ELEMID_QUIET_CHANNEL = 198, IEEE80211_ELEMID_OPMODE_NOTIF = 199, }; struct ieee80211_tim_ie { uint8_t tim_ie; /* IEEE80211_ELEMID_TIM */ uint8_t tim_len; uint8_t tim_count; /* DTIM count */ uint8_t tim_period; /* DTIM period */ uint8_t tim_bitctl; /* bitmap control */ uint8_t tim_bitmap[1]; /* variable-length bitmap */ } __packed; struct ieee80211_country_ie { uint8_t ie; /* IEEE80211_ELEMID_COUNTRY */ uint8_t len; uint8_t cc[3]; /* ISO CC+(I)ndoor/(O)utdoor */ struct { uint8_t schan; /* starting channel */ uint8_t nchan; /* number channels */ uint8_t maxtxpwr; /* tx power cap */ } __packed band[1]; /* sub bands (NB: var size) */ } __packed; #define IEEE80211_COUNTRY_MAX_BANDS 84 /* max possible bands */ #define IEEE80211_COUNTRY_MAX_SIZE \ (sizeof(struct ieee80211_country_ie) + 3*(IEEE80211_COUNTRY_MAX_BANDS-1)) struct ieee80211_bss_load_ie { uint8_t ie; uint8_t len; uint16_t sta_count; /* station count */ uint8_t chan_load; /* channel utilization */ uint8_t aac; /* available admission capacity */ } __packed; struct ieee80211_ap_chan_report_ie { uint8_t ie; uint8_t len; uint8_t i_class; /* operating class */ /* Annex E, E.1 Country information and operating classes */ uint8_t chan_list[0]; } __packed; #define IEEE80211_EXTCAP_CMS (1ULL << 0) /* 20/40 BSS coexistence management support */ #define IEEE80211_EXTCAP_RSVD_1 (1ULL << 1) #define IEEE80211_EXTCAP_ECS (1ULL << 2) /* extended channel switching */ #define IEEE80211_EXTCAP_RSVD_3 (1ULL << 3) #define IEEE80211_EXTCAP_PSMP_CAP (1ULL << 4) /* PSMP capability */ #define IEEE80211_EXTCAP_RSVD_5 (1ULL << 5) #define IEEE80211_EXTCAP_S_PSMP_SUPP (1ULL << 6) #define IEEE80211_EXTCAP_EVENT (1ULL << 7) #define IEEE80211_EXTCAP_DIAGNOSTICS (1ULL << 8) #define IEEE80211_EXTCAP_MCAST_DIAG (1ULL << 9) #define IEEE80211_EXTCAP_LOC_TRACKING (1ULL << 10) #define IEEE80211_EXTCAP_FMS (1ULL << 11) #define IEEE80211_EXTCAP_PROXY_ARP (1ULL << 12) #define IEEE80211_EXTCAP_CIR (1ULL << 13) /* collocated interference reporting */ #define IEEE80211_EXTCAP_CIVIC_LOC (1ULL << 14) #define IEEE80211_EXTCAP_GEOSPATIAL_LOC (1ULL << 15) #define IEEE80211_EXTCAP_TFS (1ULL << 16) #define IEEE80211_EXTCAP_WNM_SLEEPMODE (1ULL << 17) #define IEEE80211_EXTCAP_TIM_BROADCAST (1ULL << 18) #define IEEE80211_EXTCAP_BSS_TRANSITION (1ULL << 19) #define IEEE80211_EXTCAP_QOS_TRAF_CAP (1ULL << 20) #define IEEE80211_EXTCAP_AC_STA_COUNT (1ULL << 21) #define IEEE80211_EXTCAP_M_BSSID (1ULL << 22) /* multiple BSSID field */ #define IEEE80211_EXTCAP_TIMING_MEAS (1ULL << 23) #define IEEE80211_EXTCAP_CHAN_USAGE (1ULL << 24) #define IEEE80211_EXTCAP_SSID_LIST (1ULL << 25) #define IEEE80211_EXTCAP_DMS (1ULL << 26) #define IEEE80211_EXTCAP_UTC_TSF_OFFSET (1ULL << 27) #define IEEE80211_EXTCAP_TLDS_BUF_STA_SUPP (1ULL << 28) /* TDLS peer U-APSP buffer STA support */ #define IEEE80211_EXTCAP_TLDS_PPSM_SUPP (1ULL << 29) /* TDLS peer PSM support */ #define IEEE80211_EXTCAP_TLDS_CH_SW (1ULL << 30) /* TDLS channel switching */ #define IEEE80211_EXTCAP_INTERWORKING (1ULL << 31) #define IEEE80211_EXTCAP_QOSMAP (1ULL << 32) #define IEEE80211_EXTCAP_EBR (1ULL << 33) #define IEEE80211_EXTCAP_SSPN_IF (1ULL << 34) #define IEEE80211_EXTCAP_RSVD_35 (1ULL << 35) #define IEEE80211_EXTCAP_MSGCF_CAP (1ULL << 36) #define IEEE80211_EXTCAP_TLDS_SUPP (1ULL << 37) #define IEEE80211_EXTCAP_TLDS_PROHIB (1ULL << 38) #define IEEE80211_EXTCAP_TLDS_CH_SW_PROHIB (1ULL << 39) /* TDLS channel switching prohibited */ #define IEEE80211_EXTCAP_RUF (1ULL << 40) /* reject unadmitted frame */ /* service interval granularity */ #define IEEE80211_EXTCAP_SIG \ ((1ULL << 41) | (1ULL << 42) | (1ULL << 43)) #define IEEE80211_EXTCAP_ID_LOC (1ULL << 44) #define IEEE80211_EXTCAP_U_APSD_COEX (1ULL << 45) #define IEEE80211_EXTCAP_WNM_NOTIFICATION (1ULL << 46) #define IEEE80211_EXTCAP_RSVD_47 (1ULL << 47) #define IEEE80211_EXTCAP_SSID (1ULL << 48) /* UTF-8 SSID */ /* bits 49-n are reserved */ struct ieee80211_extcap_ie { uint8_t ie; uint8_t len; } __packed; /* * 802.11h Quiet Time Element. */ struct ieee80211_quiet_ie { uint8_t quiet_ie; /* IEEE80211_ELEMID_QUIET */ uint8_t len; uint8_t tbttcount; /* quiet start */ uint8_t period; /* beacon intervals between quiets */ uint16_t duration; /* TUs of each quiet*/ uint16_t offset; /* TUs of from TBTT of quiet start */ } __packed; /* * 802.11h Channel Switch Announcement (CSA). */ struct ieee80211_csa_ie { uint8_t csa_ie; /* IEEE80211_ELEMID_CHANSWITCHANN */ uint8_t csa_len; uint8_t csa_mode; /* Channel Switch Mode */ uint8_t csa_newchan; /* New Channel Number */ uint8_t csa_count; /* Channel Switch Count */ } __packed; /* * Note the min acceptable CSA count is used to guard against * malicious CSA injection in station mode. Defining this value * as other than 0 violates the 11h spec. */ #define IEEE80211_CSA_COUNT_MIN 2 #define IEEE80211_CSA_COUNT_MAX 255 /* rate set entries are in .5 Mb/s units, and potentially marked as basic */ #define IEEE80211_RATE_BASIC 0x80 #define IEEE80211_RATE_VAL 0x7f #define IEEE80211_RV(v) ((v) & IEEE80211_RATE_VAL) /* ERP information element flags */ #define IEEE80211_ERP_NON_ERP_PRESENT 0x01 #define IEEE80211_ERP_USE_PROTECTION 0x02 #define IEEE80211_ERP_LONG_PREAMBLE 0x04 #define IEEE80211_ERP_BITS \ "\20\1NON_ERP_PRESENT\2USE_PROTECTION\3LONG_PREAMBLE" #define ATH_OUI 0x7f0300 /* Atheros OUI */ #define ATH_OUI_TYPE 0x01 /* Atheros protocol ie */ /* NB: Atheros allocated the OUI for this purpose ~2005 but beware ... */ #define TDMA_OUI ATH_OUI #define TDMA_OUI_TYPE 0x02 /* TDMA protocol ie */ #define BCM_OUI 0x4c9000 /* Broadcom OUI */ #define BCM_OUI_HTCAP 51 /* pre-draft HTCAP ie */ #define BCM_OUI_HTINFO 52 /* pre-draft HTINFO ie */ #define WPA_OUI 0xf25000 #define WPA_OUI_TYPE 0x01 #define WPA_VERSION 1 /* current supported version */ #define WPA_CSE_NULL 0x00 #define WPA_CSE_WEP40 0x01 #define WPA_CSE_TKIP 0x02 #define WPA_CSE_CCMP 0x04 #define WPA_CSE_WEP104 0x05 #define WPA_ASE_NONE 0x00 #define WPA_ASE_8021X_UNSPEC 0x01 #define WPA_ASE_8021X_PSK 0x02 #define WPS_OUI_TYPE 0x04 #define RSN_OUI 0xac0f00 #define RSN_VERSION 1 /* current supported version */ #define RSN_CSE_NULL 0x00 #define RSN_CSE_WEP40 0x01 #define RSN_CSE_TKIP 0x02 #define RSN_CSE_WRAP 0x03 #define RSN_CSE_CCMP 0x04 #define RSN_CSE_WEP104 0x05 #define RSN_ASE_NONE 0x00 #define RSN_ASE_8021X_UNSPEC 0x01 #define RSN_ASE_8021X_PSK 0x02 #define RSN_CAP_PREAUTH 0x01 #define WME_OUI 0xf25000 #define WME_OUI_TYPE 0x02 #define WME_INFO_OUI_SUBTYPE 0x00 #define WME_PARAM_OUI_SUBTYPE 0x01 #define WME_VERSION 1 /* WME stream classes */ #define WME_AC_BE 0 /* best effort */ #define WME_AC_BK 1 /* background */ #define WME_AC_VI 2 /* video */ #define WME_AC_VO 3 /* voice */ /* * AUTH management packets * * octet algo[2] * octet seq[2] * octet status[2] * octet chal.id * octet chal.length * octet chal.text[253] NB: 1-253 bytes */ /* challenge length for shared key auth */ #define IEEE80211_CHALLENGE_LEN 128 #define IEEE80211_AUTH_ALG_OPEN 0x0000 #define IEEE80211_AUTH_ALG_SHARED 0x0001 #define IEEE80211_AUTH_ALG_LEAP 0x0080 enum { IEEE80211_AUTH_OPEN_REQUEST = 1, IEEE80211_AUTH_OPEN_RESPONSE = 2, }; enum { IEEE80211_AUTH_SHARED_REQUEST = 1, IEEE80211_AUTH_SHARED_CHALLENGE = 2, IEEE80211_AUTH_SHARED_RESPONSE = 3, IEEE80211_AUTH_SHARED_PASS = 4, }; /* * Reason and status codes. * * Reason codes are used in management frames to indicate why an * action took place (e.g. on disassociation). Status codes are * used in management frames to indicate the result of an operation. * * Unlisted codes are reserved */ enum { IEEE80211_REASON_UNSPECIFIED = 1, IEEE80211_REASON_AUTH_EXPIRE = 2, IEEE80211_REASON_AUTH_LEAVE = 3, IEEE80211_REASON_ASSOC_EXPIRE = 4, IEEE80211_REASON_ASSOC_TOOMANY = 5, IEEE80211_REASON_NOT_AUTHED = 6, IEEE80211_REASON_NOT_ASSOCED = 7, IEEE80211_REASON_ASSOC_LEAVE = 8, IEEE80211_REASON_ASSOC_NOT_AUTHED = 9, IEEE80211_REASON_DISASSOC_PWRCAP_BAD = 10, /* 11h */ IEEE80211_REASON_DISASSOC_SUPCHAN_BAD = 11, /* 11h */ IEEE80211_REASON_IE_INVALID = 13, /* 11i */ IEEE80211_REASON_MIC_FAILURE = 14, /* 11i */ IEEE80211_REASON_4WAY_HANDSHAKE_TIMEOUT = 15, /* 11i */ IEEE80211_REASON_GROUP_KEY_UPDATE_TIMEOUT = 16, /* 11i */ IEEE80211_REASON_IE_IN_4WAY_DIFFERS = 17, /* 11i */ IEEE80211_REASON_GROUP_CIPHER_INVALID = 18, /* 11i */ IEEE80211_REASON_PAIRWISE_CIPHER_INVALID= 19, /* 11i */ IEEE80211_REASON_AKMP_INVALID = 20, /* 11i */ IEEE80211_REASON_UNSUPP_RSN_IE_VERSION = 21, /* 11i */ IEEE80211_REASON_INVALID_RSN_IE_CAP = 22, /* 11i */ IEEE80211_REASON_802_1X_AUTH_FAILED = 23, /* 11i */ IEEE80211_REASON_CIPHER_SUITE_REJECTED = 24, /* 11i */ IEEE80211_REASON_UNSPECIFIED_QOS = 32, /* 11e */ IEEE80211_REASON_INSUFFICIENT_BW = 33, /* 11e */ IEEE80211_REASON_TOOMANY_FRAMES = 34, /* 11e */ IEEE80211_REASON_OUTSIDE_TXOP = 35, /* 11e */ IEEE80211_REASON_LEAVING_QBSS = 36, /* 11e */ IEEE80211_REASON_BAD_MECHANISM = 37, /* 11e */ IEEE80211_REASON_SETUP_NEEDED = 38, /* 11e */ IEEE80211_REASON_TIMEOUT = 39, /* 11e */ IEEE80211_REASON_PEER_LINK_CANCELED = 52, /* 11s */ IEEE80211_REASON_MESH_MAX_PEERS = 53, /* 11s */ IEEE80211_REASON_MESH_CPVIOLATION = 54, /* 11s */ IEEE80211_REASON_MESH_CLOSE_RCVD = 55, /* 11s */ IEEE80211_REASON_MESH_MAX_RETRIES = 56, /* 11s */ IEEE80211_REASON_MESH_CONFIRM_TIMEOUT = 57, /* 11s */ IEEE80211_REASON_MESH_INVALID_GTK = 58, /* 11s */ IEEE80211_REASON_MESH_INCONS_PARAMS = 59, /* 11s */ IEEE80211_REASON_MESH_INVALID_SECURITY = 60, /* 11s */ IEEE80211_REASON_MESH_PERR_NO_PROXY = 61, /* 11s */ IEEE80211_REASON_MESH_PERR_NO_FI = 62, /* 11s */ IEEE80211_REASON_MESH_PERR_DEST_UNREACH = 63, /* 11s */ IEEE80211_REASON_MESH_MAC_ALRDY_EXISTS_MBSS = 64, /* 11s */ IEEE80211_REASON_MESH_CHAN_SWITCH_REG = 65, /* 11s */ IEEE80211_REASON_MESH_CHAN_SWITCH_UNSPEC = 66, /* 11s */ IEEE80211_STATUS_SUCCESS = 0, IEEE80211_STATUS_UNSPECIFIED = 1, IEEE80211_STATUS_CAPINFO = 10, IEEE80211_STATUS_NOT_ASSOCED = 11, IEEE80211_STATUS_OTHER = 12, IEEE80211_STATUS_ALG = 13, IEEE80211_STATUS_SEQUENCE = 14, IEEE80211_STATUS_CHALLENGE = 15, IEEE80211_STATUS_TIMEOUT = 16, IEEE80211_STATUS_TOOMANY = 17, IEEE80211_STATUS_BASIC_RATE = 18, IEEE80211_STATUS_SP_REQUIRED = 19, /* 11b */ IEEE80211_STATUS_PBCC_REQUIRED = 20, /* 11b */ IEEE80211_STATUS_CA_REQUIRED = 21, /* 11b */ IEEE80211_STATUS_SPECMGMT_REQUIRED = 22, /* 11h */ IEEE80211_STATUS_PWRCAP_REQUIRED = 23, /* 11h */ IEEE80211_STATUS_SUPCHAN_REQUIRED = 24, /* 11h */ IEEE80211_STATUS_SHORTSLOT_REQUIRED = 25, /* 11g */ IEEE80211_STATUS_DSSSOFDM_REQUIRED = 26, /* 11g */ IEEE80211_STATUS_MISSING_HT_CAPS = 27, /* 11n D3.0 */ IEEE80211_STATUS_INVALID_IE = 40, /* 11i */ IEEE80211_STATUS_GROUP_CIPHER_INVALID = 41, /* 11i */ IEEE80211_STATUS_PAIRWISE_CIPHER_INVALID = 42, /* 11i */ IEEE80211_STATUS_AKMP_INVALID = 43, /* 11i */ IEEE80211_STATUS_UNSUPP_RSN_IE_VERSION = 44, /* 11i */ IEEE80211_STATUS_INVALID_RSN_IE_CAP = 45, /* 11i */ IEEE80211_STATUS_CIPHER_SUITE_REJECTED = 46, /* 11i */ }; #define IEEE80211_WEP_KEYLEN 5 /* 40bit */ #define IEEE80211_WEP_IVLEN 3 /* 24bit */ #define IEEE80211_WEP_KIDLEN 1 /* 1 octet */ #define IEEE80211_WEP_CRCLEN 4 /* CRC-32 */ #define IEEE80211_WEP_TOTLEN (IEEE80211_WEP_IVLEN + \ IEEE80211_WEP_KIDLEN + \ IEEE80211_WEP_CRCLEN) #define IEEE80211_WEP_NKID 4 /* number of key ids */ /* * 802.11i defines an extended IV for use with non-WEP ciphers. * When the EXTIV bit is set in the key id byte an additional * 4 bytes immediately follow the IV for TKIP. For CCMP the * EXTIV bit is likewise set but the 8 bytes represent the * CCMP header rather than IV+extended-IV. */ #define IEEE80211_WEP_EXTIV 0x20 #define IEEE80211_WEP_EXTIVLEN 4 /* extended IV length */ #define IEEE80211_WEP_MICLEN 8 /* trailing MIC */ #define IEEE80211_CRC_LEN 4 /* * Maximum acceptable MTU is: * IEEE80211_MAX_LEN - WEP overhead - CRC - * QoS overhead - RSN/WPA overhead * Min is arbitrarily chosen > IEEE80211_MIN_LEN. The default * mtu is Ethernet-compatible; it's set by ether_ifattach. */ #define IEEE80211_MTU_MAX 2290 #define IEEE80211_MTU_MIN 32 #define IEEE80211_MAX_LEN (2300 + IEEE80211_CRC_LEN + \ (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_CRCLEN)) #define IEEE80211_ACK_LEN \ (sizeof(struct ieee80211_frame_ack) + IEEE80211_CRC_LEN) #define IEEE80211_MIN_LEN \ (sizeof(struct ieee80211_frame_min) + IEEE80211_CRC_LEN) /* * The 802.11 spec says at most 2007 stations may be * associated at once. For most AP's this is way more * than is feasible so we use a default of IEEE80211_AID_DEF. * This number may be overridden by the driver and/or by * user configuration but may not be less than IEEE80211_AID_MIN * (see _ieee80211.h for implementation-specific settings). */ #define IEEE80211_AID_MAX 2007 #define IEEE80211_AID(b) ((b) &~ 0xc000) /* * RTS frame length parameters. The default is specified in * the 802.11 spec as 512; we treat it as implementation-dependent * so it's defined in ieee80211_var.h. The max may be wrong * for jumbo frames. */ #define IEEE80211_RTS_MIN 1 #define IEEE80211_RTS_MAX 2346 /* * TX fragmentation parameters. As above for RTS, we treat * default as implementation-dependent so define it elsewhere. */ #define IEEE80211_FRAG_MIN 256 #define IEEE80211_FRAG_MAX 2346 /* * Beacon interval (TU's). Min+max come from WiFi requirements. * As above, we treat default as implementation-dependent so * define it elsewhere. */ #define IEEE80211_BINTVAL_MAX 1000 /* max beacon interval (TU's) */ #define IEEE80211_BINTVAL_MIN 25 /* min beacon interval (TU's) */ /* * DTIM period (beacons). Min+max are not really defined * by the protocol but we want them publicly visible so * define them here. */ #define IEEE80211_DTIM_MAX 15 /* max DTIM period */ #define IEEE80211_DTIM_MIN 1 /* min DTIM period */ /* * Beacon miss threshold (beacons). As for DTIM, we define * them here to be publicly visible. Note the max may be * clamped depending on device capabilities. */ #define IEEE80211_HWBMISS_MIN 1 #define IEEE80211_HWBMISS_MAX 255 /* * 802.11 frame duration definitions. */ struct ieee80211_duration { uint16_t d_rts_dur; uint16_t d_data_dur; uint16_t d_plcp_len; uint8_t d_residue; /* unused octets in time slot */ }; /* One Time Unit (TU) is 1Kus = 1024 microseconds. */ #define IEEE80211_DUR_TU 1024 /* IEEE 802.11b durations for DSSS PHY in microseconds */ #define IEEE80211_DUR_DS_LONG_PREAMBLE 144 #define IEEE80211_DUR_DS_SHORT_PREAMBLE 72 #define IEEE80211_DUR_DS_SLOW_PLCPHDR 48 #define IEEE80211_DUR_DS_FAST_PLCPHDR 24 #define IEEE80211_DUR_DS_SLOW_ACK 112 #define IEEE80211_DUR_DS_FAST_ACK 56 #define IEEE80211_DUR_DS_SLOW_CTS 112 #define IEEE80211_DUR_DS_FAST_CTS 56 #define IEEE80211_DUR_DS_SLOT 20 #define IEEE80211_DUR_DS_SIFS 10 #define IEEE80211_DUR_DS_PIFS (IEEE80211_DUR_DS_SIFS + IEEE80211_DUR_DS_SLOT) #define IEEE80211_DUR_DS_DIFS (IEEE80211_DUR_DS_SIFS + \ 2 * IEEE80211_DUR_DS_SLOT) #define IEEE80211_DUR_DS_EIFS (IEEE80211_DUR_DS_SIFS + \ IEEE80211_DUR_DS_SLOW_ACK + \ IEEE80211_DUR_DS_LONG_PREAMBLE + \ IEEE80211_DUR_DS_SLOW_PLCPHDR + \ IEEE80211_DUR_DIFS) #endif /* _NET80211_IEEE80211_H_ */ diff --git a/sys/net80211/ieee80211_hostap.c b/sys/net80211/ieee80211_hostap.c index c1196961ad54..67c45bd38935 100644 --- a/sys/net80211/ieee80211_hostap.c +++ b/sys/net80211/ieee80211_hostap.c @@ -1,2480 +1,2480 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-2008 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. * * 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. */ /* * IEEE 802.11 HOSTAP mode support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #include #include #include /* for parse_wmeie */ #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) static void hostap_vattach(struct ieee80211vap *); static int hostap_newstate(struct ieee80211vap *, enum ieee80211_state, int); static int hostap_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *, int rssi, int nf); static void hostap_deliver_data(struct ieee80211vap *, struct ieee80211_node *, struct mbuf *); static void hostap_recv_mgmt(struct ieee80211_node *, struct mbuf *, int subtype, const struct ieee80211_rx_stats *rxs, int rssi, int nf); static void hostap_recv_ctl(struct ieee80211_node *, struct mbuf *, int); void ieee80211_hostap_attach(struct ieee80211com *ic) { ic->ic_vattach[IEEE80211_M_HOSTAP] = hostap_vattach; } void ieee80211_hostap_detach(struct ieee80211com *ic) { } static void hostap_vdetach(struct ieee80211vap *vap) { } static void hostap_vattach(struct ieee80211vap *vap) { vap->iv_newstate = hostap_newstate; vap->iv_input = hostap_input; vap->iv_recv_mgmt = hostap_recv_mgmt; vap->iv_recv_ctl = hostap_recv_ctl; vap->iv_opdetach = hostap_vdetach; vap->iv_deliver_data = hostap_deliver_data; vap->iv_recv_pspoll = ieee80211_recv_pspoll; } static void sta_disassoc(void *arg, struct ieee80211_node *ni) { if (ni->ni_associd != 0) { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_ASSOC_LEAVE); ieee80211_node_leave(ni); } } static void sta_csa(void *arg, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; if (ni->ni_associd != 0) if (ni->ni_inact > vap->iv_inact_init) { ni->ni_inact = vap->iv_inact_init; IEEE80211_NOTE(vap, IEEE80211_MSG_INACT, ni, "%s: inact %u", __func__, ni->ni_inact); } } static void sta_drop(void *arg, struct ieee80211_node *ni) { if (ni->ni_associd != 0) ieee80211_node_leave(ni); } /* * Does a channel change require associated stations to re-associate * so protocol state is correct. This is used when doing CSA across * bands or similar (e.g. HT -> legacy). */ static int isbandchange(struct ieee80211com *ic) { return ((ic->ic_bsschan->ic_flags ^ ic->ic_csa_newchan->ic_flags) & (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ | IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER | IEEE80211_CHAN_HT)) != 0; } /* * IEEE80211_M_HOSTAP vap state machine handler. */ static int hostap_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) { struct ieee80211com *ic = vap->iv_ic; enum ieee80211_state ostate; IEEE80211_LOCK_ASSERT(ic); ostate = vap->iv_state; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg); vap->iv_state = nstate; /* state transition */ if (ostate != IEEE80211_S_SCAN) ieee80211_cancel_scan(vap); /* background scan */ switch (nstate) { case IEEE80211_S_INIT: switch (ostate) { case IEEE80211_S_SCAN: ieee80211_cancel_scan(vap); break; case IEEE80211_S_CAC: ieee80211_dfs_cac_stop(vap); break; case IEEE80211_S_RUN: ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_disassoc, NULL); break; default: break; } if (ostate != IEEE80211_S_INIT) { /* NB: optimize INIT -> INIT case */ ieee80211_reset_bss(vap); } if (vap->iv_auth->ia_detach != NULL) vap->iv_auth->ia_detach(vap); break; case IEEE80211_S_SCAN: switch (ostate) { case IEEE80211_S_CSA: case IEEE80211_S_RUN: ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_disassoc, NULL); /* * Clear overlapping BSS state; the beacon frame * will be reconstructed on transition to the RUN * state and the timeout routines check if the flag * is set before doing anything so this is sufficient. */ vap->iv_flags_ext &= ~IEEE80211_FEXT_NONERP_PR; vap->iv_flags_ht &= ~IEEE80211_FHT_NONHT_PR; /* XXX TODO: schedule deferred update? */ /* fall thru... */ case IEEE80211_S_CAC: /* * NB: We may get here because of a manual channel * change in which case we need to stop CAC * XXX no need to stop if ostate RUN but it's ok */ ieee80211_dfs_cac_stop(vap); /* fall thru... */ case IEEE80211_S_INIT: if (vap->iv_des_chan != IEEE80211_CHAN_ANYC && !IEEE80211_IS_CHAN_RADAR(vap->iv_des_chan)) { /* * Already have a channel; bypass the * scan and startup immediately. * ieee80211_create_ibss will call back to * move us to RUN state. */ ieee80211_create_ibss(vap, vap->iv_des_chan); break; } /* * Initiate a scan. We can come here as a result * of an IEEE80211_IOC_SCAN_REQ too in which case * the vap will be marked with IEEE80211_FEXT_SCANREQ * and the scan request parameters will be present * in iv_scanreq. Otherwise we do the default. */ if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { ieee80211_check_scan(vap, vap->iv_scanreq_flags, vap->iv_scanreq_duration, vap->iv_scanreq_mindwell, vap->iv_scanreq_maxdwell, vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; } else ieee80211_check_scan_current(vap); break; case IEEE80211_S_SCAN: /* * A state change requires a reset; scan. */ ieee80211_check_scan_current(vap); break; default: break; } break; case IEEE80211_S_CAC: /* * Start CAC on a DFS channel. We come here when starting * a bss on a DFS channel (see ieee80211_create_ibss). */ ieee80211_dfs_cac_start(vap); break; case IEEE80211_S_RUN: if (vap->iv_flags & IEEE80211_F_WPA) { /* XXX validate prerequisites */ } switch (ostate) { case IEEE80211_S_INIT: /* * Already have a channel; bypass the * scan and startup immediately. * Note that ieee80211_create_ibss will call * back to do a RUN->RUN state change. */ ieee80211_create_ibss(vap, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht)); /* NB: iv_bss is changed on return */ break; case IEEE80211_S_CAC: /* * NB: This is the normal state change when CAC * expires and no radar was detected; no need to * clear the CAC timer as it's already expired. */ /* fall thru... */ case IEEE80211_S_CSA: /* * Shorten inactivity timer of associated stations * to weed out sta's that don't follow a CSA. */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_csa, NULL); /* * Update bss node channel to reflect where * we landed after CSA. */ ieee80211_node_set_chan(vap->iv_bss, ieee80211_ht_adjust_channel(ic, ic->ic_curchan, ieee80211_htchanflags(vap->iv_bss->ni_chan))); /* XXX bypass debug msgs */ break; case IEEE80211_S_SCAN: case IEEE80211_S_RUN: #ifdef IEEE80211_DEBUG if (ieee80211_msg_debug(vap)) { struct ieee80211_node *ni = vap->iv_bss; ieee80211_note(vap, "synchronized with %s ssid ", ether_sprintf(ni->ni_bssid)); ieee80211_print_essid(ni->ni_essid, ni->ni_esslen); /* XXX MCS/HT */ printf(" channel %d start %uMb\n", ieee80211_chan2ieee(ic, ic->ic_curchan), IEEE80211_RATE2MBS(ni->ni_txrate)); } #endif break; default: break; } /* * Start/stop the authenticator. We delay until here * to allow configuration to happen out of order. */ if (vap->iv_auth->ia_attach != NULL) { /* XXX check failure */ vap->iv_auth->ia_attach(vap); } else if (vap->iv_auth->ia_detach != NULL) { vap->iv_auth->ia_detach(vap); } ieee80211_node_authorize(vap->iv_bss); break; case IEEE80211_S_CSA: if (ostate == IEEE80211_S_RUN && isbandchange(ic)) { /* * On a ``band change'' silently drop associated * stations as they must re-associate before they * can pass traffic (as otherwise protocol state * such as capabilities and the negotiated rate * set may/will be wrong). */ ieee80211_iterate_nodes_vap(&ic->ic_sta, vap, sta_drop, NULL); } break; default: break; } return 0; } static void hostap_deliver_data(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = vap->iv_ifp; /* clear driver/net80211 flags before passing up */ m->m_flags &= ~(M_MCAST | M_BCAST); m_clrprotoflags(m); KASSERT(vap->iv_opmode == IEEE80211_M_HOSTAP, ("gack, opmode %d", vap->iv_opmode)); /* * Do accounting. */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { m->m_flags |= M_MCAST; /* XXX M_BCAST? */ IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); /* perform as a bridge within the AP */ if ((vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0) { struct mbuf *mcopy = NULL; if (m->m_flags & M_MCAST) { mcopy = m_dup(m, IEEE80211_M_NOWAIT); if (mcopy == NULL) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); else mcopy->m_flags |= M_MCAST; } else { /* * Check if the destination is associated with the * same vap and authorized to receive traffic. * Beware of traffic destined for the vap itself; * sending it will not work; just let it be delivered * normally. */ struct ieee80211_node *sta = ieee80211_find_vap_node( &vap->iv_ic->ic_sta, vap, eh->ether_dhost); if (sta != NULL) { if (ieee80211_node_is_authorized(sta)) { /* * Beware of sending to ourself; this * needs to happen via the normal * input path. */ if (sta != vap->iv_bss) { mcopy = m; m = NULL; } } else { vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(sta, rx_unauth); } ieee80211_free_node(sta); } } if (mcopy != NULL) (void) ieee80211_vap_xmitpkt(vap, mcopy); } if (m != NULL) { struct epoch_tracker et; /* * Mark frame as coming from vap's interface. */ m->m_pkthdr.rcvif = ifp; if (m->m_flags & M_MCAST) { /* * Spam DWDS vap's w/ multicast traffic. */ /* XXX only if dwds in use? */ ieee80211_dwds_mcast(vap, m); } if (ni->ni_vlan != 0) { /* attach vlan tag */ m->m_pkthdr.ether_vtag = ni->ni_vlan; m->m_flags |= M_VLANTAG; } NET_EPOCH_ENTER(et); ifp->if_input(ifp, m); NET_EPOCH_EXIT(et); } } /* * Decide if a received management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_BEACON: return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); case IEEE80211_FC0_SUBTYPE_PROBE_REQ: return 0; } return 1; } /* * Process a received frame. The node associated with the sender * should be supplied. If nothing was found in the node table then * the caller is assumed to supply a reference to iv_bss instead. * The RSSI and a timestamp are also supplied. The RSSI data is used * during AP scanning to select a AP to associate with; it can have * any units so long as values have consistent units and higher values * mean ``better signal''. The receive timestamp is currently not used * by the 802.11 layer. */ static int hostap_input(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_rx_stats *rxs, int rssi, int nf) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct ieee80211_key *key; struct ether_header *eh; int hdrspace, need_tap = 1; /* mbuf need to be tapped. */ uint8_t dir, type, subtype, qos; uint8_t *bssid; int is_hw_decrypted = 0; int has_decrypted = 0; /* * Some devices do hardware decryption all the way through * to pretending the frame wasn't encrypted in the first place. * So, tag it appropriately so it isn't discarded inappropriately. */ if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED)) is_hw_decrypted = 1; if (m->m_flags & M_AMPDU_MPDU) { /* * Fastpath for A-MPDU reorder q resubmission. Frames * w/ M_AMPDU_MPDU marked have already passed through * here but were received out of order and been held on * the reorder queue. When resubmitted they are marked * with the M_AMPDU_MPDU flag and we can bypass most of * the normal processing. */ wh = mtod(m, struct ieee80211_frame *); type = IEEE80211_FC0_TYPE_DATA; dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; subtype = IEEE80211_FC0_SUBTYPE_QOS_DATA; hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ goto resubmit_ampdu; } KASSERT(ni != NULL, ("null node")); ni->ni_inact = ni->ni_inact_reload; type = -1; /* undefined */ if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (1): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } /* * Bit of a cheat here, we use a pointer for a 3-address * frame format but don't reference fields past outside * ieee80211_frame_min w/o first validating the data is * present. */ wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", wh->i_fc[0], wh->i_fc[1]); vap->iv_stats.is_rx_badversion++; goto err; } dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { if (dir != IEEE80211_FC1_DIR_NODS) bssid = wh->i_addr1; else if (type == IEEE80211_FC0_TYPE_CTL) bssid = wh->i_addr1; else { if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "too short (2): len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } bssid = wh->i_addr3; } /* * Validate the bssid. */ if (!(type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_BEACON) && !IEEE80211_ADDR_EQ(bssid, vap->iv_bss->ni_bssid) && !IEEE80211_ADDR_EQ(bssid, ifp->if_broadcastaddr)) { /* not interested in */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, bssid, NULL, "%s", "not to bss"); vap->iv_stats.is_rx_wrongbss++; goto out; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (IEEE80211_HAS_SEQ(type, subtype)) { uint8_t tid = ieee80211_gettid(wh); if (IEEE80211_QOS_HAS_SEQ(wh) && TID_TO_WME_AC(tid) >= WME_AC_VI) ic->ic_wme.wme_hipri_traffic++; if (! ieee80211_check_rxseq(ni, wh, bssid, rxs)) goto out; } } switch (type) { case IEEE80211_FC0_TYPE_DATA: hdrspace = ieee80211_hdrspace(ic, wh); if (m->m_len < hdrspace && (m = m_pullup(m, hdrspace)) == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, NULL, "data too short: expecting %u", hdrspace); vap->iv_stats.is_rx_tooshort++; goto out; /* XXX */ } if (!(dir == IEEE80211_FC1_DIR_TODS || (dir == IEEE80211_FC1_DIR_DSTODS && (vap->iv_flags & IEEE80211_F_DWDS)))) { if (dir != IEEE80211_FC1_DIR_DSTODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "incorrect dir 0x%x", dir); } else { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, wh, "4-address data", "%s", "DWDS not enabled"); } vap->iv_stats.is_rx_wrongdir++; goto out; } /* check if source STA is associated */ if (ni == vap->iv_bss) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "unknown src"); ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_AUTHED); vap->iv_stats.is_rx_notassoc++; goto err; } if (ni->ni_associd == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "data", "%s", "unassoc src"); IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DISASSOC, IEEE80211_REASON_NOT_ASSOCED); vap->iv_stats.is_rx_notassoc++; goto err; } /* * Check for power save state change. * XXX out-of-order A-MPDU frames? */ if (((wh->i_fc[1] & IEEE80211_FC1_PWR_MGT) ^ (ni->ni_flags & IEEE80211_NODE_PWR_MGT))) vap->iv_node_ps(ni, wh->i_fc[1] & IEEE80211_FC1_PWR_MGT); /* * For 4-address packets handle WDS discovery * notifications. Once a WDS link is setup frames * are just delivered to the WDS vap (see below). */ if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap == NULL) { if (!ieee80211_node_is_authorized(ni)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_WDS, wh, "4-address data", "%s", "unauthorized port"); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } ieee80211_dwds_discover(ni, m); return type; } /* * Handle A-MPDU re-ordering. If the frame is to be * processed directly then ieee80211_ampdu_reorder * will return 0; otherwise it has consumed the mbuf * and we should do nothing more with it. */ if ((m->m_flags & M_AMPDU) && ieee80211_ampdu_reorder(ni, m, rxs) != 0) { m = NULL; goto out; } resubmit_ampdu: /* * Handle privacy requirements. Note that we * must not be preempted from here until after * we (potentially) call ieee80211_crypto_demic; * otherwise we may violate assumptions in the * crypto cipher modules used to do delayed update * of replay sequence numbers. */ if (is_hw_decrypted || IEEE80211_IS_PROTECTED(wh)) { if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "WEP", "%s", "PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; IEEE80211_NODE_STAT(ni, rx_noprivacy); goto out; } if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* NB: stats+msgs handled in crypto_decap */ IEEE80211_NODE_STAT(ni, rx_wepfail); goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; has_decrypted = 1; } else { /* XXX M_WEP and IEEE80211_F_PRIVACY */ key = NULL; } /* * Save QoS bits for use below--before we strip the header. */ if (subtype == IEEE80211_FC0_SUBTYPE_QOS_DATA) qos = ieee80211_getqos(wh)[0]; else qos = 0; /* * Next up, any fragmentation. */ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { m = ieee80211_defrag(ni, m, hdrspace, has_decrypted); if (m == NULL) { /* Fragment dropped or frame not complete yet */ goto out; } } wh = NULL; /* no longer valid, catch any uses */ /* * Next strip any MSDU crypto bits. */ if (key != NULL && !ieee80211_crypto_demic(vap, key, m, 0)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "demic error"); vap->iv_stats.is_rx_demicfail++; IEEE80211_NODE_STAT(ni, rx_demicfail); goto out; } /* copy to listener after decrypt */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; /* * Finally, strip the 802.11 header. */ m = ieee80211_decap(vap, m, hdrspace, qos); if (m == NULL) { /* XXX mask bit to check for both */ /* don't count Null data frames as errors */ if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) goto out; IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "%s", "decap error"); vap->iv_stats.is_rx_decap++; IEEE80211_NODE_STAT(ni, rx_decap); goto err; } if (!(qos & IEEE80211_QOS_AMSDU)) eh = mtod(m, struct ether_header *); else eh = NULL; if (!ieee80211_node_is_authorized(ni)) { /* * Deny any non-PAE frames received prior to * authorization. For open/shared-key * authentication the port is mark authorized * after authentication completes. For 802.1x * the port is not marked authorized by the * authenticator until the handshake has completed. */ if (eh == NULL || eh->ether_type != htons(ETHERTYPE_PAE)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, ni->ni_macaddr, "data", "unauthorized or " "unknown port: ether type 0x%x len %u", eh == NULL ? -1 : eh->ether_type, m->m_pkthdr.len); vap->iv_stats.is_rx_unauth++; IEEE80211_NODE_STAT(ni, rx_unauth); goto err; } } else { /* * When denying unencrypted frames, discard * any non-PAE frames received without encryption. */ if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && ((has_decrypted == 0) && (m->m_flags & M_WEP) == 0) && (is_hw_decrypted == 0) && (eh == NULL || eh->ether_type != htons(ETHERTYPE_PAE))) { /* * Drop unencrypted frames. */ vap->iv_stats.is_rx_unencrypted++; IEEE80211_NODE_STAT(ni, rx_unencrypted); goto out; } } /* XXX require HT? */ if (qos & IEEE80211_QOS_AMSDU) { m = ieee80211_decap_amsdu(ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; } else { #ifdef IEEE80211_SUPPORT_SUPERG m = ieee80211_decap_fastframe(vap, ni, m); if (m == NULL) return IEEE80211_FC0_TYPE_DATA; #endif } if (dir == IEEE80211_FC1_DIR_DSTODS && ni->ni_wdsvap != NULL) ieee80211_deliver_data(ni->ni_wdsvap, ni, m); else hostap_deliver_data(vap, ni, m); return IEEE80211_FC0_TYPE_DATA; case IEEE80211_FC0_TYPE_MGT: vap->iv_stats.is_rx_mgmt++; IEEE80211_NODE_STAT(ni, rx_mgmt); if (dir != IEEE80211_FC1_DIR_NODS) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, "mgt", "incorrect dir 0x%x", dir); vap->iv_stats.is_rx_wrongdir++; goto err; } if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, ni->ni_macaddr, "mgt", "too short: len %u", m->m_pkthdr.len); vap->iv_stats.is_rx_tooshort++; goto out; } if (IEEE80211_IS_MULTICAST(wh->i_addr2)) { /* ensure return frames are unicast */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "source is multicast: %s", ether_sprintf(wh->i_addr2)); vap->iv_stats.is_rx_mgtdiscard++; /* XXX stat */ goto out; } #ifdef IEEE80211_DEBUG if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || ieee80211_msg_dumppkts(vap)) { if_printf(ifp, "received %s from %s rssi %d\n", ieee80211_mgt_subtype_name(subtype), ether_sprintf(wh->i_addr2), rssi); } #endif if (IEEE80211_IS_PROTECTED(wh)) { if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { /* * Only shared key auth frames with a challenge * should be encrypted, discard all others. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "WEP set but not permitted"); vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ goto out; } if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { /* * Discard encrypted frames when privacy is off. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "WEP set but PRIVACY off"); vap->iv_stats.is_rx_noprivacy++; goto out; } hdrspace = ieee80211_hdrspace(ic, wh); if (ieee80211_crypto_decap(ni, m, hdrspace, &key) == 0) { /* NB: stats+msgs handled in crypto_decap */ goto out; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; has_decrypted = 1; } /* * Pass the packet to radiotap before calling iv_recv_mgmt(). * Otherwise iv_recv_mgmt() might pass another packet to * radiotap, resulting in out of order packet captures. */ if (ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); need_tap = 0; vap->iv_recv_mgmt(ni, m, subtype, rxs, rssi, nf); goto out; case IEEE80211_FC0_TYPE_CTL: vap->iv_stats.is_rx_ctl++; IEEE80211_NODE_STAT(ni, rx_ctrl); vap->iv_recv_ctl(ni, m, subtype); goto out; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "bad", "frame type 0x%x", type); /* should not come here */ break; } err: if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); out: if (m != NULL) { if (need_tap && ieee80211_radiotap_active_vap(vap)) ieee80211_radiotap_rx(vap, m); m_freem(m); } return type; } static void hostap_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "open auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX */ /* * Clear any challenge text that may be there if * a previous shared key auth failed and then an * open auth is attempted. */ if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* XXX hack to workaround calling convention */ ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (IEEE80211_STATUS_ALG<<16)); return; } if (seq != IEEE80211_AUTH_OPEN_REQUEST) { vap->iv_stats.is_rx_bad_auth++; return; } /* always accept open authentication requests */ if (ni == vap->iv_bss) { ni = ieee80211_dup_bss(vap, wh->i_addr2); if (ni == NULL) return; } else if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; /* * Mark the node as requiring a valid association id * before outbound traffic is permitted. */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; if (vap->iv_acl != NULL && vap->iv_acl->iac_getpolicy(vap) == IEEE80211_MACCMD_POLICY_RADIUS) { /* * When the ACL policy is set to RADIUS we defer the * authorization to a user agent. Dispatch an event, * a subsequent MLME call will decide the fate of the * station. If the user agent is not present then the * node will be reclaimed due to inactivity. */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_ACL, ni->ni_macaddr, "%s", "station authentication defered (radius acl)"); ieee80211_notify_node_auth(ni); } else { IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni->ni_macaddr, "%s", "station authenticated (open)"); /* * When 802.1x is not in use mark the port * authorized at this point so traffic can flow. */ if (ni->ni_authmode != IEEE80211_AUTH_8021X) ieee80211_node_authorize(ni); } } static void hostap_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, uint8_t *frm, uint8_t *efrm, int rssi, int nf, uint16_t seq, uint16_t status) { struct ieee80211vap *vap = ni->ni_vap; uint8_t *challenge; int estatus; KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); /* * NB: this can happen as we allow pre-shared key * authentication to be enabled w/o wep being turned * on so that configuration of these can be done * in any order. It may be better to enforce the * ordering in which case this check would just be * for sanity/consistency. */ if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", " PRIVACY is disabled"); estatus = IEEE80211_STATUS_ALG; goto bad; } /* * Pre-shared key authentication is evil; accept * it only if explicitly configured (it is supported * mainly for compatibility with clients like Mac OS X). */ if (ni->ni_authmode != IEEE80211_AUTH_AUTO && ni->ni_authmode != IEEE80211_AUTH_SHARED) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad sta auth mode %u", ni->ni_authmode); vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ estatus = IEEE80211_STATUS_ALG; goto bad; } challenge = NULL; if (frm + 1 < efrm) { if ((frm[1] + 2) > (efrm - frm)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "ie %d/%d too long", frm[0], (frm[1] + 2) - (efrm - frm)); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (*frm == IEEE80211_ELEMID_CHALLENGE) challenge = frm; frm += frm[1] + 2; } switch (seq) { case IEEE80211_AUTH_SHARED_CHALLENGE: case IEEE80211_AUTH_SHARED_RESPONSE: if (challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "%s", "no challenge"); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (challenge[1] != IEEE80211_CHALLENGE_LEN) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad challenge len %d", challenge[1]); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } default: break; } switch (seq) { case IEEE80211_AUTH_SHARED_REQUEST: { #ifdef IEEE80211_DEBUG bool allocbs; #endif if (ni == vap->iv_bss) { ni = ieee80211_dup_bss(vap, wh->i_addr2); if (ni == NULL) { /* NB: no way to return an error */ return; } #ifdef IEEE80211_DEBUG allocbs = 1; #endif } else { if ((ni->ni_flags & IEEE80211_NODE_AREF) == 0) (void) ieee80211_ref_node(ni); #ifdef IEEE80211_DEBUG allocbs = 0; #endif } /* * Mark the node as referenced to reflect that it's * reference count has been bumped to insure it remains * after the transaction completes. */ ni->ni_flags |= IEEE80211_NODE_AREF; /* * Mark the node as requiring a valid association id * before outbound traffic is permitted. */ ni->ni_flags |= IEEE80211_NODE_ASSOCID; IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; if (!ieee80211_alloc_challenge(ni)) { /* NB: don't return error so they rexmit */ return; } net80211_get_random_bytes(ni->ni_challenge, IEEE80211_CHALLENGE_LEN); IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "shared key %sauth request", allocbs ? "" : "re"); /* * When the ACL policy is set to RADIUS we defer the * authorization to a user agent. Dispatch an event, * a subsequent MLME call will decide the fate of the * station. If the user agent is not present then the * node will be reclaimed due to inactivity. */ if (vap->iv_acl != NULL && vap->iv_acl->iac_getpolicy(vap) == IEEE80211_MACCMD_POLICY_RADIUS) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_ACL, ni->ni_macaddr, "%s", "station authentication defered (radius acl)"); ieee80211_notify_node_auth(ni); return; } break; } case IEEE80211_AUTH_SHARED_RESPONSE: if (ni == vap->iv_bss) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "unknown station"); /* NB: don't send a response */ return; } if (ni->ni_challenge == NULL) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "no challenge recorded"); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } if (memcmp(ni->ni_challenge, &challenge[2], challenge[1]) != 0) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key response", "%s", "challenge mismatch"); vap->iv_stats.is_rx_auth_fail++; estatus = IEEE80211_STATUS_CHALLENGE; goto bad; } IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "%s", "station authenticated (shared key)"); ieee80211_node_authorize(ni); break; default: IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, ni->ni_macaddr, "shared key auth", "bad seq %d", seq); vap->iv_stats.is_rx_bad_auth++; estatus = IEEE80211_STATUS_SEQUENCE; goto bad; } IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); return; bad: /* * Send an error response; but only when operating as an AP. */ /* XXX hack to workaround calling convention */ ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq + 1) | (estatus<<16)); } /* * Convert a WPA cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int wpa_cipher(const uint8_t *sel, uint8_t *keylen, uint8_t *cipher) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) uint32_t w = le32dec(sel); switch (w) { case WPA_SEL(WPA_CSE_NULL): *cipher = IEEE80211_CIPHER_NONE; break; case WPA_SEL(WPA_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case WPA_SEL(WPA_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case WPA_SEL(WPA_CSE_TKIP): *cipher = IEEE80211_CIPHER_TKIP; break; case WPA_SEL(WPA_CSE_CCMP): *cipher = IEEE80211_CIPHER_AES_CCM; break; default: return (EINVAL); } return (0); #undef WPA_SEL } /* * Convert a WPA key management/authentication algorithm * to an internal code. */ static int wpa_keymgmt(const uint8_t *sel) { #define WPA_SEL(x) (((x)<<24)|WPA_OUI) uint32_t w = le32dec(sel); switch (w) { case WPA_SEL(WPA_ASE_8021X_UNSPEC): return WPA_ASE_8021X_UNSPEC; case WPA_SEL(WPA_ASE_8021X_PSK): return WPA_ASE_8021X_PSK; case WPA_SEL(WPA_ASE_NONE): return WPA_ASE_NONE; } return 0; /* NB: so is discarded */ #undef WPA_SEL } /* * Parse a WPA information element to collect parameters. * Note that we do not validate security parameters; that * is handled by the authenticator; the parsing done here * is just for internal use in making operational decisions. */ static int ieee80211_parse_wpa(struct ieee80211vap *vap, const uint8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { uint8_t len = frm[1]; uint32_t w; int error, n; /* * Check the length once for fixed parts: OUI, type, * version, mcast cipher, and 2 selector counts. * Other, variable-length data, must be checked separately. */ if ((vap->iv_flags & IEEE80211_F_WPA1) == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not WPA, flags 0x%x", vap->iv_flags); return IEEE80211_REASON_IE_INVALID; } if (len < 14) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } frm += 6, len -= 4; /* NB: len is payload only */ /* NB: iswpaoui already validated the OUI and type */ w = le16dec(frm); if (w != WPA_VERSION) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "bad version %u", w); return IEEE80211_REASON_IE_INVALID; } frm += 2, len -= 2; memset(rsn, 0, sizeof(*rsn)); /* multicast/group cipher */ error = wpa_cipher(frm, &rsn->rsn_mcastkeylen, &rsn->rsn_mcastcipher); if (error != 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "unknown mcast cipher suite %08X", le32dec(frm)); return IEEE80211_REASON_GROUP_CIPHER_INVALID; } frm += 4, len -= 4; /* unicast ciphers */ n = le16dec(frm); frm += 2, len -= 2; if (len < n*4+2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "ucast cipher data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { uint8_t cipher; error = wpa_cipher(frm, &rsn->rsn_ucastkeylen, &cipher); if (error == 0) w |= 1 << cipher; frm += 4, len -= 4; } if (w == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "no usable pairwise cipher suite found (w=%d)", w); return IEEE80211_REASON_PAIRWISE_CIPHER_INVALID; } /* XXX other? */ if (w & (1 << IEEE80211_CIPHER_AES_CCM)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; else rsn->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; /* key management algorithms */ n = le16dec(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= wpa_keymgmt(frm); frm += 4, len -= 4; } if (w & WPA_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = WPA_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = WPA_ASE_8021X_PSK; if (len > 2) /* optional capabilities */ rsn->rsn_caps = le16dec(frm); return 0; } /* * Convert an RSN cipher selector OUI to an internal * cipher algorithm. Where appropriate we also * record any key length. */ static int rsn_cipher(const uint8_t *sel, uint8_t *keylen, uint8_t *cipher) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) uint32_t w = le32dec(sel); switch (w) { case RSN_SEL(RSN_CSE_NULL): *cipher = IEEE80211_CIPHER_NONE; break; case RSN_SEL(RSN_CSE_WEP40): if (keylen) *keylen = 40 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case RSN_SEL(RSN_CSE_WEP104): if (keylen) *keylen = 104 / NBBY; *cipher = IEEE80211_CIPHER_WEP; break; case RSN_SEL(RSN_CSE_TKIP): *cipher = IEEE80211_CIPHER_TKIP; break; case RSN_SEL(RSN_CSE_CCMP): *cipher = IEEE80211_CIPHER_AES_CCM; break; case RSN_SEL(RSN_CSE_WRAP): *cipher = IEEE80211_CIPHER_AES_OCB; break; default: return (EINVAL); } return (0); #undef WPA_SEL } /* * Convert an RSN key management/authentication algorithm * to an internal code. */ static int rsn_keymgmt(const uint8_t *sel) { #define RSN_SEL(x) (((x)<<24)|RSN_OUI) uint32_t w = le32dec(sel); switch (w) { case RSN_SEL(RSN_ASE_8021X_UNSPEC): return RSN_ASE_8021X_UNSPEC; case RSN_SEL(RSN_ASE_8021X_PSK): return RSN_ASE_8021X_PSK; case RSN_SEL(RSN_ASE_NONE): return RSN_ASE_NONE; } return 0; /* NB: so is discarded */ #undef RSN_SEL } /* * Parse a WPA/RSN information element to collect parameters * and validate the parameters against what has been * configured for the system. */ static int ieee80211_parse_rsn(struct ieee80211vap *vap, const uint8_t *frm, struct ieee80211_rsnparms *rsn, const struct ieee80211_frame *wh) { uint8_t len = frm[1]; uint32_t w; int error, n; /* * Check the length once for fixed parts: * version, mcast cipher, and 2 selector counts. * Other, variable-length data, must be checked separately. */ if ((vap->iv_flags & IEEE80211_F_WPA2) == 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "WPA", "not RSN, flags 0x%x", vap->iv_flags); return IEEE80211_REASON_IE_INVALID; } /* XXX may be shorter */ if (len < 10) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "too short, len %u", len); return IEEE80211_REASON_IE_INVALID; } frm += 2; w = le16dec(frm); if (w != RSN_VERSION) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "bad version %u", w); return IEEE80211_REASON_UNSUPP_RSN_IE_VERSION; } frm += 2, len -= 2; memset(rsn, 0, sizeof(*rsn)); /* multicast/group cipher */ error = rsn_cipher(frm, &rsn->rsn_mcastkeylen, &rsn->rsn_mcastcipher); if (error != 0) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "unknown mcast cipher suite %08X", le32dec(frm)); return IEEE80211_REASON_GROUP_CIPHER_INVALID; } if (rsn->rsn_mcastcipher == IEEE80211_CIPHER_NONE) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "invalid mcast cipher suite %d", rsn->rsn_mcastcipher); return IEEE80211_REASON_GROUP_CIPHER_INVALID; } frm += 4, len -= 4; /* unicast ciphers */ n = le16dec(frm); frm += 2, len -= 2; if (len < n*4+2) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "ucast cipher data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { uint8_t cipher; error = rsn_cipher(frm, &rsn->rsn_ucastkeylen, &cipher); if (error == 0) w |= 1 << cipher; frm += 4, len -= 4; } if (w & (1 << IEEE80211_CIPHER_AES_CCM)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_CCM; else if (w & (1 << IEEE80211_CIPHER_AES_OCB)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_AES_OCB; else if (w & (1 << IEEE80211_CIPHER_TKIP)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_TKIP; else if ((w & (1 << IEEE80211_CIPHER_NONE)) && (rsn->rsn_mcastcipher == IEEE80211_CIPHER_WEP || rsn->rsn_mcastcipher == IEEE80211_CIPHER_TKIP)) rsn->rsn_ucastcipher = IEEE80211_CIPHER_NONE; else { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "no usable pairwise cipher suite found (w=%d)", w); return IEEE80211_REASON_PAIRWISE_CIPHER_INVALID; } /* key management algorithms */ n = le16dec(frm); frm += 2, len -= 2; if (len < n*4) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_WPA, wh, "RSN", "key mgmt alg data too short; len %u, n %u", len, n); return IEEE80211_REASON_IE_INVALID; } w = 0; for (; n > 0; n--) { w |= rsn_keymgmt(frm); frm += 4, len -= 4; } if (w & RSN_ASE_8021X_UNSPEC) rsn->rsn_keymgmt = RSN_ASE_8021X_UNSPEC; else rsn->rsn_keymgmt = RSN_ASE_8021X_PSK; /* optional RSN capabilities */ if (len > 2) rsn->rsn_caps = le16dec(frm); /* XXXPMKID */ return 0; } /* * WPA/802.11i association request processing. */ static int wpa_assocreq(struct ieee80211_node *ni, struct ieee80211_rsnparms *rsnparms, const struct ieee80211_frame *wh, const uint8_t *wpa, const uint8_t *rsn, uint16_t capinfo) { struct ieee80211vap *vap = ni->ni_vap; uint8_t reason; int badwparsn; ni->ni_flags &= ~(IEEE80211_NODE_WPS|IEEE80211_NODE_TSN); if (wpa == NULL && rsn == NULL) { if (vap->iv_flags_ext & IEEE80211_FEXT_WPS) { /* * W-Fi Protected Setup (WPS) permits * clients to associate and pass EAPOL frames * to establish initial credentials. */ ni->ni_flags |= IEEE80211_NODE_WPS; return 1; } if ((vap->iv_flags_ext & IEEE80211_FEXT_TSN) && (capinfo & IEEE80211_CAPINFO_PRIVACY)) { /* * Transitional Security Network. Permits clients * to associate and use WEP while WPA is configured. */ ni->ni_flags |= IEEE80211_NODE_TSN; return 1; } IEEE80211_DISCARD(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, wh, NULL, "%s", "no WPA/RSN IE in association request"); vap->iv_stats.is_rx_assoc_badwpaie++; reason = IEEE80211_REASON_IE_INVALID; goto bad; } /* assert right association security credentials */ badwparsn = 0; /* NB: to silence compiler */ switch (vap->iv_flags & IEEE80211_F_WPA) { case IEEE80211_F_WPA1: badwparsn = (wpa == NULL); break; case IEEE80211_F_WPA2: badwparsn = (rsn == NULL); break; case IEEE80211_F_WPA1|IEEE80211_F_WPA2: badwparsn = (wpa == NULL && rsn == NULL); break; } if (badwparsn) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, wh, NULL, "%s", "missing WPA/RSN IE in association request"); vap->iv_stats.is_rx_assoc_badwpaie++; reason = IEEE80211_REASON_IE_INVALID; goto bad; } /* * Parse WPA/RSN information element. */ if (wpa != NULL) reason = ieee80211_parse_wpa(vap, wpa, rsnparms, wh); else reason = ieee80211_parse_rsn(vap, rsn, rsnparms, wh); if (reason != 0) { /* XXX wpa->rsn fallback? */ /* XXX distinguish WPA/RSN? */ vap->iv_stats.is_rx_assoc_badwpaie++; goto bad; } IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_WPA, ni, "%s ie: mc %u/%u uc %u/%u key %u caps 0x%x", wpa != NULL ? "WPA" : "RSN", rsnparms->rsn_mcastcipher, rsnparms->rsn_mcastkeylen, rsnparms->rsn_ucastcipher, rsnparms->rsn_ucastkeylen, rsnparms->rsn_keymgmt, rsnparms->rsn_caps); return 1; bad: ieee80211_node_deauth(ni, reason); return 0; } /* XXX find a better place for definition */ struct l2_update_frame { struct ether_header eh; uint8_t dsap; uint8_t ssap; uint8_t control; uint8_t xid[3]; } __packed; /* * Deliver a TGf L2UF frame on behalf of a station. * This primes any bridge when the station is roaming * between ap's on the same wired network. */ static void ieee80211_deliver_l2uf(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ifnet *ifp = vap->iv_ifp; struct mbuf *m; struct l2_update_frame *l2uf; struct ether_header *eh; m = m_gethdr(IEEE80211_M_NOWAIT, MT_DATA); if (m == NULL) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "%s", "no mbuf for l2uf frame"); vap->iv_stats.is_rx_nobuf++; /* XXX not right */ return; } l2uf = mtod(m, struct l2_update_frame *); eh = &l2uf->eh; /* dst: Broadcast address */ IEEE80211_ADDR_COPY(eh->ether_dhost, ifp->if_broadcastaddr); /* src: associated STA */ IEEE80211_ADDR_COPY(eh->ether_shost, ni->ni_macaddr); eh->ether_type = htons(sizeof(*l2uf) - sizeof(*eh)); l2uf->dsap = 0; l2uf->ssap = 0; l2uf->control = 0xf5; l2uf->xid[0] = 0x81; l2uf->xid[1] = 0x80; l2uf->xid[2] = 0x00; m->m_pkthdr.len = m->m_len = sizeof(*l2uf); hostap_deliver_data(vap, ni, m); } static void ratesetmismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp, const char *tag, int rate) { IEEE80211_NOTE_MAC(ni->ni_vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s rate set mismatch, rate/MCS %d", reassoc ? "reassoc" : "assoc", tag, rate & IEEE80211_RATE_VAL); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_BASIC_RATE); ieee80211_node_leave(ni); } static void capinfomismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp, const char *tag, int capinfo) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s mismatch 0x%x", reassoc ? "reassoc" : "assoc", tag, capinfo); IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_CAPINFO); ieee80211_node_leave(ni); vap->iv_stats.is_rx_assoc_capmismatch++; } static void htcapmismatch(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int reassoc, int resp) { IEEE80211_NOTE_MAC(ni->ni_vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, %s missing HT ie", reassoc ? "reassoc" : "assoc"); /* XXX no better code */ IEEE80211_SEND_MGMT(ni, resp, IEEE80211_STATUS_MISSING_HT_CAPS); ieee80211_node_leave(ni); } static void authalgreject(struct ieee80211_node *ni, const struct ieee80211_frame *wh, int algo, int seq, int status) { struct ieee80211vap *vap = ni->ni_vap; IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "unsupported alg %d", algo); vap->iv_stats.is_rx_auth_unsupported++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, seq | (status << 16)); } static __inline int ishtmixed(const uint8_t *ie) { const struct ieee80211_ie_htinfo *ht = (const struct ieee80211_ie_htinfo *) ie; return (ht->hi_byte2 & IEEE80211_HTINFO_OPMODE) == IEEE80211_HTINFO_OPMODE_MIXED; } static int is11bclient(const uint8_t *rates, const uint8_t *xrates) { static const uint32_t brates = (1<<2*1)|(1<<2*2)|(1<<11)|(1<<2*11); int i; /* NB: the 11b clients we care about will not have xrates */ if (xrates != NULL || rates == NULL) return 0; for (i = 0; i < rates[1]; i++) { int r = rates[2+i] & IEEE80211_RATE_VAL; if (r > 2*11 || ((1<ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; uint8_t *frm, *efrm, *sfrm; uint8_t *ssid, *rates, *xrates, *wpa, *rsn, *wme, *ath, *htcap; uint8_t *vhtcap, *vhtinfo; int reassoc, resp; uint8_t rate; wh = mtod(m0, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m0, uint8_t *) + m0->m_len; switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: /* * We process beacon/probe response frames when scanning; * otherwise we check beacon frames for overlapping non-ERP * BSS in 11g and/or overlapping legacy BSS when in HT. */ if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* FALLTHROUGH */ case IEEE80211_FC0_SUBTYPE_BEACON: { struct ieee80211_scanparams scan; /* NB: accept off-channel frames */ /* XXX TODO: use rxstatus to determine off-channel details */ if (ieee80211_parse_beacon(ni, m0, ic->ic_curchan, &scan) &~ IEEE80211_BPARSE_OFFCHAN) return; /* * Count frame now that we know it's to be processed. */ if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { vap->iv_stats.is_rx_beacon++; /* XXX remove */ IEEE80211_NODE_STAT(ni, rx_beacons); } else IEEE80211_NODE_STAT(ni, rx_proberesp); /* * If scanning, just pass information to the scan module. */ if (ic->ic_flags & IEEE80211_F_SCAN) { if (scan.status == 0 && /* NB: on channel */ (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN)) { /* * Actively scanning a channel marked passive; * send a probe request now that we know there * is 802.11 traffic present. * * XXX check if the beacon we recv'd gives * us what we need and suppress the probe req */ ieee80211_probe_curchan(vap, 1); ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; } ieee80211_add_scan(vap, ic->ic_curchan, &scan, wh, subtype, rssi, nf); return; } /* * Check beacon for overlapping bss w/ non ERP stations. * If we detect one and protection is configured but not * enabled, enable it and start a timer that'll bring us * out if we stop seeing the bss. */ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && scan.status == 0 && /* NB: on-channel */ ((scan.erp & 0x100) == 0 || /* NB: no ERP, 11b sta*/ (scan.erp & IEEE80211_ERP_NON_ERP_PRESENT))) { vap->iv_lastnonerp = ticks; vap->iv_flags_ext |= IEEE80211_FEXT_NONERP_PR; /* * XXX TODO: this may need to check all VAPs? */ if (vap->iv_protmode != IEEE80211_PROT_NONE && (vap->iv_flags & IEEE80211_F_USEPROT) == 0) { IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_ASSOC, wh, "non-ERP present on channel %d " "(saw erp 0x%x from channel %d), " "enable use of protection", ic->ic_curchan->ic_ieee, scan.erp, scan.chan); vap->iv_flags |= IEEE80211_F_USEPROT; ieee80211_vap_update_erp_protmode(vap); } } /* * Check beacon for non-HT station on HT channel * and update HT BSS occupancy as appropriate. */ if (IEEE80211_IS_CHAN_HT(ic->ic_curchan)) { if (scan.status & IEEE80211_BPARSE_OFFCHAN) { /* * Off control channel; only check frames * that come in the extension channel when * operating w/ HT40. */ if (!IEEE80211_IS_CHAN_HT40(ic->ic_curchan)) break; if (scan.chan != ic->ic_curchan->ic_extieee) break; } if (scan.htinfo == NULL) { ieee80211_htprot_update(vap, IEEE80211_HTINFO_OPMODE_PROTOPT | IEEE80211_HTINFO_NONHT_PRESENT); } else if (ishtmixed(scan.htinfo)) { /* XXX? take NONHT_PRESENT from beacon? */ ieee80211_htprot_update(vap, IEEE80211_HTINFO_OPMODE_MIXED | IEEE80211_HTINFO_NONHT_PRESENT); } } break; } case IEEE80211_FC0_SUBTYPE_PROBE_REQ: if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } /* * Consult the ACL policy module if setup. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; return; } /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates */ ssid = rates = xrates = NULL; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if ((vap->iv_flags & IEEE80211_F_HIDESSID) && ssid[1] == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "no ssid with ssid suppression enabled"); vap->iv_stats.is_rx_ssidmismatch++; /*XXX*/ return; } /* XXX find a better class or define it's own */ IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_INPUT, wh->i_addr2, "%s", "recv probe req"); /* * Some legacy 11b clients cannot hack a complete * probe response frame. When the request includes * only a bare-bones rate set, communicate this to * the transmit side. */ ieee80211_send_proberesp(vap, wh->i_addr2, is11bclient(rates, xrates) ? IEEE80211_SEND_LEGACY_11B : 0); break; case IEEE80211_FC0_SUBTYPE_AUTH: { uint16_t algo, seq, status; if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "%s", "wrong bssid"); vap->iv_stats.is_rx_wrongbss++; /*XXX unique stat?*/ return; } /* * auth frame format * [2] algorithm * [2] sequence * [2] status * [tlv*] challenge */ IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); algo = le16toh(*(uint16_t *)frm); seq = le16toh(*(uint16_t *)(frm + 2)); status = le16toh(*(uint16_t *)(frm + 4)); IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, "recv auth frame with algorithm %d seq %d", algo, seq); /* * Consult the ACL policy module if setup. */ if (vap->iv_acl != NULL && !vap->iv_acl->iac_check(vap, wh)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ACL, wh, NULL, "%s", "disallowed by ACL"); vap->iv_stats.is_rx_acl++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, (seq+1) | (IEEE80211_STATUS_UNSPECIFIED<<16)); return; } if (vap->iv_flags & IEEE80211_F_COUNTERM) { IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, wh, NULL, "%s", "TKIP countermeasures enabled"); vap->iv_stats.is_rx_auth_countermeasures++; ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_AUTH, IEEE80211_REASON_MIC_FAILURE); return; } if (algo == IEEE80211_AUTH_ALG_SHARED) hostap_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_OPEN) hostap_auth_open(ni, wh, rssi, nf, seq, status); else if (algo == IEEE80211_AUTH_ALG_LEAP) { authalgreject(ni, wh, algo, seq+1, IEEE80211_STATUS_ALG); return; } else { /* * We assume that an unknown algorithm is the result * of a decryption failure on a shared key auth frame; * return a status code appropriate for that instead * of IEEE80211_STATUS_ALG. * * NB: a seq# of 4 is intentional; the decrypted * frame likely has a bogus seq value. */ authalgreject(ni, wh, algo, 4, IEEE80211_STATUS_CHALLENGE); return; } break; } case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: { uint16_t capinfo, lintval; struct ieee80211_rsnparms rsnparms; if (vap->iv_state != IEEE80211_S_RUN) { vap->iv_stats.is_rx_mgtdiscard++; return; } if (!IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_bss->ni_bssid)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, NULL, "%s", "wrong bssid"); vap->iv_stats.is_rx_assoc_bss++; return; } if (subtype == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { reassoc = 1; resp = IEEE80211_FC0_SUBTYPE_REASSOC_RESP; } else { reassoc = 0; resp = IEEE80211_FC0_SUBTYPE_ASSOC_RESP; } if (ni == vap->iv_bss) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ANY, wh->i_addr2, "deny %s request, sta not authenticated", reassoc ? "reassoc" : "assoc"); ieee80211_send_error(ni, wh->i_addr2, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_ASSOC_NOT_AUTHED); vap->iv_stats.is_rx_assoc_notauth++; return; } /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [tlv] WPA or RSN * [tlv] HT capabilities * [tlv] Atheros capabilities */ IEEE80211_VERIFY_LENGTH(efrm - frm, (reassoc ? 10 : 4), return); capinfo = le16toh(*(uint16_t *)frm); frm += 2; lintval = le16toh(*(uint16_t *)frm); frm += 2; if (reassoc) frm += 6; /* ignore current AP info */ ssid = rates = xrates = wpa = rsn = wme = ath = htcap = NULL; vhtcap = vhtinfo = NULL; sfrm = frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); switch (*frm) { case IEEE80211_ELEMID_SSID: ssid = frm; break; case IEEE80211_ELEMID_RATES: rates = frm; break; case IEEE80211_ELEMID_XRATES: xrates = frm; break; case IEEE80211_ELEMID_RSN: rsn = frm; break; case IEEE80211_ELEMID_HTCAP: htcap = frm; break; case IEEE80211_ELEMID_VHT_CAP: vhtcap = frm; break; case IEEE80211_ELEMID_VHT_OPMODE: vhtinfo = frm; break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) wpa = frm; else if (iswmeinfo(frm)) wme = frm; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(frm)) ath = frm; #endif else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { if (ishtcapoui(frm) && htcap == NULL) htcap = frm; } break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); if (xrates != NULL) IEEE80211_VERIFY_ELEMENT(xrates, IEEE80211_RATE_MAXSIZE - rates[1], return); IEEE80211_VERIFY_ELEMENT(ssid, IEEE80211_NWID_LEN, return); IEEE80211_VERIFY_SSID(vap->iv_bss, ssid, return); if (htcap != NULL) { IEEE80211_VERIFY_LENGTH(htcap[1], htcap[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htcap)-2 : sizeof(struct ieee80211_ie_htcap)-2, return); /* XXX just NULL out? */ } /* Validate VHT IEs */ if (vhtcap != NULL) { IEEE80211_VERIFY_LENGTH(vhtcap[1], - sizeof(struct ieee80211_ie_vhtcap) - 2, + sizeof(struct ieee80211_vht_cap), return); } if (vhtinfo != NULL) { IEEE80211_VERIFY_LENGTH(vhtinfo[1], - sizeof(struct ieee80211_ie_vht_operation) - 2, + sizeof(struct ieee80211_vht_operation), return); } if ((vap->iv_flags & IEEE80211_F_WPA) && !wpa_assocreq(ni, &rsnparms, wh, wpa, rsn, capinfo)) return; /* discard challenge after association */ if (ni->ni_challenge != NULL) { IEEE80211_FREE(ni->ni_challenge, M_80211_NODE); ni->ni_challenge = NULL; } /* NB: 802.11 spec says to ignore station's privacy bit */ if ((capinfo & IEEE80211_CAPINFO_ESS) == 0) { capinfomismatch(ni, wh, reassoc, resp, "capability", capinfo); return; } /* * Disallow re-associate w/ invalid slot time setting. */ if (ni->ni_associd != 0 && IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan) && ((ni->ni_capinfo ^ capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME)) { capinfomismatch(ni, wh, reassoc, resp, "slot time", capinfo); return; } rate = ieee80211_setup_rates(ni, rates, xrates, IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); if (rate & IEEE80211_RATE_BASIC) { ratesetmismatch(ni, wh, reassoc, resp, "legacy", rate); vap->iv_stats.is_rx_assoc_norate++; return; } /* * If constrained to 11g-only stations reject an * 11b-only station. We cheat a bit here by looking * at the max negotiated xmit rate and assuming anyone * with a best rate <24Mb/s is an 11b station. */ if ((vap->iv_flags & IEEE80211_F_PUREG) && rate < 48) { ratesetmismatch(ni, wh, reassoc, resp, "11g", rate); vap->iv_stats.is_rx_assoc_norate++; return; } /* * Do HT rate set handling and setup HT node state. */ ni->ni_chan = vap->iv_bss->ni_chan; /* VHT */ if (IEEE80211_IS_CHAN_VHT(ni->ni_chan) && vhtcap != NULL && vhtinfo != NULL) { /* XXX TODO; see below */ printf("%s: VHT TODO!\n", __func__); ieee80211_vht_node_init(ni); ieee80211_vht_update_cap(ni, vhtcap, vhtinfo); } else if (ni->ni_flags & IEEE80211_NODE_VHT) ieee80211_vht_node_cleanup(ni); /* HT */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { rate = ieee80211_setup_htrates(ni, htcap, IEEE80211_F_DOFMCS | IEEE80211_F_DONEGO | IEEE80211_F_DOBRS); if (rate & IEEE80211_RATE_BASIC) { ratesetmismatch(ni, wh, reassoc, resp, "HT", rate); vap->iv_stats.is_ht_assoc_norate++; return; } ieee80211_ht_node_init(ni); ieee80211_ht_updatehtcap(ni, htcap); } else if (ni->ni_flags & IEEE80211_NODE_HT) ieee80211_ht_node_cleanup(ni); /* Finally - this will use HT/VHT info to change node channel */ if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && htcap != NULL) { ieee80211_ht_updatehtcap_final(ni); } #ifdef IEEE80211_SUPPORT_SUPERG /* Always do ff node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); #endif /* * Allow AMPDU operation only with unencrypted traffic * or AES-CCM; the 11n spec only specifies these ciphers * so permitting any others is undefined and can lead * to interoperability problems. */ if ((ni->ni_flags & IEEE80211_NODE_HT) && (((vap->iv_flags & IEEE80211_F_WPA) && rsnparms.rsn_ucastcipher != IEEE80211_CIPHER_AES_CCM) || (vap->iv_flags & (IEEE80211_F_WPA|IEEE80211_F_PRIVACY)) == IEEE80211_F_PRIVACY)) { IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC | IEEE80211_MSG_11N, ni, "disallow HT use because WEP or TKIP requested, " "capinfo 0x%x ucastcipher %d", capinfo, rsnparms.rsn_ucastcipher); ieee80211_ht_node_cleanup(ni); #ifdef IEEE80211_SUPPORT_SUPERG /* Always do ff node cleanup; for A-MSDU */ ieee80211_ff_node_cleanup(ni); #endif vap->iv_stats.is_ht_assoc_downgrade++; } /* * If constrained to 11n-only stations reject legacy stations. */ if ((vap->iv_flags_ht & IEEE80211_FHT_PUREN) && (ni->ni_flags & IEEE80211_NODE_HT) == 0) { htcapmismatch(ni, wh, reassoc, resp); vap->iv_stats.is_ht_assoc_nohtcap++; return; } IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); ni->ni_noise = nf; ni->ni_intval = lintval; ni->ni_capinfo = capinfo; ni->ni_fhdwell = vap->iv_bss->ni_fhdwell; ni->ni_fhindex = vap->iv_bss->ni_fhindex; /* * Store the IEs. * XXX maybe better to just expand */ if (ieee80211_ies_init(&ni->ni_ies, sfrm, efrm - sfrm)) { #define setie(_ie, _off) ieee80211_ies_setie(ni->ni_ies, _ie, _off) if (wpa != NULL) setie(wpa_ie, wpa - sfrm); if (rsn != NULL) setie(rsn_ie, rsn - sfrm); if (htcap != NULL) setie(htcap_ie, htcap - sfrm); if (wme != NULL) { setie(wme_ie, wme - sfrm); /* * Mark node as capable of QoS. */ ni->ni_flags |= IEEE80211_NODE_QOS; if (ieee80211_parse_wmeie(wme, wh, ni) > 0) { if (ni->ni_uapsd != 0) ni->ni_flags |= IEEE80211_NODE_UAPSD; else ni->ni_flags &= ~IEEE80211_NODE_UAPSD; } } else ni->ni_flags &= ~(IEEE80211_NODE_QOS | IEEE80211_NODE_UAPSD); #ifdef IEEE80211_SUPPORT_SUPERG if (ath != NULL) { setie(ath_ie, ath - sfrm); /* * Parse ATH station parameters. */ ieee80211_parse_ath(ni, ni->ni_ies.ath_ie); } else #endif ni->ni_ath_flags = 0; #undef setie } else { ni->ni_flags &= ~IEEE80211_NODE_QOS; ni->ni_flags &= ~IEEE80211_NODE_UAPSD; ni->ni_ath_flags = 0; } ieee80211_node_join(ni, resp); ieee80211_deliver_l2uf(ni); break; } case IEEE80211_FC0_SUBTYPE_DEAUTH: case IEEE80211_FC0_SUBTYPE_DISASSOC: { #ifdef IEEE80211_DEBUG uint16_t reason; #endif if (vap->iv_state != IEEE80211_S_RUN || /* NB: can happen when in promiscuous mode */ !IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { vap->iv_stats.is_rx_mgtdiscard++; break; } /* * deauth/disassoc frame format * [2] reason */ IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); #ifdef IEEE80211_DEBUG reason = le16toh(*(uint16_t *)frm); #endif if (subtype == IEEE80211_FC0_SUBTYPE_DEAUTH) { vap->iv_stats.is_rx_deauth++; IEEE80211_NODE_STAT(ni, rx_deauth); } else { vap->iv_stats.is_rx_disassoc++; IEEE80211_NODE_STAT(ni, rx_disassoc); } IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "recv %s (reason: %d (%s))", ieee80211_mgt_subtype_name(subtype), reason, ieee80211_reason_to_string(reason)); if (ni != vap->iv_bss) ieee80211_node_leave(ni); break; } case IEEE80211_FC0_SUBTYPE_ACTION: case IEEE80211_FC0_SUBTYPE_ACTION_NOACK: if (ni == vap->iv_bss) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "unknown node"); vap->iv_stats.is_rx_mgtdiscard++; } else if (!IEEE80211_ADDR_EQ(vap->iv_myaddr, wh->i_addr1) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not for us"); vap->iv_stats.is_rx_mgtdiscard++; } else if (vap->iv_state != IEEE80211_S_RUN) { IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "wrong state %s", ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_rx_mgtdiscard++; } else { if (ieee80211_parse_action(ni, m0) == 0) (void)ic->ic_recv_action(ni, wh, frm, efrm); } break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: case IEEE80211_FC0_SUBTYPE_TIMING_ADV: case IEEE80211_FC0_SUBTYPE_ATIM: IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, NULL, "%s", "not handled"); vap->iv_stats.is_rx_mgtdiscard++; break; default: IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, wh, "mgt", "subtype 0x%x not handled", subtype); vap->iv_stats.is_rx_badsubtype++; break; } } static void hostap_recv_ctl(struct ieee80211_node *ni, struct mbuf *m, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PS_POLL: ni->ni_vap->iv_recv_pspoll(ni, m); break; case IEEE80211_FC0_SUBTYPE_BAR: ieee80211_recv_bar(ni, m); break; } } /* * Process a received ps-poll frame. */ void ieee80211_recv_pspoll(struct ieee80211_node *ni, struct mbuf *m0) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame_min *wh; struct mbuf *m; uint16_t aid; int qlen; wh = mtod(m0, struct ieee80211_frame_min *); if (ni->ni_associd == 0) { IEEE80211_DISCARD(vap, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, NULL, "%s", "unassociated station"); vap->iv_stats.is_ps_unassoc++; IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_NOT_ASSOCED); return; } aid = le16toh(*(uint16_t *)wh->i_dur); if (aid != ni->ni_associd) { IEEE80211_DISCARD(vap, IEEE80211_MSG_POWER | IEEE80211_MSG_DEBUG, (struct ieee80211_frame *) wh, NULL, "aid mismatch: sta aid 0x%x poll aid 0x%x", ni->ni_associd, aid); vap->iv_stats.is_ps_badaid++; /* * NB: We used to deauth the station but it turns out * the Blackberry Curve 8230 (and perhaps other devices) * sometimes send the wrong AID when WME is negotiated. * Being more lenient here seems ok as we already check * the station is associated and we only return frames * queued for the station (i.e. we don't use the AID). */ return; } /* Okay, take the first queued packet and put it out... */ m = ieee80211_node_psq_dequeue(ni, &qlen); if (m == NULL) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_POWER, wh->i_addr2, "%s", "recv ps-poll, but queue empty"); ieee80211_send_nulldata(ieee80211_ref_node(ni)); vap->iv_stats.is_ps_qempty++; /* XXX node stat */ if (vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); /* just in case */ return; } /* * If there are more packets, set the more packets bit * in the packet dispatched to the station; otherwise * turn off the TIM bit. */ if (qlen != 0) { IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "recv ps-poll, send packet, %u still queued", qlen); m->m_flags |= M_MORE_DATA; } else { IEEE80211_NOTE(vap, IEEE80211_MSG_POWER, ni, "%s", "recv ps-poll, send packet, queue empty"); if (vap->iv_set_tim != NULL) vap->iv_set_tim(ni, 0); } m->m_flags |= M_PWR_SAV; /* bypass PS handling */ /* * Do the right thing; if it's an encap'ed frame then * call ieee80211_parent_xmitpkt() else * call ieee80211_vap_xmitpkt(). */ if (m->m_flags & M_ENCAP) { (void) ieee80211_parent_xmitpkt(ic, m); } else { (void) ieee80211_vap_xmitpkt(vap, m); } } diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c index a4bfe49bd8c4..925918c872dc 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -1,1061 +1,1061 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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. * * 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 #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_MESH #include #endif #include #ifdef INET #include #include #endif static void ieee80211_process_mimo(struct ieee80211_node *ni, struct ieee80211_rx_stats *rx) { int i; /* Verify the required MIMO bits are set */ if ((rx->r_flags & (IEEE80211_R_C_CHAIN | IEEE80211_R_C_NF | IEEE80211_R_C_RSSI)) != (IEEE80211_R_C_CHAIN | IEEE80211_R_C_NF | IEEE80211_R_C_RSSI)) return; /* XXX This assumes the MIMO radios have both ctl and ext chains */ for (i = 0; i < MIN(rx->c_chain, IEEE80211_MAX_CHAINS); i++) { IEEE80211_RSSI_LPF(ni->ni_mimo_rssi_ctl[i], rx->c_rssi_ctl[i]); IEEE80211_RSSI_LPF(ni->ni_mimo_rssi_ext[i], rx->c_rssi_ext[i]); } /* XXX This also assumes the MIMO radios have both ctl and ext chains */ for(i = 0; i < MIN(rx->c_chain, IEEE80211_MAX_CHAINS); i++) { ni->ni_mimo_noise_ctl[i] = rx->c_nf_ctl[i]; ni->ni_mimo_noise_ext[i] = rx->c_nf_ext[i]; } ni->ni_mimo_chains = rx->c_chain; } int ieee80211_input_mimo(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211_rx_stats rxs; /* try to read stats from mbuf */ bzero(&rxs, sizeof(rxs)); if (ieee80211_get_rx_params(m, &rxs) != 0) return (-1); /* XXX should assert IEEE80211_R_NF and IEEE80211_R_RSSI are set */ ieee80211_process_mimo(ni, &rxs); //return ieee80211_input(ni, m, rx->rssi, rx->nf); return ni->ni_vap->iv_input(ni, m, &rxs, rxs.c_rssi, rxs.c_nf); } int ieee80211_input_all(struct ieee80211com *ic, struct mbuf *m, int rssi, int nf) { struct ieee80211_rx_stats rx; rx.r_flags = IEEE80211_R_NF | IEEE80211_R_RSSI; rx.c_nf = nf; rx.c_rssi = rssi; if (!ieee80211_add_rx_params(m, &rx)) return (-1); return ieee80211_input_mimo_all(ic, m); } int ieee80211_input_mimo_all(struct ieee80211com *ic, struct mbuf *m) { struct ieee80211vap *vap; int type = -1; m->m_flags |= M_BCAST; /* NB: mark for bpf tap'ing */ /* XXX locking */ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { struct ieee80211_node *ni; struct mbuf *mcopy; /* NB: could check for IFF_UP but this is cheaper */ if (vap->iv_state == IEEE80211_S_INIT) continue; /* * WDS vap's only receive directed traffic from the * station at the ``far end''. That traffic should * be passed through the AP vap the station is associated * to--so don't spam them with mcast frames. */ if (vap->iv_opmode == IEEE80211_M_WDS) continue; if (TAILQ_NEXT(vap, iv_next) != NULL) { /* * Packet contents are changed by ieee80211_decap * so do a deep copy of the packet. * NB: tags are copied too. */ mcopy = m_dup(m, IEEE80211_M_NOWAIT); if (mcopy == NULL) { /* XXX stat+msg */ continue; } } else { mcopy = m; m = NULL; } ni = ieee80211_ref_node(vap->iv_bss); type = ieee80211_input_mimo(ni, mcopy); ieee80211_free_node(ni); } if (m != NULL) /* no vaps, reclaim mbuf */ m_freem(m); return type; } /* * This function reassembles fragments. * * XXX should handle 3 concurrent reassemblies per-spec. */ struct mbuf * ieee80211_defrag(struct ieee80211_node *ni, struct mbuf *m, int hdrspace, int has_decrypted) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); struct ieee80211_frame *lwh; uint16_t rxseq; uint8_t fragno; uint8_t more_frag = wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG; struct mbuf *mfrag; KASSERT(!IEEE80211_IS_MULTICAST(wh->i_addr1), ("multicast fragm?")); rxseq = le16toh(*(uint16_t *)wh->i_seq); fragno = rxseq & IEEE80211_SEQ_FRAG_MASK; /* Quick way out, if there's nothing to defragment */ if (!more_frag && fragno == 0 && ni->ni_rxfrag[0] == NULL) return m; /* Temporarily set flag to remember if fragment was encrypted. */ /* XXX use a non-packet altering storage for this in the future. */ if (has_decrypted) wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; /* * Remove frag to insure it doesn't get reaped by timer. */ if (ni->ni_table == NULL) { /* * Should never happen. If the node is orphaned (not in * the table) then input packets should not reach here. * Otherwise, a concurrent request that yanks the table * should be blocked by other interlocking and/or by first * shutting the driver down. Regardless, be defensive * here and just bail */ /* XXX need msg+stat */ m_freem(m); return NULL; } IEEE80211_NODE_LOCK(ni->ni_table); mfrag = ni->ni_rxfrag[0]; ni->ni_rxfrag[0] = NULL; IEEE80211_NODE_UNLOCK(ni->ni_table); /* * Validate new fragment is in order and * related to the previous ones. */ if (mfrag != NULL) { uint16_t last_rxseq; lwh = mtod(mfrag, struct ieee80211_frame *); last_rxseq = le16toh(*(uint16_t *)lwh->i_seq); /* * NB: check seq # and frag together. Also check that both * fragments are plaintext or that both are encrypted. */ if (rxseq == last_rxseq+1 && IEEE80211_ADDR_EQ(wh->i_addr1, lwh->i_addr1) && IEEE80211_ADDR_EQ(wh->i_addr2, lwh->i_addr2) && !((wh->i_fc[1] ^ lwh->i_fc[1]) & IEEE80211_FC1_PROTECTED)) { /* XXX clear MORE_FRAG bit? */ /* track last seqnum and fragno */ *(uint16_t *) lwh->i_seq = *(uint16_t *) wh->i_seq; m_adj(m, hdrspace); /* strip header */ m_catpkt(mfrag, m); /* concatenate */ } else { /* * Unrelated fragment or no space for it, * clear current fragments. */ m_freem(mfrag); mfrag = NULL; } } if (mfrag == NULL) { if (fragno != 0) { /* !first fragment, discard */ vap->iv_stats.is_rx_defrag++; IEEE80211_NODE_STAT(ni, rx_defrag); m_freem(m); return NULL; } mfrag = m; } if (more_frag) { /* more to come, save */ ni->ni_rxfragstamp = ticks; ni->ni_rxfrag[0] = mfrag; mfrag = NULL; } /* Remember to clear protected flag that was temporarily set. */ if (mfrag != NULL) { wh = mtod(mfrag, struct ieee80211_frame *); wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED; } return mfrag; } void ieee80211_deliver_data(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { struct epoch_tracker et; struct ether_header *eh = mtod(m, struct ether_header *); struct ifnet *ifp = vap->iv_ifp; /* clear driver/net80211 flags before passing up */ m->m_flags &= ~(M_MCAST | M_BCAST); m_clrprotoflags(m); /* NB: see hostap_deliver_data, this path doesn't handle hostap */ KASSERT(vap->iv_opmode != IEEE80211_M_HOSTAP, ("gack, hostap")); /* * Do accounting. */ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); IEEE80211_NODE_STAT(ni, rx_data); IEEE80211_NODE_STAT_ADD(ni, rx_bytes, m->m_pkthdr.len); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (ETHER_IS_BROADCAST(eh->ether_dhost)) m->m_flags |= M_BCAST; else m->m_flags |= M_MCAST; IEEE80211_NODE_STAT(ni, rx_mcast); } else IEEE80211_NODE_STAT(ni, rx_ucast); m->m_pkthdr.rcvif = ifp; if (ni->ni_vlan != 0) { /* attach vlan tag */ m->m_pkthdr.ether_vtag = ni->ni_vlan; m->m_flags |= M_VLANTAG; } NET_EPOCH_ENTER(et); ifp->if_input(ifp, m); NET_EPOCH_EXIT(et); } struct mbuf * ieee80211_decap(struct ieee80211vap *vap, struct mbuf *m, int hdrlen, uint8_t qos) { struct ieee80211_qosframe_addr4 wh; struct ether_header *eh; struct llc *llc; KASSERT(hdrlen <= sizeof(wh), ("hdrlen %d > max %zd", hdrlen, sizeof(wh))); if (m->m_len < hdrlen + sizeof(*llc) && (m = m_pullup(m, hdrlen + sizeof(*llc))) == NULL) { vap->iv_stats.is_rx_tooshort++; /* XXX msg */ return NULL; } memcpy(&wh, mtod(m, caddr_t), hdrlen); llc = (struct llc *)(mtod(m, caddr_t) + hdrlen); if (llc->llc_dsap == LLC_SNAP_LSAP && llc->llc_ssap == LLC_SNAP_LSAP && llc->llc_control == LLC_UI && llc->llc_snap.org_code[0] == 0 && llc->llc_snap.org_code[1] == 0 && llc->llc_snap.org_code[2] == 0 && /* NB: preserve AppleTalk frames that have a native SNAP hdr */ !(llc->llc_snap.ether_type == htons(ETHERTYPE_AARP) || llc->llc_snap.ether_type == htons(ETHERTYPE_IPX)) && /* Do not want to touch A-MSDU frames. */ !(qos & IEEE80211_QOS_AMSDU)) { m_adj(m, hdrlen + sizeof(struct llc) - sizeof(*eh)); llc = NULL; } else { m_adj(m, hdrlen - sizeof(*eh)); } eh = mtod(m, struct ether_header *); switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); break; case IEEE80211_FC1_DIR_TODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr2); break; case IEEE80211_FC1_DIR_FROMDS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr1); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr3); break; case IEEE80211_FC1_DIR_DSTODS: IEEE80211_ADDR_COPY(eh->ether_dhost, wh.i_addr3); IEEE80211_ADDR_COPY(eh->ether_shost, wh.i_addr4); break; } #ifndef __NO_STRICT_ALIGNMENT if (!ALIGNED_POINTER(mtod(m, caddr_t) + sizeof(*eh), uint32_t)) { m = ieee80211_realign(vap, m, sizeof(*eh)); if (m == NULL) return NULL; } #endif /* !__NO_STRICT_ALIGNMENT */ if (llc != NULL) { eh = mtod(m, struct ether_header *); eh->ether_type = htons(m->m_pkthdr.len - sizeof(*eh)); } return m; } /* * Decap a frame encapsulated in a fast-frame/A-MSDU. */ struct mbuf * ieee80211_decap1(struct mbuf *m, int *framelen) { #define FF_LLC_SIZE (sizeof(struct ether_header) + sizeof(struct llc)) struct ether_header *eh; struct llc *llc; const uint8_t llc_hdr_mac[ETHER_ADDR_LEN] = { /* MAC address matching the 802.2 LLC header */ LLC_SNAP_LSAP, LLC_SNAP_LSAP, LLC_UI, 0, 0, 0 }; /* * The frame has an 802.3 header followed by an 802.2 * LLC header. The encapsulated frame length is in the * first header type field; save that and overwrite it * with the true type field found in the second. Then * copy the 802.3 header up to where it belongs and * adjust the mbuf contents to remove the void. */ if (m->m_len < FF_LLC_SIZE && (m = m_pullup(m, FF_LLC_SIZE)) == NULL) return NULL; eh = mtod(m, struct ether_header *); /* 802.3 header is first */ /* * Detect possible attack where a single 802.11 frame is processed * as an A-MSDU frame due to an adversary setting the A-MSDU present * bit in the 802.11 QoS header. [FragAttacks] */ if (memcmp(eh->ether_dhost, llc_hdr_mac, ETHER_ADDR_LEN) == 0) return NULL; llc = (struct llc *)&eh[1]; /* 802.2 header follows */ *framelen = ntohs(eh->ether_type) /* encap'd frame size */ + sizeof(struct ether_header) - sizeof(struct llc); eh->ether_type = llc->llc_un.type_snap.ether_type; ovbcopy(eh, mtod(m, uint8_t *) + sizeof(struct llc), sizeof(struct ether_header)); m_adj(m, sizeof(struct llc)); return m; #undef FF_LLC_SIZE } /* * Install received rate set information in the node's state block. */ int ieee80211_setup_rates(struct ieee80211_node *ni, const uint8_t *rates, const uint8_t *xrates, int flags) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_rateset *rs = &ni->ni_rates; memset(rs, 0, sizeof(*rs)); rs->rs_nrates = rates[1]; memcpy(rs->rs_rates, rates + 2, rs->rs_nrates); if (xrates != NULL) { uint8_t nxrates; /* * Tack on 11g extended supported rate element. */ nxrates = xrates[1]; if (rs->rs_nrates + nxrates > IEEE80211_RATE_MAXSIZE) { nxrates = IEEE80211_RATE_MAXSIZE - rs->rs_nrates; IEEE80211_NOTE(vap, IEEE80211_MSG_XRATE, ni, "extended rate set too large; only using " "%u of %u rates", nxrates, xrates[1]); vap->iv_stats.is_rx_rstoobig++; } memcpy(rs->rs_rates + rs->rs_nrates, xrates+2, nxrates); rs->rs_nrates += nxrates; } return ieee80211_fix_rate(ni, rs, flags); } /* * Send a management frame error response to the specified * station. If ni is associated with the station then use * it; otherwise allocate a temporary node suitable for * transmitting the frame and then free the reference so * it will go away as soon as the frame has been transmitted. */ void ieee80211_send_error(struct ieee80211_node *ni, const uint8_t mac[IEEE80211_ADDR_LEN], int subtype, int arg) { struct ieee80211vap *vap = ni->ni_vap; int istmp; if (ni == vap->iv_bss) { if (vap->iv_state != IEEE80211_S_RUN) { /* * XXX hack until we get rid of this routine. * We can be called prior to the vap reaching * run state under certain conditions in which * case iv_bss->ni_chan will not be setup. * Check for this explicitly and and just ignore * the request. */ return; } ni = ieee80211_tmp_node(vap, mac); if (ni == NULL) { /* XXX msg */ return; } istmp = 1; } else istmp = 0; IEEE80211_SEND_MGMT(ni, subtype, arg); if (istmp) ieee80211_free_node(ni); } int ieee80211_alloc_challenge(struct ieee80211_node *ni) { if (ni->ni_challenge == NULL) ni->ni_challenge = (uint32_t *) IEEE80211_MALLOC(IEEE80211_CHALLENGE_LEN, M_80211_NODE, IEEE80211_M_NOWAIT); if (ni->ni_challenge == NULL) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, ni, "%s", "shared key challenge alloc failed"); /* XXX statistic */ } return (ni->ni_challenge != NULL); } /* * Parse a Beacon or ProbeResponse frame and return the * useful information in an ieee80211_scanparams structure. * Status is set to 0 if no problems were found; otherwise * a bitmask of IEEE80211_BPARSE_* items is returned that * describes the problems detected. */ int ieee80211_parse_beacon(struct ieee80211_node *ni, struct mbuf *m, struct ieee80211_channel *rxchan, struct ieee80211_scanparams *scan) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; uint8_t *frm, *efrm; wh = mtod(m, struct ieee80211_frame *); frm = (uint8_t *)&wh[1]; efrm = mtod(m, uint8_t *) + m->m_len; scan->status = 0; /* * beacon/probe response frame format * * XXX Update from 802.11-2012 - eg where HT is * [8] time stamp * [2] beacon interval * [2] capability information * [tlv] ssid * [tlv] supported rates * [tlv] country information * [tlv] channel switch announcement (CSA) * [tlv] parameter set (FH/DS) * [tlv] erp information * [tlv] extended supported rates * [tlv] WME * [tlv] WPA or RSN * [tlv] HT capabilities * [tlv] HT information * [tlv] VHT capabilities * [tlv] VHT information * [tlv] Atheros capabilities * [tlv] Mesh ID * [tlv] Mesh Configuration */ IEEE80211_VERIFY_LENGTH(efrm - frm, 12, return (scan->status = IEEE80211_BPARSE_BADIELEN)); memset(scan, 0, sizeof(*scan)); scan->tstamp = frm; frm += 8; scan->bintval = le16toh(*(uint16_t *)frm); frm += 2; scan->capinfo = le16toh(*(uint16_t *)frm); frm += 2; scan->bchan = ieee80211_chan2ieee(ic, rxchan); scan->chan = scan->bchan; scan->ies = frm; scan->ies_len = efrm - frm; while (efrm - frm > 1) { IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return (scan->status = IEEE80211_BPARSE_BADIELEN)); switch (*frm) { case IEEE80211_ELEMID_SSID: scan->ssid = frm; break; case IEEE80211_ELEMID_RATES: scan->rates = frm; break; case IEEE80211_ELEMID_COUNTRY: scan->country = frm; break; case IEEE80211_ELEMID_CSA: scan->csa = frm; break; case IEEE80211_ELEMID_QUIET: scan->quiet = frm; break; case IEEE80211_ELEMID_FHPARMS: if (ic->ic_phytype == IEEE80211_T_FH) { scan->fhdwell = le16dec(&frm[2]); scan->chan = IEEE80211_FH_CHAN(frm[4], frm[5]); scan->fhindex = frm[6]; } break; case IEEE80211_ELEMID_DSPARMS: /* * XXX hack this since depending on phytype * is problematic for multi-mode devices. */ if (ic->ic_phytype != IEEE80211_T_FH) scan->chan = frm[2]; break; case IEEE80211_ELEMID_TIM: /* XXX ATIM? */ scan->tim = frm; scan->timoff = frm - mtod(m, uint8_t *); break; case IEEE80211_ELEMID_IBSSPARMS: case IEEE80211_ELEMID_CFPARMS: case IEEE80211_ELEMID_PWRCNSTR: case IEEE80211_ELEMID_BSSLOAD: case IEEE80211_ELEMID_APCHANREP: /* NB: avoid debugging complaints */ break; case IEEE80211_ELEMID_XRATES: scan->xrates = frm; break; case IEEE80211_ELEMID_ERP: if (frm[1] != 1) { IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID, wh, "ERP", "bad len %u", frm[1]); vap->iv_stats.is_rx_elem_toobig++; break; } scan->erp = frm[2] | 0x100; break; case IEEE80211_ELEMID_HTCAP: scan->htcap = frm; break; case IEEE80211_ELEMID_VHT_CAP: scan->vhtcap = frm; break; case IEEE80211_ELEMID_VHT_OPMODE: scan->vhtopmode = frm; break; case IEEE80211_ELEMID_RSN: scan->rsn = frm; break; case IEEE80211_ELEMID_HTINFO: scan->htinfo = frm; break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ELEMID_MESHID: scan->meshid = frm; break; case IEEE80211_ELEMID_MESHCONF: scan->meshconf = frm; break; #endif /* Extended capabilities; nothing handles it for now */ case IEEE80211_ELEMID_EXTCAP: break; case IEEE80211_ELEMID_VENDOR: if (iswpaoui(frm)) scan->wpa = frm; else if (iswmeparam(frm) || iswmeinfo(frm)) scan->wme = frm; #ifdef IEEE80211_SUPPORT_SUPERG else if (isatherosoui(frm)) scan->ath = frm; #endif #ifdef IEEE80211_SUPPORT_TDMA else if (istdmaoui(frm)) scan->tdma = frm; #endif else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { /* * Accept pre-draft HT ie's if the * standard ones have not been seen. */ if (ishtcapoui(frm)) { if (scan->htcap == NULL) scan->htcap = frm; } else if (ishtinfooui(frm)) { if (scan->htinfo == NULL) scan->htcap = frm; } } break; default: IEEE80211_DISCARD_IE(vap, IEEE80211_MSG_ELEMID, wh, "unhandled", "id %u, len %u", *frm, frm[1]); vap->iv_stats.is_rx_elem_unknown++; break; } frm += frm[1] + 2; } IEEE80211_VERIFY_ELEMENT(scan->rates, IEEE80211_RATE_MAXSIZE, scan->status |= IEEE80211_BPARSE_RATES_INVALID); if (scan->rates != NULL && scan->xrates != NULL) { /* * NB: don't process XRATES if RATES is missing. This * avoids a potential null ptr deref and should be ok * as the return code will already note RATES is missing * (so callers shouldn't otherwise process the frame). */ IEEE80211_VERIFY_ELEMENT(scan->xrates, IEEE80211_RATE_MAXSIZE - scan->rates[1], scan->status |= IEEE80211_BPARSE_XRATES_INVALID); } IEEE80211_VERIFY_ELEMENT(scan->ssid, IEEE80211_NWID_LEN, scan->status |= IEEE80211_BPARSE_SSID_INVALID); if (scan->chan != scan->bchan && ic->ic_phytype != IEEE80211_T_FH) { /* * Frame was received on a channel different from the * one indicated in the DS params element id; * silently discard it. * * NB: this can happen due to signal leakage. * But we should take it for FH phy because * the rssi value should be correct even for * different hop pattern in FH. */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "for off-channel %u (bchan=%u)", scan->chan, scan->bchan); vap->iv_stats.is_rx_chanmismatch++; scan->status |= IEEE80211_BPARSE_OFFCHAN; } if (!(IEEE80211_BINTVAL_MIN <= scan->bintval && scan->bintval <= IEEE80211_BINTVAL_MAX)) { IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "bogus beacon interval (%d TU)", (int) scan->bintval); vap->iv_stats.is_rx_badbintval++; scan->status |= IEEE80211_BPARSE_BINTVAL_INVALID; } if (scan->country != NULL) { /* * Validate we have at least enough data to extract * the country code. Not sure if we should return an * error instead of discarding the IE; consider this * being lenient as we don't depend on the data for * correct operation. */ IEEE80211_VERIFY_LENGTH(scan->country[1], 3 * sizeof(uint8_t), scan->country = NULL); } if (scan->csa != NULL) { /* * Validate Channel Switch Announcement; this must * be the correct length or we toss the frame. */ IEEE80211_VERIFY_LENGTH(scan->csa[1], 3 * sizeof(uint8_t), scan->status |= IEEE80211_BPARSE_CSA_INVALID); } #ifdef IEEE80211_SUPPORT_MESH if (scan->meshid != NULL) { IEEE80211_VERIFY_ELEMENT(scan->meshid, IEEE80211_MESHID_LEN, scan->status |= IEEE80211_BPARSE_MESHID_INVALID); } #endif /* * Process HT ie's. This is complicated by our * accepting both the standard ie's and the pre-draft * vendor OUI ie's that some vendors still use/require. */ if (scan->htcap != NULL) { IEEE80211_VERIFY_LENGTH(scan->htcap[1], scan->htcap[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htcap)-2 : sizeof(struct ieee80211_ie_htcap)-2, scan->htcap = NULL); } if (scan->htinfo != NULL) { IEEE80211_VERIFY_LENGTH(scan->htinfo[1], scan->htinfo[0] == IEEE80211_ELEMID_VENDOR ? 4 + sizeof(struct ieee80211_ie_htinfo)-2 : sizeof(struct ieee80211_ie_htinfo)-2, scan->htinfo = NULL); } /* Process VHT IEs */ if (scan->vhtcap != NULL) { IEEE80211_VERIFY_LENGTH(scan->vhtcap[1], - sizeof(struct ieee80211_ie_vhtcap) - 2, + sizeof(struct ieee80211_vht_cap), scan->vhtcap = NULL); } if (scan->vhtopmode != NULL) { IEEE80211_VERIFY_LENGTH(scan->vhtopmode[1], - sizeof(struct ieee80211_ie_vht_operation) - 2, + sizeof(struct ieee80211_vht_operation), scan->vhtopmode = NULL); } return scan->status; } /* * Parse an Action frame. Return 0 on success, non-zero on failure. */ int ieee80211_parse_action(struct ieee80211_node *ni, struct mbuf *m) { struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_action *ia; struct ieee80211_frame *wh; uint8_t *frm, *efrm; /* * action frame format: * [1] category * [1] action * [tlv] parameters */ wh = mtod(m, struct ieee80211_frame *); frm = (u_int8_t *)&wh[1]; efrm = mtod(m, u_int8_t *) + m->m_len; IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action), return EINVAL); ia = (const struct ieee80211_action *) frm; vap->iv_stats.is_rx_action++; IEEE80211_NODE_STAT(ni, rx_action); /* verify frame payloads but defer processing */ switch (ia->ia_category) { case IEEE80211_ACTION_CAT_BA: switch (ia->ia_action) { case IEEE80211_ACTION_BA_ADDBA_REQUEST: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addbarequest), return EINVAL); break; case IEEE80211_ACTION_BA_ADDBA_RESPONSE: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_addbaresponse), return EINVAL); break; case IEEE80211_ACTION_BA_DELBA: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ba_delba), return EINVAL); break; } break; case IEEE80211_ACTION_CAT_HT: switch (ia->ia_action) { case IEEE80211_ACTION_HT_TXCHWIDTH: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_txchwidth), return EINVAL); break; case IEEE80211_ACTION_HT_MIMOPWRSAVE: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_action_ht_mimopowersave), return EINVAL); break; } break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_ACTION_CAT_MESH: switch (ia->ia_action) { case IEEE80211_ACTION_MESH_LMETRIC: /* * XXX: verification is true only if we are using * Airtime link metric (default) */ IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_meshlmetric_ie), return EINVAL); break; case IEEE80211_ACTION_MESH_HWMP: /* verify something */ break; case IEEE80211_ACTION_MESH_GANN: IEEE80211_VERIFY_LENGTH(efrm - frm, sizeof(struct ieee80211_meshgann_ie), return EINVAL); break; case IEEE80211_ACTION_MESH_CC: case IEEE80211_ACTION_MESH_MCCA_SREQ: case IEEE80211_ACTION_MESH_MCCA_SREP: case IEEE80211_ACTION_MESH_MCCA_AREQ: case IEEE80211_ACTION_MESH_MCCA_ADVER: case IEEE80211_ACTION_MESH_MCCA_TRDOWN: case IEEE80211_ACTION_MESH_TBTT_REQ: case IEEE80211_ACTION_MESH_TBTT_RES: /* reject these early on, not implemented */ IEEE80211_DISCARD(vap, IEEE80211_MSG_ELEMID | IEEE80211_MSG_INPUT, wh, NULL, "not implemented yet, act=0x%02X", ia->ia_action); return EINVAL; } break; case IEEE80211_ACTION_CAT_SELF_PROT: /* If TA or RA group address discard silently */ if (IEEE80211_IS_MULTICAST(wh->i_addr1) || IEEE80211_IS_MULTICAST(wh->i_addr2)) return EINVAL; /* * XXX: Should we verify complete length now or it is * to varying in sizes? */ switch (ia->ia_action) { case IEEE80211_ACTION_MESHPEERING_CONFIRM: case IEEE80211_ACTION_MESHPEERING_CLOSE: /* is not a peering candidate (yet) */ if (ni == vap->iv_bss) return EINVAL; break; } break; #endif case IEEE80211_ACTION_CAT_VHT: printf("%s: TODO: VHT handling!\n", __func__); break; } return 0; } #ifdef IEEE80211_DEBUG /* * Debugging support. */ void ieee80211_ssid_mismatch(struct ieee80211vap *vap, const char *tag, uint8_t mac[IEEE80211_ADDR_LEN], uint8_t *ssid) { printf("[%s] discard %s frame, ssid mismatch: ", ether_sprintf(mac), tag); ieee80211_print_essid(ssid + 2, ssid[1]); printf("\n"); } /* * Return the bssid of a frame. */ static const uint8_t * ieee80211_getbssid(const struct ieee80211vap *vap, const struct ieee80211_frame *wh) { if (vap->iv_opmode == IEEE80211_M_STA) return wh->i_addr2; if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) return wh->i_addr1; if ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) return wh->i_addr1; return wh->i_addr3; } #include void ieee80211_note(const struct ieee80211vap *vap, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "%s", buf); /* NB: no \n */ if (len >= sizeof(buf)) printf("%s: XXX buffer too small: len = %d\n", __func__, len); } void ieee80211_note_frame(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), buf); if (len >= sizeof(buf)) printf("%s: XXX buffer too small: len = %d\n", __func__, len); } void ieee80211_note_mac(const struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] %s\n", ether_sprintf(mac), buf); if (len >= sizeof(buf)) printf("%s: XXX buffer too small: len = %d\n", __func__, len); } void ieee80211_discard_frame(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] discard %s frame, %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), type != NULL ? type : ieee80211_mgt_subtype_name(wh->i_fc[0]), buf); if (len >= sizeof(buf)) printf("%s: XXX buffer too small: len = %d\n", __func__, len); } void ieee80211_discard_ie(const struct ieee80211vap *vap, const struct ieee80211_frame *wh, const char *type, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] discard%s%s information element, %s\n", ether_sprintf(ieee80211_getbssid(vap, wh)), type != NULL ? " " : "", type != NULL ? type : "", buf); if (len >= sizeof(buf)) printf("%s: XXX buffer too small: len = %d\n", __func__, len); } void ieee80211_discard_mac(const struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN], const char *type, const char *fmt, ...) { char buf[256]; /* XXX */ va_list ap; int len; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if_printf(vap->iv_ifp, "[%s] discard%s%s frame, %s\n", ether_sprintf(mac), type != NULL ? " " : "", type != NULL ? type : "", buf); if (len >= sizeof(buf)) printf("%s: XXX buffer too small: len = %d\n", __func__, len); } #endif /* IEEE80211_DEBUG */ diff --git a/sys/net80211/ieee80211_output.c b/sys/net80211/ieee80211_output.c index a5fad2462598..22bbfe98f4b5 100644 --- a/sys/net80211/ieee80211_output.c +++ b/sys/net80211/ieee80211_output.c @@ -1,4197 +1,4197 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2009 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. * * 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 #include "opt_inet.h" #include "opt_inet6.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IEEE80211_SUPPORT_SUPERG #include #endif #ifdef IEEE80211_SUPPORT_TDMA #include #endif #include #include #include #if defined(INET) || defined(INET6) #include #endif #ifdef INET #include #include #include #endif #ifdef INET6 #include #endif #include #define ETHER_HEADER_COPY(dst, src) \ memcpy(dst, src, sizeof(struct ether_header)) static int ieee80211_fragment(struct ieee80211vap *, struct mbuf *, u_int hdrsize, u_int ciphdrsize, u_int mtu); static void ieee80211_tx_mgt_cb(struct ieee80211_node *, void *, int); #ifdef IEEE80211_DEBUG /* * Decide if an outbound management frame should be * printed when debugging is enabled. This filters some * of the less interesting frames that come frequently * (e.g. beacons). */ static __inline int doprint(struct ieee80211vap *vap, int subtype) { switch (subtype) { case IEEE80211_FC0_SUBTYPE_PROBE_RESP: return (vap->iv_opmode == IEEE80211_M_IBSS); } return 1; } #endif /* * Transmit a frame to the given destination on the given VAP. * * It's up to the caller to figure out the details of who this * is going to and resolving the node. * * This routine takes care of queuing it for power save, * A-MPDU state stuff, fast-frames state stuff, encapsulation * if required, then passing it up to the driver layer. * * This routine (for now) consumes the mbuf and frees the node * reference; it ideally will return a TX status which reflects * whether the mbuf was consumed or not, so the caller can * free the mbuf (if appropriate) and the node reference (again, * if appropriate.) */ int ieee80211_vap_pkt_send_dest(struct ieee80211vap *vap, struct mbuf *m, struct ieee80211_node *ni) { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; int mcast; int do_ampdu = 0; #ifdef IEEE80211_SUPPORT_SUPERG int do_amsdu = 0; int do_ampdu_amsdu = 0; int no_ampdu = 1; /* Will be set to 0 if ampdu is active */ int do_ff = 0; #endif if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && (m->m_flags & M_PWR_SAV) == 0) { /* * Station in power save mode; pass the frame * to the 802.11 layer and continue. We'll get * the frame back when the time is right. * XXX lose WDS vap linkage? */ if (ieee80211_pwrsave(ni, m) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); /* * We queued it fine, so tell the upper layer * that we consumed it. */ return (0); } /* calculate priority so drivers can find the tx queue */ if (ieee80211_classify(ni, m)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT, ni->ni_macaddr, NULL, "%s", "classification failure"); vap->iv_stats.is_tx_classify++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); ieee80211_free_node(ni); /* XXX better status? */ return (0); } /* * Stash the node pointer. Note that we do this after * any call to ieee80211_dwds_mcast because that code * uses any existing value for rcvif to identify the * interface it (might have been) received on. */ MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0); m->m_pkthdr.rcvif = (void *)ni; mcast = (m->m_flags & (M_MCAST | M_BCAST)) ? 1: 0; BPF_MTAP(ifp, m); /* 802.3 tx */ /* * Figure out if we can do A-MPDU, A-MSDU or FF. * * A-MPDU depends upon vap/node config. * A-MSDU depends upon vap/node config. * FF depends upon vap config, IE and whether * it's 11abg (and not 11n/11ac/etc.) * * Note that these flags indiciate whether we can do * it at all, rather than the situation (eg traffic type.) */ do_ampdu = ((ni->ni_flags & IEEE80211_NODE_AMPDU_TX) && (vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX)); #ifdef IEEE80211_SUPPORT_SUPERG do_amsdu = ((ni->ni_flags & IEEE80211_NODE_AMSDU_TX) && (vap->iv_flags_ht & IEEE80211_FHT_AMSDU_TX)); do_ff = ((ni->ni_flags & IEEE80211_NODE_HT) == 0) && ((ni->ni_flags & IEEE80211_NODE_VHT) == 0) && (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF)); #endif /* * Check if A-MPDU tx aggregation is setup or if we * should try to enable it. The sta must be associated * with HT and A-MPDU enabled for use. When the policy * routine decides we should enable A-MPDU we issue an * ADDBA request and wait for a reply. The frame being * encapsulated will go out w/o using A-MPDU, or possibly * it might be collected by the driver and held/retransmit. * The default ic_ampdu_enable routine handles staggering * ADDBA requests in case the receiver NAK's us or we are * otherwise unable to establish a BA stream. * * Don't treat group-addressed frames as candidates for aggregation; * net80211 doesn't support 802.11aa-2012 and so group addressed * frames will always have sequence numbers allocated from the NON_QOS * TID. */ if (do_ampdu) { if ((m->m_flags & M_EAPOL) == 0 && (! mcast)) { int tid = WME_AC_TO_TID(M_WME_GETAC(m)); struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid]; ieee80211_txampdu_count_packet(tap); if (IEEE80211_AMPDU_RUNNING(tap)) { /* * Operational, mark frame for aggregation. * * XXX do tx aggregation here */ m->m_flags |= M_AMPDU_MPDU; } else if (!IEEE80211_AMPDU_REQUESTED(tap) && ic->ic_ampdu_enable(ni, tap)) { /* * Not negotiated yet, request service. */ ieee80211_ampdu_request(ni, tap); /* XXX hold frame for reply? */ } /* * Now update the no-ampdu flag. A-MPDU may have been * started or administratively disabled above; so now we * know whether we're running yet or not. * * This will let us know whether we should be doing A-MSDU * at this point. We only do A-MSDU if we're either not * doing A-MPDU, or A-MPDU is NACKed, or A-MPDU + A-MSDU * is available. * * Whilst here, update the amsdu-ampdu flag. The above may * have also set or cleared the amsdu-in-ampdu txa_flags * combination so we can correctly do A-MPDU + A-MSDU. */ #ifdef IEEE80211_SUPPORT_SUPERG no_ampdu = (! IEEE80211_AMPDU_RUNNING(tap) || (IEEE80211_AMPDU_NACKED(tap))); do_ampdu_amsdu = IEEE80211_AMPDU_RUNNING_AMSDU(tap); #endif } } #ifdef IEEE80211_SUPPORT_SUPERG /* * Check for AMSDU/FF; queue for aggregation * * Note: we don't bother trying to do fast frames or * A-MSDU encapsulation for 802.3 drivers. Now, we * likely could do it for FF (because it's a magic * atheros tunnel LLC type) but I don't think we're going * to really need to. For A-MSDU we'd have to set the * A-MSDU QoS bit in the wifi header, so we just plain * can't do it. */ if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) { if ((! mcast) && (do_ampdu_amsdu || (no_ampdu && do_amsdu)) && ieee80211_amsdu_tx_ok(ni)) { m = ieee80211_amsdu_check(ni, m); if (m == NULL) { /* NB: any ni ref held on stageq */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: amsdu_check queued frame\n", __func__); return (0); } } else if ((! mcast) && do_ff) { m = ieee80211_ff_check(ni, m); if (m == NULL) { /* NB: any ni ref held on stageq */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: ff_check queued frame\n", __func__); return (0); } } } #endif /* IEEE80211_SUPPORT_SUPERG */ /* * Grab the TX lock - serialise the TX process from this * point (where TX state is being checked/modified) * through to driver queue. */ IEEE80211_TX_LOCK(ic); /* * XXX make the encap and transmit code a separate function * so things like the FF (and later A-MSDU) path can just call * it for flushed frames. */ if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) { /* * Encapsulate the packet in prep for transmission. */ m = ieee80211_encap(vap, ni, m); if (m == NULL) { /* NB: stat+msg handled in ieee80211_encap */ IEEE80211_TX_UNLOCK(ic); ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } } (void) ieee80211_parent_xmitpkt(ic, m); /* * Unlock at this point - no need to hold it across * ieee80211_free_node() (ie, the comlock) */ IEEE80211_TX_UNLOCK(ic); ic->ic_lastdata = ticks; return (0); } /* * Send the given mbuf through the given vap. * * This consumes the mbuf regardless of whether the transmit * was successful or not. * * This does none of the initial checks that ieee80211_start() * does (eg CAC timeout, interface wakeup) - the caller must * do this first. */ static int ieee80211_start_pkt(struct ieee80211vap *vap, struct mbuf *m) { #define IS_DWDS(vap) \ (vap->iv_opmode == IEEE80211_M_WDS && \ (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY) == 0) struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_node *ni; struct ether_header *eh; /* * Cancel any background scan. */ if (ic->ic_flags & IEEE80211_F_SCAN) ieee80211_cancel_anyscan(vap); /* * Find the node for the destination so we can do * things like power save and fast frames aggregation. * * NB: past this point various code assumes the first * mbuf has the 802.3 header present (and contiguous). */ ni = NULL; if (m->m_len < sizeof(struct ether_header) && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "discard frame, %s\n", "m_pullup failed"); vap->iv_stats.is_tx_nobuf++; /* XXX */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENOBUFS); } eh = mtod(m, struct ether_header *); if (ETHER_IS_MULTICAST(eh->ether_dhost)) { if (IS_DWDS(vap)) { /* * Only unicast frames from the above go out * DWDS vaps; multicast frames are handled by * dispatching the frame as it comes through * the AP vap (see below). */ IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_WDS, eh->ether_dhost, "mcast", "%s", "on DWDS"); vap->iv_stats.is_dwds_mcast++; m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* XXX better status? */ return (ENOBUFS); } if (vap->iv_opmode == IEEE80211_M_HOSTAP) { /* * Spam DWDS vap's w/ multicast traffic. */ /* XXX only if dwds in use? */ ieee80211_dwds_mcast(vap, m); } } #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode != IEEE80211_M_MBSS) { #endif ni = ieee80211_find_txnode(vap, eh->ether_dhost); if (ni == NULL) { /* NB: ieee80211_find_txnode does stat+msg */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); /* XXX better status? */ return (ENOBUFS); } if (ni->ni_associd == 0 && (ni->ni_flags & IEEE80211_NODE_ASSOCID)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT, eh->ether_dhost, NULL, "sta not associated (type 0x%04x)", htons(eh->ether_type)); vap->iv_stats.is_tx_notassoc++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); ieee80211_free_node(ni); /* XXX better status? */ return (ENOBUFS); } #ifdef IEEE80211_SUPPORT_MESH } else { if (!IEEE80211_ADDR_EQ(eh->ether_shost, vap->iv_myaddr)) { /* * Proxy station only if configured. */ if (!ieee80211_mesh_isproxyena(vap)) { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_MESH, eh->ether_dhost, NULL, "%s", "proxy not enabled"); vap->iv_stats.is_mesh_notproxy++; if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); m_freem(m); /* XXX better status? */ return (ENOBUFS); } IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "forward frame from DS SA(%6D), DA(%6D)\n", eh->ether_shost, ":", eh->ether_dhost, ":"); ieee80211_mesh_proxy_check(vap, eh->ether_shost); } ni = ieee80211_mesh_discover(vap, eh->ether_dhost, m); if (ni == NULL) { /* * NB: ieee80211_mesh_discover holds/disposes * frame (e.g. queueing on path discovery). */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); /* XXX better status? */ return (ENOBUFS); } } #endif /* * We've resolved the sender, so attempt to transmit it. */ if (vap->iv_state == IEEE80211_S_SLEEP) { /* * In power save; queue frame and then wakeup device * for transmit. */ ic->ic_lastdata = ticks; if (ieee80211_pwrsave(ni, m) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); ieee80211_new_state(vap, IEEE80211_S_RUN, 0); return (0); } if (ieee80211_vap_pkt_send_dest(vap, m, ni) != 0) return (ENOBUFS); return (0); #undef IS_DWDS } /* * Start method for vap's. All packets from the stack come * through here. We handle common processing of the packets * before dispatching them to the underlying device. * * if_transmit() requires that the mbuf be consumed by this call * regardless of the return condition. */ int ieee80211_vap_transmit(struct ifnet *ifp, struct mbuf *m) { struct ieee80211vap *vap = ifp->if_softc; struct ieee80211com *ic = vap->iv_ic; /* * No data frames go out unless we're running. * Note in particular this covers CAC and CSA * states (though maybe we should check muting * for CSA). */ if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_SLEEP) { IEEE80211_LOCK(ic); /* re-check under the com lock to avoid races */ if (vap->iv_state != IEEE80211_S_RUN && vap->iv_state != IEEE80211_S_SLEEP) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: ignore queue, in %s state\n", __func__, ieee80211_state_name[vap->iv_state]); vap->iv_stats.is_tx_badstate++; IEEE80211_UNLOCK(ic); ifp->if_drv_flags |= IFF_DRV_OACTIVE; m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return (ENETDOWN); } IEEE80211_UNLOCK(ic); } /* * Sanitize mbuf flags for net80211 use. We cannot * clear M_PWR_SAV or M_MORE_DATA because these may * be set for frames that are re-submitted from the * power save queue. * * NB: This must be done before ieee80211_classify as * it marks EAPOL in frames with M_EAPOL. */ m->m_flags &= ~(M_80211_TX - M_PWR_SAV - M_MORE_DATA); /* * Bump to the packet transmission path. * The mbuf will be consumed here. */ return (ieee80211_start_pkt(vap, m)); } void ieee80211_vap_qflush(struct ifnet *ifp) { /* Empty for now */ } /* * 802.11 raw output routine. * * XXX TODO: this (and other send routines) should correctly * XXX keep the pwr mgmt bit set if it decides to call into the * XXX driver to send a frame whilst the state is SLEEP. * * Otherwise the peer may decide that we're awake and flood us * with traffic we are still too asleep to receive! */ int ieee80211_raw_output(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = vap->iv_ic; int error; /* * Set node - the caller has taken a reference, so ensure * that the mbuf has the same node value that * it would if it were going via the normal path. */ MPASS((m->m_pkthdr.csum_flags & CSUM_SND_TAG) == 0); m->m_pkthdr.rcvif = (void *)ni; /* * Attempt to add bpf transmit parameters. * * For now it's ok to fail; the raw_xmit api still takes * them as an option. * * Later on when ic_raw_xmit() has params removed, * they'll have to be added - so fail the transmit if * they can't be. */ if (params) (void) ieee80211_add_xmit_params(m, params); error = ic->ic_raw_xmit(ni, m, params); if (error) { if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, 1); ieee80211_free_node(ni); } return (error); } static int ieee80211_validate_frame(struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211_frame *wh; int type; if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_ack)) return (EINVAL); wh = mtod(m, struct ieee80211_frame *); if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != IEEE80211_FC0_VERSION_0) return (EINVAL); type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; if (type != IEEE80211_FC0_TYPE_DATA) { if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) != IEEE80211_FC1_DIR_NODS) return (EINVAL); if (type != IEEE80211_FC0_TYPE_MGT && (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) != 0) return (EINVAL); /* XXX skip other field checks? */ } if ((params && (params->ibp_flags & IEEE80211_BPF_CRYPTO) != 0) || (IEEE80211_IS_PROTECTED(wh))) { int subtype; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; /* * See IEEE Std 802.11-2012, * 8.2.4.1.9 'Protected Frame field' */ /* XXX no support for robust management frames yet. */ if (!(type == IEEE80211_FC0_TYPE_DATA || (type == IEEE80211_FC0_TYPE_MGT && subtype == IEEE80211_FC0_SUBTYPE_AUTH))) return (EINVAL); wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; } if (m->m_pkthdr.len < ieee80211_anyhdrsize(wh)) return (EINVAL); return (0); } static int ieee80211_validate_rate(struct ieee80211_node *ni, uint8_t rate) { struct ieee80211com *ic = ni->ni_ic; if (IEEE80211_IS_HT_RATE(rate)) { if ((ic->ic_htcaps & IEEE80211_HTC_HT) == 0) return (EINVAL); rate = IEEE80211_RV(rate); if (rate <= 31) { if (rate > ic->ic_txstream * 8 - 1) return (EINVAL); return (0); } if (rate == 32) { if ((ic->ic_htcaps & IEEE80211_HTC_TXMCS32) == 0) return (EINVAL); return (0); } if ((ic->ic_htcaps & IEEE80211_HTC_TXUNEQUAL) == 0) return (EINVAL); switch (ic->ic_txstream) { case 0: case 1: return (EINVAL); case 2: if (rate > 38) return (EINVAL); return (0); case 3: if (rate > 52) return (EINVAL); return (0); case 4: default: if (rate > 76) return (EINVAL); return (0); } } if (!ieee80211_isratevalid(ic->ic_rt, rate)) return (EINVAL); return (0); } static int ieee80211_sanitize_rates(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { int error; if (!params) return (0); /* nothing to do */ /* NB: most drivers assume that ibp_rate0 is set (!= 0). */ if (params->ibp_rate0 != 0) { error = ieee80211_validate_rate(ni, params->ibp_rate0); if (error != 0) return (error); } else { /* XXX pre-setup some default (e.g., mgmt / mcast) rate */ /* XXX __DECONST? */ (void) m; } if (params->ibp_rate1 != 0 && (error = ieee80211_validate_rate(ni, params->ibp_rate1)) != 0) return (error); if (params->ibp_rate2 != 0 && (error = ieee80211_validate_rate(ni, params->ibp_rate2)) != 0) return (error); if (params->ibp_rate3 != 0 && (error = ieee80211_validate_rate(ni, params->ibp_rate3)) != 0) return (error); return (0); } /* * 802.11 output routine. This is (currently) used only to * connect bpf write calls to the 802.11 layer for injecting * raw 802.11 frames. */ int ieee80211_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, struct route *ro) { #define senderr(e) do { error = (e); goto bad;} while (0) const struct ieee80211_bpf_params *params = NULL; struct ieee80211_node *ni = NULL; struct ieee80211vap *vap; struct ieee80211_frame *wh; struct ieee80211com *ic = NULL; int error; int ret; if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { /* * Short-circuit requests if the vap is marked OACTIVE * as this can happen because a packet came down through * ieee80211_start before the vap entered RUN state in * which case it's ok to just drop the frame. This * should not be necessary but callers of if_output don't * check OACTIVE. */ senderr(ENETDOWN); } vap = ifp->if_softc; ic = vap->iv_ic; /* * Hand to the 802.3 code if not tagged as * a raw 802.11 frame. */ if (dst->sa_family != AF_IEEE80211) return vap->iv_output(ifp, m, dst, ro); #ifdef MAC error = mac_ifnet_check_transmit(ifp, m); if (error) senderr(error); #endif if (ifp->if_flags & IFF_MONITOR) senderr(ENETDOWN); if (!IFNET_IS_UP_RUNNING(ifp)) senderr(ENETDOWN); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, "block %s frame in CAC state\n", "raw data"); vap->iv_stats.is_tx_badstate++; senderr(EIO); /* XXX */ } else if (vap->iv_state == IEEE80211_S_SCAN) senderr(EIO); /* XXX bypass bridge, pfil, carp, etc. */ /* * NB: DLT_IEEE802_11_RADIO identifies the parameters are * present by setting the sa_len field of the sockaddr (yes, * this is a hack). * NB: we assume sa_data is suitably aligned to cast. */ if (dst->sa_len != 0) params = (const struct ieee80211_bpf_params *)dst->sa_data; error = ieee80211_validate_frame(m, params); if (error != 0) senderr(error); wh = mtod(m, struct ieee80211_frame *); /* locate destination node */ switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { case IEEE80211_FC1_DIR_NODS: case IEEE80211_FC1_DIR_FROMDS: ni = ieee80211_find_txnode(vap, wh->i_addr1); break; case IEEE80211_FC1_DIR_TODS: case IEEE80211_FC1_DIR_DSTODS: ni = ieee80211_find_txnode(vap, wh->i_addr3); break; default: senderr(EDOOFUS); } if (ni == NULL) { /* * Permit packets w/ bpf params through regardless * (see below about sa_len). */ if (dst->sa_len == 0) senderr(EHOSTUNREACH); ni = ieee80211_ref_node(vap->iv_bss); } /* * Sanitize mbuf for net80211 flags leaked from above. * * NB: This must be done before ieee80211_classify as * it marks EAPOL in frames with M_EAPOL. */ m->m_flags &= ~M_80211_TX; m->m_flags |= M_ENCAP; /* mark encapsulated */ if (IEEE80211_IS_DATA(wh)) { /* calculate priority so drivers can find the tx queue */ if (ieee80211_classify(ni, m)) senderr(EIO); /* XXX */ /* NB: ieee80211_encap does not include 802.11 header */ IEEE80211_NODE_STAT_ADD(ni, tx_bytes, m->m_pkthdr.len - ieee80211_hdrsize(wh)); } else M_WME_SETAC(m, WME_AC_BE); error = ieee80211_sanitize_rates(ni, m, params); if (error != 0) senderr(error); IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_NODE_STAT(ni, tx_mcast); m->m_flags |= M_MCAST; } else IEEE80211_NODE_STAT(ni, tx_ucast); IEEE80211_TX_LOCK(ic); ret = ieee80211_raw_output(vap, ni, m, params); IEEE80211_TX_UNLOCK(ic); return (ret); bad: if (m != NULL) m_freem(m); if (ni != NULL) ieee80211_free_node(ni); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); return error; #undef senderr } /* * Set the direction field and address fields of an outgoing * frame. Note this should be called early on in constructing * a frame as it sets i_fc[1]; other bits can then be or'd in. */ void ieee80211_send_setup( struct ieee80211_node *ni, struct mbuf *m, int type, int tid, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN]) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)wh) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_tx_ampdu *tap; struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); ieee80211_seq seqno; IEEE80211_TX_LOCK_ASSERT(ni->ni_ic); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | type; if ((type & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) { switch (vap->iv_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, bssid); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, da); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); IEEE80211_ADDR_COPY(wh->i_addr3, bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, bssid); IEEE80211_ADDR_COPY(wh->i_addr3, sa); break; case IEEE80211_M_WDS: wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, da); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, sa); break; case IEEE80211_M_MBSS: #ifdef IEEE80211_SUPPORT_MESH if (IEEE80211_IS_MULTICAST(da)) { wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; /* XXX next hop */ IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); } else { wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, da); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, sa); } #endif break; case IEEE80211_M_MONITOR: /* NB: to quiet compiler */ break; } } else { wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, da); IEEE80211_ADDR_COPY(wh->i_addr2, sa); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) IEEE80211_ADDR_COPY(wh->i_addr3, sa); else #endif IEEE80211_ADDR_COPY(wh->i_addr3, bssid); } *(uint16_t *)&wh->i_dur[0] = 0; /* * XXX TODO: this is what the TX lock is for. * Here we're incrementing sequence numbers, and they * need to be in lock-step with what the driver is doing * both in TX ordering and crypto encap (IV increment.) * * If the driver does seqno itself, then we can skip * assigning sequence numbers here, and we can avoid * requiring the TX lock. */ tap = &ni->ni_tx_ampdu[tid]; if (tid != IEEE80211_NONQOS_TID && IEEE80211_AMPDU_RUNNING(tap)) { m->m_flags |= M_AMPDU_MPDU; /* NB: zero out i_seq field (for s/w encryption etc) */ *(uint16_t *)&wh->i_seq[0] = 0; } else { if (IEEE80211_HAS_SEQ(type & IEEE80211_FC0_TYPE_MASK, type & IEEE80211_FC0_SUBTYPE_MASK)) /* * 802.11-2012 9.3.2.10 - QoS multicast frames * come out of a different seqno space. */ if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; } else { seqno = ni->ni_txseqs[tid]++; } else seqno = 0; *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); } if (IEEE80211_IS_MULTICAST(wh->i_addr1)) m->m_flags |= M_MCAST; #undef WH4 } /* * Send a management frame to the specified node. The node pointer * must have a reference as the pointer will be passed to the driver * and potentially held for a long time. If the frame is successfully * dispatched to the driver, then it is responsible for freeing the * reference (and potentially free'ing up any associated storage); * otherwise deal with reclaiming any reference (on error). */ int ieee80211_mgmt_output(struct ieee80211_node *ni, struct mbuf *m, int type, struct ieee80211_bpf_params *params) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_frame *wh; int ret; KASSERT(ni != NULL, ("null node")); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, ni, "block %s frame in CAC state", ieee80211_mgt_subtype_name(type)); vap->iv_stats.is_tx_badstate++; ieee80211_free_node(ni); m_freem(m); return EIO; /* XXX */ } M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); if (m == NULL) { ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | type, IEEE80211_NONQOS_TID, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); if (params->ibp_flags & IEEE80211_BPF_CRYPTO) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr1, "encrypting frame (%s)", __func__); wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; } m->m_flags |= M_ENCAP; /* mark encapsulated */ KASSERT(type != IEEE80211_FC0_SUBTYPE_PROBE_RESP, ("probe response?")); M_WME_SETAC(m, params->ibp_pri); #ifdef IEEE80211_DEBUG /* avoid printing too many frames */ if ((ieee80211_msg_debug(vap) && doprint(vap, type)) || ieee80211_msg_dumppkts(vap)) { ieee80211_note(vap, "[%s] send %s on channel %u\n", ether_sprintf(wh->i_addr1), ieee80211_mgt_subtype_name(type), ieee80211_chan2ieee(ic, ic->ic_curchan)); } #endif IEEE80211_NODE_STAT(ni, tx_mgmt); ret = ieee80211_raw_output(vap, ni, m, params); IEEE80211_TX_UNLOCK(ic); return (ret); } static void ieee80211_nulldata_transmitted(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211vap *vap = ni->ni_vap; wakeup(vap); } /* * Send a null data frame to the specified node. If the station * is setup for QoS then a QoS Null Data frame is constructed. * If this is a WDS station then a 4-address frame is constructed. * * NB: the caller is assumed to have setup a node reference * for use; this is necessary to deal with a race condition * when probing for inactive stations. Like ieee80211_mgmt_output * we must cleanup any node reference on error; however we * can safely just unref it as we know it will never be the * last reference to the node. */ int ieee80211_send_nulldata(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct mbuf *m; struct ieee80211_frame *wh; int hdrlen; uint8_t *frm; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT | IEEE80211_MSG_DOTH, ni, "block %s frame in CAC state", "null data"); ieee80211_node_decref(ni); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } if (ni->ni_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT)) hdrlen = sizeof(struct ieee80211_qosframe); else hdrlen = sizeof(struct ieee80211_frame); /* NB: only WDS vap's get 4-address frames */ if (vap->iv_opmode == IEEE80211_M_WDS) hdrlen += IEEE80211_ADDR_LEN; if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrlen = roundup(hdrlen, sizeof(uint32_t)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + hdrlen, 0); if (m == NULL) { /* XXX debug msg */ ieee80211_node_decref(ni); vap->iv_stats.is_tx_nobuf++; return ENOMEM; } KASSERT(M_LEADINGSPACE(m) >= hdrlen, ("leading space %zd", M_LEADINGSPACE(m))); M_PREPEND(m, hdrlen, IEEE80211_M_NOWAIT); if (m == NULL) { /* NB: cannot happen */ ieee80211_free_node(ni); return ENOMEM; } IEEE80211_TX_LOCK(ic); wh = mtod(m, struct ieee80211_frame *); /* NB: a little lie */ if (ni->ni_flags & IEEE80211_NODE_QOS) { const int tid = WME_AC_TO_TID(WME_AC_BE); uint8_t *qos; ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_QOS_NULL, tid, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); if (vap->iv_opmode == IEEE80211_M_WDS) qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; else qos = ((struct ieee80211_qosframe *) wh)->i_qos; qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[WME_AC_BE].wmep_noackPolicy) qos[0] |= IEEE80211_QOS_ACKPOLICY_NOACK; qos[1] = 0; } else { ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_NODATA, IEEE80211_NONQOS_TID, vap->iv_myaddr, ni->ni_macaddr, ni->ni_bssid); } if (vap->iv_opmode != IEEE80211_M_WDS) { /* NB: power management bit is never sent by an AP */ if ((ni->ni_flags & IEEE80211_NODE_PWR_MGT) && vap->iv_opmode != IEEE80211_M_HOSTAP) wh->i_fc[1] |= IEEE80211_FC1_PWR_MGT; } if ((ic->ic_flags & IEEE80211_F_SCAN) && (ni->ni_flags & IEEE80211_NODE_PWR_MGT)) { ieee80211_add_callback(m, ieee80211_nulldata_transmitted, NULL); } m->m_len = m->m_pkthdr.len = hdrlen; m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_NODE_STAT(ni, tx_data); IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, ni, "send %snull data frame on channel %u, pwr mgt %s", ni->ni_flags & IEEE80211_NODE_QOS ? "QoS " : "", ieee80211_chan2ieee(ic, ic->ic_curchan), wh->i_fc[1] & IEEE80211_FC1_PWR_MGT ? "ena" : "dis"); ret = ieee80211_raw_output(vap, ni, m, NULL); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Assign priority to a frame based on any vlan tag assigned * to the station and/or any Diffserv setting in an IP header. * Finally, if an ACM policy is setup (in station mode) it's * applied. */ int ieee80211_classify(struct ieee80211_node *ni, struct mbuf *m) { const struct ether_header *eh = NULL; uint16_t ether_type; int v_wme_ac, d_wme_ac, ac; if (__predict_false(m->m_flags & M_ENCAP)) { struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); struct llc *llc; int hdrlen, subtype; subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; if (subtype & IEEE80211_FC0_SUBTYPE_NODATA) { ac = WME_AC_BE; goto done; } hdrlen = ieee80211_hdrsize(wh); if (m->m_pkthdr.len < hdrlen + sizeof(*llc)) return 1; llc = (struct llc *)mtodo(m, hdrlen); if (llc->llc_dsap != LLC_SNAP_LSAP || llc->llc_ssap != LLC_SNAP_LSAP || llc->llc_control != LLC_UI || llc->llc_snap.org_code[0] != 0 || llc->llc_snap.org_code[1] != 0 || llc->llc_snap.org_code[2] != 0) return 1; ether_type = llc->llc_snap.ether_type; } else { eh = mtod(m, struct ether_header *); ether_type = eh->ether_type; } /* * Always promote PAE/EAPOL frames to high priority. */ if (ether_type == htons(ETHERTYPE_PAE)) { /* NB: mark so others don't need to check header */ m->m_flags |= M_EAPOL; ac = WME_AC_VO; goto done; } /* * Non-qos traffic goes to BE. */ if ((ni->ni_flags & IEEE80211_NODE_QOS) == 0) { ac = WME_AC_BE; goto done; } /* * If node has a vlan tag then all traffic * to it must have a matching tag. */ v_wme_ac = 0; if (ni->ni_vlan != 0) { if ((m->m_flags & M_VLANTAG) == 0) { IEEE80211_NODE_STAT(ni, tx_novlantag); return 1; } if (EVL_VLANOFTAG(m->m_pkthdr.ether_vtag) != EVL_VLANOFTAG(ni->ni_vlan)) { IEEE80211_NODE_STAT(ni, tx_vlanmismatch); return 1; } /* map vlan priority to AC */ v_wme_ac = TID_TO_WME_AC(EVL_PRIOFTAG(ni->ni_vlan)); } if (eh == NULL) goto no_eh; /* XXX m_copydata may be too slow for fast path */ switch (ntohs(eh->ether_type)) { #ifdef INET case ETHERTYPE_IP: { uint8_t tos; /* * IP frame, map the DSCP bits from the TOS field. */ /* NB: ip header may not be in first mbuf */ m_copydata(m, sizeof(struct ether_header) + offsetof(struct ip, ip_tos), sizeof(tos), &tos); tos >>= 5; /* NB: ECN + low 3 bits of DSCP */ d_wme_ac = TID_TO_WME_AC(tos); break; } #endif #ifdef INET6 case ETHERTYPE_IPV6: { uint32_t flow; uint8_t tos; /* * IPv6 frame, map the DSCP bits from the traffic class field. */ m_copydata(m, sizeof(struct ether_header) + offsetof(struct ip6_hdr, ip6_flow), sizeof(flow), (caddr_t) &flow); tos = (uint8_t)(ntohl(flow) >> 20); tos >>= 5; /* NB: ECN + low 3 bits of DSCP */ d_wme_ac = TID_TO_WME_AC(tos); break; } #endif default: no_eh: d_wme_ac = WME_AC_BE; break; } /* * Use highest priority AC. */ if (v_wme_ac > d_wme_ac) ac = v_wme_ac; else ac = d_wme_ac; /* * Apply ACM policy. */ if (ni->ni_vap->iv_opmode == IEEE80211_M_STA) { static const int acmap[4] = { WME_AC_BK, /* WME_AC_BE */ WME_AC_BK, /* WME_AC_BK */ WME_AC_BE, /* WME_AC_VI */ WME_AC_VI, /* WME_AC_VO */ }; struct ieee80211com *ic = ni->ni_ic; while (ac != WME_AC_BK && ic->ic_wme.wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm) ac = acmap[ac]; } done: M_WME_SETAC(m, ac); return 0; } /* * Insure there is sufficient contiguous space to encapsulate the * 802.11 data frame. If room isn't already there, arrange for it. * Drivers and cipher modules assume we have done the necessary work * and fail rudely if they don't find the space they need. */ struct mbuf * ieee80211_mbuf_adjust(struct ieee80211vap *vap, int hdrsize, struct ieee80211_key *key, struct mbuf *m) { #define TO_BE_RECLAIMED (sizeof(struct ether_header) - sizeof(struct llc)) int needed_space = vap->iv_ic->ic_headroom + hdrsize; if (key != NULL) { /* XXX belongs in crypto code? */ needed_space += key->wk_cipher->ic_header; /* XXX frags */ /* * When crypto is being done in the host we must insure * the data are writable for the cipher routines; clone * a writable mbuf chain. * XXX handle SWMIC specially */ if (key->wk_flags & (IEEE80211_KEY_SWENCRYPT|IEEE80211_KEY_SWENMIC)) { m = m_unshare(m, IEEE80211_M_NOWAIT); if (m == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: cannot get writable mbuf\n", __func__); vap->iv_stats.is_tx_nobuf++; /* XXX new stat */ return NULL; } } } /* * We know we are called just before stripping an Ethernet * header and prepending an LLC header. This means we know * there will be * sizeof(struct ether_header) - sizeof(struct llc) * bytes recovered to which we need additional space for the * 802.11 header and any crypto header. */ /* XXX check trailing space and copy instead? */ if (M_LEADINGSPACE(m) < needed_space - TO_BE_RECLAIMED) { struct mbuf *n = m_gethdr(IEEE80211_M_NOWAIT, m->m_type); if (n == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_OUTPUT, "%s: cannot expand storage\n", __func__); vap->iv_stats.is_tx_nobuf++; m_freem(m); return NULL; } KASSERT(needed_space <= MHLEN, ("not enough room, need %u got %d\n", needed_space, MHLEN)); /* * Setup new mbuf to have leading space to prepend the * 802.11 header and any crypto header bits that are * required (the latter are added when the driver calls * back to ieee80211_crypto_encap to do crypto encapsulation). */ /* NB: must be first 'cuz it clobbers m_data */ m_move_pkthdr(n, m); n->m_len = 0; /* NB: m_gethdr does not set */ n->m_data += needed_space; /* * Pull up Ethernet header to create the expected layout. * We could use m_pullup but that's overkill (i.e. we don't * need the actual data) and it cannot fail so do it inline * for speed. */ /* NB: struct ether_header is known to be contiguous */ n->m_len += sizeof(struct ether_header); m->m_len -= sizeof(struct ether_header); m->m_data += sizeof(struct ether_header); /* * Replace the head of the chain. */ n->m_next = m; m = n; } return m; #undef TO_BE_RECLAIMED } /* * Return the transmit key to use in sending a unicast frame. * If a unicast key is set we use that. When no unicast key is set * we fall back to the default transmit key. */ static __inline struct ieee80211_key * ieee80211_crypto_getucastkey(struct ieee80211vap *vap, struct ieee80211_node *ni) { if (IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE || IEEE80211_KEY_UNDEFINED(&vap->iv_nw_keys[vap->iv_def_txkey])) return NULL; return &vap->iv_nw_keys[vap->iv_def_txkey]; } else { return &ni->ni_ucastkey; } } /* * Return the transmit key to use in sending a multicast frame. * Multicast traffic always uses the group key which is installed as * the default tx key. */ static __inline struct ieee80211_key * ieee80211_crypto_getmcastkey(struct ieee80211vap *vap, struct ieee80211_node *ni) { if (vap->iv_def_txkey == IEEE80211_KEYIX_NONE || IEEE80211_KEY_UNDEFINED(&vap->iv_nw_keys[vap->iv_def_txkey])) return NULL; return &vap->iv_nw_keys[vap->iv_def_txkey]; } /* * Encapsulate an outbound data frame. The mbuf chain is updated. * If an error is encountered NULL is returned. The caller is required * to provide a node reference and pullup the ethernet header in the * first mbuf. * * NB: Packet is assumed to be processed by ieee80211_classify which * marked EAPOL frames w/ M_EAPOL. */ struct mbuf * ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni, struct mbuf *m) { #define WH4(wh) ((struct ieee80211_frame_addr4 *)(wh)) #define MC01(mc) ((struct ieee80211_meshcntl_ae01 *)mc) struct ieee80211com *ic = ni->ni_ic; #ifdef IEEE80211_SUPPORT_MESH struct ieee80211_mesh_state *ms = vap->iv_mesh; struct ieee80211_meshcntl_ae10 *mc; struct ieee80211_mesh_route *rt = NULL; int dir = -1; #endif struct ether_header eh; struct ieee80211_frame *wh; struct ieee80211_key *key; struct llc *llc; int hdrsize, hdrspace, datalen, addqos, txfrag, is4addr, is_mcast; ieee80211_seq seqno; int meshhdrsize, meshae; uint8_t *qos; int is_amsdu = 0; IEEE80211_TX_LOCK_ASSERT(ic); is_mcast = !! (m->m_flags & (M_MCAST | M_BCAST)); /* * Copy existing Ethernet header to a safe place. The * rest of the code assumes it's ok to strip it when * reorganizing state for the final encapsulation. */ KASSERT(m->m_len >= sizeof(eh), ("no ethernet header!")); ETHER_HEADER_COPY(&eh, mtod(m, caddr_t)); /* * Insure space for additional headers. First identify * transmit key to use in calculating any buffer adjustments * required. This is also used below to do privacy * encapsulation work. Then calculate the 802.11 header * size and any padding required by the driver. * * Note key may be NULL if we fall back to the default * transmit key and that is not set. In that case the * buffer may not be expanded as needed by the cipher * routines, but they will/should discard it. */ if (vap->iv_flags & IEEE80211_F_PRIVACY) { if (vap->iv_opmode == IEEE80211_M_STA || !IEEE80211_IS_MULTICAST(eh.ether_dhost) || (vap->iv_opmode == IEEE80211_M_WDS && (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY))) { key = ieee80211_crypto_getucastkey(vap, ni); } else if ((vap->iv_opmode == IEEE80211_M_WDS) && (! (vap->iv_flags_ext & IEEE80211_FEXT_WDSLEGACY))) { /* * Use ucastkey for DWDS transmit nodes, multicast * or otherwise. * * This is required to ensure that multicast frames * from a DWDS AP to a DWDS STA is encrypted with * a key that can actually work. * * There's no default key for multicast traffic * on a DWDS WDS VAP node (note NOT the DWDS enabled * AP VAP, the dynamically created per-STA WDS node) * so encap fails and transmit fails. */ key = ieee80211_crypto_getucastkey(vap, ni); } else { key = ieee80211_crypto_getmcastkey(vap, ni); } if (key == NULL && (m->m_flags & M_EAPOL) == 0) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, eh.ether_dhost, "no default transmit key (%s) deftxkey %u", __func__, vap->iv_def_txkey); vap->iv_stats.is_tx_nodefkey++; goto bad; } } else key = NULL; /* * XXX Some ap's don't handle QoS-encapsulated EAPOL * frames so suppress use. This may be an issue if other * ap's require all data frames to be QoS-encapsulated * once negotiated in which case we'll need to make this * configurable. * * Don't send multicast QoS frames. * Technically multicast frames can be QoS if all stations in the * BSS are also QoS. * * NB: mesh data frames are QoS, including multicast frames. */ addqos = (((is_mcast == 0) && (ni->ni_flags & (IEEE80211_NODE_QOS|IEEE80211_NODE_HT))) || (vap->iv_opmode == IEEE80211_M_MBSS)) && (m->m_flags & M_EAPOL) == 0; if (addqos) hdrsize = sizeof(struct ieee80211_qosframe); else hdrsize = sizeof(struct ieee80211_frame); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { /* * Mesh data frames are encapsulated according to the * rules of Section 11B.8.5 (p.139 of D3.0 spec). * o Group Addressed data (aka multicast) originating * at the local sta are sent w/ 3-address format and * address extension mode 00 * o Individually Addressed data (aka unicast) originating * at the local sta are sent w/ 4-address format and * address extension mode 00 * o Group Addressed data forwarded from a non-mesh sta are * sent w/ 3-address format and address extension mode 01 * o Individually Address data from another sta are sent * w/ 4-address format and address extension mode 10 */ is4addr = 0; /* NB: don't use, disable */ if (!IEEE80211_IS_MULTICAST(eh.ether_dhost)) { rt = ieee80211_mesh_rt_find(vap, eh.ether_dhost); KASSERT(rt != NULL, ("route is NULL")); dir = IEEE80211_FC1_DIR_DSTODS; hdrsize += IEEE80211_ADDR_LEN; if (rt->rt_flags & IEEE80211_MESHRT_FLAGS_PROXY) { if (IEEE80211_ADDR_EQ(rt->rt_mesh_gate, vap->iv_myaddr)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_MESH, eh.ether_dhost, "%s", "trying to send to ourself"); goto bad; } meshae = IEEE80211_MESH_AE_10; meshhdrsize = sizeof(struct ieee80211_meshcntl_ae10); } else { meshae = IEEE80211_MESH_AE_00; meshhdrsize = sizeof(struct ieee80211_meshcntl); } } else { dir = IEEE80211_FC1_DIR_FROMDS; if (!IEEE80211_ADDR_EQ(eh.ether_shost, vap->iv_myaddr)) { /* proxy group */ meshae = IEEE80211_MESH_AE_01; meshhdrsize = sizeof(struct ieee80211_meshcntl_ae01); } else { /* group */ meshae = IEEE80211_MESH_AE_00; meshhdrsize = sizeof(struct ieee80211_meshcntl); } } } else { #endif /* * 4-address frames need to be generated for: * o packets sent through a WDS vap (IEEE80211_M_WDS) * o packets sent through a vap marked for relaying * (e.g. a station operating with dynamic WDS) */ is4addr = vap->iv_opmode == IEEE80211_M_WDS || ((vap->iv_flags_ext & IEEE80211_FEXT_4ADDR) && !IEEE80211_ADDR_EQ(eh.ether_shost, vap->iv_myaddr)); if (is4addr) hdrsize += IEEE80211_ADDR_LEN; meshhdrsize = meshae = 0; #ifdef IEEE80211_SUPPORT_MESH } #endif /* * Honor driver DATAPAD requirement. */ if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrspace = roundup(hdrsize, sizeof(uint32_t)); else hdrspace = hdrsize; if (__predict_true((m->m_flags & M_FF) == 0)) { /* * Normal frame. */ m = ieee80211_mbuf_adjust(vap, hdrspace + meshhdrsize, key, m); if (m == NULL) { /* NB: ieee80211_mbuf_adjust handles msgs+statistics */ goto bad; } /* NB: this could be optimized 'cuz of ieee80211_mbuf_adjust */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh.ether_type; } else { #ifdef IEEE80211_SUPPORT_SUPERG /* * Aggregated frame. Check if it's for AMSDU or FF. * * XXX TODO: IEEE80211_NODE_AMSDU* isn't implemented * anywhere for some reason. But, since 11n requires * AMSDU RX, we can just assume "11n" == "AMSDU". */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: called; M_FF\n", __func__); if (ieee80211_amsdu_tx_ok(ni)) { m = ieee80211_amsdu_encap(vap, m, hdrspace + meshhdrsize, key); is_amsdu = 1; } else { m = ieee80211_ff_encap(vap, m, hdrspace + meshhdrsize, key); } if (m == NULL) #endif goto bad; } datalen = m->m_pkthdr.len; /* NB: w/o 802.11 header */ M_PREPEND(m, hdrspace + meshhdrsize, IEEE80211_M_NOWAIT); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; goto bad; } wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_DATA; *(uint16_t *)wh->i_dur = 0; qos = NULL; /* NB: quiet compiler */ if (is4addr) { wh->i_fc[1] = IEEE80211_FC1_DIR_DSTODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, eh.ether_shost); } else switch (vap->iv_opmode) { case IEEE80211_M_STA: wh->i_fc[1] = IEEE80211_FC1_DIR_TODS; IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); break; case IEEE80211_M_IBSS: case IEEE80211_M_AHDEMO: wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, eh.ether_shost); /* * NB: always use the bssid from iv_bss as the * neighbor's may be stale after an ibss merge */ IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_bss->ni_bssid); break; case IEEE80211_M_HOSTAP: wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, ni->ni_bssid); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); break; #ifdef IEEE80211_SUPPORT_MESH case IEEE80211_M_MBSS: /* NB: offset by hdrspace to deal with DATAPAD */ mc = (struct ieee80211_meshcntl_ae10 *) (mtod(m, uint8_t *) + hdrspace); wh->i_fc[1] = dir; switch (meshae) { case IEEE80211_MESH_AE_00: /* no proxy */ mc->mc_flags = 0; if (dir == IEEE80211_FC1_DIR_DSTODS) { /* ucast */ IEEE80211_ADDR_COPY(wh->i_addr1, ni->ni_macaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_dhost); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, eh.ether_shost); qos =((struct ieee80211_qosframe_addr4 *) wh)->i_qos; } else if (dir == IEEE80211_FC1_DIR_FROMDS) { /* mcast */ IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, eh.ether_shost); qos = ((struct ieee80211_qosframe *) wh)->i_qos; } break; case IEEE80211_MESH_AE_01: /* mcast, proxy */ wh->i_fc[1] = IEEE80211_FC1_DIR_FROMDS; IEEE80211_ADDR_COPY(wh->i_addr1, eh.ether_dhost); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, vap->iv_myaddr); mc->mc_flags = 1; IEEE80211_ADDR_COPY(MC01(mc)->mc_addr4, eh.ether_shost); qos = ((struct ieee80211_qosframe *) wh)->i_qos; break; case IEEE80211_MESH_AE_10: /* ucast, proxy */ KASSERT(rt != NULL, ("route is NULL")); IEEE80211_ADDR_COPY(wh->i_addr1, rt->rt_nexthop); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, rt->rt_mesh_gate); IEEE80211_ADDR_COPY(WH4(wh)->i_addr4, vap->iv_myaddr); mc->mc_flags = IEEE80211_MESH_AE_10; IEEE80211_ADDR_COPY(mc->mc_addr5, eh.ether_dhost); IEEE80211_ADDR_COPY(mc->mc_addr6, eh.ether_shost); qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; break; default: KASSERT(0, ("meshae %d", meshae)); break; } mc->mc_ttl = ms->ms_ttl; ms->ms_seq++; le32enc(mc->mc_seq, ms->ms_seq); break; #endif case IEEE80211_M_WDS: /* NB: is4addr should always be true */ default: goto bad; } if (m->m_flags & M_MORE_DATA) wh->i_fc[1] |= IEEE80211_FC1_MORE_DATA; if (addqos) { int ac, tid; if (is4addr) { qos = ((struct ieee80211_qosframe_addr4 *) wh)->i_qos; /* NB: mesh case handled earlier */ } else if (vap->iv_opmode != IEEE80211_M_MBSS) qos = ((struct ieee80211_qosframe *) wh)->i_qos; ac = M_WME_GETAC(m); /* map from access class/queue to 11e header priorty value */ tid = WME_AC_TO_TID(ac); qos[0] = tid & IEEE80211_QOS_TID; if (ic->ic_wme.wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy) qos[0] |= IEEE80211_QOS_ACKPOLICY_NOACK; #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) qos[1] = IEEE80211_QOS_MC; else #endif qos[1] = 0; wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS_DATA; /* * If this is an A-MSDU then ensure we set the * relevant field. */ if (is_amsdu) qos[0] |= IEEE80211_QOS_AMSDU; /* * XXX TODO TX lock is needed for atomic updates of sequence * numbers. If the driver does it, then don't do it here; * and we don't need the TX lock held. */ if ((m->m_flags & M_AMPDU_MPDU) == 0) { /* * 802.11-2012 9.3.2.10 - * * If this is a multicast frame then we need * to ensure that the sequence number comes from * a separate seqno space and not the TID space. * * Otherwise multicast frames may actually cause * holes in the TX blockack window space and * upset various things. */ if (IEEE80211_IS_MULTICAST(wh->i_addr1)) seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; else seqno = ni->ni_txseqs[tid]++; /* * NB: don't assign a sequence # to potential * aggregates; we expect this happens at the * point the frame comes off any aggregation q * as otherwise we may introduce holes in the * BA sequence space and/or make window accouting * more difficult. * * XXX may want to control this with a driver * capability; this may also change when we pull * aggregation up into net80211 */ *(uint16_t *)wh->i_seq = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); } else { /* NB: zero out i_seq field (for s/w encryption etc) */ *(uint16_t *)wh->i_seq = 0; } } else { /* * XXX TODO TX lock is needed for atomic updates of sequence * numbers. If the driver does it, then don't do it here; * and we don't need the TX lock held. */ seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)wh->i_seq = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); /* * XXX TODO: we shouldn't allow EAPOL, etc that would * be forced to be non-QoS traffic to be A-MSDU encapsulated. */ if (is_amsdu) printf("%s: XXX ERROR: is_amsdu set; not QoS!\n", __func__); } /* * Check if xmit fragmentation is required. * * If the hardware does fragmentation offload, then don't bother * doing it here. */ if (IEEE80211_CONF_FRAG_OFFLOAD(ic)) txfrag = 0; else txfrag = (m->m_pkthdr.len > vap->iv_fragthreshold && !IEEE80211_IS_MULTICAST(wh->i_addr1) && (vap->iv_caps & IEEE80211_C_TXFRAG) && (m->m_flags & (M_FF | M_AMPDU_MPDU)) == 0); if (key != NULL) { /* * IEEE 802.1X: send EAPOL frames always in the clear. * WPA/WPA2: encrypt EAPOL keys when pairwise keys are set. */ if ((m->m_flags & M_EAPOL) == 0 || ((vap->iv_flags & IEEE80211_F_WPA) && (vap->iv_opmode == IEEE80211_M_STA ? !IEEE80211_KEY_UNDEFINED(key) : !IEEE80211_KEY_UNDEFINED(&ni->ni_ucastkey)))) { wh->i_fc[1] |= IEEE80211_FC1_PROTECTED; if (!ieee80211_crypto_enmic(vap, key, m, txfrag)) { IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_OUTPUT, eh.ether_dhost, "%s", "enmic failed, discard frame"); vap->iv_stats.is_crypto_enmicfail++; goto bad; } } } if (txfrag && !ieee80211_fragment(vap, m, hdrsize, key != NULL ? key->wk_cipher->ic_header : 0, vap->iv_fragthreshold)) goto bad; m->m_flags |= M_ENCAP; /* mark encapsulated */ IEEE80211_NODE_STAT(ni, tx_data); if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { IEEE80211_NODE_STAT(ni, tx_mcast); m->m_flags |= M_MCAST; } else IEEE80211_NODE_STAT(ni, tx_ucast); IEEE80211_NODE_STAT_ADD(ni, tx_bytes, datalen); return m; bad: if (m != NULL) m_freem(m); return NULL; #undef WH4 #undef MC01 } void ieee80211_free_mbuf(struct mbuf *m) { struct mbuf *next; if (m == NULL) return; do { next = m->m_nextpkt; m->m_nextpkt = NULL; m_freem(m); } while ((m = next) != NULL); } /* * Fragment the frame according to the specified mtu. * The size of the 802.11 header (w/o padding) is provided * so we don't need to recalculate it. We create a new * mbuf for each fragment and chain it through m_nextpkt; * we might be able to optimize this by reusing the original * packet's mbufs but that is significantly more complicated. */ static int ieee80211_fragment(struct ieee80211vap *vap, struct mbuf *m0, u_int hdrsize, u_int ciphdrsize, u_int mtu) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_frame *wh, *whf; struct mbuf *m, *prev; u_int totalhdrsize, fragno, fragsize, off, remainder, payload; u_int hdrspace; KASSERT(m0->m_nextpkt == NULL, ("mbuf already chained?")); KASSERT(m0->m_pkthdr.len > mtu, ("pktlen %u mtu %u", m0->m_pkthdr.len, mtu)); /* * Honor driver DATAPAD requirement. */ if (ic->ic_flags & IEEE80211_F_DATAPAD) hdrspace = roundup(hdrsize, sizeof(uint32_t)); else hdrspace = hdrsize; wh = mtod(m0, struct ieee80211_frame *); /* NB: mark the first frag; it will be propagated below */ wh->i_fc[1] |= IEEE80211_FC1_MORE_FRAG; totalhdrsize = hdrspace + ciphdrsize; fragno = 1; off = mtu - ciphdrsize; remainder = m0->m_pkthdr.len - off; prev = m0; do { fragsize = MIN(totalhdrsize + remainder, mtu); m = m_get2(fragsize, IEEE80211_M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) goto bad; /* leave room to prepend any cipher header */ m_align(m, fragsize - ciphdrsize); /* * Form the header in the fragment. Note that since * we mark the first fragment with the MORE_FRAG bit * it automatically is propagated to each fragment; we * need only clear it on the last fragment (done below). * NB: frag 1+ dont have Mesh Control field present. */ whf = mtod(m, struct ieee80211_frame *); memcpy(whf, wh, hdrsize); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_getqos(wh)[1] &= ~IEEE80211_QOS_MC; #endif *(uint16_t *)&whf->i_seq[0] |= htole16( (fragno & IEEE80211_SEQ_FRAG_MASK) << IEEE80211_SEQ_FRAG_SHIFT); fragno++; payload = fragsize - totalhdrsize; /* NB: destination is known to be contiguous */ m_copydata(m0, off, payload, mtod(m, uint8_t *) + hdrspace); m->m_len = hdrspace + payload; m->m_pkthdr.len = hdrspace + payload; m->m_flags |= M_FRAG; /* chain up the fragment */ prev->m_nextpkt = m; prev = m; /* deduct fragment just formed */ remainder -= payload; off += payload; } while (remainder != 0); /* set the last fragment */ m->m_flags |= M_LASTFRAG; whf->i_fc[1] &= ~IEEE80211_FC1_MORE_FRAG; /* strip first mbuf now that everything has been copied */ m_adj(m0, -(m0->m_pkthdr.len - (mtu - ciphdrsize))); m0->m_flags |= M_FIRSTFRAG | M_FRAG; vap->iv_stats.is_tx_fragframes++; vap->iv_stats.is_tx_frags += fragno-1; return 1; bad: /* reclaim fragments but leave original frame for caller to free */ ieee80211_free_mbuf(m0->m_nextpkt); m0->m_nextpkt = NULL; return 0; } /* * Add a supported rates element id to a frame. */ uint8_t * ieee80211_add_rates(uint8_t *frm, const struct ieee80211_rateset *rs) { int nrates; *frm++ = IEEE80211_ELEMID_RATES; nrates = rs->rs_nrates; if (nrates > IEEE80211_RATE_SIZE) nrates = IEEE80211_RATE_SIZE; *frm++ = nrates; memcpy(frm, rs->rs_rates, nrates); return frm + nrates; } /* * Add an extended supported rates element id to a frame. */ uint8_t * ieee80211_add_xrates(uint8_t *frm, const struct ieee80211_rateset *rs) { /* * Add an extended supported rates element if operating in 11g mode. */ if (rs->rs_nrates > IEEE80211_RATE_SIZE) { int nrates = rs->rs_nrates - IEEE80211_RATE_SIZE; *frm++ = IEEE80211_ELEMID_XRATES; *frm++ = nrates; memcpy(frm, rs->rs_rates + IEEE80211_RATE_SIZE, nrates); frm += nrates; } return frm; } /* * Add an ssid element to a frame. */ uint8_t * ieee80211_add_ssid(uint8_t *frm, const uint8_t *ssid, u_int len) { *frm++ = IEEE80211_ELEMID_SSID; *frm++ = len; memcpy(frm, ssid, len); return frm + len; } /* * Add an erp element to a frame. */ static uint8_t * ieee80211_add_erp(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; uint8_t erp; *frm++ = IEEE80211_ELEMID_ERP; *frm++ = 1; erp = 0; /* * TODO: This uses the global flags for now because * the per-VAP flags are fine for per-VAP, but don't * take into account which VAPs share the same channel * and which are on different channels. * * ERP and HT/VHT protection mode is a function of * how many stations are on a channel, not specifically * the VAP or global. But, until we grow that status, * the global flag will have to do. */ if (ic->ic_flags_ext & IEEE80211_FEXT_NONERP_PR) erp |= IEEE80211_ERP_NON_ERP_PRESENT; /* * TODO: same as above; these should be based not * on the vap or ic flags, but instead on a combination * of per-VAP and channels. */ if (ic->ic_flags & IEEE80211_F_USEPROT) erp |= IEEE80211_ERP_USE_PROTECTION; if (ic->ic_flags & IEEE80211_F_USEBARKER) erp |= IEEE80211_ERP_LONG_PREAMBLE; *frm++ = erp; return frm; } /* * Add a CFParams element to a frame. */ static uint8_t * ieee80211_add_cfparms(uint8_t *frm, struct ieee80211com *ic) { #define ADDSHORT(frm, v) do { \ le16enc(frm, v); \ frm += 2; \ } while (0) *frm++ = IEEE80211_ELEMID_CFPARMS; *frm++ = 6; *frm++ = 0; /* CFP count */ *frm++ = 2; /* CFP period */ ADDSHORT(frm, 0); /* CFP MaxDuration (TU) */ ADDSHORT(frm, 0); /* CFP CurRemaining (TU) */ return frm; #undef ADDSHORT } static __inline uint8_t * add_appie(uint8_t *frm, const struct ieee80211_appie *ie) { memcpy(frm, ie->ie_data, ie->ie_len); return frm + ie->ie_len; } static __inline uint8_t * add_ie(uint8_t *frm, const uint8_t *ie) { memcpy(frm, ie, 2 + ie[1]); return frm + 2 + ie[1]; } #define WME_OUI_BYTES 0x00, 0x50, 0xf2 /* * Add a WME information element to a frame. */ uint8_t * ieee80211_add_wme_info(uint8_t *frm, struct ieee80211_wme_state *wme, struct ieee80211_node *ni) { static const uint8_t oui[4] = { WME_OUI_BYTES, WME_OUI_TYPE }; struct ieee80211vap *vap = ni->ni_vap; *frm++ = IEEE80211_ELEMID_VENDOR; *frm++ = sizeof(struct ieee80211_wme_info) - 2; memcpy(frm, oui, sizeof(oui)); frm += sizeof(oui); *frm++ = WME_INFO_OUI_SUBTYPE; *frm++ = WME_VERSION; /* QoS info field depends upon operating mode */ switch (vap->iv_opmode) { case IEEE80211_M_HOSTAP: *frm = wme->wme_bssChanParams.cap_info; if (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD) *frm |= WME_CAPINFO_UAPSD_EN; frm++; break; case IEEE80211_M_STA: /* * NB: UAPSD drivers must set this up in their * VAP creation method. */ *frm++ = vap->iv_uapsdinfo; break; default: *frm++ = 0; break; } return frm; } /* * Add a WME parameters element to a frame. */ static uint8_t * ieee80211_add_wme_param(uint8_t *frm, struct ieee80211_wme_state *wme, int uapsd_enable) { #define ADDSHORT(frm, v) do { \ le16enc(frm, v); \ frm += 2; \ } while (0) /* NB: this works 'cuz a param has an info at the front */ static const struct ieee80211_wme_info param = { .wme_id = IEEE80211_ELEMID_VENDOR, .wme_len = sizeof(struct ieee80211_wme_param) - 2, .wme_oui = { WME_OUI_BYTES }, .wme_type = WME_OUI_TYPE, .wme_subtype = WME_PARAM_OUI_SUBTYPE, .wme_version = WME_VERSION, }; int i; memcpy(frm, ¶m, sizeof(param)); frm += __offsetof(struct ieee80211_wme_info, wme_info); *frm = wme->wme_bssChanParams.cap_info; /* AC info */ if (uapsd_enable) *frm |= WME_CAPINFO_UAPSD_EN; frm++; *frm++ = 0; /* reserved field */ /* XXX TODO - U-APSD bits - SP, flags below */ for (i = 0; i < WME_NUM_AC; i++) { const struct wmeParams *ac = &wme->wme_bssChanParams.cap_wmeParams[i]; *frm++ = _IEEE80211_SHIFTMASK(i, WME_PARAM_ACI) | _IEEE80211_SHIFTMASK(ac->wmep_acm, WME_PARAM_ACM) | _IEEE80211_SHIFTMASK(ac->wmep_aifsn, WME_PARAM_AIFSN) ; *frm++ = _IEEE80211_SHIFTMASK(ac->wmep_logcwmax, WME_PARAM_LOGCWMAX) | _IEEE80211_SHIFTMASK(ac->wmep_logcwmin, WME_PARAM_LOGCWMIN) ; ADDSHORT(frm, ac->wmep_txopLimit); } return frm; #undef ADDSHORT } #undef WME_OUI_BYTES /* * Add an 11h Power Constraint element to a frame. */ static uint8_t * ieee80211_add_powerconstraint(uint8_t *frm, struct ieee80211vap *vap) { const struct ieee80211_channel *c = vap->iv_bss->ni_chan; /* XXX per-vap tx power limit? */ int8_t limit = vap->iv_ic->ic_txpowlimit / 2; frm[0] = IEEE80211_ELEMID_PWRCNSTR; frm[1] = 1; frm[2] = c->ic_maxregpower > limit ? c->ic_maxregpower - limit : 0; return frm + 3; } /* * Add an 11h Power Capability element to a frame. */ static uint8_t * ieee80211_add_powercapability(uint8_t *frm, const struct ieee80211_channel *c) { frm[0] = IEEE80211_ELEMID_PWRCAP; frm[1] = 2; frm[2] = c->ic_minpower; frm[3] = c->ic_maxpower; return frm + 4; } /* * Add an 11h Supported Channels element to a frame. */ static uint8_t * ieee80211_add_supportedchannels(uint8_t *frm, struct ieee80211com *ic) { static const int ielen = 26; frm[0] = IEEE80211_ELEMID_SUPPCHAN; frm[1] = ielen; /* XXX not correct */ memcpy(frm+2, ic->ic_chan_avail, ielen); return frm + 2 + ielen; } /* * Add an 11h Quiet time element to a frame. */ static uint8_t * ieee80211_add_quiet(uint8_t *frm, struct ieee80211vap *vap, int update) { struct ieee80211_quiet_ie *quiet = (struct ieee80211_quiet_ie *) frm; quiet->quiet_ie = IEEE80211_ELEMID_QUIET; quiet->len = 6; /* * Only update every beacon interval - otherwise probe responses * would update the quiet count value. */ if (update) { if (vap->iv_quiet_count_value == 1) vap->iv_quiet_count_value = vap->iv_quiet_count; else if (vap->iv_quiet_count_value > 1) vap->iv_quiet_count_value--; } if (vap->iv_quiet_count_value == 0) { /* value 0 is reserved as per 802.11h standerd */ vap->iv_quiet_count_value = 1; } quiet->tbttcount = vap->iv_quiet_count_value; quiet->period = vap->iv_quiet_period; quiet->duration = htole16(vap->iv_quiet_duration); quiet->offset = htole16(vap->iv_quiet_offset); return frm + sizeof(*quiet); } /* * Add an 11h Channel Switch Announcement element to a frame. * Note that we use the per-vap CSA count to adjust the global * counter so we can use this routine to form probe response * frames and get the current count. */ static uint8_t * ieee80211_add_csa(uint8_t *frm, struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) frm; csa->csa_ie = IEEE80211_ELEMID_CSA; csa->csa_len = 3; csa->csa_mode = 1; /* XXX force quiet on channel */ csa->csa_newchan = ieee80211_chan2ieee(ic, ic->ic_csa_newchan); csa->csa_count = ic->ic_csa_count - vap->iv_csa_count; return frm + sizeof(*csa); } /* * Add an 11h country information element to a frame. */ static uint8_t * ieee80211_add_countryie(uint8_t *frm, struct ieee80211com *ic) { if (ic->ic_countryie == NULL || ic->ic_countryie_chan != ic->ic_bsschan) { /* * Handle lazy construction of ie. This is done on * first use and after a channel change that requires * re-calculation. */ if (ic->ic_countryie != NULL) IEEE80211_FREE(ic->ic_countryie, M_80211_NODE_IE); ic->ic_countryie = ieee80211_alloc_countryie(ic); if (ic->ic_countryie == NULL) return frm; ic->ic_countryie_chan = ic->ic_bsschan; } return add_appie(frm, ic->ic_countryie); } uint8_t * ieee80211_add_wpa(uint8_t *frm, const struct ieee80211vap *vap) { if (vap->iv_flags & IEEE80211_F_WPA1 && vap->iv_wpa_ie != NULL) return (add_ie(frm, vap->iv_wpa_ie)); else { /* XXX else complain? */ return (frm); } } uint8_t * ieee80211_add_rsn(uint8_t *frm, const struct ieee80211vap *vap) { if (vap->iv_flags & IEEE80211_F_WPA2 && vap->iv_rsn_ie != NULL) return (add_ie(frm, vap->iv_rsn_ie)); else { /* XXX else complain? */ return (frm); } } uint8_t * ieee80211_add_qos(uint8_t *frm, const struct ieee80211_node *ni) { if (ni->ni_flags & IEEE80211_NODE_QOS) { *frm++ = IEEE80211_ELEMID_QOS; *frm++ = 1; *frm++ = 0; } return (frm); } /* * ieee80211_send_probereq(): send a probe request frame with the specified ssid * and any optional information element data; some helper functions as FW based * HW scans need some of that information passed too. */ static uint32_t ieee80211_probereq_ie_len(struct ieee80211vap *vap, struct ieee80211com *ic) { const struct ieee80211_rateset *rs; rs = ieee80211_get_suprates(ic, ic->ic_curchan); /* * prreq frame format * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates (if needed) * [tlv] HT cap (optional) * [tlv] VHT cap (optional) * [tlv] WPA (optional) * [tlv] user-specified ie's */ return ( 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + ((rs->rs_nrates > IEEE80211_RATE_SIZE) ? 2 + (rs->rs_nrates - IEEE80211_RATE_SIZE) : 0) + (((vap->iv_opmode == IEEE80211_M_IBSS) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) ? sizeof(struct ieee80211_ie_htcap) : 0) #ifdef notyet + sizeof(struct ieee80211_ie_htinfo) /* XXX not needed? */ - + sizeof(struct ieee80211_ie_vhtcap) + + 2 + sizeof(struct ieee80211_vht_cap) #endif + ((vap->iv_flags & IEEE80211_F_WPA1 && vap->iv_wpa_ie != NULL) ? vap->iv_wpa_ie[1] : 0) + (vap->iv_appie_probereq != NULL ? vap->iv_appie_probereq->ie_len : 0) ); } int ieee80211_probereq_ie(struct ieee80211vap *vap, struct ieee80211com *ic, uint8_t **frmp, uint32_t *frmlen, const uint8_t *ssid, size_t ssidlen, bool alloc) { const struct ieee80211_rateset *rs; uint8_t *frm; uint32_t len; if (!alloc && (frmp == NULL || frmlen == NULL)) return (EINVAL); len = ieee80211_probereq_ie_len(vap, ic); if (!alloc && len > *frmlen) return (ENOBUFS); /* For HW scans we usually do not pass in the SSID as IE. */ if (ssidlen == -1) len -= (2 + IEEE80211_NWID_LEN); if (alloc) { frm = IEEE80211_MALLOC(len, M_80211_VAP, IEEE80211_M_WAITOK | IEEE80211_M_ZERO); *frmp = frm; *frmlen = len; } else frm = *frmp; if (ssidlen != -1) frm = ieee80211_add_ssid(frm, ssid, ssidlen); rs = ieee80211_get_suprates(ic, ic->ic_curchan); frm = ieee80211_add_rates(frm, rs); frm = ieee80211_add_xrates(frm, rs); /* * Note: we can't use bss; we don't have one yet. * * So, we should announce our capabilities * in this channel mode (2g/5g), not the * channel details itself. */ if ((vap->iv_opmode == IEEE80211_M_IBSS) && (vap->iv_flags_ht & IEEE80211_FHT_HT)) { struct ieee80211_channel *c; /* * Get the HT channel that we should try upgrading to. * If we can do 40MHz then this'll upgrade it appropriately. */ c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht); frm = ieee80211_add_htcap_ch(frm, vap, c); } /* * XXX TODO: need to figure out what/how to update the * VHT channel. */ #ifdef notyet if (vap->iv_vht_flags & IEEE80211_FVHT_VHT) { struct ieee80211_channel *c; c = ieee80211_ht_adjust_channel(ic, ic->ic_curchan, vap->iv_flags_ht); c = ieee80211_vht_adjust_channel(ic, c, vap->iv_vht_flags); frm = ieee80211_add_vhtcap_ch(frm, vap, c); } #endif frm = ieee80211_add_wpa(frm, vap); if (vap->iv_appie_probereq != NULL) frm = add_appie(frm, vap->iv_appie_probereq); if (!alloc) { *frmp = frm; *frmlen = len; } return (0); } int ieee80211_send_probereq(struct ieee80211_node *ni, const uint8_t sa[IEEE80211_ADDR_LEN], const uint8_t da[IEEE80211_ADDR_LEN], const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t *ssid, size_t ssidlen) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node *bss; const struct ieee80211_txparam *tp; struct ieee80211_bpf_params params; struct mbuf *m; uint8_t *frm; uint32_t frmlen; int ret; bss = ieee80211_ref_node(vap->iv_bss); if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, ni, "block %s frame in CAC state", "probe request"); vap->iv_stats.is_tx_badstate++; ieee80211_free_node(bss); return EIO; /* XXX */ } /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); /* See comments above for entire frame format. */ frmlen = ieee80211_probereq_ie_len(vap, ic); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), frmlen); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; ieee80211_free_node(ni); ieee80211_free_node(bss); return ENOMEM; } ret = ieee80211_probereq_ie(vap, ic, &frm, &frmlen, ssid, ssidlen, false); KASSERT(ret == 0, ("%s: ieee80211_probereq_ie failed: %d\n", __func__, ret)); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); KASSERT(M_LEADINGSPACE(m) >= sizeof(struct ieee80211_frame), ("leading space %zd", M_LEADINGSPACE(m))); M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); if (m == NULL) { /* NB: cannot happen */ ieee80211_free_node(ni); ieee80211_free_node(bss); return ENOMEM; } IEEE80211_TX_LOCK(ic); ieee80211_send_setup(ni, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ, IEEE80211_NONQOS_TID, sa, da, bssid); /* XXX power management? */ m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_NODE_STAT(ni, tx_probereq); IEEE80211_NODE_STAT(ni, tx_mgmt); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "send probe req on channel %u bssid %s sa %6D da %6D ssid \"%.*s\"\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ether_sprintf(bssid), sa, ":", da, ":", ssidlen, ssid); memset(¶ms, 0, sizeof(params)); params.ibp_pri = M_WME_GETAC(m); tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; params.ibp_rate0 = tp->mgmtrate; if (IEEE80211_IS_MULTICAST(da)) { params.ibp_flags |= IEEE80211_BPF_NOACK; params.ibp_try0 = 1; } else params.ibp_try0 = tp->maxretry; params.ibp_power = ni->ni_txpower; ret = ieee80211_raw_output(vap, ni, m, ¶ms); IEEE80211_TX_UNLOCK(ic); ieee80211_free_node(bss); return (ret); } /* * Calculate capability information for mgt frames. */ uint16_t ieee80211_getcapinfo(struct ieee80211vap *vap, struct ieee80211_channel *chan) { uint16_t capinfo; KASSERT(vap->iv_opmode != IEEE80211_M_STA, ("station mode")); if (vap->iv_opmode == IEEE80211_M_HOSTAP) capinfo = IEEE80211_CAPINFO_ESS; else if (vap->iv_opmode == IEEE80211_M_IBSS) capinfo = IEEE80211_CAPINFO_IBSS; else capinfo = 0; if (vap->iv_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; if ((vap->iv_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(chan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (vap->iv_flags & IEEE80211_F_SHSLOT) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; if (IEEE80211_IS_CHAN_5GHZ(chan) && (vap->iv_flags & IEEE80211_F_DOTH)) capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT; return capinfo; } /* * Send a management frame. The node is for the destination (or ic_bss * when in station mode). Nodes other than ic_bss have their reference * count bumped to reflect our use for an indeterminant time. */ int ieee80211_send_mgmt(struct ieee80211_node *ni, int type, int arg) { #define HTFLAGS (IEEE80211_NODE_HT | IEEE80211_NODE_HTCOMPAT) #define senderr(_x, _v) do { vap->iv_stats._v++; ret = _x; goto bad; } while (0) struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_node *bss = vap->iv_bss; struct ieee80211_bpf_params params; struct mbuf *m; uint8_t *frm; uint16_t capinfo; int has_challenge, is_shared_key, ret, status; KASSERT(ni != NULL, ("null node")); /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, ni, ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)+1); ieee80211_ref_node(ni); memset(¶ms, 0, sizeof(params)); switch (type) { case IEEE80211_FC0_SUBTYPE_AUTH: status = arg >> 16; arg &= 0xffff; has_challenge = ((arg == IEEE80211_AUTH_SHARED_CHALLENGE || arg == IEEE80211_AUTH_SHARED_RESPONSE) && ni->ni_challenge != NULL); /* * Deduce whether we're doing open authentication or * shared key authentication. We do the latter if * we're in the middle of a shared key authentication * handshake or if we're initiating an authentication * request and configured to use shared key. */ is_shared_key = has_challenge || arg >= IEEE80211_AUTH_SHARED_RESPONSE || (arg == IEEE80211_AUTH_SHARED_REQUEST && bss->ni_authmode == IEEE80211_AUTH_SHARED); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 3 * sizeof(uint16_t) + (has_challenge && status == IEEE80211_STATUS_SUCCESS ? sizeof(uint16_t)+IEEE80211_CHALLENGE_LEN : 0)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); ((uint16_t *)frm)[0] = (is_shared_key) ? htole16(IEEE80211_AUTH_ALG_SHARED) : htole16(IEEE80211_AUTH_ALG_OPEN); ((uint16_t *)frm)[1] = htole16(arg); /* sequence number */ ((uint16_t *)frm)[2] = htole16(status);/* status */ if (has_challenge && status == IEEE80211_STATUS_SUCCESS) { ((uint16_t *)frm)[3] = htole16((IEEE80211_CHALLENGE_LEN << 8) | IEEE80211_ELEMID_CHALLENGE); memcpy(&((uint16_t *)frm)[4], ni->ni_challenge, IEEE80211_CHALLENGE_LEN); m->m_pkthdr.len = m->m_len = 4 * sizeof(uint16_t) + IEEE80211_CHALLENGE_LEN; if (arg == IEEE80211_AUTH_SHARED_RESPONSE) { IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "request encrypt frame (%s)", __func__); /* mark frame for encryption */ params.ibp_flags |= IEEE80211_BPF_CRYPTO; } } else m->m_pkthdr.len = m->m_len = 3 * sizeof(uint16_t); /* XXX not right for shared key */ if (status == IEEE80211_STATUS_SUCCESS) IEEE80211_NODE_STAT(ni, tx_auth); else IEEE80211_NODE_STAT(ni, tx_auth_fail); if (vap->iv_opmode == IEEE80211_M_STA) ieee80211_add_callback(m, ieee80211_tx_mgt_cb, (void *) vap->iv_state); break; case IEEE80211_FC0_SUBTYPE_DEAUTH: IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, "send station deauthenticate (reason: %d (%s))", arg, ieee80211_reason_to_string(arg)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(uint16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(uint16_t); IEEE80211_NODE_STAT(ni, tx_deauth); IEEE80211_NODE_STAT_SET(ni, tx_deauth_code, arg); ieee80211_node_unauthorize(ni); /* port closed */ break; case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: /* * asreq frame format * [2] capability information * [2] listen interval * [6*] current AP address (reassoc only) * [tlv] ssid * [tlv] supported rates * [tlv] extended supported rates * [4] power capability (optional) * [28] supported channels (optional) * [tlv] HT capabilities * [tlv] VHT capabilities * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Atheros capabilities (if negotiated) * [tlv] AppIE's (optional) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + sizeof(uint16_t) + IEEE80211_ADDR_LEN + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + 4 + 2 + 26 + sizeof(struct ieee80211_wme_info) + sizeof(struct ieee80211_ie_htcap) - + sizeof(struct ieee80211_ie_vhtcap) + + 2 + sizeof(struct ieee80211_vht_cap) + 4 + sizeof(struct ieee80211_ie_htcap) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif + (vap->iv_appie_wpa != NULL ? vap->iv_appie_wpa->ie_len : 0) + (vap->iv_appie_assocreq != NULL ? vap->iv_appie_assocreq->ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode %u", vap->iv_opmode)); capinfo = IEEE80211_CAPINFO_ESS; if (vap->iv_flags & IEEE80211_F_PRIVACY) capinfo |= IEEE80211_CAPINFO_PRIVACY; /* * NB: Some 11a AP's reject the request when * short preamble is set. */ if ((vap->iv_flags & IEEE80211_F_SHPREAMBLE) && IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) capinfo |= IEEE80211_CAPINFO_SHORT_PREAMBLE; if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && (ic->ic_caps & IEEE80211_C_SHSLOT)) capinfo |= IEEE80211_CAPINFO_SHORT_SLOTTIME; if ((ni->ni_capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) && (vap->iv_flags & IEEE80211_F_DOTH)) capinfo |= IEEE80211_CAPINFO_SPECTRUM_MGMT; *(uint16_t *)frm = htole16(capinfo); frm += 2; KASSERT(bss->ni_intval != 0, ("beacon interval is zero!")); *(uint16_t *)frm = htole16(howmany(ic->ic_lintval, bss->ni_intval)); frm += 2; if (type == IEEE80211_FC0_SUBTYPE_REASSOC_REQ) { IEEE80211_ADDR_COPY(frm, bss->ni_bssid); frm += IEEE80211_ADDR_LEN; } frm = ieee80211_add_ssid(frm, ni->ni_essid, ni->ni_esslen); frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_rsn(frm, vap); frm = ieee80211_add_xrates(frm, &ni->ni_rates); if (capinfo & IEEE80211_CAPINFO_SPECTRUM_MGMT) { frm = ieee80211_add_powercapability(frm, ic->ic_curchan); frm = ieee80211_add_supportedchannels(frm, ic); } /* * Check the channel - we may be using an 11n NIC with an * 11n capable station, but we're configured to be an 11b * channel. */ if ((vap->iv_flags_ht & IEEE80211_FHT_HT) && IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_ies.htcap_ie != NULL && ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_HTCAP) { frm = ieee80211_add_htcap(frm, ni); } if ((vap->iv_vht_flags & IEEE80211_FVHT_VHT) && IEEE80211_IS_CHAN_VHT(ni->ni_chan) && ni->ni_ies.vhtcap_ie != NULL && ni->ni_ies.vhtcap_ie[0] == IEEE80211_ELEMID_VHT_CAP) { frm = ieee80211_add_vhtcap(frm, ni); } frm = ieee80211_add_wpa(frm, vap); if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) frm = ieee80211_add_wme_info(frm, &ic->ic_wme, ni); /* * Same deal - only send HT info if we're on an 11n * capable channel. */ if ((vap->iv_flags_ht & IEEE80211_FHT_HT) && IEEE80211_IS_CHAN_HT(ni->ni_chan) && ni->ni_ies.htcap_ie != NULL && ni->ni_ies.htcap_ie[0] == IEEE80211_ELEMID_VENDOR) { frm = ieee80211_add_htcap_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) { frm = ieee80211_add_ath(frm, IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS), ((vap->iv_flags & IEEE80211_F_WPA) == 0 && ni->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); } #endif /* IEEE80211_SUPPORT_SUPERG */ if (vap->iv_appie_assocreq != NULL) frm = add_appie(frm, vap->iv_appie_assocreq); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); ieee80211_add_callback(m, ieee80211_tx_mgt_cb, (void *) vap->iv_state); break; case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: /* * asresp frame format * [2] capability information * [2] status * [2] association ID * [tlv] supported rates * [tlv] extended supported rates * [tlv] HT capabilities (standard, if STA enabled) * [tlv] HT information (standard, if STA enabled) * [tlv] VHT capabilities (standard, if STA enabled) * [tlv] VHT information (standard, if STA enabled) * [tlv] WME (if configured and STA enabled) * [tlv] HT capabilities (vendor OUI, if STA enabled) * [tlv] HT information (vendor OUI, if STA enabled) * [tlv] Atheros capabilities (if STA enabled) * [tlv] AppIE's (optional) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + 2 + IEEE80211_RATE_SIZE + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) + 4 - + sizeof(struct ieee80211_ie_vhtcap) - + sizeof(struct ieee80211_ie_vht_operation) + + 2 + sizeof(struct ieee80211_vht_cap) + + 2 + sizeof(struct ieee80211_vht_operation) + sizeof(struct ieee80211_wme_param) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif + (vap->iv_appie_assocresp != NULL ? vap->iv_appie_assocresp->ie_len : 0) ); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); capinfo = ieee80211_getcapinfo(vap, bss->ni_chan); *(uint16_t *)frm = htole16(capinfo); frm += 2; *(uint16_t *)frm = htole16(arg); /* status */ frm += 2; if (arg == IEEE80211_STATUS_SUCCESS) { *(uint16_t *)frm = htole16(ni->ni_associd); IEEE80211_NODE_STAT(ni, tx_assoc); } else IEEE80211_NODE_STAT(ni, tx_assoc_fail); frm += 2; frm = ieee80211_add_rates(frm, &ni->ni_rates); frm = ieee80211_add_xrates(frm, &ni->ni_rates); /* NB: respond according to what we received */ if ((ni->ni_flags & HTFLAGS) == IEEE80211_NODE_HT) { frm = ieee80211_add_htcap(frm, ni); frm = ieee80211_add_htinfo(frm, ni); } if ((vap->iv_flags & IEEE80211_F_WME) && ni->ni_ies.wme_ie != NULL) frm = ieee80211_add_wme_param(frm, &ic->ic_wme, !! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)); if ((ni->ni_flags & HTFLAGS) == HTFLAGS) { frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } if (ni->ni_flags & IEEE80211_NODE_VHT) { frm = ieee80211_add_vhtcap(frm, ni); frm = ieee80211_add_vhtinfo(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS)) frm = ieee80211_add_ath(frm, IEEE80211_ATH_CAP(vap, ni, IEEE80211_F_ATHEROS), ((vap->iv_flags & IEEE80211_F_WPA) == 0 && ni->ni_authmode != IEEE80211_AUTH_8021X) ? vap->iv_def_txkey : IEEE80211_KEYIX_NONE); #endif /* IEEE80211_SUPPORT_SUPERG */ if (vap->iv_appie_assocresp != NULL) frm = add_appie(frm, vap->iv_appie_assocresp); m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); break; case IEEE80211_FC0_SUBTYPE_DISASSOC: IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, "send station disassociate (reason: %d (%s))", arg, ieee80211_reason_to_string(arg)); m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), sizeof(uint16_t)); if (m == NULL) senderr(ENOMEM, is_tx_nobuf); *(uint16_t *)frm = htole16(arg); /* reason */ m->m_pkthdr.len = m->m_len = sizeof(uint16_t); IEEE80211_NODE_STAT(ni, tx_disassoc); IEEE80211_NODE_STAT_SET(ni, tx_disassoc_code, arg); break; default: IEEE80211_NOTE(vap, IEEE80211_MSG_ANY, ni, "invalid mgmt frame type %u", type); senderr(EINVAL, is_tx_unknownmgt); /* NOTREACHED */ } /* NB: force non-ProbeResp frames to the highest queue */ params.ibp_pri = WME_AC_VO; params.ibp_rate0 = bss->ni_txparms->mgmtrate; /* NB: we know all frames are unicast */ params.ibp_try0 = bss->ni_txparms->maxretry; params.ibp_power = bss->ni_txpower; return ieee80211_mgmt_output(ni, m, type, ¶ms); bad: ieee80211_free_node(ni); return ret; #undef senderr #undef HTFLAGS } /* * Return an mbuf with a probe response frame in it. * Space is left to prepend and 802.11 header at the * front but it's left to the caller to fill in. */ struct mbuf * ieee80211_alloc_proberesp(struct ieee80211_node *bss, int legacy) { struct ieee80211vap *vap = bss->ni_vap; struct ieee80211com *ic = bss->ni_ic; const struct ieee80211_rateset *rs; struct mbuf *m; uint16_t capinfo; uint8_t *frm; /* * probe response frame format * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [tlv] parameter set (FH/DS) * [tlv] parameter set (IBSS) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN (optional) * [tlv] HT capabilities * [tlv] HT information * [tlv] VHT capabilities * [tlv] VHT information * [tlv] WPA (optional) * [tlv] WME (optional) * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * [tlv] Atheros capabilities * [tlv] AppIE's (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) */ m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), 8 + sizeof(uint16_t) + sizeof(uint16_t) + 2 + IEEE80211_NWID_LEN + 2 + IEEE80211_RATE_SIZE + 7 /* max(7,3) */ + IEEE80211_COUNTRY_MAX_SIZE + 3 + sizeof(struct ieee80211_csa_ie) + sizeof(struct ieee80211_quiet_ie) + 3 + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + sizeof(struct ieee80211_ie_wpa) + sizeof(struct ieee80211_ie_htcap) + sizeof(struct ieee80211_ie_htinfo) + sizeof(struct ieee80211_ie_wpa) + sizeof(struct ieee80211_wme_param) + 4 + sizeof(struct ieee80211_ie_htcap) + 4 + sizeof(struct ieee80211_ie_htinfo) - + sizeof(struct ieee80211_ie_vhtcap) - + sizeof(struct ieee80211_ie_vht_operation) + + 2 + sizeof(struct ieee80211_vht_cap) + + 2 + sizeof(struct ieee80211_vht_operation) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) #endif #ifdef IEEE80211_SUPPORT_MESH + 2 + IEEE80211_MESHID_LEN + sizeof(struct ieee80211_meshconf_ie) #endif + (vap->iv_appie_proberesp != NULL ? vap->iv_appie_proberesp->ie_len : 0) ); if (m == NULL) { vap->iv_stats.is_tx_nobuf++; return NULL; } memset(frm, 0, 8); /* timestamp should be filled later */ frm += 8; *(uint16_t *)frm = htole16(bss->ni_intval); frm += 2; capinfo = ieee80211_getcapinfo(vap, bss->ni_chan); *(uint16_t *)frm = htole16(capinfo); frm += 2; frm = ieee80211_add_ssid(frm, bss->ni_essid, bss->ni_esslen); rs = ieee80211_get_suprates(ic, bss->ni_chan); frm = ieee80211_add_rates(frm, rs); if (IEEE80211_IS_CHAN_FHSS(bss->ni_chan)) { *frm++ = IEEE80211_ELEMID_FHPARMS; *frm++ = 5; *frm++ = bss->ni_fhdwell & 0x00ff; *frm++ = (bss->ni_fhdwell >> 8) & 0x00ff; *frm++ = IEEE80211_FH_CHANSET( ieee80211_chan2ieee(ic, bss->ni_chan)); *frm++ = IEEE80211_FH_CHANPAT( ieee80211_chan2ieee(ic, bss->ni_chan)); *frm++ = bss->ni_fhindex; } else { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, bss->ni_chan); } if (vap->iv_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ } if ((vap->iv_flags & IEEE80211_F_DOTH) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) frm = ieee80211_add_countryie(frm, ic); if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_5GHZ(bss->ni_chan)) frm = ieee80211_add_powerconstraint(frm, vap); if (ic->ic_flags & IEEE80211_F_CSAPENDING) frm = ieee80211_add_csa(frm, vap); } if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS)) { if (vap->iv_quiet) frm = ieee80211_add_quiet(frm, vap, 0); } } if (IEEE80211_IS_CHAN_ANYG(bss->ni_chan)) frm = ieee80211_add_erp(frm, vap); frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_rsn(frm, vap); /* * NB: legacy 11b clients do not get certain ie's. * The caller identifies such clients by passing * a token in legacy to us. Could expand this to be * any legacy client for stuff like HT ie's. */ if (IEEE80211_IS_CHAN_HT(bss->ni_chan) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_htcap(frm, bss); frm = ieee80211_add_htinfo(frm, bss); } if (IEEE80211_IS_CHAN_VHT(bss->ni_chan) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_vhtcap(frm, bss); frm = ieee80211_add_vhtinfo(frm, bss); } frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) frm = ieee80211_add_wme_param(frm, &ic->ic_wme, !! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)); if (IEEE80211_IS_CHAN_HT(bss->ni_chan) && (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) && legacy != IEEE80211_SEND_LEGACY_11B) { frm = ieee80211_add_htcap_vendor(frm, bss); frm = ieee80211_add_htinfo_vendor(frm, bss); } #ifdef IEEE80211_SUPPORT_SUPERG if ((vap->iv_flags & IEEE80211_F_ATHEROS) && legacy != IEEE80211_SEND_LEGACY_11B) frm = ieee80211_add_athcaps(frm, bss); #endif if (vap->iv_appie_proberesp != NULL) frm = add_appie(frm, vap->iv_appie_proberesp); #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); frm = ieee80211_add_meshconf(frm, vap); } #endif m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); return m; } /* * Send a probe response frame to the specified mac address. * This does not go through the normal mgt frame api so we * can specify the destination address and re-use the bss node * for the sta reference. */ int ieee80211_send_proberesp(struct ieee80211vap *vap, const uint8_t da[IEEE80211_ADDR_LEN], int legacy) { struct ieee80211_node *bss = vap->iv_bss; struct ieee80211com *ic = vap->iv_ic; struct mbuf *m; int ret; if (vap->iv_state == IEEE80211_S_CAC) { IEEE80211_NOTE(vap, IEEE80211_MSG_OUTPUT, bss, "block %s frame in CAC state", "probe response"); vap->iv_stats.is_tx_badstate++; return EIO; /* XXX */ } /* * Hold a reference on the node so it doesn't go away until after * the xmit is complete all the way in the driver. On error we * will remove our reference. */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_NODE, "ieee80211_ref_node (%s:%u) %p<%s> refcnt %d\n", __func__, __LINE__, bss, ether_sprintf(bss->ni_macaddr), ieee80211_node_refcnt(bss)+1); ieee80211_ref_node(bss); m = ieee80211_alloc_proberesp(bss, legacy); if (m == NULL) { ieee80211_free_node(bss); return ENOMEM; } M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); KASSERT(m != NULL, ("no room for header")); IEEE80211_TX_LOCK(ic); ieee80211_send_setup(bss, m, IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP, IEEE80211_NONQOS_TID, vap->iv_myaddr, da, bss->ni_bssid); /* XXX power management? */ m->m_flags |= M_ENCAP; /* mark encapsulated */ M_WME_SETAC(m, WME_AC_BE); IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_DUMPPKTS, "send probe resp on channel %u to %s%s\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ether_sprintf(da), legacy ? " " : ""); IEEE80211_NODE_STAT(bss, tx_mgmt); ret = ieee80211_raw_output(vap, bss, m, NULL); IEEE80211_TX_UNLOCK(ic); return (ret); } /* * Allocate and build a RTS (Request To Send) control frame. */ struct mbuf * ieee80211_alloc_rts(struct ieee80211com *ic, const uint8_t ra[IEEE80211_ADDR_LEN], const uint8_t ta[IEEE80211_ADDR_LEN], uint16_t dur) { struct ieee80211_frame_rts *rts; struct mbuf *m; /* XXX honor ic_headroom */ m = m_gethdr(IEEE80211_M_NOWAIT, MT_DATA); if (m != NULL) { rts = mtod(m, struct ieee80211_frame_rts *); rts->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_RTS; rts->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)rts->i_dur = htole16(dur); IEEE80211_ADDR_COPY(rts->i_ra, ra); IEEE80211_ADDR_COPY(rts->i_ta, ta); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_rts); } return m; } /* * Allocate and build a CTS (Clear To Send) control frame. */ struct mbuf * ieee80211_alloc_cts(struct ieee80211com *ic, const uint8_t ra[IEEE80211_ADDR_LEN], uint16_t dur) { struct ieee80211_frame_cts *cts; struct mbuf *m; /* XXX honor ic_headroom */ m = m_gethdr(IEEE80211_M_NOWAIT, MT_DATA); if (m != NULL) { cts = mtod(m, struct ieee80211_frame_cts *); cts->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_CTS; cts->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(u_int16_t *)cts->i_dur = htole16(dur); IEEE80211_ADDR_COPY(cts->i_ra, ra); m->m_pkthdr.len = m->m_len = sizeof(struct ieee80211_frame_cts); } return m; } /* * Wrapper for CTS/RTS frame allocation. */ struct mbuf * ieee80211_alloc_prot(struct ieee80211_node *ni, const struct mbuf *m, uint8_t rate, int prot) { struct ieee80211com *ic = ni->ni_ic; struct ieee80211vap *vap = ni->ni_vap; const struct ieee80211_frame *wh; struct mbuf *mprot; uint16_t dur; int pktlen, isshort; KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, ("wrong protection type %d", prot)); wh = mtod(m, const struct ieee80211_frame *); pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; isshort = (vap->iv_flags & IEEE80211_F_SHPREAMBLE) != 0; dur = ieee80211_compute_duration(ic->ic_rt, pktlen, rate, isshort) + ieee80211_ack_duration(ic->ic_rt, rate, isshort); if (prot == IEEE80211_PROT_RTSCTS) { /* NB: CTS is the same size as an ACK */ dur += ieee80211_ack_duration(ic->ic_rt, rate, isshort); mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); } else mprot = ieee80211_alloc_cts(ic, vap->iv_myaddr, dur); return (mprot); } static void ieee80211_tx_mgt_timeout(void *arg) { struct ieee80211vap *vap = arg; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "vap %p mode %s state %s flags %#x & %#x\n", vap, ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state], vap->iv_ic->ic_flags, IEEE80211_F_SCAN); IEEE80211_LOCK(vap->iv_ic); if (vap->iv_state != IEEE80211_S_INIT && (vap->iv_ic->ic_flags & IEEE80211_F_SCAN) == 0) { /* * NB: it's safe to specify a timeout as the reason here; * it'll only be used in the right state. */ ieee80211_new_state_locked(vap, IEEE80211_S_SCAN, IEEE80211_SCAN_FAIL_TIMEOUT); } IEEE80211_UNLOCK(vap->iv_ic); } /* * This is the callback set on net80211-sourced transmitted * authentication request frames. * * This does a couple of things: * * + If the frame transmitted was a success, it schedules a future * event which will transition the interface to scan. * If a state transition _then_ occurs before that event occurs, * said state transition will cancel this callout. * * + If the frame transmit was a failure, it immediately schedules * the transition back to scan. */ static void ieee80211_tx_mgt_cb(struct ieee80211_node *ni, void *arg, int status) { struct ieee80211vap *vap = ni->ni_vap; enum ieee80211_state ostate = (enum ieee80211_state)(uintptr_t)arg; /* * Frame transmit completed; arrange timer callback. If * transmit was successfully we wait for response. Otherwise * we arrange an immediate callback instead of doing the * callback directly since we don't know what state the driver * is in (e.g. what locks it is holding). This work should * not be too time-critical and not happen too often so the * added overhead is acceptable. * * XXX what happens if !acked but response shows up before callback? */ if (vap->iv_state == ostate) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "ni %p mode %s state %s arg %p status %d\n", ni, ieee80211_opmode_name[vap->iv_opmode], ieee80211_state_name[vap->iv_state], arg, status); callout_reset(&vap->iv_mgtsend, status == 0 ? IEEE80211_TRANS_WAIT*hz : 0, ieee80211_tx_mgt_timeout, vap); } } static void ieee80211_beacon_construct(struct mbuf *m, uint8_t *frm, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211com *ic = ni->ni_ic; struct ieee80211_rateset *rs = &ni->ni_rates; uint16_t capinfo; /* * beacon frame format * * TODO: update to 802.11-2012; a lot of stuff has changed; * vendor extensions should be at the end, etc. * * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [8] CF parameter set (optional) * [tlv] parameter set (IBSS/TIM) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * XXX TODO: Quiet * XXX TODO: IBSS DFS * XXX TODO: TPC report * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters * XXX TODO: BSSLOAD * (XXX EDCA parameter set, QoS capability?) * XXX TODO: AP channel report * * [tlv] HT capabilities * [tlv] HT information * XXX TODO: 20/40 BSS coexistence * Mesh: * XXX TODO: Meshid * XXX TODO: mesh config * XXX TODO: mesh awake window * XXX TODO: beacon timing (mesh, etc) * XXX TODO: MCCAOP Advertisement Overview * XXX TODO: MCCAOP Advertisement * XXX TODO: Mesh channel switch parameters * VHT: * XXX TODO: VHT capabilities * XXX TODO: VHT operation * XXX TODO: VHT transmit power envelope * XXX TODO: channel switch wrapper element * XXX TODO: extended BSS load element * * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * [tlv] Atheros capabilities (optional) * [tlv] TDMA parameters (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) * [tlv] application data (optional) */ memset(bo, 0, sizeof(*bo)); memset(frm, 0, 8); /* XXX timestamp is set by hardware/driver */ frm += 8; *(uint16_t *)frm = htole16(ni->ni_intval); frm += 2; capinfo = ieee80211_getcapinfo(vap, ni->ni_chan); bo->bo_caps = (uint16_t *)frm; *(uint16_t *)frm = htole16(capinfo); frm += 2; *frm++ = IEEE80211_ELEMID_SSID; if ((vap->iv_flags & IEEE80211_F_HIDESSID) == 0) { *frm++ = ni->ni_esslen; memcpy(frm, ni->ni_essid, ni->ni_esslen); frm += ni->ni_esslen; } else *frm++ = 0; frm = ieee80211_add_rates(frm, rs); if (!IEEE80211_IS_CHAN_FHSS(ni->ni_chan)) { *frm++ = IEEE80211_ELEMID_DSPARMS; *frm++ = 1; *frm++ = ieee80211_chan2ieee(ic, ni->ni_chan); } if (ic->ic_flags & IEEE80211_F_PCF) { bo->bo_cfp = frm; frm = ieee80211_add_cfparms(frm, ic); } bo->bo_tim = frm; if (vap->iv_opmode == IEEE80211_M_IBSS) { *frm++ = IEEE80211_ELEMID_IBSSPARMS; *frm++ = 2; *frm++ = 0; *frm++ = 0; /* TODO: ATIM window */ bo->bo_tim_len = 0; } else if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { /* TIM IE is the same for Mesh and Hostap */ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) frm; tie->tim_ie = IEEE80211_ELEMID_TIM; tie->tim_len = 4; /* length */ tie->tim_count = 0; /* DTIM count */ tie->tim_period = vap->iv_dtim_period; /* DTIM period */ tie->tim_bitctl = 0; /* bitmap control */ tie->tim_bitmap[0] = 0; /* Partial Virtual Bitmap */ frm += sizeof(struct ieee80211_tim_ie); bo->bo_tim_len = 1; } bo->bo_tim_trailer = frm; if ((vap->iv_flags & IEEE80211_F_DOTH) || (vap->iv_flags_ext & IEEE80211_FEXT_DOTD)) frm = ieee80211_add_countryie(frm, ic); if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) frm = ieee80211_add_powerconstraint(frm, vap); bo->bo_csa = frm; if (ic->ic_flags & IEEE80211_F_CSAPENDING) frm = ieee80211_add_csa(frm, vap); } else bo->bo_csa = frm; bo->bo_quiet = NULL; if (vap->iv_flags & IEEE80211_F_DOTH) { if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS) && (vap->iv_quiet == 1)) { /* * We only insert the quiet IE offset if * the quiet IE is enabled. Otherwise don't * put it here or we'll just overwrite * some other beacon contents. */ if (vap->iv_quiet) { bo->bo_quiet = frm; frm = ieee80211_add_quiet(frm,vap, 0); } } } if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan)) { bo->bo_erp = frm; frm = ieee80211_add_erp(frm, vap); } frm = ieee80211_add_xrates(frm, rs); frm = ieee80211_add_rsn(frm, vap); if (IEEE80211_IS_CHAN_HT(ni->ni_chan)) { frm = ieee80211_add_htcap(frm, ni); bo->bo_htinfo = frm; frm = ieee80211_add_htinfo(frm, ni); } if (IEEE80211_IS_CHAN_VHT(ni->ni_chan)) { frm = ieee80211_add_vhtcap(frm, ni); bo->bo_vhtinfo = frm; frm = ieee80211_add_vhtinfo(frm, ni); /* Transmit power envelope */ /* Channel switch wrapper element */ /* Extended bss load element */ } frm = ieee80211_add_wpa(frm, vap); if (vap->iv_flags & IEEE80211_F_WME) { bo->bo_wme = frm; frm = ieee80211_add_wme_param(frm, &ic->ic_wme, !! (vap->iv_flags_ext & IEEE80211_FEXT_UAPSD)); } if (IEEE80211_IS_CHAN_HT(ni->ni_chan) && (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT)) { frm = ieee80211_add_htcap_vendor(frm, ni); frm = ieee80211_add_htinfo_vendor(frm, ni); } #ifdef IEEE80211_SUPPORT_SUPERG if (vap->iv_flags & IEEE80211_F_ATHEROS) { bo->bo_ath = frm; frm = ieee80211_add_athcaps(frm, ni); } #endif #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_caps & IEEE80211_C_TDMA) { bo->bo_tdma = frm; frm = ieee80211_add_tdma(frm, vap); } #endif if (vap->iv_appie_beacon != NULL) { bo->bo_appie = frm; bo->bo_appie_len = vap->iv_appie_beacon->ie_len; frm = add_appie(frm, vap->iv_appie_beacon); } /* XXX TODO: move meshid/meshconf up to before vendor extensions? */ #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) { frm = ieee80211_add_meshid(frm, vap); bo->bo_meshconf = frm; frm = ieee80211_add_meshconf(frm, vap); } #endif bo->bo_tim_trailer_len = frm - bo->bo_tim_trailer; bo->bo_csa_trailer_len = frm - bo->bo_csa; m->m_pkthdr.len = m->m_len = frm - mtod(m, uint8_t *); } /* * Allocate a beacon frame and fillin the appropriate bits. */ struct mbuf * ieee80211_beacon_alloc(struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = vap->iv_ifp; struct ieee80211_frame *wh; struct mbuf *m; int pktlen; uint8_t *frm; /* * Update the "We're putting the quiet IE in the beacon" state. */ if (vap->iv_quiet == 1) vap->iv_flags_ext |= IEEE80211_FEXT_QUIET_IE; else if (vap->iv_quiet == 0) vap->iv_flags_ext &= ~IEEE80211_FEXT_QUIET_IE; /* * beacon frame format * * Note: This needs updating for 802.11-2012. * * [8] time stamp * [2] beacon interval * [2] cabability information * [tlv] ssid * [tlv] supported rates * [3] parameter set (DS) * [8] CF parameter set (optional) * [tlv] parameter set (IBSS/TIM) * [tlv] country (optional) * [3] power control (optional) * [5] channel switch announcement (CSA) (optional) * [tlv] extended rate phy (ERP) * [tlv] extended supported rates * [tlv] RSN parameters * [tlv] HT capabilities * [tlv] HT information * [tlv] VHT capabilities * [tlv] VHT operation * [tlv] Vendor OUI HT capabilities (optional) * [tlv] Vendor OUI HT information (optional) * XXX Vendor-specific OIDs (e.g. Atheros) * [tlv] WPA parameters * [tlv] WME parameters * [tlv] TDMA parameters (optional) * [tlv] Mesh ID (MBSS) * [tlv] Mesh Conf (MBSS) * [tlv] application data (optional) * NB: we allocate the max space required for the TIM bitmap. * XXX how big is this? */ pktlen = 8 /* time stamp */ + sizeof(uint16_t) /* beacon interval */ + sizeof(uint16_t) /* capabilities */ + 2 + ni->ni_esslen /* ssid */ + 2 + IEEE80211_RATE_SIZE /* supported rates */ + 2 + 1 /* DS parameters */ + 2 + 6 /* CF parameters */ + 2 + 4 + vap->iv_tim_len /* DTIM/IBSSPARMS */ + IEEE80211_COUNTRY_MAX_SIZE /* country */ + 2 + 1 /* power control */ + sizeof(struct ieee80211_csa_ie) /* CSA */ + sizeof(struct ieee80211_quiet_ie) /* Quiet */ + 2 + 1 /* ERP */ + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE) + (vap->iv_caps & IEEE80211_C_WPA ? /* WPA 1+2 */ 2*sizeof(struct ieee80211_ie_wpa) : 0) /* XXX conditional? */ + 4+2*sizeof(struct ieee80211_ie_htcap)/* HT caps */ + 4+2*sizeof(struct ieee80211_ie_htinfo)/* HT info */ - + sizeof(struct ieee80211_ie_vhtcap)/* VHT caps */ - + sizeof(struct ieee80211_ie_vht_operation)/* VHT info */ + + 2 + sizeof(struct ieee80211_vht_cap)/* VHT caps */ + + 2 + sizeof(struct ieee80211_vht_operation)/* VHT info */ + (vap->iv_caps & IEEE80211_C_WME ? /* WME */ sizeof(struct ieee80211_wme_param) : 0) #ifdef IEEE80211_SUPPORT_SUPERG + sizeof(struct ieee80211_ath_ie) /* ATH */ #endif #ifdef IEEE80211_SUPPORT_TDMA + (vap->iv_caps & IEEE80211_C_TDMA ? /* TDMA */ sizeof(struct ieee80211_tdma_param) : 0) #endif #ifdef IEEE80211_SUPPORT_MESH + 2 + ni->ni_meshidlen + sizeof(struct ieee80211_meshconf_ie) #endif + IEEE80211_MAX_APPIE ; m = ieee80211_getmgtframe(&frm, ic->ic_headroom + sizeof(struct ieee80211_frame), pktlen); if (m == NULL) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_ANY, "%s: cannot get buf; size %u\n", __func__, pktlen); vap->iv_stats.is_tx_nobuf++; return NULL; } ieee80211_beacon_construct(m, frm, ni); M_PREPEND(m, sizeof(struct ieee80211_frame), IEEE80211_M_NOWAIT); KASSERT(m != NULL, ("no space for 802.11 header?")); wh = mtod(m, struct ieee80211_frame *); wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON; wh->i_fc[1] = IEEE80211_FC1_DIR_NODS; *(uint16_t *)wh->i_dur = 0; IEEE80211_ADDR_COPY(wh->i_addr1, ifp->if_broadcastaddr); IEEE80211_ADDR_COPY(wh->i_addr2, vap->iv_myaddr); IEEE80211_ADDR_COPY(wh->i_addr3, ni->ni_bssid); *(uint16_t *)wh->i_seq = 0; return m; } /* * Update the dynamic parts of a beacon frame based on the current state. */ int ieee80211_beacon_update(struct ieee80211_node *ni, struct mbuf *m, int mcast) { struct ieee80211vap *vap = ni->ni_vap; struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off; struct ieee80211com *ic = ni->ni_ic; int len_changed = 0; uint16_t capinfo; struct ieee80211_frame *wh; ieee80211_seq seqno; IEEE80211_LOCK(ic); /* * Handle 11h channel change when we've reached the count. * We must recalculate the beacon frame contents to account * for the new channel. Note we do this only for the first * vap that reaches this point; subsequent vaps just update * their beacon state to reflect the recalculated channel. */ if (isset(bo->bo_flags, IEEE80211_BEACON_CSA) && vap->iv_csa_count == ic->ic_csa_count) { vap->iv_csa_count = 0; /* * Effect channel change before reconstructing the beacon * frame contents as many places reference ni_chan. */ if (ic->ic_csa_newchan != NULL) ieee80211_csa_completeswitch(ic); /* * NB: ieee80211_beacon_construct clears all pending * updates in bo_flags so we don't need to explicitly * clear IEEE80211_BEACON_CSA. */ ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } /* * Handle the quiet time element being added and removed. * Again, for now we just cheat and reconstruct the whole * beacon - that way the gap is provided as appropriate. * * So, track whether we have already added the IE versus * whether we want to be adding the IE. */ if ((vap->iv_flags_ext & IEEE80211_FEXT_QUIET_IE) && (vap->iv_quiet == 0)) { /* * Quiet time beacon IE enabled, but it's disabled; * recalc */ vap->iv_flags_ext &= ~IEEE80211_FEXT_QUIET_IE; ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } if (((vap->iv_flags_ext & IEEE80211_FEXT_QUIET_IE) == 0) && (vap->iv_quiet == 1)) { /* * Quiet time beacon IE disabled, but it's now enabled; * recalc */ vap->iv_flags_ext |= IEEE80211_FEXT_QUIET_IE; ieee80211_beacon_construct(m, mtod(m, uint8_t*) + sizeof(struct ieee80211_frame), ni); /* XXX do WME aggressive mode processing? */ IEEE80211_UNLOCK(ic); return 1; /* just assume length changed */ } wh = mtod(m, struct ieee80211_frame *); /* * XXX TODO Strictly speaking this should be incremented with the TX * lock held so as to serialise access to the non-qos TID sequence * number space. * * If the driver identifies it does its own TX seqno management then * we can skip this (and still not do the TX seqno.) */ seqno = ni->ni_txseqs[IEEE80211_NONQOS_TID]++; *(uint16_t *)&wh->i_seq[0] = htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT); M_SEQNO_SET(m, seqno); /* XXX faster to recalculate entirely or just changes? */ capinfo = ieee80211_getcapinfo(vap, ni->ni_chan); *bo->bo_caps = htole16(capinfo); if (vap->iv_flags & IEEE80211_F_WME) { struct ieee80211_wme_state *wme = &ic->ic_wme; /* * Check for aggressive mode change. When there is * significant high priority traffic in the BSS * throttle back BE traffic by using conservative * parameters. Otherwise BE uses aggressive params * to optimize performance of legacy/non-QoS traffic. */ if (wme->wme_flags & WME_F_AGGRMODE) { if (wme->wme_hipri_traffic > wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: traffic %u, disable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags &= ~WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(vap); wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } else wme->wme_hipri_traffic = 0; } else { if (wme->wme_hipri_traffic <= wme->wme_hipri_switch_thresh) { IEEE80211_DPRINTF(vap, IEEE80211_MSG_WME, "%s: traffic %u, enable aggressive mode\n", __func__, wme->wme_hipri_traffic); wme->wme_flags |= WME_F_AGGRMODE; ieee80211_wme_updateparams_locked(vap); wme->wme_hipri_traffic = 0; } else wme->wme_hipri_traffic = wme->wme_hipri_switch_hysteresis; } if (isset(bo->bo_flags, IEEE80211_BEACON_WME)) { (void) ieee80211_add_wme_param(bo->bo_wme, wme, vap->iv_flags_ext & IEEE80211_FEXT_UAPSD); clrbit(bo->bo_flags, IEEE80211_BEACON_WME); } } if (isset(bo->bo_flags, IEEE80211_BEACON_HTINFO)) { ieee80211_ht_update_beacon(vap, bo); clrbit(bo->bo_flags, IEEE80211_BEACON_HTINFO); } #ifdef IEEE80211_SUPPORT_TDMA if (vap->iv_caps & IEEE80211_C_TDMA) { /* * NB: the beacon is potentially updated every TBTT. */ ieee80211_tdma_update_beacon(vap, bo); } #endif #ifdef IEEE80211_SUPPORT_MESH if (vap->iv_opmode == IEEE80211_M_MBSS) ieee80211_mesh_update_beacon(vap, bo); #endif if (vap->iv_opmode == IEEE80211_M_HOSTAP || vap->iv_opmode == IEEE80211_M_MBSS) { /* NB: no IBSS support*/ struct ieee80211_tim_ie *tie = (struct ieee80211_tim_ie *) bo->bo_tim; if (isset(bo->bo_flags, IEEE80211_BEACON_TIM)) { u_int timlen, timoff, i; /* * ATIM/DTIM needs updating. If it fits in the * current space allocated then just copy in the * new bits. Otherwise we need to move any trailing * data to make room. Note that we know there is * contiguous space because ieee80211_beacon_allocate * insures there is space in the mbuf to write a * maximal-size virtual bitmap (based on iv_max_aid). */ /* * Calculate the bitmap size and offset, copy any * trailer out of the way, and then copy in the * new bitmap and update the information element. * Note that the tim bitmap must contain at least * one byte and any offset must be even. */ if (vap->iv_ps_pending != 0) { timoff = 128; /* impossibly large */ for (i = 0; i < vap->iv_tim_len; i++) if (vap->iv_tim_bitmap[i]) { timoff = i &~ 1; break; } KASSERT(timoff != 128, ("tim bitmap empty!")); for (i = vap->iv_tim_len-1; i >= timoff; i--) if (vap->iv_tim_bitmap[i]) break; timlen = 1 + (i - timoff); } else { timoff = 0; timlen = 1; } /* * TODO: validate this! */ if (timlen != bo->bo_tim_len) { /* copy up/down trailer */ int adjust = tie->tim_bitmap+timlen - bo->bo_tim_trailer; ovbcopy(bo->bo_tim_trailer, bo->bo_tim_trailer+adjust, bo->bo_tim_trailer_len); bo->bo_tim_trailer += adjust; bo->bo_erp += adjust; bo->bo_htinfo += adjust; bo->bo_vhtinfo += adjust; #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += adjust; #endif #ifdef IEEE80211_SUPPORT_TDMA bo->bo_tdma += adjust; #endif #ifdef IEEE80211_SUPPORT_MESH bo->bo_meshconf += adjust; #endif bo->bo_appie += adjust; bo->bo_wme += adjust; bo->bo_csa += adjust; bo->bo_quiet += adjust; bo->bo_tim_len = timlen; /* update information element */ tie->tim_len = 3 + timlen; tie->tim_bitctl = timoff; len_changed = 1; } memcpy(tie->tim_bitmap, vap->iv_tim_bitmap + timoff, bo->bo_tim_len); clrbit(bo->bo_flags, IEEE80211_BEACON_TIM); IEEE80211_DPRINTF(vap, IEEE80211_MSG_POWER, "%s: TIM updated, pending %u, off %u, len %u\n", __func__, vap->iv_ps_pending, timoff, timlen); } /* count down DTIM period */ if (tie->tim_count == 0) tie->tim_count = tie->tim_period - 1; else tie->tim_count--; /* update state for buffered multicast frames on DTIM */ if (mcast && tie->tim_count == 0) tie->tim_bitctl |= 1; else tie->tim_bitctl &= ~1; if (isset(bo->bo_flags, IEEE80211_BEACON_CSA)) { struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) bo->bo_csa; /* * Insert or update CSA ie. If we're just starting * to count down to the channel switch then we need * to insert the CSA ie. Otherwise we just need to * drop the count. The actual change happens above * when the vap's count reaches the target count. */ if (vap->iv_csa_count == 0) { memmove(&csa[1], csa, bo->bo_csa_trailer_len); bo->bo_erp += sizeof(*csa); bo->bo_htinfo += sizeof(*csa); bo->bo_vhtinfo += sizeof(*csa); bo->bo_wme += sizeof(*csa); #ifdef IEEE80211_SUPPORT_SUPERG bo->bo_ath += sizeof(*csa); #endif #ifdef IEEE80211_SUPPORT_TDMA bo->bo_tdma += sizeof(*csa); #endif #ifdef IEEE80211_SUPPORT_MESH bo->bo_meshconf += sizeof(*csa); #endif bo->bo_appie += sizeof(*csa); bo->bo_csa_trailer_len += sizeof(*csa); bo->bo_quiet += sizeof(*csa); bo->bo_tim_trailer_len += sizeof(*csa); m->m_len += sizeof(*csa); m->m_pkthdr.len += sizeof(*csa); ieee80211_add_csa(bo->bo_csa, vap); } else csa->csa_count--; vap->iv_csa_count++; /* NB: don't clear IEEE80211_BEACON_CSA */ } /* * Only add the quiet time IE if we've enabled it * as appropriate. */ if (IEEE80211_IS_CHAN_DFS(ic->ic_bsschan) && (vap->iv_flags_ext & IEEE80211_FEXT_DFS)) { if (vap->iv_quiet && (vap->iv_flags_ext & IEEE80211_FEXT_QUIET_IE)) { ieee80211_add_quiet(bo->bo_quiet, vap, 1); } } if (isset(bo->bo_flags, IEEE80211_BEACON_ERP)) { /* * ERP element needs updating. */ (void) ieee80211_add_erp(bo->bo_erp, vap); clrbit(bo->bo_flags, IEEE80211_BEACON_ERP); } #ifdef IEEE80211_SUPPORT_SUPERG if (isset(bo->bo_flags, IEEE80211_BEACON_ATH)) { ieee80211_add_athcaps(bo->bo_ath, ni); clrbit(bo->bo_flags, IEEE80211_BEACON_ATH); } #endif } if (isset(bo->bo_flags, IEEE80211_BEACON_APPIE)) { const struct ieee80211_appie *aie = vap->iv_appie_beacon; int aielen; uint8_t *frm; aielen = 0; if (aie != NULL) aielen += aie->ie_len; if (aielen != bo->bo_appie_len) { /* copy up/down trailer */ int adjust = aielen - bo->bo_appie_len; ovbcopy(bo->bo_tim_trailer, bo->bo_tim_trailer+adjust, bo->bo_tim_trailer_len); bo->bo_tim_trailer += adjust; bo->bo_appie += adjust; bo->bo_appie_len = aielen; len_changed = 1; } frm = bo->bo_appie; if (aie != NULL) frm = add_appie(frm, aie); clrbit(bo->bo_flags, IEEE80211_BEACON_APPIE); } IEEE80211_UNLOCK(ic); return len_changed; } /* * Do Ethernet-LLC encapsulation for each payload in a fast frame * tunnel encapsulation. The frame is assumed to have an Ethernet * header at the front that must be stripped before prepending the * LLC followed by the Ethernet header passed in (with an Ethernet * type that specifies the payload size). */ struct mbuf * ieee80211_ff_encap1(struct ieee80211vap *vap, struct mbuf *m, const struct ether_header *eh) { struct llc *llc; uint16_t payload; /* XXX optimize by combining m_adj+M_PREPEND */ m_adj(m, sizeof(struct ether_header) - sizeof(struct llc)); llc = mtod(m, struct llc *); llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP; llc->llc_control = LLC_UI; llc->llc_snap.org_code[0] = 0; llc->llc_snap.org_code[1] = 0; llc->llc_snap.org_code[2] = 0; llc->llc_snap.ether_type = eh->ether_type; payload = m->m_pkthdr.len; /* NB: w/o Ethernet header */ M_PREPEND(m, sizeof(struct ether_header), IEEE80211_M_NOWAIT); if (m == NULL) { /* XXX cannot happen */ IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: no space for ether_header\n", __func__); vap->iv_stats.is_tx_nobuf++; return NULL; } ETHER_HEADER_COPY(mtod(m, void *), eh); mtod(m, struct ether_header *)->ether_type = htons(payload); return m; } /* * Complete an mbuf transmission. * * For now, this simply processes a completed frame after the * driver has completed it's transmission and/or retransmission. * It assumes the frame is an 802.11 encapsulated frame. * * Later on it will grow to become the exit path for a given frame * from the driver and, depending upon how it's been encapsulated * and already transmitted, it may end up doing A-MPDU retransmission, * power save requeuing, etc. * * In order for the above to work, the driver entry point to this * must not hold any driver locks. Thus, the driver needs to delay * any actual mbuf completion until it can release said locks. * * This frees the mbuf and if the mbuf has a node reference, * the node reference will be freed. */ void ieee80211_tx_complete(struct ieee80211_node *ni, struct mbuf *m, int status) { if (ni != NULL) { struct ifnet *ifp = ni->ni_vap->iv_ifp; if (status == 0) { if_inc_counter(ifp, IFCOUNTER_OBYTES, m->m_pkthdr.len); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); if (m->m_flags & M_MCAST) if_inc_counter(ifp, IFCOUNTER_OMCASTS, 1); } else if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (m->m_flags & M_TXCB) { IEEE80211_DPRINTF(ni->ni_vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, "ni %p vap %p mode %s state %s m %p status %d\n", ni, ni->ni_vap, ieee80211_opmode_name[ni->ni_vap->iv_opmode], ieee80211_state_name[ni->ni_vap->iv_state], m, status); ieee80211_process_callback(ni, m, status); } ieee80211_free_node(ni); } m_freem(m); } diff --git a/sys/net80211/ieee80211_vht.c b/sys/net80211/ieee80211_vht.c index 4b5a53d583ce..0839f426e09c 100644 --- a/sys/net80211/ieee80211_vht.c +++ b/sys/net80211/ieee80211_vht.c @@ -1,888 +1,877 @@ /*- * Copyright (c) 2017 Adrian Chadd * 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. * * 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. */ /* * IEEE 802.11ac-2013 protocol support. */ #include "opt_inet.h" #include "opt_wlan.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ADDSHORT(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = (v) >> 8; \ frm += 2; \ } while (0) #define ADDWORD(frm, v) do { \ frm[0] = (v) & 0xff; \ frm[1] = ((v) >> 8) & 0xff; \ frm[2] = ((v) >> 16) & 0xff; \ frm[3] = ((v) >> 24) & 0xff; \ frm += 4; \ } while (0) /* * Immediate TODO: * * + handle WLAN_ACTION_VHT_OPMODE_NOTIF and other VHT action frames * + ensure vhtinfo/vhtcap parameters correctly use the negotiated * capabilities and ratesets * + group ID management operation */ /* * XXX TODO: handle WLAN_ACTION_VHT_OPMODE_NOTIF * * Look at mac80211/vht.c:ieee80211_vht_handle_opmode() for further details. */ static int vht_recv_action_placeholder(struct ieee80211_node *ni, const struct ieee80211_frame *wh, const uint8_t *frm, const uint8_t *efrm) { #ifdef IEEE80211_DEBUG ieee80211_note(ni->ni_vap, "%s: called; fc=0x%.2x/0x%.2x", __func__, wh->i_fc[0], wh->i_fc[1]); #endif return (0); } static int vht_send_action_placeholder(struct ieee80211_node *ni, int category, int action, void *arg0) { #ifdef IEEE80211_DEBUG ieee80211_note(ni->ni_vap, "%s: called; category=%d, action=%d", __func__, category, action); #endif return (EINVAL); } static void ieee80211_vht_init(void) { ieee80211_recv_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_COMPRESSED_BF, vht_recv_action_placeholder); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_GROUPID_MGMT, vht_recv_action_placeholder); ieee80211_recv_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_OPMODE_NOTIF, vht_recv_action_placeholder); ieee80211_send_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_COMPRESSED_BF, vht_send_action_placeholder); ieee80211_send_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_GROUPID_MGMT, vht_send_action_placeholder); ieee80211_send_action_register(IEEE80211_ACTION_CAT_VHT, WLAN_ACTION_VHT_OPMODE_NOTIF, vht_send_action_placeholder); } SYSINIT(wlan_vht, SI_SUB_DRIVERS, SI_ORDER_FIRST, ieee80211_vht_init, NULL); void ieee80211_vht_attach(struct ieee80211com *ic) { } void ieee80211_vht_detach(struct ieee80211com *ic) { } void ieee80211_vht_vattach(struct ieee80211vap *vap) { struct ieee80211com *ic = vap->iv_ic; if (! IEEE80211_CONF_VHT(ic)) return; vap->iv_vht_cap.vht_cap_info = ic->ic_vht_cap.vht_cap_info; vap->iv_vhtextcaps = ic->ic_vhtextcaps; /* XXX assume VHT80 support; should really check vhtcaps */ vap->iv_vht_flags = IEEE80211_FVHT_VHT | IEEE80211_FVHT_USEVHT40 | IEEE80211_FVHT_USEVHT80; if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160MHZ(vap->iv_vht_cap.vht_cap_info)) vap->iv_vht_flags |= IEEE80211_FVHT_USEVHT160; if (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ(vap->iv_vht_cap.vht_cap_info)) vap->iv_vht_flags |= IEEE80211_FVHT_USEVHT80P80; memcpy(&vap->iv_vht_cap.supp_mcs, &ic->ic_vht_cap.supp_mcs, sizeof(struct ieee80211_vht_mcs_info)); } void ieee80211_vht_vdetach(struct ieee80211vap *vap) { } #if 0 static void vht_announce(struct ieee80211com *ic, enum ieee80211_phymode mode) { } #endif static int vht_mcs_to_num(int m) { switch (m) { case IEEE80211_VHT_MCS_SUPPORT_0_7: return (7); case IEEE80211_VHT_MCS_SUPPORT_0_8: return (8); case IEEE80211_VHT_MCS_SUPPORT_0_9: return (9); default: return (0); } } void ieee80211_vht_announce(struct ieee80211com *ic) { int i, tx, rx; if (! IEEE80211_CONF_VHT(ic)) return; /* Channel width */ ic_printf(ic, "[VHT] Channel Widths: 20MHz, 40MHz, 80MHz%s%s\n", (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160MHZ(ic->ic_vht_cap.vht_cap_info)) ? ", 160MHz" : "", (IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_IS_160_80P80MHZ(ic->ic_vht_cap.vht_cap_info)) ? ", 80+80MHz" : ""); /* Features */ ic_printf(ic, "[VHT] Features: %b\n", ic->ic_vht_cap.vht_cap_info, IEEE80211_VHTCAP_BITS); /* For now, just 5GHz VHT. Worry about 2GHz VHT later */ for (i = 0; i < 8; i++) { /* Each stream is 2 bits */ tx = (ic->ic_vht_cap.supp_mcs.tx_mcs_map >> (2*i)) & 0x3; rx = (ic->ic_vht_cap.supp_mcs.rx_mcs_map >> (2*i)) & 0x3; if (tx == 3 && rx == 3) continue; ic_printf(ic, "[VHT] NSS %d: TX MCS 0..%d, RX MCS 0..%d\n", i + 1, vht_mcs_to_num(tx), vht_mcs_to_num(rx)); } } void ieee80211_vht_node_init(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); ni->ni_flags |= IEEE80211_NODE_VHT; } void ieee80211_vht_node_cleanup(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); ni->ni_flags &= ~IEEE80211_NODE_VHT; ni->ni_vhtcap = 0; bzero(&ni->ni_vht_mcsinfo, sizeof(struct ieee80211_vht_mcs_info)); } /* * Parse an 802.11ac VHT operation IE. */ void ieee80211_parse_vhtopmode(struct ieee80211_node *ni, const uint8_t *ie) { /* vht operation */ ni->ni_vht_chanwidth = ie[2]; ni->ni_vht_chan1 = ie[3]; ni->ni_vht_chan2 = ie[4]; ni->ni_vht_basicmcs = le16dec(ie + 5); #if 0 printf("%s: chan1=%d, chan2=%d, chanwidth=%d, basicmcs=0x%04x\n", __func__, ni->ni_vht_chan1, ni->ni_vht_chan2, ni->ni_vht_chanwidth, ni->ni_vht_basicmcs); #endif } /* * Parse an 802.11ac VHT capability IE. */ void ieee80211_parse_vhtcap(struct ieee80211_node *ni, const uint8_t *ie) { /* vht capability */ ni->ni_vhtcap = le32dec(ie + 2); /* suppmcs */ ni->ni_vht_mcsinfo.rx_mcs_map = le16dec(ie + 6); ni->ni_vht_mcsinfo.rx_highest = le16dec(ie + 8); ni->ni_vht_mcsinfo.tx_mcs_map = le16dec(ie + 10); ni->ni_vht_mcsinfo.tx_highest = le16dec(ie + 12); } int ieee80211_vht_updateparams(struct ieee80211_node *ni, const uint8_t *vhtcap_ie, const uint8_t *vhtop_ie) { //printf("%s: called\n", __func__); ieee80211_parse_vhtcap(ni, vhtcap_ie); ieee80211_parse_vhtopmode(ni, vhtop_ie); return (0); } void ieee80211_setup_vht_rates(struct ieee80211_node *ni, const uint8_t *vhtcap_ie, const uint8_t *vhtop_ie) { //printf("%s: called\n", __func__); /* XXX TODO */ } void ieee80211_vht_timeout(struct ieee80211vap *vap) { } void ieee80211_vht_node_join(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); } void ieee80211_vht_node_leave(struct ieee80211_node *ni) { IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, "%s: called", __func__); } /* * Calculate the VHTCAP IE for a given node. * * This includes calculating the capability intersection based on the * current operating mode and intersection of the TX/RX MCS maps. * * The standard only makes it clear about MCS rate negotiation * and MCS basic rates (which must be a subset of the general * negotiated rates). It doesn't make it clear that the AP should * figure out the minimum functional overlap with the STA and * support that. * * Note: this is in host order, not in 802.11 endian order. * * TODO: ensure I re-read 9.7.11 Rate Selection for VHT STAs. * * TODO: investigate what we should negotiate for MU-MIMO beamforming * options. * * opmode is '1' for "vhtcap as if I'm a STA", 0 otherwise. */ void ieee80211_vht_get_vhtcap_ie(struct ieee80211_node *ni, - struct ieee80211_ie_vhtcap *vhtcap, int opmode) + struct ieee80211_vht_cap *vhtcap, int opmode) { struct ieee80211vap *vap = ni->ni_vap; // struct ieee80211com *ic = vap->iv_ic; uint32_t val, val1, val2; uint32_t new_vhtcap; int i; - vhtcap->ie = IEEE80211_ELEMID_VHT_CAP; - vhtcap->len = sizeof(struct ieee80211_ie_vhtcap) - 2; - /* * Capabilities - it depends on whether we are a station * or not. */ new_vhtcap = 0; /* * Station - use our desired configuration based on * local config, local device bits and the already-learnt * vhtcap/vhtinfo IE in the node. */ /* Limit MPDU size to the smaller of the two */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MAX_MPDU_MASK); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MAX_MPDU_MASK); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_MAX_MPDU_MASK); /* Limit supp channel config */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK); } if ((val2 == 2) && ((vap->iv_vht_flags & IEEE80211_FVHT_USEVHT80P80) == 0)) val2 = 1; if ((val2 == 1) && ((vap->iv_vht_flags & IEEE80211_FVHT_USEVHT160) == 0)) val2 = 0; val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SUPP_CHAN_WIDTH_MASK); /* RX LDPC */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_RXLDPC); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_RXLDPC); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_RXLDPC); /* Short-GI 80 */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SHORT_GI_80); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SHORT_GI_80); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SHORT_GI_80); /* Short-GI 160 */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SHORT_GI_160); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SHORT_GI_160); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SHORT_GI_160); /* * STBC is slightly more complicated. * * In non-STA mode, we just announce our capabilities and that * is that. * * In STA mode, we should calculate our capabilities based on * local capabilities /and/ what the remote says. So: * * + Only TX STBC if we support it and the remote supports RX STBC; * + Only announce RX STBC if we support it and the remote supports * TX STBC; * + RX STBC should be the minimum of local and remote RX STBC; */ /* TX STBC */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_TXSTBC); if (opmode == 1) { /* STA mode - enable it only if node RXSTBC is non-zero */ val2 = !! _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_RXSTBC_MASK); } val = MIN(val1, val2); /* XXX For now, use the 11n config flag */ if ((vap->iv_flags_ht & IEEE80211_FHT_STBC_TX) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_TXSTBC); /* RX STBC1..4 */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_RXSTBC_MASK); if (opmode == 1) { /* STA mode - enable it only if node TXSTBC is non-zero */ val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_TXSTBC); } val = MIN(val1, val2); /* XXX For now, use the 11n config flag */ if ((vap->iv_flags_ht & IEEE80211_FHT_STBC_RX) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_RXSTBC_MASK); /* * Finally - if RXSTBC is 0, then don't enable TXSTBC. * Strictly speaking a device can TXSTBC and not RXSTBC, but * it would be silly. */ if (val == 0) new_vhtcap &= ~IEEE80211_VHTCAP_TXSTBC; /* * Some of these fields require other fields to exist. * So before using it, the parent field needs to be checked * otherwise the overridden value may be wrong. * * For example, if SU beamformee is set to 0, then BF STS * needs to be 0. */ /* SU Beamformer capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); /* SU Beamformee capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); } val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); /* Beamformee STS capability - only if SU beamformee capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK); if (opmode == 1) { val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK); } val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_BEAMFORMEE_STS_MASK); /* Sounding dimensions - only if SU beamformer capable */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SOUNDING_DIMENSIONS_MASK); /* * MU Beamformer capable - only if SU BFF capable, MU BFF capable * and STA (not AP) */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MU_BEAMFORMER_CAPABLE); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MU_BEAMFORMER_CAPABLE); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE) == 0) val = 0; if (opmode != 1) /* Only enable for STA mode */ val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMER_CAPABLE); /* * MU Beamformee capable - only if SU BFE capable, MU BFE capable * and AP (not STA) */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MU_BEAMFORMEE_CAPABLE); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MU_BEAMFORMEE_CAPABLE); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE) == 0) val = 0; if (opmode != 0) /* Only enable for AP mode */ val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_SU_BEAMFORMEE_CAPABLE); /* VHT TXOP PS */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_VHT_TXOP_PS); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_VHT_TXOP_PS); val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_VHT_TXOP_PS); /* HTC_VHT */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_HTC_VHT); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_HTC_VHT); val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_HTC_VHT); /* A-MPDU length max */ /* XXX TODO: we need a userland config knob for this */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); val = MIN(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK); /* * Link adaptation is only valid if HTC-VHT capable is 1. * Otherwise, always set it to 0. */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK); val = MIN(val1, val2); if ((new_vhtcap & IEEE80211_VHTCAP_HTC_VHT) == 0) val = 0; new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_VHT_LINK_ADAPTATION_VHT_MASK); /* * The following two options are 0 if the pattern may change, 1 if it * does not change. So, downgrade to the higher value. */ /* RX antenna pattern */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_RX_ANTENNA_PATTERN); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_RX_ANTENNA_PATTERN); val = MAX(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_RX_ANTENNA_PATTERN); /* TX antenna pattern */ val2 = val1 = _IEEE80211_MASKSHIFT(vap->iv_vht_cap.vht_cap_info, IEEE80211_VHTCAP_TX_ANTENNA_PATTERN); if (opmode == 1) val2 = _IEEE80211_MASKSHIFT(ni->ni_vhtcap, IEEE80211_VHTCAP_TX_ANTENNA_PATTERN); val = MAX(val1, val2); new_vhtcap |= _IEEE80211_SHIFTMASK(val, IEEE80211_VHTCAP_TX_ANTENNA_PATTERN); /* * MCS set - again, we announce what we want to use * based on configuration, device capabilities and * already-learnt vhtcap/vhtinfo IE information. */ /* MCS set - start with whatever the device supports */ vhtcap->supp_mcs.rx_mcs_map = vap->iv_vht_cap.supp_mcs.rx_mcs_map; vhtcap->supp_mcs.rx_highest = 0; vhtcap->supp_mcs.tx_mcs_map = vap->iv_vht_cap.supp_mcs.tx_mcs_map; vhtcap->supp_mcs.tx_highest = 0; vhtcap->vht_cap_info = new_vhtcap; /* * Now, if we're a STA, mask off whatever the AP doesn't support. * Ie, we continue to state we can receive whatever we can do, * but we only announce that we will transmit rates that meet * the AP requirement. * * Note: 0 - MCS0..7; 1 - MCS0..8; 2 - MCS0..9; 3 = not supported. * We can't just use MIN() because '3' means "no", so special case it. */ if (opmode) { for (i = 0; i < 8; i++) { val1 = (vhtcap->supp_mcs.tx_mcs_map >> (i*2)) & 0x3; val2 = (ni->ni_vht_mcsinfo.tx_mcs_map >> (i*2)) & 0x3; val = MIN(val1, val2); if (val1 == 3 || val2 == 3) val = 3; vhtcap->supp_mcs.tx_mcs_map &= ~(0x3 << (i*2)); vhtcap->supp_mcs.tx_mcs_map |= (val << (i*2)); } } } /* * Add a VHTCAP field. * * If in station mode, we announce what we would like our * desired configuration to be. * * Else, we announce our capabilities based on our current * configuration. */ uint8_t * ieee80211_add_vhtcap(uint8_t *frm, struct ieee80211_node *ni) { - struct ieee80211_ie_vhtcap vhtcap; - int opmode; - - opmode = 0; - if (ni->ni_vap->iv_opmode == IEEE80211_M_STA) - opmode = 1; - - ieee80211_vht_get_vhtcap_ie(ni, &vhtcap, opmode); + struct ieee80211_vht_cap vhtcap; - memset(frm, '\0', sizeof(struct ieee80211_ie_vhtcap)); + ieee80211_vht_get_vhtcap_ie(ni, &vhtcap, 1); frm[0] = IEEE80211_ELEMID_VHT_CAP; - frm[1] = sizeof(struct ieee80211_ie_vhtcap) - 2; + frm[1] = sizeof(vhtcap); frm += 2; /* 32-bit VHT capability */ ADDWORD(frm, vhtcap.vht_cap_info); /* suppmcs */ ADDSHORT(frm, vhtcap.supp_mcs.rx_mcs_map); ADDSHORT(frm, vhtcap.supp_mcs.rx_highest); ADDSHORT(frm, vhtcap.supp_mcs.tx_mcs_map); ADDSHORT(frm, vhtcap.supp_mcs.tx_highest); return (frm); } /* * Non-associated probe requests. Add VHT capabilities based on * the current channel configuration. No BSS yet. */ uint8_t * ieee80211_add_vhtcap_ch(uint8_t *frm, struct ieee80211vap *vap, struct ieee80211_channel *c) { struct ieee80211_vht_cap *vhtcap; memset(frm, 0, 2 + sizeof(*vhtcap)); frm[0] = IEEE80211_ELEMID_VHT_CAP; frm[1] = sizeof(*vhtcap); frm += 2; /* 32-bit VHT capability */ ADDWORD(frm, vap->iv_vht_cap.vht_cap_info); /* supp_mcs */ ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.rx_mcs_map); ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.rx_highest); ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.tx_mcs_map); ADDSHORT(frm, vap->iv_vht_cap.supp_mcs.tx_highest); return (frm); } static uint8_t ieee80211_vht_get_chwidth_ie(struct ieee80211_channel *c) { /* * XXX TODO: look at the node configuration as * well? */ if (IEEE80211_IS_CHAN_VHT80P80(c)) return IEEE80211_VHT_CHANWIDTH_80P80MHZ; if (IEEE80211_IS_CHAN_VHT160(c)) return IEEE80211_VHT_CHANWIDTH_160MHZ; if (IEEE80211_IS_CHAN_VHT80(c)) return IEEE80211_VHT_CHANWIDTH_80MHZ; if (IEEE80211_IS_CHAN_VHT40(c)) return IEEE80211_VHT_CHANWIDTH_USE_HT; if (IEEE80211_IS_CHAN_VHT20(c)) return IEEE80211_VHT_CHANWIDTH_USE_HT; /* We shouldn't get here */ printf("%s: called on a non-VHT channel (freq=%d, flags=0x%08x\n", __func__, (int) c->ic_freq, c->ic_flags); return IEEE80211_VHT_CHANWIDTH_USE_HT; } /* * Note: this just uses the current channel information; * it doesn't use the node info after parsing. * * XXX TODO: need to make the basic MCS set configurable. * XXX TODO: read 802.11-2013 to determine what to set * chwidth to when scanning. I have a feeling * it isn't involved in scanning and we shouldn't * be sending it; and I don't yet know what to set * it to for IBSS or hostap where the peer may be * a completely different channel width to us. */ uint8_t * ieee80211_add_vhtinfo(uint8_t *frm, struct ieee80211_node *ni) { - memset(frm, '\0', sizeof(struct ieee80211_ie_vht_operation)); frm[0] = IEEE80211_ELEMID_VHT_OPMODE; - frm[1] = sizeof(struct ieee80211_ie_vht_operation) - 2; + frm[1] = sizeof(struct ieee80211_vht_operation); frm += 2; /* 8-bit chanwidth */ *frm++ = ieee80211_vht_get_chwidth_ie(ni->ni_chan); /* 8-bit freq1 */ *frm++ = ni->ni_chan->ic_vht_ch_freq1; /* 8-bit freq2 */ *frm++ = ni->ni_chan->ic_vht_ch_freq2; /* 16-bit basic MCS set - just MCS0..7 for NSS=1 for now */ ADDSHORT(frm, 0xfffc); return (frm); } void ieee80211_vht_update_cap(struct ieee80211_node *ni, const uint8_t *vhtcap_ie, const uint8_t *vhtop_ie) { ieee80211_parse_vhtcap(ni, vhtcap_ie); ieee80211_parse_vhtopmode(ni, vhtop_ie); } static struct ieee80211_channel * findvhtchan(struct ieee80211com *ic, struct ieee80211_channel *c, int vhtflags) { return (ieee80211_find_channel(ic, c->ic_freq, (c->ic_flags & ~IEEE80211_CHAN_VHT) | vhtflags)); } /* * Handle channel promotion to VHT, similar to ieee80211_ht_adjust_channel(). */ struct ieee80211_channel * ieee80211_vht_adjust_channel(struct ieee80211com *ic, struct ieee80211_channel *chan, int flags) { struct ieee80211_channel *c; /* First case - handle channel demotion - if VHT isn't set */ if ((flags & IEEE80211_FVHT_MASK) == 0) { #if 0 printf("%s: demoting channel %d/0x%08x\n", __func__, chan->ic_ieee, chan->ic_flags); #endif c = ieee80211_find_channel(ic, chan->ic_freq, chan->ic_flags & ~IEEE80211_CHAN_VHT); if (c == NULL) c = chan; #if 0 printf("%s: .. to %d/0x%08x\n", __func__, c->ic_ieee, c->ic_flags); #endif return (c); } /* * We can upgrade to VHT - attempt to do so * * Note: we don't clear the HT flags, these are the hints * for HT40U/HT40D when selecting VHT40 or larger channels. */ c = NULL; if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT160)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT160); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT80P80)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT80P80); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT80)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT80); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT40)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT40U); if ((c == NULL) && (flags & IEEE80211_FVHT_USEVHT40)) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT40D); /* * If we get here, VHT20 is always possible because we checked * for IEEE80211_FVHT_VHT above. */ if (c == NULL) c = findvhtchan(ic, chan, IEEE80211_CHAN_VHT20); if (c != NULL) chan = c; #if 0 printf("%s: selected %d/0x%08x\n", __func__, c->ic_ieee, c->ic_flags); #endif return (chan); } /* * Calculate the VHT operation IE for a given node. * * This includes calculating the suitable channel width/parameters * and basic MCS set. * * TODO: ensure I read 9.7.11 Rate Selection for VHT STAs. * TODO: ensure I read 10.39.7 - BSS Basic VHT-MCS and NSS set operation. */ void ieee80211_vht_get_vhtinfo_ie(struct ieee80211_node *ni, - struct ieee80211_ie_vht_operation *vhtop, int opmode) + struct ieee80211_vht_operation *vhtop, int opmode) { printf("%s: called; TODO!\n", __func__); } diff --git a/sys/net80211/ieee80211_vht.h b/sys/net80211/ieee80211_vht.h index 756c2e367485..f2d1706ea0f2 100644 --- a/sys/net80211/ieee80211_vht.h +++ b/sys/net80211/ieee80211_vht.h @@ -1,68 +1,68 @@ /*- * Copyright (c) 2016 Adrian Chadd * 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. * * 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. */ #ifndef _NET80211_IEEE80211_VHT_H_ #define _NET80211_IEEE80211_VHT_H_ void ieee80211_vht_attach(struct ieee80211com *); void ieee80211_vht_detach(struct ieee80211com *); void ieee80211_vht_vattach(struct ieee80211vap *); void ieee80211_vht_vdetach(struct ieee80211vap *); void ieee80211_vht_announce(struct ieee80211com *); void ieee80211_vht_node_init(struct ieee80211_node *); void ieee80211_vht_node_cleanup(struct ieee80211_node *); void ieee80211_parse_vhtopmode(struct ieee80211_node *, const uint8_t *); void ieee80211_parse_vhtcap(struct ieee80211_node *, const uint8_t *); int ieee80211_vht_updateparams(struct ieee80211_node *, const uint8_t *, const uint8_t *); void ieee80211_setup_vht_rates(struct ieee80211_node *, const uint8_t *, const uint8_t *); void ieee80211_vht_timeout(struct ieee80211vap *vap); void ieee80211_vht_node_join(struct ieee80211_node *ni); void ieee80211_vht_node_leave(struct ieee80211_node *ni); uint8_t * ieee80211_add_vhtcap(uint8_t *frm, struct ieee80211_node *); uint8_t * ieee80211_add_vhtinfo(uint8_t *frm, struct ieee80211_node *); uint8_t *ieee80211_add_vhtcap_ch(uint8_t *, struct ieee80211vap *, struct ieee80211_channel *); void ieee80211_vht_update_cap(struct ieee80211_node *, const uint8_t *, const uint8_t *); struct ieee80211_channel * ieee80211_vht_adjust_channel(struct ieee80211com *, struct ieee80211_channel *, int); void ieee80211_vht_get_vhtcap_ie(struct ieee80211_node *ni, - struct ieee80211_ie_vhtcap *, int); + struct ieee80211_vht_cap *, int); void ieee80211_vht_get_vhtinfo_ie(struct ieee80211_node *ni, - struct ieee80211_ie_vht_operation *, int); + struct ieee80211_vht_operation *, int); #endif /* _NET80211_IEEE80211_VHT_H_ */