summaryrefslogtreecommitdiffstats
path: root/sbin/ifconfig/regdomain.c
diff options
context:
space:
mode:
authorsam <sam@FreeBSD.org>2008-04-20 20:35:46 +0000
committersam <sam@FreeBSD.org>2008-04-20 20:35:46 +0000
commit3569e353ca63336d80ab0143dd9669b0b9e6b123 (patch)
treebc7985c57e7ecfa1ac03e48c406a25430dba634b /sbin/ifconfig/regdomain.c
parent682b4ae9be70192e298129ada878af3486683aaf (diff)
downloadFreeBSD-src-3569e353ca63336d80ab0143dd9669b0b9e6b123.zip
FreeBSD-src-3569e353ca63336d80ab0143dd9669b0b9e6b123.tar.gz
Multi-bss (aka vap) support for 802.11 devices.
Note this includes changes to all drivers and moves some device firmware loading to use firmware(9) and a separate module (e.g. ral). Also there no longer are separate wlan_scan* modules; this functionality is now bundled into the wlan module. Supported by: Hobnob and Marvell Reviewed by: many Obtained from: Atheros (some bits)
Diffstat (limited to 'sbin/ifconfig/regdomain.c')
-rw-r--r--sbin/ifconfig/regdomain.c636
1 files changed, 636 insertions, 0 deletions
diff --git a/sbin/ifconfig/regdomain.c b/sbin/ifconfig/regdomain.c
new file mode 100644
index 0000000..55c382e
--- /dev/null
+++ b/sbin/ifconfig/regdomain.c
@@ -0,0 +1,636 @@
+/*-
+ * 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/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 "regdomain.h"
+
+#include <net80211/_ieee80211.h>
+
+#define MAXLEVEL 20
+
+struct mystate {
+ 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(NULL, NULL, 0, SBUF_AUTOEXTEND);
+ 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) {
+ /* XXX complain */
+ 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;
+ /* XXX else complain */
+ return;
+ }
+ if (iseq(name, "band") && mt->netband == NULL) {
+ if (mt->curband == NULL) {
+ /* XXX complain */
+ 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) {
+ /* 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->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 uint32_t
+decode_flag(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), x }
+ FLAG(IEEE80211_CHAN_A),
+ FLAG(IEEE80211_CHAN_B),
+ FLAG(IEEE80211_CHAN_G),
+ FLAG(IEEE80211_CHAN_HT20),
+ FLAG(IEEE80211_CHAN_HT40),
+ 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
+ };
+ int i;
+
+ for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++)
+ if (len == flags[i].len && iseq(p, flags[i].name))
+ return flags[i].value;
+ 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(p, len);
+ else if (mt->netband != NULL)
+ mt->netband->flags |= decode_flag(p, len);
+ else {
+ /* XXX complain */
+ }
+ 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;
+ }
+
+ /* <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) {
+ printf("Unexpected XML: name \"%s\" data \"%s\"\n", name, p);
+ /* 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) {
+ printf("No frequency band information at line %d\n",
+#if 0
+ XML_GetCurrentLineNumber(parser));
+#else
+ 0);
+#endif
+ }
+ if (mt->netband->maxPower == 0) {
+ /* XXX complain */
+ }
+ /* 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) {
+ if (mt->country->code == 0) {
+ /* XXX must have iso cc */
+ }
+ if (mt->country->name == NULL) {
+ /* XXX must have name */
+ }
+ if (mt->country->rd == NULL) {
+ /* XXX? rd ref? */
+ }
+ 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 (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)
+{
+ XML_Parser parser;
+ struct mystate *mt;
+ struct regdomain *dp;
+ struct country *cp;
+ struct freqband *fp;
+ struct netband *nb;
+ const void *id;
+ int i;
+
+ memset(rdp, 0, sizeof(struct regdata));
+ mt = calloc(1, sizeof(struct mystate));
+ if (mt == NULL)
+ return ENOMEM;
+ /* parse the XML input */
+ mt->rdp = rdp;
+ parser = XML_ParserCreate(NULL);
+ XML_SetUserData(parser, mt);
+ XML_SetElementHandler(parser, start_element, end_element);
+ XML_SetCharacterDataHandler(parser, char_data);
+ if (XML_Parse(parser, p, len, 1) != XML_STATUS_OK) {
+ warnx("%s: %s at line %d", __func__,
+ XML_ErrorString(XML_GetErrorCode(parser)),
+ XML_GetCurrentLineNumber(parser));
+ return -1;
+ }
+ XML_ParserFree(parser);
+
+ /* setup the identifer table */
+ rdp->ident = calloc(sizeof(struct ident), mt->nident + 1);
+ if (rdp->ident == NULL)
+ return ENOMEM;
+ free(mt);
+ 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);
+ free(__DECONST(char *, id));
+ }
+ LIST_FOREACH(nb, &dp->bands_11b, next)
+ nb->band = findid(rdp, nb->band, FREQBAND);
+ LIST_FOREACH(nb, &dp->bands_11g, next)
+ nb->band = findid(rdp, nb->band, FREQBAND);
+ LIST_FOREACH(nb, &dp->bands_11a, next)
+ nb->band = findid(rdp, nb->band, FREQBAND);
+ LIST_FOREACH(nb, &dp->bands_11ng, next)
+ nb->band = findid(rdp, nb->band, FREQBAND);
+ LIST_FOREACH(nb, &dp->bands_11na, next)
+ nb->band = findid(rdp, nb->band, FREQBAND);
+ }
+ LIST_FOREACH(cp, &rdp->countries, next) {
+ id = cp->rd;
+ cp->rd = findid(rdp, id, DOMAIN);
+ free(__DECONST(char *, id));
+ }
+
+ return 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);
+ 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 ||
+ strncasecmp(cp->name, name, len) == 0)
+ return cp;
+ }
+ return NULL;
+}
OpenPOWER on IntegriCloud