freebsd-dev/lib/lib80211/lib80211_regdomain.c
Adrian Chadd 36ea5759aa [lib80211] add VHT bands and channel flags.
This is preparation work for 11ac support.  The regulatory database
needs to know about VHT channel flags and 80MHz (and later 160MHz)
available channel bands.

Whilst here, add the 2GHz VHT band (which is a terrible, terrible vendor
extension that almost all vendors do) just in preparation, even though
I don't (yet) plan on supporting it.
2017-01-07 01:56:10 +00:00

740 lines
18 KiB
C

/*-
* Copyright (c) 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.
*/
#ifndef lint
static const char rcsid[] = "$FreeBSD$";
#endif /* not lint */
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/sbuf.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <err.h>
#include <unistd.h>
#include <bsdxml.h>
#include "lib80211_regdomain.h"
#include <net80211/_ieee80211.h>
#define MAXLEVEL 20
struct mystate {
XML_Parser parser;
struct regdata *rdp;
struct regdomain *rd; /* current domain */
struct netband *netband; /* current netband */
struct freqband *freqband; /* current freqband */
struct country *country; /* current country */
netband_head *curband; /* current netband list */
int level;
struct sbuf *sbuf[MAXLEVEL];
int nident;
};
struct ident {
const void *id;
void *p;
enum { DOMAIN, COUNTRY, FREQBAND } type;
};
static void
start_element(void *data, const char *name, const char **attr)
{
#define iseq(a,b) (strcasecmp(a,b) == 0)
struct mystate *mt;
const void *id, *ref, *mode;
int i;
mt = data;
if (++mt->level == MAXLEVEL) {
/* XXX force parser to abort */
return;
}
mt->sbuf[mt->level] = sbuf_new_auto();
id = ref = mode = NULL;
for (i = 0; attr[i] != NULL; i += 2) {
if (iseq(attr[i], "id")) {
id = attr[i+1];
} else if (iseq(attr[i], "ref")) {
ref = attr[i+1];
} else if (iseq(attr[i], "mode")) {
mode = attr[i+1];
} else
printf("%*.*s[%s = %s]\n", mt->level + 1,
mt->level + 1, "", attr[i], attr[i+1]);
}
if (iseq(name, "rd") && mt->rd == NULL) {
if (mt->country == NULL) {
mt->rd = calloc(1, sizeof(struct regdomain));
mt->rd->name = strdup(id);
mt->nident++;
LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next);
} else
mt->country->rd = (void *)strdup(ref);
return;
}
if (iseq(name, "defcc") && mt->rd != NULL) {
mt->rd->cc = (void *)strdup(ref);
return;
}
if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) {
if (mode == NULL) {
warnx("no mode for netband at line %ld",
XML_GetCurrentLineNumber(mt->parser));
return;
}
if (iseq(mode, "11b"))
mt->curband = &mt->rd->bands_11b;
else if (iseq(mode, "11g"))
mt->curband = &mt->rd->bands_11g;
else if (iseq(mode, "11a"))
mt->curband = &mt->rd->bands_11a;
else if (iseq(mode, "11ng"))
mt->curband = &mt->rd->bands_11ng;
else if (iseq(mode, "11na"))
mt->curband = &mt->rd->bands_11na;
else if (iseq(mode, "11ac"))
mt->curband = &mt->rd->bands_11ac;
else if (iseq(mode, "11acg"))
mt->curband = &mt->rd->bands_11acg;
else
warnx("unknown mode \"%s\" at line %ld",
__DECONST(char *, mode),
XML_GetCurrentLineNumber(mt->parser));
return;
}
if (iseq(name, "band") && mt->netband == NULL) {
if (mt->curband == NULL) {
warnx("band without enclosing netband at line %ld",
XML_GetCurrentLineNumber(mt->parser));
return;
}
mt->netband = calloc(1, sizeof(struct netband));
LIST_INSERT_HEAD(mt->curband, mt->netband, next);
return;
}
if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) {
/* XXX handle inlines and merge into table? */
if (mt->netband->band != NULL) {
warnx("duplicate freqband at line %ld ignored",
XML_GetCurrentLineNumber(mt->parser));
/* XXX complain */
} else
mt->netband->band = (void *)strdup(ref);
return;
}
if (iseq(name, "country") && mt->country == NULL) {
mt->country = calloc(1, sizeof(struct country));
mt->country->isoname = strdup(id);
mt->country->code = NO_COUNTRY;
mt->nident++;
LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next);
return;
}
if (iseq(name, "freqband") && mt->freqband == NULL) {
mt->freqband = calloc(1, sizeof(struct freqband));
mt->freqband->id = strdup(id);
mt->nident++;
LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next);
return;
}
#undef iseq
}
static int
decode_flag(struct mystate *mt, const char *p, int len)
{
#define iseq(a,b) (strcasecmp(a,b) == 0)
static const struct {
const char *name;
int len;
uint32_t value;
} flags[] = {
#define FLAG(x) { #x, sizeof(#x)-1, x }
FLAG(IEEE80211_CHAN_A),
FLAG(IEEE80211_CHAN_B),
FLAG(IEEE80211_CHAN_G),
FLAG(IEEE80211_CHAN_HT20),
FLAG(IEEE80211_CHAN_HT40),
FLAG(IEEE80211_CHAN_VHT20),
FLAG(IEEE80211_CHAN_VHT40),
FLAG(IEEE80211_CHAN_VHT80),
/*
* XXX VHT80_80? This likely should be done by
* 80MHz chan logic in net80211 / ifconfig.
*/
FLAG(IEEE80211_CHAN_VHT160),
FLAG(IEEE80211_CHAN_ST),
FLAG(IEEE80211_CHAN_TURBO),
FLAG(IEEE80211_CHAN_PASSIVE),
FLAG(IEEE80211_CHAN_DFS),
FLAG(IEEE80211_CHAN_CCK),
FLAG(IEEE80211_CHAN_OFDM),
FLAG(IEEE80211_CHAN_2GHZ),
FLAG(IEEE80211_CHAN_5GHZ),
FLAG(IEEE80211_CHAN_DYN),
FLAG(IEEE80211_CHAN_GFSK),
FLAG(IEEE80211_CHAN_GSM),
FLAG(IEEE80211_CHAN_STURBO),
FLAG(IEEE80211_CHAN_HALF),
FLAG(IEEE80211_CHAN_QUARTER),
FLAG(IEEE80211_CHAN_HT40U),
FLAG(IEEE80211_CHAN_HT40D),
FLAG(IEEE80211_CHAN_4MSXMIT),
FLAG(IEEE80211_CHAN_NOADHOC),
FLAG(IEEE80211_CHAN_NOHOSTAP),
FLAG(IEEE80211_CHAN_11D),
FLAG(IEEE80211_CHAN_FHSS),
FLAG(IEEE80211_CHAN_PUREG),
FLAG(IEEE80211_CHAN_108A),
FLAG(IEEE80211_CHAN_108G),
#undef FLAG
{ "ECM", 3, REQ_ECM },
{ "INDOOR", 6, REQ_INDOOR },
{ "OUTDOOR", 7, REQ_OUTDOOR },
};
unsigned int i;
for (i = 0; i < nitems(flags); i++)
if (len == flags[i].len && iseq(p, flags[i].name))
return flags[i].value;
warnx("unknown flag \"%.*s\" at line %ld ignored",
len, p, XML_GetCurrentLineNumber(mt->parser));
return 0;
#undef iseq
}
static void
end_element(void *data, const char *name)
{
#define iseq(a,b) (strcasecmp(a,b) == 0)
struct mystate *mt;
int len;
char *p;
mt = data;
sbuf_finish(mt->sbuf[mt->level]);
p = sbuf_data(mt->sbuf[mt->level]);
len = sbuf_len(mt->sbuf[mt->level]);
/* <freqband>...</freqband> */
if (iseq(name, "freqstart") && mt->freqband != NULL) {
mt->freqband->freqStart = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "freqend") && mt->freqband != NULL) {
mt->freqband->freqEnd = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "chanwidth") && mt->freqband != NULL) {
mt->freqband->chanWidth = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "chansep") && mt->freqband != NULL) {
mt->freqband->chanSep = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "flags")) {
if (mt->freqband != NULL)
mt->freqband->flags |= decode_flag(mt, p, len);
else if (mt->netband != NULL)
mt->netband->flags |= decode_flag(mt, p, len);
else {
warnx("flags without freqband or netband at line %ld ignored",
XML_GetCurrentLineNumber(mt->parser));
}
goto done;
}
/* <rd> ... </rd> */
if (iseq(name, "name") && mt->rd != NULL) {
mt->rd->name = strdup(p);
goto done;
}
if (iseq(name, "sku") && mt->rd != NULL) {
mt->rd->sku = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "netband") && mt->rd != NULL) {
mt->curband = NULL;
goto done;
}
/* <band> ... </band> */
if (iseq(name, "freqband") && mt->netband != NULL) {
/* XXX handle inline freqbands */
goto done;
}
if (iseq(name, "maxpower") && mt->netband != NULL) {
mt->netband->maxPower = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "maxpowerdfs") && mt->netband != NULL) {
mt->netband->maxPowerDFS = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "maxantgain") && mt->netband != NULL) {
mt->netband->maxAntGain = strtoul(p, NULL, 0);
goto done;
}
/* <country>...</country> */
if (iseq(name, "isocc") && mt->country != NULL) {
mt->country->code = strtoul(p, NULL, 0);
goto done;
}
if (iseq(name, "name") && mt->country != NULL) {
mt->country->name = strdup(p);
goto done;
}
if (len != 0) {
warnx("unexpected XML token \"%s\" data \"%s\" at line %ld",
name, p, XML_GetCurrentLineNumber(mt->parser));
/* XXX goto done? */
}
/* </freqband> */
if (iseq(name, "freqband") && mt->freqband != NULL) {
/* XXX must have start/end frequencies */
/* XXX must have channel width/sep */
mt->freqband = NULL;
goto done;
}
/* </rd> */
if (iseq(name, "rd") && mt->rd != NULL) {
mt->rd = NULL;
goto done;
}
/* </band> */
if (iseq(name, "band") && mt->netband != NULL) {
if (mt->netband->band == NULL) {
warnx("no freqbands for band at line %ld",
XML_GetCurrentLineNumber(mt->parser));
}
if (mt->netband->maxPower == 0) {
warnx("no maxpower for band at line %ld",
XML_GetCurrentLineNumber(mt->parser));
}
/* default max power w/ DFS to max power */
if (mt->netband->maxPowerDFS == 0)
mt->netband->maxPowerDFS = mt->netband->maxPower;
mt->netband = NULL;
goto done;
}
/* </netband> */
if (iseq(name, "netband") && mt->netband != NULL) {
mt->curband = NULL;
goto done;
}
/* </country> */
if (iseq(name, "country") && mt->country != NULL) {
/* XXX NO_COUNTRY should be in the net80211 country enum */
if ((int) mt->country->code == NO_COUNTRY) {
warnx("no ISO cc for country at line %ld",
XML_GetCurrentLineNumber(mt->parser));
}
if (mt->country->name == NULL) {
warnx("no name for country at line %ld",
XML_GetCurrentLineNumber(mt->parser));
}
if (mt->country->rd == NULL) {
warnx("no regdomain reference for country at line %ld",
XML_GetCurrentLineNumber(mt->parser));
}
mt->country = NULL;
goto done;
}
done:
sbuf_delete(mt->sbuf[mt->level]);
mt->sbuf[mt->level--] = NULL;
#undef iseq
}
static void
char_data(void *data, const XML_Char *s, int len)
{
struct mystate *mt;
const char *b, *e;
mt = data;
b = s;
e = s + len-1;
for (; isspace(*b) && b < e; b++)
;
for (; isspace(*e) && e > b; e++)
;
if (e != b || (*b != '\0' && !isspace(*b)))
sbuf_bcat(mt->sbuf[mt->level], b, e-b+1);
}
static void *
findid(struct regdata *rdp, const void *id, int type)
{
struct ident *ip;
for (ip = rdp->ident; ip->id != NULL; ip++)
if ((int) ip->type == type && strcasecmp(ip->id, id) == 0)
return ip->p;
return NULL;
}
/*
* Parse an regdomain XML configuration and build the internal representation.
*/
int
lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len)
{
struct mystate *mt;
struct regdomain *dp;
struct country *cp;
struct freqband *fp;
struct netband *nb;
const void *id;
int i, errors;
memset(rdp, 0, sizeof(struct regdata));
mt = calloc(1, sizeof(struct mystate));
if (mt == NULL)
return ENOMEM;
/* parse the XML input */
mt->rdp = rdp;
mt->parser = XML_ParserCreate(NULL);
XML_SetUserData(mt->parser, mt);
XML_SetElementHandler(mt->parser, start_element, end_element);
XML_SetCharacterDataHandler(mt->parser, char_data);
if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) {
warnx("%s: %s at line %ld", __func__,
XML_ErrorString(XML_GetErrorCode(mt->parser)),
XML_GetCurrentLineNumber(mt->parser));
return -1;
}
XML_ParserFree(mt->parser);
/* setup the identifer table */
rdp->ident = calloc(sizeof(struct ident), mt->nident + 1);
if (rdp->ident == NULL)
return ENOMEM;
free(mt);
errors = 0;
i = 0;
LIST_FOREACH(dp, &rdp->domains, next) {
rdp->ident[i].id = dp->name;
rdp->ident[i].p = dp;
rdp->ident[i].type = DOMAIN;
i++;
}
LIST_FOREACH(fp, &rdp->freqbands, next) {
rdp->ident[i].id = fp->id;
rdp->ident[i].p = fp;
rdp->ident[i].type = FREQBAND;
i++;
}
LIST_FOREACH(cp, &rdp->countries, next) {
rdp->ident[i].id = cp->isoname;
rdp->ident[i].p = cp;
rdp->ident[i].type = COUNTRY;
i++;
}
/* patch references */
LIST_FOREACH(dp, &rdp->domains, next) {
if (dp->cc != NULL) {
id = dp->cc;
dp->cc = findid(rdp, id, COUNTRY);
if (dp->cc == NULL) {
warnx("undefined country \"%s\"",
__DECONST(char *, id));
errors++;
}
free(__DECONST(char *, id));
}
LIST_FOREACH(nb, &dp->bands_11b, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11b band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
LIST_FOREACH(nb, &dp->bands_11g, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11g band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
LIST_FOREACH(nb, &dp->bands_11a, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11a band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
LIST_FOREACH(nb, &dp->bands_11ng, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11ng band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
LIST_FOREACH(nb, &dp->bands_11na, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11na band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
LIST_FOREACH(nb, &dp->bands_11ac, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11ac band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
LIST_FOREACH(nb, &dp->bands_11acg, next) {
id = findid(rdp, nb->band, FREQBAND);
if (id == NULL) {
warnx("undefined 11acg band \"%s\"",
__DECONST(char *, nb->band));
errors++;
}
nb->band = id;
}
}
LIST_FOREACH(cp, &rdp->countries, next) {
id = cp->rd;
cp->rd = findid(rdp, id, DOMAIN);
if (cp->rd == NULL) {
warnx("undefined country \"%s\"",
__DECONST(char *, id));
errors++;
}
free(__DECONST(char *, id));
}
return errors ? EINVAL : 0;
}
static void
cleanup_bands(netband_head *head)
{
struct netband *nb;
for (;;) {
nb = LIST_FIRST(head);
if (nb == NULL)
break;
free(nb);
}
}
/*
* Cleanup state/resources for a previously parsed regdomain database.
*/
void
lib80211_regdomain_cleanup(struct regdata *rdp)
{
free(rdp->ident);
rdp->ident = NULL;
for (;;) {
struct regdomain *dp = LIST_FIRST(&rdp->domains);
if (dp == NULL)
break;
LIST_REMOVE(dp, next);
cleanup_bands(&dp->bands_11b);
cleanup_bands(&dp->bands_11g);
cleanup_bands(&dp->bands_11a);
cleanup_bands(&dp->bands_11ng);
cleanup_bands(&dp->bands_11na);
cleanup_bands(&dp->bands_11ac);
cleanup_bands(&dp->bands_11acg);
if (dp->name != NULL)
free(__DECONST(char *, dp->name));
}
for (;;) {
struct country *cp = LIST_FIRST(&rdp->countries);
if (cp == NULL)
break;
LIST_REMOVE(cp, next);
if (cp->name != NULL)
free(__DECONST(char *, cp->name));
free(cp);
}
for (;;) {
struct freqband *fp = LIST_FIRST(&rdp->freqbands);
if (fp == NULL)
break;
LIST_REMOVE(fp, next);
free(fp);
}
}
struct regdata *
lib80211_alloc_regdata(void)
{
struct regdata *rdp;
struct stat sb;
void *xml;
int fd;
rdp = calloc(1, sizeof(struct regdata));
fd = open(_PATH_REGDOMAIN, O_RDONLY);
if (fd < 0) {
#ifdef DEBUG
warn("%s: open(%s)", __func__, _PATH_REGDOMAIN);
#endif
free(rdp);
return NULL;
}
if (fstat(fd, &sb) < 0) {
#ifdef DEBUG
warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN);
#endif
close(fd);
free(rdp);
return NULL;
}
xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (xml == MAP_FAILED) {
#ifdef DEBUG
warn("%s: mmap", __func__);
#endif
close(fd);
free(rdp);
return NULL;
}
if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) {
#ifdef DEBUG
warn("%s: error reading regulatory database", __func__);
#endif
munmap(xml, sb.st_size);
close(fd);
free(rdp);
return NULL;
}
munmap(xml, sb.st_size);
close(fd);
return rdp;
}
void
lib80211_free_regdata(struct regdata *rdp)
{
lib80211_regdomain_cleanup(rdp);
free(rdp);
}
/*
* Lookup a regdomain by SKU.
*/
const struct regdomain *
lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku)
{
const struct regdomain *dp;
LIST_FOREACH(dp, &rdp->domains, next) {
if (dp->sku == sku)
return dp;
}
return NULL;
}
/*
* Lookup a regdomain by name.
*/
const struct regdomain *
lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name)
{
const struct regdomain *dp;
LIST_FOREACH(dp, &rdp->domains, next) {
if (strcasecmp(dp->name, name) == 0)
return dp;
}
return NULL;
}
/*
* Lookup a country by ISO country code.
*/
const struct country *
lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc)
{
const struct country *cp;
LIST_FOREACH(cp, &rdp->countries, next) {
if (cp->code == cc)
return cp;
}
return NULL;
}
/*
* Lookup a country by ISO/long name.
*/
const struct country *
lib80211_country_findbyname(const struct regdata *rdp, const char *name)
{
const struct country *cp;
int len;
len = strlen(name);
LIST_FOREACH(cp, &rdp->countries, next) {
if (strcasecmp(cp->isoname, name) == 0)
return cp;
}
LIST_FOREACH(cp, &rdp->countries, next) {
if (strncasecmp(cp->name, name, len) == 0)
return cp;
}
return NULL;
}