Path: blob/main/lib/lib80211/lib80211_regdomain.c
104874 views
/*-1* Copyright (c) 2008 Sam Leffler, Errno Consulting2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR14* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES15* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.16* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,17* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT18* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,19* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY20* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT21* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF22* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.23*/2425#include <sys/types.h>26#include <sys/errno.h>27#include <sys/param.h>28#include <sys/mman.h>29#include <sys/sbuf.h>30#include <sys/stat.h>3132#include <stdio.h>33#include <string.h>34#include <ctype.h>35#include <fcntl.h>36#include <err.h>37#include <unistd.h>3839#include <bsdxml.h>4041#include "lib80211_regdomain.h"4243#include <net80211/_ieee80211.h>4445#define MAXLEVEL 204647struct mystate {48XML_Parser parser;49struct regdata *rdp;50struct regdomain *rd; /* current domain */51struct netband *netband; /* current netband */52struct freqband *freqband; /* current freqband */53struct country *country; /* current country */54netband_head *curband; /* current netband list */55int level;56struct sbuf *sbuf[MAXLEVEL];57int nident;58};5960struct ident {61const void *id;62void *p;63enum { DOMAIN, COUNTRY, FREQBAND } type;64};6566static void67start_element(void *data, const char *name, const char **attr)68{69#define iseq(a,b) (strcasecmp(a,b) == 0)70struct mystate *mt;71const void *id, *ref, *mode;72int i;7374mt = data;75if (++mt->level == MAXLEVEL) {76/* XXX force parser to abort */77return;78}79mt->sbuf[mt->level] = sbuf_new_auto();80id = ref = mode = NULL;81for (i = 0; attr[i] != NULL; i += 2) {82if (iseq(attr[i], "id")) {83id = attr[i+1];84} else if (iseq(attr[i], "ref")) {85ref = attr[i+1];86} else if (iseq(attr[i], "mode")) {87mode = attr[i+1];88} else89printf("%*.*s[%s = %s]\n", mt->level + 1,90mt->level + 1, "", attr[i], attr[i+1]);91}92if (iseq(name, "rd") && mt->rd == NULL) {93if (mt->country == NULL) {94mt->rd = calloc(1, sizeof(struct regdomain));95mt->rd->name = strdup(id);96mt->nident++;97LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next);98} else99mt->country->rd = (void *)strdup(ref);100return;101}102if (iseq(name, "defcc") && mt->rd != NULL) {103mt->rd->cc = (void *)strdup(ref);104return;105}106if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) {107if (mode == NULL) {108warnx("no mode for netband at line %ld",109XML_GetCurrentLineNumber(mt->parser));110return;111}112if (iseq(mode, "11b"))113mt->curband = &mt->rd->bands_11b;114else if (iseq(mode, "11g"))115mt->curband = &mt->rd->bands_11g;116else if (iseq(mode, "11a"))117mt->curband = &mt->rd->bands_11a;118else if (iseq(mode, "11ng"))119mt->curband = &mt->rd->bands_11ng;120else if (iseq(mode, "11na"))121mt->curband = &mt->rd->bands_11na;122else if (iseq(mode, "11ac"))123mt->curband = &mt->rd->bands_11ac;124else if (iseq(mode, "11acg"))125mt->curband = &mt->rd->bands_11acg;126else127warnx("unknown mode \"%s\" at line %ld",128__DECONST(char *, mode),129XML_GetCurrentLineNumber(mt->parser));130return;131}132if (iseq(name, "band") && mt->netband == NULL) {133if (mt->curband == NULL) {134warnx("band without enclosing netband at line %ld",135XML_GetCurrentLineNumber(mt->parser));136return;137}138mt->netband = calloc(1, sizeof(struct netband));139LIST_INSERT_HEAD(mt->curband, mt->netband, next);140return;141}142if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) {143/* XXX handle inlines and merge into table? */144if (mt->netband->band != NULL) {145warnx("duplicate freqband at line %ld ignored",146XML_GetCurrentLineNumber(mt->parser));147/* XXX complain */148} else149mt->netband->band = (void *)strdup(ref);150return;151}152153if (iseq(name, "country") && mt->country == NULL) {154mt->country = calloc(1, sizeof(struct country));155mt->country->isoname = strdup(id);156mt->country->code = NO_COUNTRY;157mt->nident++;158LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next);159return;160}161162if (iseq(name, "freqband") && mt->freqband == NULL) {163mt->freqband = calloc(1, sizeof(struct freqband));164mt->freqband->id = strdup(id);165mt->nident++;166LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next);167return;168}169#undef iseq170}171172static int173decode_flag(struct mystate *mt, const char *p, int len)174{175#define iseq(a,b) (strcasecmp(a,b) == 0)176static const struct {177const char *name;178int len;179uint32_t value;180} flags[] = {181#define FLAG(x) { #x, sizeof(#x)-1, x }182FLAG(IEEE80211_CHAN_A),183FLAG(IEEE80211_CHAN_B),184FLAG(IEEE80211_CHAN_G),185FLAG(IEEE80211_CHAN_HT20),186FLAG(IEEE80211_CHAN_HT40),187FLAG(IEEE80211_CHAN_VHT20),188FLAG(IEEE80211_CHAN_VHT40),189FLAG(IEEE80211_CHAN_VHT80),190FLAG(IEEE80211_CHAN_VHT160),191/*192* XXX VHT80P80? This likely should be done by193* 80MHz chan logic in net80211 / ifconfig.194*/195FLAG(IEEE80211_CHAN_ST),196FLAG(IEEE80211_CHAN_TURBO),197FLAG(IEEE80211_CHAN_PASSIVE),198FLAG(IEEE80211_CHAN_DFS),199FLAG(IEEE80211_CHAN_CCK),200FLAG(IEEE80211_CHAN_OFDM),201FLAG(IEEE80211_CHAN_2GHZ),202FLAG(IEEE80211_CHAN_5GHZ),203FLAG(IEEE80211_CHAN_DYN),204FLAG(IEEE80211_CHAN_GFSK),205FLAG(IEEE80211_CHAN_GSM),206FLAG(IEEE80211_CHAN_STURBO),207FLAG(IEEE80211_CHAN_HALF),208FLAG(IEEE80211_CHAN_QUARTER),209FLAG(IEEE80211_CHAN_HT40U),210FLAG(IEEE80211_CHAN_HT40D),211FLAG(IEEE80211_CHAN_4MSXMIT),212FLAG(IEEE80211_CHAN_NOADHOC),213FLAG(IEEE80211_CHAN_NOHOSTAP),214FLAG(IEEE80211_CHAN_11D),215FLAG(IEEE80211_CHAN_FHSS),216FLAG(IEEE80211_CHAN_PUREG),217FLAG(IEEE80211_CHAN_108A),218FLAG(IEEE80211_CHAN_108G),219#undef FLAG220{ "ECM", 3, REQ_ECM },221{ "INDOOR", 6, REQ_INDOOR },222{ "OUTDOOR", 7, REQ_OUTDOOR },223};224unsigned int i;225226for (i = 0; i < nitems(flags); i++)227if (len == flags[i].len && iseq(p, flags[i].name))228return flags[i].value;229warnx("unknown flag \"%.*s\" at line %ld ignored",230len, p, XML_GetCurrentLineNumber(mt->parser));231return 0;232#undef iseq233}234235static void236end_element(void *data, const char *name)237{238#define iseq(a,b) (strcasecmp(a,b) == 0)239struct mystate *mt;240int len;241char *p;242243mt = data;244sbuf_finish(mt->sbuf[mt->level]);245p = sbuf_data(mt->sbuf[mt->level]);246len = sbuf_len(mt->sbuf[mt->level]);247248/* <freqband>...</freqband> */249if (iseq(name, "freqstart") && mt->freqband != NULL) {250mt->freqband->freqStart = strtoul(p, NULL, 0);251goto done;252}253if (iseq(name, "freqend") && mt->freqband != NULL) {254mt->freqband->freqEnd = strtoul(p, NULL, 0);255goto done;256}257if (iseq(name, "chanwidth") && mt->freqband != NULL) {258mt->freqband->chanWidth = strtoul(p, NULL, 0);259goto done;260}261if (iseq(name, "chansep") && mt->freqband != NULL) {262mt->freqband->chanSep = strtoul(p, NULL, 0);263goto done;264}265if (iseq(name, "flags")) {266if (mt->freqband != NULL)267mt->freqband->flags |= decode_flag(mt, p, len);268else if (mt->netband != NULL)269mt->netband->flags |= decode_flag(mt, p, len);270else {271warnx("flags without freqband or netband at line %ld ignored",272XML_GetCurrentLineNumber(mt->parser));273}274goto done;275}276277/* <rd> ... </rd> */278if (iseq(name, "name") && mt->rd != NULL) {279mt->rd->name = strdup(p);280goto done;281}282if (iseq(name, "sku") && mt->rd != NULL) {283mt->rd->sku = strtoul(p, NULL, 0);284goto done;285}286if (iseq(name, "netband") && mt->rd != NULL) {287mt->curband = NULL;288goto done;289}290291/* <band> ... </band> */292if (iseq(name, "freqband") && mt->netband != NULL) {293/* XXX handle inline freqbands */294goto done;295}296if (iseq(name, "maxpower") && mt->netband != NULL) {297mt->netband->maxPower = strtoul(p, NULL, 0);298goto done;299}300if (iseq(name, "maxpowerdfs") && mt->netband != NULL) {301mt->netband->maxPowerDFS = strtoul(p, NULL, 0);302goto done;303}304if (iseq(name, "maxantgain") && mt->netband != NULL) {305mt->netband->maxAntGain = strtoul(p, NULL, 0);306goto done;307}308309/* <country>...</country> */310if (iseq(name, "isocc") && mt->country != NULL) {311mt->country->code = strtoul(p, NULL, 0);312goto done;313}314if (iseq(name, "name") && mt->country != NULL) {315mt->country->name = strdup(p);316goto done;317}318319if (len != 0) {320warnx("unexpected XML token \"%s\" data \"%s\" at line %ld",321name, p, XML_GetCurrentLineNumber(mt->parser));322/* XXX goto done? */323}324/* </freqband> */325if (iseq(name, "freqband") && mt->freqband != NULL) {326/* XXX must have start/end frequencies */327/* XXX must have channel width/sep */328mt->freqband = NULL;329goto done;330}331/* </rd> */332if (iseq(name, "rd") && mt->rd != NULL) {333mt->rd = NULL;334goto done;335}336/* </band> */337if (iseq(name, "band") && mt->netband != NULL) {338if (mt->netband->band == NULL) {339warnx("no freqbands for band at line %ld",340XML_GetCurrentLineNumber(mt->parser));341}342if (mt->netband->maxPower == 0) {343warnx("no maxpower for band at line %ld",344XML_GetCurrentLineNumber(mt->parser));345}346/* default max power w/ DFS to max power */347if (mt->netband->maxPowerDFS == 0)348mt->netband->maxPowerDFS = mt->netband->maxPower;349mt->netband = NULL;350goto done;351}352/* </netband> */353if (iseq(name, "netband") && mt->netband != NULL) {354mt->curband = NULL;355goto done;356}357/* </country> */358if (iseq(name, "country") && mt->country != NULL) {359/* XXX NO_COUNTRY should be in the net80211 country enum */360if ((int) mt->country->code == NO_COUNTRY) {361warnx("no ISO cc for country at line %ld",362XML_GetCurrentLineNumber(mt->parser));363}364if (mt->country->name == NULL) {365warnx("no name for country at line %ld",366XML_GetCurrentLineNumber(mt->parser));367}368if (mt->country->rd == NULL) {369warnx("no regdomain reference for country at line %ld",370XML_GetCurrentLineNumber(mt->parser));371}372mt->country = NULL;373goto done;374}375done:376sbuf_delete(mt->sbuf[mt->level]);377mt->sbuf[mt->level--] = NULL;378#undef iseq379}380381static void382char_data(void *data, const XML_Char *s, int len)383{384struct mystate *mt;385const char *b, *e;386387mt = data;388389b = s;390e = s + len-1;391for (; isspace(*b) && b < e; b++)392;393for (; isspace(*e) && e > b; e++)394;395if (e != b || (*b != '\0' && !isspace(*b)))396sbuf_bcat(mt->sbuf[mt->level], b, e-b+1);397}398399static void *400findid(struct regdata *rdp, const void *id, int type)401{402struct ident *ip;403404for (ip = rdp->ident; ip->id != NULL; ip++)405if ((int) ip->type == type && strcasecmp(ip->id, id) == 0)406return ip->p;407return NULL;408}409410/*411* Parse an regdomain XML configuration and build the internal representation.412*/413int414lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len)415{416struct mystate *mt;417struct regdomain *dp;418struct country *cp;419struct freqband *fp;420struct netband *nb;421const void *id;422int i, errors;423424memset(rdp, 0, sizeof(struct regdata));425mt = calloc(1, sizeof(struct mystate));426if (mt == NULL)427return ENOMEM;428/* parse the XML input */429mt->rdp = rdp;430mt->parser = XML_ParserCreate(NULL);431XML_SetUserData(mt->parser, mt);432XML_SetElementHandler(mt->parser, start_element, end_element);433XML_SetCharacterDataHandler(mt->parser, char_data);434if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) {435warnx("%s: %s at line %ld", __func__,436XML_ErrorString(XML_GetErrorCode(mt->parser)),437XML_GetCurrentLineNumber(mt->parser));438return -1;439}440XML_ParserFree(mt->parser);441442/* setup the identifer table */443rdp->ident = calloc(mt->nident + 1, sizeof(struct ident));444if (rdp->ident == NULL)445return ENOMEM;446free(mt);447448errors = 0;449i = 0;450LIST_FOREACH(dp, &rdp->domains, next) {451rdp->ident[i].id = dp->name;452rdp->ident[i].p = dp;453rdp->ident[i].type = DOMAIN;454i++;455}456LIST_FOREACH(fp, &rdp->freqbands, next) {457rdp->ident[i].id = fp->id;458rdp->ident[i].p = fp;459rdp->ident[i].type = FREQBAND;460i++;461}462LIST_FOREACH(cp, &rdp->countries, next) {463rdp->ident[i].id = cp->isoname;464rdp->ident[i].p = cp;465rdp->ident[i].type = COUNTRY;466i++;467}468469/* patch references */470LIST_FOREACH(dp, &rdp->domains, next) {471if (dp->cc != NULL) {472id = dp->cc;473dp->cc = findid(rdp, id, COUNTRY);474if (dp->cc == NULL) {475warnx("undefined country \"%s\"",476__DECONST(char *, id));477errors++;478}479free(__DECONST(char *, id));480}481LIST_FOREACH(nb, &dp->bands_11b, next) {482id = findid(rdp, nb->band, FREQBAND);483if (id == NULL) {484warnx("undefined 11b band \"%s\"",485__DECONST(char *, nb->band));486errors++;487}488nb->band = id;489}490LIST_FOREACH(nb, &dp->bands_11g, next) {491id = findid(rdp, nb->band, FREQBAND);492if (id == NULL) {493warnx("undefined 11g band \"%s\"",494__DECONST(char *, nb->band));495errors++;496}497nb->band = id;498}499LIST_FOREACH(nb, &dp->bands_11a, next) {500id = findid(rdp, nb->band, FREQBAND);501if (id == NULL) {502warnx("undefined 11a band \"%s\"",503__DECONST(char *, nb->band));504errors++;505}506nb->band = id;507}508LIST_FOREACH(nb, &dp->bands_11ng, next) {509id = findid(rdp, nb->band, FREQBAND);510if (id == NULL) {511warnx("undefined 11ng band \"%s\"",512__DECONST(char *, nb->band));513errors++;514}515nb->band = id;516}517LIST_FOREACH(nb, &dp->bands_11na, next) {518id = findid(rdp, nb->band, FREQBAND);519if (id == NULL) {520warnx("undefined 11na band \"%s\"",521__DECONST(char *, nb->band));522errors++;523}524nb->band = id;525}526LIST_FOREACH(nb, &dp->bands_11ac, next) {527id = findid(rdp, nb->band, FREQBAND);528if (id == NULL) {529warnx("undefined 11ac band \"%s\"",530__DECONST(char *, nb->band));531errors++;532}533nb->band = id;534}535LIST_FOREACH(nb, &dp->bands_11acg, next) {536id = findid(rdp, nb->band, FREQBAND);537if (id == NULL) {538warnx("undefined 11acg band \"%s\"",539__DECONST(char *, nb->band));540errors++;541}542nb->band = id;543}544}545LIST_FOREACH(cp, &rdp->countries, next) {546id = cp->rd;547cp->rd = findid(rdp, id, DOMAIN);548if (cp->rd == NULL) {549warnx("undefined country \"%s\"",550__DECONST(char *, id));551errors++;552}553free(__DECONST(char *, id));554}555556return errors ? EINVAL : 0;557}558559static void560cleanup_bands(netband_head *head)561{562struct netband *nb;563564for (;;) {565nb = LIST_FIRST(head);566if (nb == NULL)567break;568LIST_REMOVE(nb, next);569free(nb);570}571}572573/*574* Cleanup state/resources for a previously parsed regdomain database.575*/576void577lib80211_regdomain_cleanup(struct regdata *rdp)578{579580free(rdp->ident);581rdp->ident = NULL;582for (;;) {583struct regdomain *dp = LIST_FIRST(&rdp->domains);584if (dp == NULL)585break;586LIST_REMOVE(dp, next);587cleanup_bands(&dp->bands_11b);588cleanup_bands(&dp->bands_11g);589cleanup_bands(&dp->bands_11a);590cleanup_bands(&dp->bands_11ng);591cleanup_bands(&dp->bands_11na);592cleanup_bands(&dp->bands_11ac);593cleanup_bands(&dp->bands_11acg);594if (dp->name != NULL)595free(__DECONST(char *, dp->name));596}597for (;;) {598struct country *cp = LIST_FIRST(&rdp->countries);599if (cp == NULL)600break;601LIST_REMOVE(cp, next);602if (cp->name != NULL)603free(__DECONST(char *, cp->name));604free(cp);605}606for (;;) {607struct freqband *fp = LIST_FIRST(&rdp->freqbands);608if (fp == NULL)609break;610LIST_REMOVE(fp, next);611free(fp);612}613}614615struct regdata *616lib80211_alloc_regdata(void)617{618struct regdata *rdp;619struct stat sb;620void *xml;621int fd;622623rdp = calloc(1, sizeof(struct regdata));624625fd = open(_PATH_REGDOMAIN, O_RDONLY);626if (fd < 0) {627#ifdef DEBUG628warn("%s: open(%s)", __func__, _PATH_REGDOMAIN);629#endif630free(rdp);631return NULL;632}633if (fstat(fd, &sb) < 0) {634#ifdef DEBUG635warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN);636#endif637close(fd);638free(rdp);639return NULL;640}641xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);642if (xml == MAP_FAILED) {643#ifdef DEBUG644warn("%s: mmap", __func__);645#endif646close(fd);647free(rdp);648return NULL;649}650if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) {651#ifdef DEBUG652warn("%s: error reading regulatory database", __func__);653#endif654munmap(xml, sb.st_size);655close(fd);656free(rdp);657return NULL;658}659munmap(xml, sb.st_size);660close(fd);661662return rdp;663}664665void666lib80211_free_regdata(struct regdata *rdp)667{668lib80211_regdomain_cleanup(rdp);669free(rdp);670}671672/*673* Lookup a regdomain by SKU.674*/675const struct regdomain *676lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku)677{678const struct regdomain *dp;679680LIST_FOREACH(dp, &rdp->domains, next) {681if (dp->sku == sku)682return dp;683}684return NULL;685}686687/*688* Lookup a regdomain by name.689*/690const struct regdomain *691lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name)692{693const struct regdomain *dp;694695LIST_FOREACH(dp, &rdp->domains, next) {696if (strcasecmp(dp->name, name) == 0)697return dp;698}699return NULL;700}701702/*703* Lookup a country by ISO country code.704*/705const struct country *706lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc)707{708const struct country *cp;709710LIST_FOREACH(cp, &rdp->countries, next) {711if (cp->code == cc)712return cp;713}714return NULL;715}716717/*718* Lookup a country by ISO/long name.719*/720const struct country *721lib80211_country_findbyname(const struct regdata *rdp, const char *name)722{723const struct country *cp;724int len;725726len = strlen(name);727LIST_FOREACH(cp, &rdp->countries, next) {728if (strcasecmp(cp->isoname, name) == 0)729return cp;730}731LIST_FOREACH(cp, &rdp->countries, next) {732if (strncasecmp(cp->name, name, len) == 0)733return cp;734}735return NULL;736}737738739