From 77eaa55d96e26fe6219633c41506e5aad1d983cb Mon Sep 17 00:00:00 2001 From: HappyZ Date: Tue, 23 Jan 2018 15:11:31 -0600 Subject: [PATCH] iw modified for FTM This is a modified version from https://git.kernel.org/pub/scm/linux/kernel/git/jberg/iw.git/commit/?id= f32884808324ea38732e278ea2df910f96348a38. Referred from https://bugzilla.kernel.org/show_bug.cgi?id=197187. --- .gitignore | 7 + Android.mk | 22 + CONTRIBUTING | 49 + COPYING | 16 + Makefile | 128 + README | 15 + ap.c | 139 ++ bitrate.c | 267 +++ coalesce.c | 283 +++ connect.c | 238 ++ cqm.c | 57 + event.c | 899 +++++++ genl.c | 117 + hwsim.c | 148 ++ ibss.c | 143 ++ ieee80211.h | 61 + info.c | 705 ++++++ interface.c | 717 ++++++ iw.8 | 71 + iw.c | 619 +++++ iw.h | 238 ++ link.c | 272 +++ measurements.c | 329 +++ mesh.c | 585 +++++ mgmt.c | 150 ++ mpath.c | 188 ++ mpp.c | 81 + nl80211.h | 6059 ++++++++++++++++++++++++++++++++++++++++++++++++ ocb.c | 44 + offch.c | 47 + p2p.c | 26 + phy.c | 729 ++++++ ps.c | 80 + reason.c | 50 + reg.c | 261 +++ roc.c | 40 + scan.c | 2331 +++++++++++++++++++ sections.c | 4 + station.c | 746 ++++++ status.c | 59 + survey.c | 80 + util.c | 1129 +++++++++ vendor.c | 144 ++ version.sh | 41 + wowlan.c | 486 ++++ 45 files changed, 18900 insertions(+) create mode 100644 .gitignore create mode 100644 Android.mk create mode 100644 CONTRIBUTING create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README create mode 100644 ap.c create mode 100644 bitrate.c create mode 100644 coalesce.c create mode 100644 connect.c create mode 100644 cqm.c create mode 100644 event.c create mode 100644 genl.c create mode 100644 hwsim.c create mode 100644 ibss.c create mode 100644 ieee80211.h create mode 100644 info.c create mode 100644 interface.c create mode 100644 iw.8 create mode 100644 iw.c create mode 100644 iw.h create mode 100644 link.c create mode 100644 measurements.c create mode 100644 mesh.c create mode 100644 mgmt.c create mode 100644 mpath.c create mode 100644 mpp.c create mode 100644 nl80211.h create mode 100644 ocb.c create mode 100644 offch.c create mode 100644 p2p.c create mode 100644 phy.c create mode 100644 ps.c create mode 100644 reason.c create mode 100644 reg.c create mode 100644 roc.c create mode 100644 scan.c create mode 100644 sections.c create mode 100644 station.c create mode 100644 status.c create mode 100644 survey.c create mode 100644 util.c create mode 100644 vendor.c create mode 100755 version.sh create mode 100644 wowlan.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16bb88b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +iw +*~ +*.o +.config +version.c +iw.8.gz +*-stamp diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..4a50f89 --- /dev/null +++ b/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) +IW_SOURCE_DIR := $(LOCAL_PATH) + +include $(CLEAR_VARS) + +IW_ANDROID_BUILD=y +NO_PKG_CONFIG=y +include $(LOCAL_PATH)/Makefile + +LOCAL_SRC_FILES := $(patsubst %.o,%.c,$(OBJS)) + +LOCAL_CFLAGS += -DCONFIG_LIBNL20 +LOCAL_LDFLAGS := -Wl,--no-gc-sections +#LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_TAGS := eng +LOCAL_STATIC_LIBRARIES := libnl +LOCAL_MODULE := iw + +$(IW_SOURCE_DIR)/version.c: + $(IW_SOURCE_DIR)/version.sh $(IW_SOURCE_DIR)/version.c + +include $(BUILD_EXECUTABLE) diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..f288959 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,49 @@ + +This project embraces the Developer Certificate of Origin (DCO) for +contributions. This means you must agree to the following prior to submitting +patches, if you agree with this developer certificate you acknowledge this by +adding a Signed-off-by tag to your patch commit log. Every submitted patch +must have this. + +The source for the DCO: + +http://developercertificate.org/ + +----------------------------------------------------------------------- + +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..73e19ac --- /dev/null +++ b/COPYING @@ -0,0 +1,16 @@ +Copyright (c) 2007, 2008 Johannes Berg +Copyright (c) 2007 Andy Lutomirski +Copyright (c) 2007 Mike Kershaw +Copyright (c) 2008-2009 Luis R. Rodriguez + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..60da916 --- /dev/null +++ b/Makefile @@ -0,0 +1,128 @@ +MAKEFLAGS += --no-print-directory + +PREFIX ?= /usr +SBINDIR ?= $(PREFIX)/sbin +MANDIR ?= $(PREFIX)/share/man +PKG_CONFIG ?= pkg-config + +MKDIR ?= mkdir -p +INSTALL ?= install +CC ?= "gcc" + +CFLAGS ?= -O2 -g +CFLAGS += -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common \ + -Werror-implicit-function-declaration -Wsign-compare + +OBJS = iw.o genl.o event.o info.o phy.o \ + interface.o ibss.o station.o survey.o util.o ocb.o \ + mesh.o mpath.o mpp.o scan.o reg.o version.o \ + reason.o status.o connect.o link.o offch.o ps.o cqm.o \ + bitrate.o wowlan.o coalesce.o roc.o p2p.o vendor.o mgmt.o \ + ap.o measurements.o +OBJS += sections.o + +OBJS-$(HWSIM) += hwsim.o + +OBJS += $(OBJS-y) $(OBJS-Y) + +ALL = iw + +ifeq ($(NO_PKG_CONFIG),) +NL3xFOUND := $(shell $(PKG_CONFIG) --atleast-version=3.2 libnl-3.0 && echo Y) +ifneq ($(NL3xFOUND),Y) +NL31FOUND := $(shell $(PKG_CONFIG) --exact-version=3.1 libnl-3.1 && echo Y) +ifneq ($(NL31FOUND),Y) +NL3FOUND := $(shell $(PKG_CONFIG) --atleast-version=3 libnl-3.0 && echo Y) +ifneq ($(NL3FOUND),Y) +NL2FOUND := $(shell $(PKG_CONFIG) --atleast-version=2 libnl-2.0 && echo Y) +ifneq ($(NL2FOUND),Y) +NL1FOUND := $(shell $(PKG_CONFIG) --atleast-version=1 libnl-1 && echo Y) +endif +endif +endif +endif + +ifeq ($(NL1FOUND),Y) +NLLIBNAME = libnl-1 +endif + +ifeq ($(NL2FOUND),Y) +CFLAGS += -DCONFIG_LIBNL20 +LIBS += -lnl-genl +NLLIBNAME = libnl-2.0 +endif + +ifeq ($(NL3xFOUND),Y) +# libnl 3.2 might be found as 3.2 and 3.0 +NL3FOUND = N +CFLAGS += -DCONFIG_LIBNL30 +LIBS += -lnl-genl-3 +NLLIBNAME = libnl-3.0 +endif + +ifeq ($(NL3FOUND),Y) +CFLAGS += -DCONFIG_LIBNL30 +LIBS += -lnl-genl +NLLIBNAME = libnl-3.0 +endif + +# nl-3.1 has a broken libnl-gnl-3.1.pc file +# as show by pkg-config --debug --libs --cflags --exact-version=3.1 libnl-genl-3.1;echo $? +ifeq ($(NL31FOUND),Y) +CFLAGS += -DCONFIG_LIBNL30 +LIBS += -lnl-genl +NLLIBNAME = libnl-3.1 +endif + +ifeq ($(NLLIBNAME),) +$(error Cannot find development files for any supported version of libnl) +endif + +LIBS += $(shell $(PKG_CONFIG) --libs $(NLLIBNAME)) +CFLAGS += $(shell $(PKG_CONFIG) --cflags $(NLLIBNAME)) +endif # NO_PKG_CONFIG + +ifeq ($(V),1) +Q= +NQ=true +else +Q=@ +NQ=echo +endif + +all: $(ALL) + +VERSION_OBJS := $(filter-out version.o, $(OBJS)) + +version.c: version.sh $(patsubst %.o,%.c,$(VERSION_OBJS)) nl80211.h iw.h Makefile \ + $(wildcard .git/index .git/refs/tags) + @$(NQ) ' GEN ' $@ + $(Q)./version.sh $@ + +%.o: %.c iw.h nl80211.h + @$(NQ) ' CC ' $@ + $(Q)$(CC) $(CFLAGS) -c -o $@ $< + +ifeq ($(IW_ANDROID_BUILD),) +iw: $(OBJS) + @$(NQ) ' CC ' iw + $(Q)$(CC) $(LDFLAGS) $(OBJS) $(LIBS) -o iw +endif + +check: + $(Q)$(MAKE) all CC="REAL_CC=$(CC) CHECK=\"sparse -Wall\" cgcc" + +%.gz: % + @$(NQ) ' GZIP' $< + $(Q)gzip < $< > $@ + +install: iw iw.8.gz + @$(NQ) ' INST iw' + $(Q)$(MKDIR) $(DESTDIR)$(SBINDIR) + $(Q)$(INSTALL) -m 755 iw $(DESTDIR)$(SBINDIR) + @$(NQ) ' INST iw.8' + $(Q)$(MKDIR) $(DESTDIR)$(MANDIR)/man8/ + $(Q)$(INSTALL) -m 644 iw.8.gz $(DESTDIR)$(MANDIR)/man8/ + +clean: + $(Q)rm -f iw *.o *~ *.gz version.c *-stamp diff --git a/README b/README new file mode 100644 index 0000000..1d0eaae --- /dev/null +++ b/README @@ -0,0 +1,15 @@ + +This is 'iw', a tool to use nl80211. + + +To build iw, just enter 'make'. If that fails, set the +PKG_CONFIG_PATH environment variable to allow the Makefile +to find libnl. + + +'iw' is currently maintained at http://git.sipsolutions.net/iw.git/, +some more documentation is available at +http://wireless.kernel.org/en/users/Documentation/iw. + +Please send all patches to Johannes Berg +and CC linux-wireless@vger.kernel.org for community review. diff --git a/ap.c b/ap.c new file mode 100644 index 0000000..4bab5b9 --- /dev/null +++ b/ap.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include "nl80211.h" +#include "iw.h" + +SECTION(ap); + +static int handle_start_ap(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct chandef chandef; + int res, parsed; + char *end; + int val, len; + char buf[2304]; + + if (argc < 6) + return 1; + + /* SSID */ + NLA_PUT(msg, NL80211_ATTR_SSID, strlen(argv[0]), argv[0]); + argv++; + argc--; + + /* chandef */ + res = parse_freqchan(&chandef, false, argc, argv, &parsed); + if (res) + return res; + argc -= parsed; + argv += parsed; + res = put_chandef(msg, &chandef); + if (res) + return res; + + /* beacon interval */ + val = strtoul(argv[0], &end, 10); + if (*end != '\0') + return -EINVAL; + + NLA_PUT_U32(msg, NL80211_ATTR_BEACON_INTERVAL, val); + argv++; + argc--; + + /* dtim */ + val = strtoul(argv[0], &end, 10); + if (*end != '\0') + return -EINVAL; + + NLA_PUT_U32(msg, NL80211_ATTR_DTIM_PERIOD, val); + argv++; + argc--; + + if (strcmp(argv[0], "hidden-ssid") == 0) { + argc--; + argv++; + NLA_PUT_U32(msg, NL80211_ATTR_HIDDEN_SSID, + NL80211_HIDDEN_SSID_ZERO_LEN); + } else if (strcmp(argv[0], "zeroed-ssid") == 0) { + argc--; + argv++; + NLA_PUT_U32(msg, NL80211_ATTR_HIDDEN_SSID, + NL80211_HIDDEN_SSID_ZERO_CONTENTS); + } + + /* beacon head must be provided */ + if (strcmp(argv[0], "head") != 0) + return 1; + argv++; + argc--; + + len = strlen(argv[0]); + if (!len || (len % 2)) + return -EINVAL; + + if (!hex2bin(&argv[0][0], buf)) + return -EINVAL; + + NLA_PUT(msg, NL80211_ATTR_BEACON_HEAD, (len / 2), &buf); + argv++; + argc--; + + if (!argc) + return 0; + + /* tail is optional */ + if (strcmp(argv[0], "tail") == 0) { + argv++; + argc--; + + if (!argc) + return -EINVAL; + + len = strlen(argv[0]); + if (!len || (len % 2)) + return -EINVAL; + + if (!hex2bin(&argv[0][0], buf)) + return -EINVAL; + + NLA_PUT(msg, NL80211_ATTR_BEACON_TAIL, (len / 2), &buf); + argv++; + argc--; + } + + if (!argc) + return 0; + + if (strcmp(*argv, "key") != 0 && strcmp(*argv, "keys") != 0) + return 1; + + argv++; + argc--; + + return parse_keys(msg, argv, argc); + nla_put_failure: + return -ENOSPC; +} +COMMAND(ap, start, "", + NL80211_CMD_NEW_BEACON, 0, CIB_NETDEV, handle_start_ap, + " [5|10|20|40|80|80+80|160] [ []]" + " [hidden-ssid|zeroed-ssid] head" + " [tail ]" + " [key0:abcde d:1:6162636465]\n"); + +static int handle_stop_ap(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + return 0; +} +COMMAND(ap, stop, "", + NL80211_CMD_DEL_BEACON, 0, CIB_NETDEV, handle_stop_ap, + "Stop AP functionality\n"); diff --git a/bitrate.c b/bitrate.c new file mode 100644 index 0000000..4a026a4 --- /dev/null +++ b/bitrate.c @@ -0,0 +1,267 @@ +#include + +#include "nl80211.h" +#include "iw.h" + + +static int parse_vht_chunk(const char *arg, __u8 *nss, __u16 *mcs) +{ + unsigned int count, i; + unsigned int inss, mcs_start, mcs_end, tab[10]; + + *nss = 0; *mcs = 0; + + if (strchr(arg, '-')) { + /* Format: NSS:MCS_START-MCS_END */ + count = sscanf(arg, "%u:%u-%u", &inss, &mcs_start, &mcs_end); + + if (count != 3) + return 0; + + if (inss < 1 || inss > NL80211_VHT_NSS_MAX) + return 0; + + if (mcs_start > mcs_end) + return 0; + + if (mcs_start > 9 || mcs_end > 9) + return 0; + + *nss = inss; + for (i = mcs_start; i <= mcs_end; i++) + *mcs |= 1 << i; + + } else { + /* Format: NSS:MCSx,MCSy,... */ + count = sscanf(arg, "%u:%u,%u,%u,%u,%u,%u,%u,%u,%u,%u", &inss, + &tab[0], &tab[1], &tab[2], &tab[3], &tab[4], &tab[5], + &tab[6], &tab[7], &tab[8], &tab[9]); + + if (count < 2) + return 0; + + if (inss < 1 || inss > NL80211_VHT_NSS_MAX) + return 0; + + *nss = inss; + for (i = 0; i < count - 1; i++) { + if (tab[i] > 9) + return 0; + *mcs |= 1 << tab[i]; + } + } + + return 1; +} + +static int setup_vht(struct nl80211_txrate_vht *txrate_vht, + int argc, char **argv) +{ + __u8 nss; + __u16 mcs; + int i; + + memset(txrate_vht, 0, sizeof(*txrate_vht)); + + for (i = 0; i < argc; i++) { + if(!parse_vht_chunk(argv[i], &nss, &mcs)) + return 0; + + nss--; + txrate_vht->mcs[nss] |= mcs; + } + + return 1; +} + +#define VHT_ARGC_MAX 100 + +static int handle_bitrates(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + struct nlattr *nl_rates, *nl_band; + int i; + bool have_legacy_24 = false, have_legacy_5 = false; + uint8_t legacy_24[32], legacy_5[32]; + int n_legacy_24 = 0, n_legacy_5 = 0; + uint8_t *legacy = NULL; + int *n_legacy = NULL; + bool have_ht_mcs_24 = false, have_ht_mcs_5 = false; + bool have_vht_mcs_24 = false, have_vht_mcs_5 = false; + uint8_t ht_mcs_24[77], ht_mcs_5[77]; + int n_ht_mcs_24 = 0, n_ht_mcs_5 = 0; + struct nl80211_txrate_vht txrate_vht_24 = {}; + struct nl80211_txrate_vht txrate_vht_5 = {}; + uint8_t *mcs = NULL; + int *n_mcs = NULL; + char *vht_argv_5[VHT_ARGC_MAX] = {}; char *vht_argv_24[VHT_ARGC_MAX] = {}; + char **vht_argv = NULL; + int vht_argc_5 = 0; int vht_argc_24 = 0; + int *vht_argc = NULL; + int sgi_24 = 0, sgi_5 = 0, lgi_24 = 0, lgi_5 = 0; + + enum { + S_NONE, + S_LEGACY, + S_HT, + S_VHT, + S_GI, + } parser_state = S_NONE; + + for (i = 0; i < argc; i++) { + char *end; + double tmpd; + long tmpl; + + if (strcmp(argv[i], "legacy-2.4") == 0) { + if (have_legacy_24) + return 1; + parser_state = S_LEGACY; + legacy = legacy_24; + n_legacy = &n_legacy_24; + have_legacy_24 = true; + } else if (strcmp(argv[i], "legacy-5") == 0) { + if (have_legacy_5) + return 1; + parser_state = S_LEGACY; + legacy = legacy_5; + n_legacy = &n_legacy_5; + have_legacy_5 = true; + } + else if (strcmp(argv[i], "ht-mcs-2.4") == 0) { + if (have_ht_mcs_24) + return 1; + parser_state = S_HT; + mcs = ht_mcs_24; + n_mcs = &n_ht_mcs_24; + have_ht_mcs_24 = true; + } else if (strcmp(argv[i], "ht-mcs-5") == 0) { + if (have_ht_mcs_5) + return 1; + parser_state = S_HT; + mcs = ht_mcs_5; + n_mcs = &n_ht_mcs_5; + have_ht_mcs_5 = true; + } else if (strcmp(argv[i], "vht-mcs-2.4") == 0) { + if (have_vht_mcs_24) + return 1; + parser_state = S_VHT; + vht_argv = vht_argv_24; + vht_argc = &vht_argc_24; + have_vht_mcs_24 = true; + } else if (strcmp(argv[i], "vht-mcs-5") == 0) { + if (have_vht_mcs_5) + return 1; + parser_state = S_VHT; + vht_argv = vht_argv_5; + vht_argc = &vht_argc_5; + have_vht_mcs_5 = true; + } else if (strcmp(argv[i], "sgi-2.4") == 0) { + sgi_24 = 1; + parser_state = S_GI; + } else if (strcmp(argv[i], "sgi-5") == 0) { + sgi_5 = 1; + parser_state = S_GI; + } else if (strcmp(argv[i], "lgi-2.4") == 0) { + lgi_24 = 1; + parser_state = S_GI; + } else if (strcmp(argv[i], "lgi-5") == 0) { + lgi_5 = 1; + parser_state = S_GI; + } else switch (parser_state) { + case S_LEGACY: + tmpd = strtod(argv[i], &end); + if (*end != '\0') + return 1; + if (tmpd < 1 || tmpd > 255 * 2) + return 1; + legacy[(*n_legacy)++] = tmpd * 2; + break; + case S_HT: + tmpl = strtol(argv[i], &end, 0); + if (*end != '\0') + return 1; + if (tmpl < 0 || tmpl > 255) + return 1; + mcs[(*n_mcs)++] = tmpl; + break; + case S_VHT: + if (*vht_argc >= VHT_ARGC_MAX) + return 1; + vht_argv[(*vht_argc)++] = argv[i]; + break; + case S_GI: + break; + default: + return 1; + } + } + + if (have_vht_mcs_24) + if(!setup_vht(&txrate_vht_24, vht_argc_24, vht_argv_24)) + return -EINVAL; + + if (have_vht_mcs_5) + if(!setup_vht(&txrate_vht_5, vht_argc_5, vht_argv_5)) + return -EINVAL; + + if (sgi_5 && lgi_5) + return 1; + + if (sgi_24 && lgi_24) + return 1; + + nl_rates = nla_nest_start(msg, NL80211_ATTR_TX_RATES); + if (!nl_rates) + goto nla_put_failure; + + if (have_legacy_24 || have_ht_mcs_24 || have_vht_mcs_24 || sgi_24 || lgi_24) { + nl_band = nla_nest_start(msg, NL80211_BAND_2GHZ); + if (!nl_band) + goto nla_put_failure; + if (have_legacy_24) + nla_put(msg, NL80211_TXRATE_LEGACY, n_legacy_24, legacy_24); + if (have_ht_mcs_24) + nla_put(msg, NL80211_TXRATE_HT, n_ht_mcs_24, ht_mcs_24); + if (have_vht_mcs_24) + nla_put(msg, NL80211_TXRATE_VHT, sizeof(txrate_vht_24), &txrate_vht_24); + if (sgi_24) + nla_put_u8(msg, NL80211_TXRATE_GI, NL80211_TXRATE_FORCE_SGI); + if (lgi_24) + nla_put_u8(msg, NL80211_TXRATE_GI, NL80211_TXRATE_FORCE_LGI); + nla_nest_end(msg, nl_band); + } + + if (have_legacy_5 || have_ht_mcs_5 || have_vht_mcs_5 || sgi_5 || lgi_5) { + nl_band = nla_nest_start(msg, NL80211_BAND_5GHZ); + if (!nl_band) + goto nla_put_failure; + if (have_legacy_5) + nla_put(msg, NL80211_TXRATE_LEGACY, n_legacy_5, legacy_5); + if (have_ht_mcs_5) + nla_put(msg, NL80211_TXRATE_HT, n_ht_mcs_5, ht_mcs_5); + if (have_vht_mcs_5) + nla_put(msg, NL80211_TXRATE_VHT, sizeof(txrate_vht_5), &txrate_vht_5); + if (sgi_5) + nla_put_u8(msg, NL80211_TXRATE_GI, NL80211_TXRATE_FORCE_SGI); + if (lgi_5) + nla_put_u8(msg, NL80211_TXRATE_GI, NL80211_TXRATE_FORCE_LGI); + nla_nest_end(msg, nl_band); + } + + nla_nest_end(msg, nl_rates); + + return 0; + nla_put_failure: + return -ENOBUFS; +} + +#define DESCR_LEGACY "[legacy-<2.4|5> *]" +#define DESCR DESCR_LEGACY " [ht-mcs-<2.4|5> *] [vht-mcs-<2.4|5> *] [sgi-2.4|lgi-2.4] [sgi-5|lgi-5]" + +COMMAND(set, bitrates, "[legacy-<2.4|5> *] [ht-mcs-<2.4|5> *] [vht-mcs-<2.4|5> *] [sgi-2.4|lgi-2.4] [sgi-5|lgi-5]", + NL80211_CMD_SET_TX_BITRATE_MASK, 0, CIB_NETDEV, handle_bitrates, + "Sets up the specified rate masks.\n" + "Not passing any arguments would clear the existing mask (if any)."); diff --git a/coalesce.c b/coalesce.c new file mode 100644 index 0000000..36dcaef --- /dev/null +++ b/coalesce.c @@ -0,0 +1,283 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +SECTION(coalesce); + +static int handle_coalesce_enable(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nlattr *nl_rules, *nl_rule = NULL, *nl_pats, *nl_pat; + unsigned char *pat, *mask; + size_t patlen; + int patnum = 0, pkt_offset, err = 1; + char *eptr, *value1, *value2, *sptr = NULL, *end, buf[16768]; + enum nl80211_coalesce_condition condition; + FILE *f = fopen(argv[0], "r"); + enum { + PS_DELAY, + PS_CONDITION, + PS_PATTERNS + } parse_state = PS_DELAY; + int rule_num = 0; + + if (!f) + return 1; + + nl_rules = nla_nest_start(msg, NL80211_ATTR_COALESCE_RULE); + if (!nl_rules) { + fclose(f); + return -ENOBUFS; + } + + while (!feof(f)) { + char *eol; + + if (!fgets(buf, sizeof(buf), f)) + break; + + eol = strchr(buf + 5, '\r'); + if (eol) + *eol = 0; + eol = strchr(buf + 5, '\n'); + if (eol) + *eol = 0; + + switch (parse_state) { + case PS_DELAY: + if (strncmp(buf, "delay=", 6) == 0) { + char *delay = buf + 6; + + rule_num++; + nl_rule = nla_nest_start(msg, rule_num); + if (!nl_rule) + goto close; + + NLA_PUT_U32(msg, NL80211_ATTR_COALESCE_RULE_DELAY, + strtoul(delay, &end, 10)); + if (*end != '\0') + goto close; + parse_state = PS_CONDITION; + } else { + goto close; + } + break; + case PS_CONDITION: + if (strncmp(buf, "condition=", 10) == 0) { + char *cond = buf + 10; + + condition = strtoul(cond, &end, 10); + if (*end != '\0') + goto close; + NLA_PUT_U32(msg, NL80211_ATTR_COALESCE_RULE_CONDITION, + condition); + parse_state = PS_PATTERNS; + } else { + goto close; + } + break; + case PS_PATTERNS: + if (strncmp(buf, "patterns=", 9) == 0) { + char *cur_pat = buf + 9; + char *next_pat = strchr(buf + 9, ','); + + if (next_pat) { + *next_pat = 0; + next_pat++; + } + + nl_pats = nla_nest_start(msg, NL80211_ATTR_COALESCE_RULE_PKT_PATTERN); + while (1) { + value1 = strtok_r(cur_pat, "+", &sptr); + value2 = strtok_r(NULL, "+", &sptr); + + if (!value2) { + pkt_offset = 0; + if (!value1) + goto close; + value2 = value1; + } else { + pkt_offset = strtoul(value1, &eptr, 10); + if (eptr != value1 + strlen(value1)) + goto close; + } + + if (parse_hex_mask(value2, &pat, &patlen, &mask)) + goto close; + + nl_pat = nla_nest_start(msg, ++patnum); + NLA_PUT(msg, NL80211_PKTPAT_MASK, + DIV_ROUND_UP(patlen, 8), mask); + NLA_PUT(msg, NL80211_PKTPAT_PATTERN, patlen, pat); + NLA_PUT_U32(msg, NL80211_PKTPAT_OFFSET, + pkt_offset); + nla_nest_end(msg, nl_pat); + free(mask); + free(pat); + + if (!next_pat) + break; + cur_pat = next_pat; + next_pat = strchr(cur_pat, ','); + if (next_pat) { + *next_pat = 0; + next_pat++; + } + } + nla_nest_end(msg, nl_pats); + nla_nest_end(msg, nl_rule); + parse_state = PS_DELAY; + + } else { + goto close; + } + break; + default: + if (buf[0] == '#') + continue; + goto close; + } + } + + if (parse_state == PS_DELAY) + err = 0; + else + err = 1; + goto close; +nla_put_failure: + err = -ENOBUFS; +close: + fclose(f); + nla_nest_end(msg, nl_rules); + return err; +} + +COMMAND(coalesce, enable, "", + NL80211_CMD_SET_COALESCE, 0, CIB_PHY, handle_coalesce_enable, + "Enable coalesce with given configuration.\n" + "The configuration file contains coalesce rules:\n" + " delay=\n" + " condition=\n" + " patterns=<[offset1+],<[offset2+],...>\n" + " delay=\n" + " condition=\n" + " patterns=<[offset1+],<[offset2+],...>\n" + " ...\n" + "delay: maximum coalescing delay in msec.\n" + "condition: 1/0 i.e. 'not match'/'match' the patterns\n" + "patterns: each pattern is given as a bytestring with '-' in\n" + "places where any byte may be present, e.g. 00:11:22:-:44 will\n" + "match 00:11:22:33:44 and 00:11:22:33:ff:44 etc. Offset and\n" + "pattern should be separated by '+', e.g. 18+43:34:00:12 will\n" + "match '43:34:00:12' after 18 bytes of offset in Rx packet.\n"); + +static int +handle_coalesce_disable(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + /* just a set w/o coalesce attribute */ + return 0; +} +COMMAND(coalesce, disable, "", NL80211_CMD_SET_COALESCE, 0, CIB_PHY, + handle_coalesce_disable, "Disable coalesce."); + +static int print_coalesce_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *attrs[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *pattern, *rule; + int rem_pattern, rem_rule; + enum nl80211_coalesce_condition condition; + int delay; + + nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!attrs[NL80211_ATTR_COALESCE_RULE]) { + printf("Coalesce is disabled.\n"); + return NL_SKIP; + } + + printf("Coalesce is enabled:\n"); + + nla_for_each_nested(rule, attrs[NL80211_ATTR_COALESCE_RULE], rem_rule) { + struct nlattr *ruleattr[NUM_NL80211_ATTR_COALESCE_RULE]; + + nla_parse(ruleattr, NL80211_ATTR_COALESCE_RULE_MAX, + nla_data(rule), nla_len(rule), NULL); + + delay = nla_get_u32(ruleattr[NL80211_ATTR_COALESCE_RULE_DELAY]); + condition = + nla_get_u32(ruleattr[NL80211_ATTR_COALESCE_RULE_CONDITION]); + + printf("Rule - max coalescing delay: %dmsec condition:", delay); + if (condition) + printf("not match\n"); + else + printf("match\n"); + + if (ruleattr[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN]) { + nla_for_each_nested(pattern, + ruleattr[NL80211_ATTR_COALESCE_RULE_PKT_PATTERN], + rem_pattern) { + struct nlattr *patattr[NUM_NL80211_PKTPAT]; + int i, patlen, masklen, pkt_offset; + uint8_t *mask, *pat; + + nla_parse(patattr, MAX_NL80211_PKTPAT, + nla_data(pattern), nla_len(pattern), + NULL); + if (!patattr[NL80211_PKTPAT_MASK] || + !patattr[NL80211_PKTPAT_PATTERN] || + !patattr[NL80211_PKTPAT_OFFSET]) { + printf(" * (invalid pattern specification)\n"); + continue; + } + masklen = nla_len(patattr[NL80211_PKTPAT_MASK]); + patlen = nla_len(patattr[NL80211_PKTPAT_PATTERN]); + pkt_offset = nla_get_u32(patattr[NL80211_PKTPAT_OFFSET]); + if (DIV_ROUND_UP(patlen, 8) != masklen) { + printf(" * (invalid pattern specification)\n"); + continue; + } + printf(" * packet offset: %d", pkt_offset); + printf(" pattern: "); + pat = nla_data(patattr[NL80211_PKTPAT_PATTERN]); + mask = nla_data(patattr[NL80211_PKTPAT_MASK]); + for (i = 0; i < patlen; i++) { + if (mask[i / 8] & (1 << (i % 8))) + printf("%.2x", pat[i]); + else + printf("--"); + if (i != patlen - 1) + printf(":"); + } + printf("\n"); + } + } + } + + return NL_SKIP; +} + +static int handle_coalesce_show(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + register_handler(print_coalesce_handler, NULL); + + return 0; +} +COMMAND(coalesce, show, "", NL80211_CMD_GET_COALESCE, 0, CIB_PHY, handle_coalesce_show, + "Show coalesce status."); diff --git a/connect.c b/connect.c new file mode 100644 index 0000000..339fc73 --- /dev/null +++ b/connect.c @@ -0,0 +1,238 @@ +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +static int iw_conn(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + char *end; + unsigned char bssid[6]; + int freq; + int ret; + + if (argc < 1) + return 1; + + /* SSID */ + NLA_PUT(msg, NL80211_ATTR_SSID, strlen(argv[0]), argv[0]); + argv++; + argc--; + + /* freq */ + if (argc) { + freq = strtoul(argv[0], &end, 10); + if (*end == '\0') { + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq); + argv++; + argc--; + } + } + + /* bssid */ + if (argc) { + if (mac_addr_a2n(bssid, argv[0]) == 0) { + NLA_PUT(msg, NL80211_ATTR_MAC, 6, bssid); + argv++; + argc--; + } + } + + if (!argc) + return 0; + + if (strcmp(*argv, "key") != 0 && strcmp(*argv, "keys") != 0) + return 1; + + argv++; + argc--; + + ret = parse_keys(msg, argv, argc); + if (ret) + return ret; + + argc -= 4; + argv += 4; + + if (!argc) + return 0; + + if (!strcmp(*argv, "mfp:req")) + NLA_PUT_U32(msg, NL80211_ATTR_USE_MFP, NL80211_MFP_REQUIRED); + else if (!strcmp(*argv, "mfp:opt")) + NLA_PUT_U32(msg, NL80211_ATTR_USE_MFP, NL80211_MFP_OPTIONAL); + else if (!strcmp(*argv, "mfp:no")) + NLA_PUT_U32(msg, NL80211_ATTR_USE_MFP, NL80211_MFP_NO); + else + return -EINVAL; + + return 0; + + nla_put_failure: + return -ENOSPC; +} + +static int disconnect(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + return 0; +} +TOPLEVEL(disconnect, NULL, + NL80211_CMD_DISCONNECT, 0, CIB_NETDEV, disconnect, + "Disconnect from the current network."); + +static int iw_connect(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + char **conn_argv, *dev = argv[0]; + static const __u32 cmds[] = { + NL80211_CMD_CONNECT, + }; + struct print_event_args printargs = { }; + int conn_argc, err; + bool wait = false; + int i; + + /* strip "wlan0 connect" */ + argc -= 2; + argv += 2; + + /* check -w */ + if (argc && strcmp(argv[0], "-w") == 0) { + wait = true; + argc--; + argv++; + } + + err = __prepare_listen_events(state); + if (err) + return err; + + conn_argc = 3 + argc; + conn_argv = calloc(conn_argc, sizeof(*conn_argv)); + if (!conn_argv) + return -ENOMEM; + + conn_argv[0] = dev; + conn_argv[1] = "connect"; + conn_argv[2] = "establish"; + for (i = 0; i < argc; i++) + conn_argv[i + 3] = argv[i]; + err = handle_cmd(state, id, conn_argc, conn_argv); + free(conn_argv); + if (err) + return err; + + if (!wait) + return 0; + + /* + * WARNING: DO NOT COPY THIS CODE INTO YOUR APPLICATION + * + * This code has a bug: + * + * It is possible for a connect result message from another + * connect attempt to be processed here first, because we + * start listening to the multicast group before starting + * our own connect request, which may succeed but we get a + * fail message from a previous attempt that raced with us, + * or similar. + * + * The only proper way to fix this would be to listen to events + * before sending the command, and for the kernel to send the + * connect request or a cookie along with the event, so that you + * can match up whether the connect _you_ requested was finished + * or aborted. + * + * Alas, the kernel doesn't do that (yet). + */ + + __do_listen_events(state, ARRAY_SIZE(cmds), cmds, &printargs); + return 0; +} +TOPLEVEL(connect, "[-w] [] [] [key 0:abcde d:1:6162636465] [mfp:req/opt/no]", + 0, 0, CIB_NETDEV, iw_connect, + "Join the network with the given SSID (and frequency, BSSID).\n" + "With -w, wait for the connect to finish or fail."); +HIDDEN(connect, establish, "", NL80211_CMD_CONNECT, 0, CIB_NETDEV, iw_conn); + +static int iw_auth(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + char *end; + unsigned char bssid[6]; + int freq; + bool need_key = false; + + if (argc < 4) + return 1; + + /* SSID */ + NLA_PUT(msg, NL80211_ATTR_SSID, strlen(argv[0]), argv[0]); + argv++; + argc--; + + /* bssid */ + if (mac_addr_a2n(bssid, argv[0]) == 0) { + NLA_PUT(msg, NL80211_ATTR_MAC, 6, bssid); + argv++; + argc--; + } else { + return 1; + } + + /* FIXME */ + if (strcmp(argv[0], "open") == 0) { + NLA_PUT_U32(msg, NL80211_ATTR_AUTH_TYPE, + NL80211_AUTHTYPE_OPEN_SYSTEM); + } else if (strcmp(argv[0], "shared") == 0) { + NLA_PUT_U32(msg, NL80211_ATTR_AUTH_TYPE, + NL80211_AUTHTYPE_SHARED_KEY); + need_key = true; + } else { + return 1; + } + argv++; + argc--; + + freq = strtoul(argv[0], &end, 10); + if (*end == '\0') { + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq); + argv++; + argc--; + } else { + return 1; + } + + if (!argc && need_key) + return 1; + if (argc && !need_key) + return 1; + if (!argc) + return 0; + + if (strcmp(*argv, "key") != 0 && strcmp(*argv, "keys") != 0) + return 1; + + argv++; + argc--; + + return parse_keys(msg, argv, argc); + nla_put_failure: + return -ENOSPC; +} + +TOPLEVEL(auth, " [key 0:abcde d:1:6162636465]", + NL80211_CMD_AUTHENTICATE, 0, CIB_NETDEV, iw_auth, + "Authenticate with the given network.\n"); diff --git a/cqm.c b/cqm.c new file mode 100644 index 0000000..093b744 --- /dev/null +++ b/cqm.c @@ -0,0 +1,57 @@ +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +static int iw_cqm_rssi(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nl_msg *cqm = NULL; + int thold = 0; + int hyst = 0; + int ret = -ENOSPC; + + /* get the required args */ + if (argc < 1 || argc > 2) + return 1; + + if (strcmp(argv[0], "off")) { + thold = atoi(argv[0]); + + if (thold == 0) + return -EINVAL; + + if (argc == 2) + hyst = atoi(argv[1]); + } + + /* connection quality monitor attributes */ + cqm = nlmsg_alloc(); + if (!cqm) + return -ENOMEM; + + NLA_PUT_U32(cqm, NL80211_ATTR_CQM_RSSI_THOLD, thold); + NLA_PUT_U32(cqm, NL80211_ATTR_CQM_RSSI_HYST, hyst); + + nla_put_nested(msg, NL80211_ATTR_CQM, cqm); + ret = 0; + + nla_put_failure: + nlmsg_free(cqm); + return ret; +} + +TOPLEVEL(cqm, "", + 0, 0, CIB_NETDEV, NULL, + "Configure the WLAN connection quality monitor.\n"); + +COMMAND(cqm, rssi, " []", + NL80211_CMD_SET_CQM, 0, CIB_NETDEV, iw_cqm_rssi, + "Set connection quality monitor RSSI threshold.\n"); diff --git a/event.c b/event.c new file mode 100644 index 0000000..c080f36 --- /dev/null +++ b/event.c @@ -0,0 +1,899 @@ +#include +#include +#include +#include +#include "iw.h" + +static int no_seq_check(struct nl_msg *msg, void *arg) +{ + return NL_OK; +} + +struct ieee80211_beacon_channel { + __u16 center_freq; + bool no_ir; + bool no_ibss; +}; + +static int parse_beacon_hint_chan(struct nlattr *tb, + struct ieee80211_beacon_channel *chan) +{ + struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; + static struct nla_policy beacon_freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = { + [NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 }, + [NL80211_FREQUENCY_ATTR_NO_IR] = { .type = NLA_FLAG }, + [__NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG }, + }; + + if (nla_parse_nested(tb_freq, + NL80211_FREQUENCY_ATTR_MAX, + tb, + beacon_freq_policy)) + return -EINVAL; + + chan->center_freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IR]) + chan->no_ir = true; + if (tb_freq[__NL80211_FREQUENCY_ATTR_NO_IBSS]) + chan->no_ibss = true; + + return 0; +} + +static void print_frame(struct print_event_args *args, struct nlattr *attr) +{ + uint8_t *frame; + size_t len; + unsigned int i; + char macbuf[6*3]; + uint16_t tmp; + + if (!attr) { + printf(" [no frame]"); + return; + } + + frame = nla_data(attr); + len = nla_len(attr); + + if (len < 26) { + printf(" [invalid frame: "); + goto print_frame; + } + + mac_addr_n2a(macbuf, frame + 10); + printf(" %s -> ", macbuf); + mac_addr_n2a(macbuf, frame + 4); + printf("%s", macbuf); + + switch (frame[0] & 0xfc) { + case 0x10: /* assoc resp */ + case 0x30: /* reassoc resp */ + /* status */ + tmp = (frame[27] << 8) + frame[26]; + printf(" status: %d: %s", tmp, get_status_str(tmp)); + break; + case 0x00: /* assoc req */ + case 0x20: /* reassoc req */ + break; + case 0xb0: /* auth */ + /* status */ + tmp = (frame[29] << 8) + frame[28]; + printf(" status: %d: %s", tmp, get_status_str(tmp)); + break; + case 0xa0: /* disassoc */ + case 0xc0: /* deauth */ + /* reason */ + tmp = (frame[25] << 8) + frame[24]; + printf(" reason %d: %s", tmp, get_reason_str(tmp)); + break; + } + + if (!args->frame) + return; + + printf(" [frame:"); + + print_frame: + for (i = 0; i < len; i++) + printf(" %.02x", frame[i]); + printf("]"); +} + +static void parse_cqm_event(struct nlattr **attrs) +{ + static struct nla_policy cqm_policy[NL80211_ATTR_CQM_MAX + 1] = { + [NL80211_ATTR_CQM_RSSI_THOLD] = { .type = NLA_U32 }, + [NL80211_ATTR_CQM_RSSI_HYST] = { .type = NLA_U32 }, + [NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT] = { .type = NLA_U32 }, + }; + struct nlattr *cqm[NL80211_ATTR_CQM_MAX + 1]; + struct nlattr *cqm_attr = attrs[NL80211_ATTR_CQM]; + + printf("CQM event: "); + + if (!cqm_attr || + nla_parse_nested(cqm, NL80211_ATTR_CQM_MAX, cqm_attr, cqm_policy)) { + printf("missing data!\n"); + return; + } + + if (cqm[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT]) { + enum nl80211_cqm_rssi_threshold_event rssi_event; + bool found_one = false; + + rssi_event = nla_get_u32(cqm[NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT]); + + switch (rssi_event) { + case NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH: + printf("RSSI went above threshold\n"); + found_one = true; + break; + case NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW: + printf("RSSI went below threshold\n"); + found_one = true; + break; + case NL80211_CQM_RSSI_BEACON_LOSS_EVENT: + printf("Beacon loss detected\n"); + found_one = true; + break; + } + + if (!found_one) + printf("Unknown event type: %i\n", rssi_event); + } else if (cqm[NL80211_ATTR_CQM_PKT_LOSS_EVENT]) { + if (attrs[NL80211_ATTR_MAC]) { + uint32_t frames; + char buf[3*6]; + + frames = nla_get_u32(cqm[NL80211_ATTR_CQM_PKT_LOSS_EVENT]); + mac_addr_n2a(buf, nla_data(attrs[NL80211_ATTR_MAC])); + printf("peer %s didn't ACK %d packets\n", buf, frames); + } else { + printf("PKT-LOSS-EVENT did not have MAC attribute!\n"); + } + } else if (cqm[NL80211_ATTR_CQM_BEACON_LOSS_EVENT]) { + printf("beacon loss\n"); + } else { + printf("unknown event\n"); + } +} + +static const char * key_type_str(enum nl80211_key_type key_type) +{ + static char buf[30]; + switch (key_type) { + case NL80211_KEYTYPE_GROUP: + return "Group"; + case NL80211_KEYTYPE_PAIRWISE: + return "Pairwise"; + case NL80211_KEYTYPE_PEERKEY: + return "PeerKey"; + default: + snprintf(buf, sizeof(buf), "unknown(%d)", key_type); + return buf; + } +} + +static void parse_mic_failure(struct nlattr **attrs) +{ + printf("Michael MIC failure event:"); + + if (attrs[NL80211_ATTR_MAC]) { + char addr[3 * ETH_ALEN]; + mac_addr_n2a(addr, nla_data(attrs[NL80211_ATTR_MAC])); + printf(" source MAC address %s", addr); + } + + if (attrs[NL80211_ATTR_KEY_SEQ] && + nla_len(attrs[NL80211_ATTR_KEY_SEQ]) == 6) { + unsigned char *seq = nla_data(attrs[NL80211_ATTR_KEY_SEQ]); + printf(" seq=%02x%02x%02x%02x%02x%02x", + seq[0], seq[1], seq[2], seq[3], seq[4], seq[5]); + } + if (attrs[NL80211_ATTR_KEY_TYPE]) { + enum nl80211_key_type key_type = + nla_get_u32(attrs[NL80211_ATTR_KEY_TYPE]); + printf(" Key Type %s", key_type_str(key_type)); + } + + if (attrs[NL80211_ATTR_KEY_IDX]) { + __u8 key_id = nla_get_u8(attrs[NL80211_ATTR_KEY_IDX]); + printf(" Key Id %d", key_id); + } + + printf("\n"); +} + +static void parse_wowlan_wake_event(struct nlattr **attrs) +{ + struct nlattr *tb[NUM_NL80211_WOWLAN_TRIG], + *tb_match[NUM_NL80211_ATTR]; + + printf("WoWLAN wakeup\n"); + if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS]) { + printf("\twakeup not due to WoWLAN\n"); + return; + } + + nla_parse(tb, MAX_NL80211_WOWLAN_TRIG, + nla_data(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), + nla_len(attrs[NL80211_ATTR_WOWLAN_TRIGGERS]), NULL); + + if (tb[NL80211_WOWLAN_TRIG_DISCONNECT]) + printf("\t* was disconnected\n"); + if (tb[NL80211_WOWLAN_TRIG_MAGIC_PKT]) + printf("\t* magic packet received\n"); + if (tb[NL80211_WOWLAN_TRIG_PKT_PATTERN]) + printf("\t* pattern index: %u\n", + nla_get_u32(tb[NL80211_WOWLAN_TRIG_PKT_PATTERN])); + if (tb[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) + printf("\t* GTK rekey failure\n"); + if (tb[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) + printf("\t* EAP identity request\n"); + if (tb[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) + printf("\t* 4-way handshake\n"); + if (tb[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) + printf("\t* RF-kill released\n"); + if (tb[NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS]) { + struct nlattr *match, *freq; + int rem_nst, rem_nst2; + + printf("\t* network detected\n"); + nla_for_each_nested(match, + tb[NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS], + rem_nst) { + nla_parse(tb_match, NUM_NL80211_ATTR, nla_data(match), + nla_len(match), + NULL); + printf("\t\tSSID: \""); + print_ssid_escaped(nla_len(tb_match[NL80211_ATTR_SSID]), + nla_data(tb_match[NL80211_ATTR_SSID])); + printf("\""); + if (tb_match[NL80211_ATTR_SCAN_FREQUENCIES]) { + printf(" freq(s):"); + nla_for_each_nested(freq, + tb_match[NL80211_ATTR_SCAN_FREQUENCIES], + rem_nst2) + printf(" %d", nla_get_u32(freq)); + } + printf("\n"); + } + } + if (tb[NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211]) { + uint8_t *d = nla_data(tb[NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211]); + int l = nla_len(tb[NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211]); + int i; + printf("\t* packet (might be truncated): "); + for (i = 0; i < l; i++) { + if (i > 0) + printf(":"); + printf("%.2x", d[i]); + } + printf("\n"); + } + if (tb[NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023]) { + uint8_t *d = nla_data(tb[NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023]); + int l = nla_len(tb[NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023]); + int i; + printf("\t* packet (might be truncated): "); + for (i = 0; i < l; i++) { + if (i > 0) + printf(":"); + printf("%.2x", d[i]); + } + printf("\n"); + } + if (tb[NL80211_WOWLAN_TRIG_WAKEUP_TCP_MATCH]) + printf("\t* TCP connection wakeup received\n"); + if (tb[NL80211_WOWLAN_TRIG_WAKEUP_TCP_CONNLOST]) + printf("\t* TCP connection lost\n"); + if (tb[NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS]) + printf("\t* TCP connection ran out of tokens\n"); +} + +static int parse_ftm_target(struct nlattr *attr, unsigned char *bssid) +{ + struct nlattr *tb[NL80211_FTM_TARGET_ATTR_MAX + 1]; + + if (nla_parse_nested(tb, NL80211_FTM_TARGET_ATTR_MAX, attr, NULL)) + return -1; + + if (!tb[NL80211_FTM_TARGET_ATTR_BSSID]) + return -1; + + memcpy(bssid, nla_data(tb[NL80211_FTM_TARGET_ATTR_BSSID]), ETH_ALEN); + + return 0; +} + +/* Speed of light in cm/picosec */ +#define SOL_CM_PSEC 0.02998 + +static void parse_ftm_results(struct nlattr **attrs, int status, + struct print_event_args *pargs) +{ + struct nlattr *tb[NL80211_FTM_RESP_ENTRY_ATTR_MAX + 1]; + unsigned char bssid[ETH_ALEN]; + char macbuf[6 * 3]; + long long int rtt, dist; + int err; + + printf("FTM result! Status: %d\n", status); + + if (!pargs) + return; + + pargs->continue_listening = 0; + + if (status != NL80211_MSRMENT_STATUS_SUCCESS && + status != NL80211_MSRMENT_STATUS_TIMEOUT) { + printf("Failed to measure, status: %d\n", status); + return; + } + + if (!attrs[NL80211_ATTR_MSRMENT_FTM_RESPONSE]) { + printf("Error parsing FTM response\n"); + return; + } + + err = nla_parse_nested(tb, NL80211_FTM_RESP_ENTRY_ATTR_MAX, + attrs[NL80211_ATTR_MSRMENT_FTM_RESPONSE], NULL); + + if (err || + !tb[NL80211_FTM_RESP_ENTRY_ATTR_STATUS] || + !tb[NL80211_FTM_RESP_ENTRY_ATTR_TARGET] || + !tb[NL80211_FTM_RESP_ENTRY_ATTR_RTT] || + parse_ftm_target(tb[NL80211_FTM_RESP_ENTRY_ATTR_TARGET] ,bssid)) { + printf("Error parsing FTM target\n"); + return; + } + + mac_addr_n2a(macbuf, bssid); + rtt = (long long int)nla_get_u64(tb[NL80211_FTM_RESP_ENTRY_ATTR_RTT]); + dist = tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE] ? + (long long int)nla_get_u64(tb[NL80211_FTM_RESP_ENTRY_ATTR_DISTANCE]) : + rtt * SOL_CM_PSEC; + + printf("Target: %s, status: %d, rtt: %lld psec, distance: %lld cm", + macbuf, nla_get_u8(tb[NL80211_FTM_RESP_ENTRY_ATTR_STATUS]), rtt, + dist); + + if (tb[NL80211_FTM_RESP_ENTRY_ATTR_LCI]) + iw_hexdump("LCI", + nla_data(tb[NL80211_FTM_RESP_ENTRY_ATTR_LCI]), + nla_len(tb[NL80211_FTM_RESP_ENTRY_ATTR_LCI])); + + if (tb[NL80211_FTM_RESP_ENTRY_ATTR_CIVIC]) + iw_hexdump("Civic", + nla_data(tb[NL80211_FTM_RESP_ENTRY_ATTR_CIVIC]), + nla_len(tb[NL80211_FTM_RESP_ENTRY_ATTR_CIVIC])); + + + printf("\n"); + + pargs->continue_listening = attrs[NL80211_ATTR_LAST_MSG] ? 0 : 1; +} + +static void parse_measurement_response(struct nlattr **tb, + struct print_event_args *pargs) +{ + int status; + + if (!tb[NL80211_ATTR_MSRMENT_TYPE] || + !tb[NL80211_ATTR_MSRMENT_STATUS]) { + printf("Measurements: failed to parse event!\n"); + return; + } + + status = nla_get_u8(tb[NL80211_ATTR_MSRMENT_STATUS]); + + switch(nla_get_u32(tb[NL80211_ATTR_MSRMENT_TYPE])) { + case NL80211_MSRMENT_TYPE_FTM: + parse_ftm_results(tb, status, pargs); + break; + default: + printf("Measurements: type %d is not supported!\n", + nla_get_u32(tb[NL80211_ATTR_MSRMENT_TYPE])); + } +} + +static int print_event(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[NL80211_ATTR_MAX + 1], *nst; + struct print_event_args *args = arg; + char ifname[100]; + char macbuf[6*3]; + __u8 reg_type; + struct ieee80211_beacon_channel chan_before_beacon, chan_after_beacon; + __u32 wiphy_idx = 0; + int rem_nst; + __u16 status; + + if (args->time || args->reltime) { + unsigned long long usecs, previous; + + previous = 1000000ULL * args->ts.tv_sec + args->ts.tv_usec; + gettimeofday(&args->ts, NULL); + usecs = 1000000ULL * args->ts.tv_sec + args->ts.tv_usec; + if (args->reltime) { + if (!args->have_ts) { + usecs = 0; + args->have_ts = true; + } else + usecs -= previous; + } + printf("%llu.%06llu: ", usecs/1000000, usecs % 1000000); + } + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (tb[NL80211_ATTR_IFINDEX] && tb[NL80211_ATTR_WIPHY]) { + if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), ifname); + printf("%s (phy #%d): ", ifname, nla_get_u32(tb[NL80211_ATTR_WIPHY])); + } else if (tb[NL80211_ATTR_WDEV] && tb[NL80211_ATTR_WIPHY]) { + printf("wdev 0x%llx (phy #%d): ", + (unsigned long long)nla_get_u64(tb[NL80211_ATTR_WDEV]), + nla_get_u32(tb[NL80211_ATTR_WIPHY])); + } else if (tb[NL80211_ATTR_IFINDEX]) { + if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), ifname); + printf("%s: ", ifname); + } else if (tb[NL80211_ATTR_WDEV]) { + printf("wdev 0x%llx: ", (unsigned long long)nla_get_u64(tb[NL80211_ATTR_WDEV])); + } else if (tb[NL80211_ATTR_WIPHY]) { + printf("phy #%d: ", nla_get_u32(tb[NL80211_ATTR_WIPHY])); + } + + switch (gnlh->cmd) { + case NL80211_CMD_NEW_WIPHY: + printf("renamed to %s\n", nla_get_string(tb[NL80211_ATTR_WIPHY_NAME])); + break; + case NL80211_CMD_TRIGGER_SCAN: + printf("scan started\n"); + break; + case NL80211_CMD_NEW_SCAN_RESULTS: + printf("scan finished:"); + case NL80211_CMD_SCAN_ABORTED: + if (gnlh->cmd == NL80211_CMD_SCAN_ABORTED) + printf("scan aborted:"); + if (tb[NL80211_ATTR_SCAN_FREQUENCIES]) { + nla_for_each_nested(nst, tb[NL80211_ATTR_SCAN_FREQUENCIES], rem_nst) + printf(" %d", nla_get_u32(nst)); + printf(","); + } + if (tb[NL80211_ATTR_SCAN_SSIDS]) { + nla_for_each_nested(nst, tb[NL80211_ATTR_SCAN_SSIDS], rem_nst) { + printf(" \""); + print_ssid_escaped(nla_len(nst), nla_data(nst)); + printf("\""); + } + } + printf("\n"); + break; + case NL80211_CMD_START_SCHED_SCAN: + printf("scheduled scan started\n"); + break; + case NL80211_CMD_SCHED_SCAN_STOPPED: + printf("sched scan stopped\n"); + break; + case NL80211_CMD_SCHED_SCAN_RESULTS: + printf("got scheduled scan results\n"); + break; + case NL80211_CMD_REG_CHANGE: + printf("regulatory domain change: "); + + reg_type = nla_get_u8(tb[NL80211_ATTR_REG_TYPE]); + + switch (reg_type) { + case NL80211_REGDOM_TYPE_COUNTRY: + printf("set to %s by %s request", + nla_get_string(tb[NL80211_ATTR_REG_ALPHA2]), + reg_initiator_to_string(nla_get_u8(tb[NL80211_ATTR_REG_INITIATOR]))); + if (tb[NL80211_ATTR_WIPHY]) + printf(" on phy%d", nla_get_u32(tb[NL80211_ATTR_WIPHY])); + break; + case NL80211_REGDOM_TYPE_WORLD: + printf("set to world roaming by %s request", + reg_initiator_to_string(nla_get_u8(tb[NL80211_ATTR_REG_INITIATOR]))); + break; + case NL80211_REGDOM_TYPE_CUSTOM_WORLD: + printf("custom world roaming rules in place on phy%d by %s request", + nla_get_u32(tb[NL80211_ATTR_WIPHY]), + reg_initiator_to_string(nla_get_u32(tb[NL80211_ATTR_REG_INITIATOR]))); + break; + case NL80211_REGDOM_TYPE_INTERSECTION: + printf("intersection used due to a request made by %s", + reg_initiator_to_string(nla_get_u32(tb[NL80211_ATTR_REG_INITIATOR]))); + if (tb[NL80211_ATTR_WIPHY]) + printf(" on phy%d", nla_get_u32(tb[NL80211_ATTR_WIPHY])); + break; + default: + printf("unknown source (upgrade this utility)"); + break; + } + + printf("\n"); + break; + case NL80211_CMD_REG_BEACON_HINT: + + wiphy_idx = nla_get_u32(tb[NL80211_ATTR_WIPHY]); + + memset(&chan_before_beacon, 0, sizeof(chan_before_beacon)); + memset(&chan_after_beacon, 0, sizeof(chan_after_beacon)); + + if (parse_beacon_hint_chan(tb[NL80211_ATTR_FREQ_BEFORE], + &chan_before_beacon)) + break; + if (parse_beacon_hint_chan(tb[NL80211_ATTR_FREQ_AFTER], + &chan_after_beacon)) + break; + + if (chan_before_beacon.center_freq != chan_after_beacon.center_freq) + break; + + /* A beacon hint is sent _only_ if something _did_ change */ + printf("beacon hint:\n"); + + printf("phy%d %d MHz [%d]:\n", + wiphy_idx, + chan_before_beacon.center_freq, + ieee80211_frequency_to_channel(chan_before_beacon.center_freq)); + + if (chan_before_beacon.no_ir && !chan_after_beacon.no_ir) { + if (chan_before_beacon.no_ibss && !chan_after_beacon.no_ibss) + printf("\to Initiating radiation enabled\n"); + else + printf("\to active scan enabled\n"); + } else if (chan_before_beacon.no_ibss && !chan_after_beacon.no_ibss) { + printf("\to ibss enabled\n"); + } + + break; + case NL80211_CMD_NEW_STATION: + mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); + printf("new station %s\n", macbuf); + break; + case NL80211_CMD_DEL_STATION: + mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); + printf("del station %s\n", macbuf); + break; + case NL80211_CMD_JOIN_IBSS: + mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); + printf("IBSS %s joined\n", macbuf); + break; + case NL80211_CMD_AUTHENTICATE: + printf("auth"); + if (tb[NL80211_ATTR_FRAME]) + print_frame(args, tb[NL80211_ATTR_FRAME]); + else if (tb[NL80211_ATTR_TIMED_OUT]) + printf(": timed out"); + else + printf(": unknown event"); + printf("\n"); + break; + case NL80211_CMD_ASSOCIATE: + printf("assoc"); + if (tb[NL80211_ATTR_FRAME]) + print_frame(args, tb[NL80211_ATTR_FRAME]); + else if (tb[NL80211_ATTR_TIMED_OUT]) + printf(": timed out"); + else + printf(": unknown event"); + printf("\n"); + break; + case NL80211_CMD_DEAUTHENTICATE: + printf("deauth"); + print_frame(args, tb[NL80211_ATTR_FRAME]); + printf("\n"); + break; + case NL80211_CMD_DISASSOCIATE: + printf("disassoc"); + print_frame(args, tb[NL80211_ATTR_FRAME]); + printf("\n"); + break; + case NL80211_CMD_UNPROT_DEAUTHENTICATE: + printf("unprotected deauth"); + print_frame(args, tb[NL80211_ATTR_FRAME]); + printf("\n"); + break; + case NL80211_CMD_UNPROT_DISASSOCIATE: + printf("unprotected disassoc"); + print_frame(args, tb[NL80211_ATTR_FRAME]); + printf("\n"); + break; + case NL80211_CMD_CONNECT: + status = 0; + if (tb[NL80211_ATTR_TIMED_OUT]) + printf("timed out"); + else if (!tb[NL80211_ATTR_STATUS_CODE]) + printf("unknown connect status"); + else if (nla_get_u16(tb[NL80211_ATTR_STATUS_CODE]) == 0) + printf("connected"); + else { + status = nla_get_u16(tb[NL80211_ATTR_STATUS_CODE]); + printf("failed to connect"); + } + if (tb[NL80211_ATTR_MAC]) { + mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); + printf(" to %s", macbuf); + } + if (status) + printf(", status: %d: %s", status, get_status_str(status)); + printf("\n"); + break; + case NL80211_CMD_ROAM: + printf("roamed"); + if (tb[NL80211_ATTR_MAC]) { + mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); + printf(" to %s", macbuf); + } + printf("\n"); + break; + case NL80211_CMD_DISCONNECT: + printf("disconnected"); + if (tb[NL80211_ATTR_DISCONNECTED_BY_AP]) + printf(" (by AP)"); + else + printf(" (local request)"); + if (tb[NL80211_ATTR_REASON_CODE]) + printf(" reason: %d: %s", nla_get_u16(tb[NL80211_ATTR_REASON_CODE]), + get_reason_str(nla_get_u16(tb[NL80211_ATTR_REASON_CODE]))); + printf("\n"); + break; + case NL80211_CMD_REMAIN_ON_CHANNEL: + printf("remain on freq %d (%dms, cookie %llx)\n", + nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]), + nla_get_u32(tb[NL80211_ATTR_DURATION]), + (unsigned long long)nla_get_u64(tb[NL80211_ATTR_COOKIE])); + break; + case NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL: + printf("done with remain on freq %d (cookie %llx)\n", + nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]), + (unsigned long long)nla_get_u64(tb[NL80211_ATTR_COOKIE])); + break; + case NL80211_CMD_NOTIFY_CQM: + parse_cqm_event(tb); + break; + case NL80211_CMD_MICHAEL_MIC_FAILURE: + parse_mic_failure(tb); + break; + case NL80211_CMD_FRAME_TX_STATUS: + printf("mgmt TX status (cookie %llx): %s\n", + (unsigned long long)nla_get_u64(tb[NL80211_ATTR_COOKIE]), + tb[NL80211_ATTR_ACK] ? "acked" : "no ack"); + break; + case NL80211_CMD_PMKSA_CANDIDATE: + printf("PMKSA candidate found\n"); + break; + case NL80211_CMD_SET_WOWLAN: + parse_wowlan_wake_event(tb); + break; + case NL80211_CMD_PROBE_CLIENT: + if (tb[NL80211_ATTR_MAC]) + mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); + else + strcpy(macbuf, "??"); + printf("probe client %s (cookie %llx): %s\n", + macbuf, + (unsigned long long)nla_get_u64(tb[NL80211_ATTR_COOKIE]), + tb[NL80211_ATTR_ACK] ? "acked" : "no ack"); + break; + case NL80211_CMD_VENDOR: + printf("vendor event %.6x:%d\n", + nla_get_u32(tb[NL80211_ATTR_VENDOR_ID]), + nla_get_u32(tb[NL80211_ATTR_VENDOR_SUBCMD])); + if (args->frame && tb[NL80211_ATTR_VENDOR_DATA]) + iw_hexdump("vendor event", + nla_data(tb[NL80211_ATTR_VENDOR_DATA]), + nla_len(tb[NL80211_ATTR_VENDOR_DATA])); + break; + case NL80211_CMD_RADAR_DETECT: { + enum nl80211_radar_event event_type; + uint32_t freq; + + if (!tb[NL80211_ATTR_RADAR_EVENT] || + !tb[NL80211_ATTR_WIPHY_FREQ]) { + printf("BAD radar event\n"); + break; + } + + freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]); + event_type = nla_get_u32(tb[NL80211_ATTR_RADAR_EVENT]); + + switch (event_type) { + case NL80211_RADAR_DETECTED: + printf("%d MHz: radar detected\n", freq); + break; + case NL80211_RADAR_CAC_FINISHED: + printf("%d MHz: CAC finished\n", freq); + break; + case NL80211_RADAR_CAC_ABORTED: + printf("%d MHz: CAC was aborted\n", freq); + break; + case NL80211_RADAR_NOP_FINISHED: + printf("%d MHz: NOP finished\n", freq); + break; + default: + printf("%d MHz: unknown radar event\n", freq); + } + } + break; + case NL80211_CMD_DEL_WIPHY: + printf("delete wiphy\n"); + break; + case NL80211_CMD_MSRMENT_RESPONSE: + parse_measurement_response(tb, args); + break; + default: + printf("unknown event %d (%s)\n", + gnlh->cmd, command_name(gnlh->cmd)); + break; + } + + fflush(stdout); + return NL_SKIP; +} + +struct wait_event { + int n_cmds; + const __u32 *cmds; + __u32 cmd; + struct print_event_args *pargs; +}; + +static int wait_event(struct nl_msg *msg, void *arg) +{ + struct wait_event *wait = arg; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + int i; + + for (i = 0; i < wait->n_cmds; i++) { + if (gnlh->cmd == wait->cmds[i]) { + wait->cmd = gnlh->cmd; + if (wait->pargs) + print_event(msg, wait->pargs); + } + } + + return NL_SKIP; +} + +int __prepare_listen_events(struct nl80211_state *state) +{ + int mcid, ret; + + /* Configuration multicast group */ + mcid = nl_get_multicast_id(state->nl_sock, "nl80211", "config"); + if (mcid < 0) + return mcid; + + ret = nl_socket_add_membership(state->nl_sock, mcid); + if (ret) + return ret; + + /* Scan multicast group */ + mcid = nl_get_multicast_id(state->nl_sock, "nl80211", "scan"); + if (mcid >= 0) { + ret = nl_socket_add_membership(state->nl_sock, mcid); + if (ret) + return ret; + } + + /* Regulatory multicast group */ + mcid = nl_get_multicast_id(state->nl_sock, "nl80211", "regulatory"); + if (mcid >= 0) { + ret = nl_socket_add_membership(state->nl_sock, mcid); + if (ret) + return ret; + } + + /* MLME multicast group */ + mcid = nl_get_multicast_id(state->nl_sock, "nl80211", "mlme"); + if (mcid >= 0) { + ret = nl_socket_add_membership(state->nl_sock, mcid); + if (ret) + return ret; + } + + mcid = nl_get_multicast_id(state->nl_sock, "nl80211", "vendor"); + if (mcid >= 0) { + ret = nl_socket_add_membership(state->nl_sock, mcid); + if (ret) + return ret; + } + + return 0; +} + +__u32 __do_listen_events(struct nl80211_state *state, + const int n_waits, const __u32 *waits, + struct print_event_args *args) +{ + struct nl_cb *cb = nl_cb_alloc(iw_debug ? NL_CB_DEBUG : NL_CB_DEFAULT); + struct wait_event wait_ev; + + if (!cb) { + fprintf(stderr, "failed to allocate netlink callbacks\n"); + return -ENOMEM; + } + + /* no sequence checking for multicast messages */ + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, NULL); + + if (n_waits && waits) { + wait_ev.cmds = waits; + wait_ev.n_cmds = n_waits; + wait_ev.pargs = args; + register_handler(wait_event, &wait_ev); + } else + register_handler(print_event, args); + + wait_ev.cmd = 0; + + while (!wait_ev.cmd || (args && args->continue_listening)) + nl_recvmsgs(state->nl_sock, cb); + + nl_cb_put(cb); + + return wait_ev.cmd; +} + +__u32 listen_events(struct nl80211_state *state, + const int n_waits, const __u32 *waits) +{ + int ret; + + ret = __prepare_listen_events(state); + if (ret) + return ret; + + return __do_listen_events(state, n_waits, waits, NULL); +} + +static int print_events(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + struct print_event_args args; + int ret; + + memset(&args, 0, sizeof(args)); + + argc--; + argv++; + + while (argc > 0) { + if (strcmp(argv[0], "-f") == 0) + args.frame = true; + else if (strcmp(argv[0], "-t") == 0) + args.time = true; + else if (strcmp(argv[0], "-r") == 0) + args.reltime = true; + else + return 1; + argc--; + argv++; + } + + if (args.time && args.reltime) + return 1; + + if (argc) + return 1; + + ret = __prepare_listen_events(state); + if (ret) + return ret; + + return __do_listen_events(state, 0, NULL, &args); +} +TOPLEVEL(event, "[-t|-r] [-f]", 0, 0, CIB_NONE, print_events, + "Monitor events from the kernel.\n" + "-t - print timestamp\n" + "-r - print relative timstamp\n" + "-f - print full frame for auth/assoc etc."); diff --git a/genl.c b/genl.c new file mode 100644 index 0000000..7dc27f7 --- /dev/null +++ b/genl.c @@ -0,0 +1,117 @@ +/* + * This ought to be provided by libnl + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iw.h" + +static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, + void *arg) +{ + int *ret = arg; + *ret = err->error; + return NL_STOP; +} + +static int ack_handler(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + *ret = 0; + return NL_STOP; +} + +struct handler_args { + const char *group; + int id; +}; + +static int family_handler(struct nl_msg *msg, void *arg) +{ + struct handler_args *grp = arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *mcgrp; + int rem_mcgrp; + + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return NL_SKIP; + + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; + + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, + nla_data(mcgrp), nla_len(mcgrp), NULL); + + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) + continue; + if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), + grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) + continue; + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); + break; + } + + return NL_SKIP; +} + +int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group) +{ + struct nl_msg *msg; + struct nl_cb *cb; + int ret, ctrlid; + struct handler_args grp = { + .group = group, + .id = -ENOENT, + }; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) { + ret = -ENOMEM; + goto out_fail_cb; + } + + ctrlid = genl_ctrl_resolve(sock, "nlctrl"); + + genlmsg_put(msg, 0, 0, ctrlid, 0, + 0, CTRL_CMD_GETFAMILY, 0); + + ret = -ENOBUFS; + NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family); + + ret = nl_send_auto_complete(sock, msg); + if (ret < 0) + goto out; + + ret = 1; + + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp); + + while (ret > 0) + nl_recvmsgs(sock, cb); + + if (ret == 0) + ret = grp.id; + nla_put_failure: + out: + nl_cb_put(cb); + out_fail_cb: + nlmsg_free(msg); + return ret; +} diff --git a/hwsim.c b/hwsim.c new file mode 100644 index 0000000..6f82207 --- /dev/null +++ b/hwsim.c @@ -0,0 +1,148 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +/* These enums need to be kept in sync with the kernel */ +enum hwsim_testmode_attr { + __HWSIM_TM_ATTR_INVALID = 0, + HWSIM_TM_ATTR_CMD = 1, + HWSIM_TM_ATTR_PS = 2, + + /* keep last */ + __HWSIM_TM_ATTR_AFTER_LAST, + HWSIM_TM_ATTR_MAX = __HWSIM_TM_ATTR_AFTER_LAST - 1 +}; + +enum hwsim_testmode_cmd { + HWSIM_TM_CMD_SET_PS = 0, + HWSIM_TM_CMD_GET_PS = 1, + HWSIM_TM_CMD_STOP_QUEUES = 2, + HWSIM_TM_CMD_WAKE_QUEUES = 3, +}; + + +SECTION(hwsim); + +static int print_hwsim_ps_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *attrs[NL80211_ATTR_MAX + 1]; + struct nlattr *tb[HWSIM_TM_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + + nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!attrs[NL80211_ATTR_TESTDATA]) + return NL_SKIP; + + nla_parse(tb, HWSIM_TM_ATTR_MAX, nla_data(attrs[NL80211_ATTR_TESTDATA]), + nla_len(attrs[NL80211_ATTR_TESTDATA]), NULL); + + printf("HWSIM PS: %d\n", nla_get_u32(tb[HWSIM_TM_ATTR_PS])); + + return NL_SKIP; +} + +static int handle_hwsim_getps(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nlattr *tmdata; + + tmdata = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!tmdata) + goto nla_put_failure; + + NLA_PUT_U32(msg, HWSIM_TM_ATTR_CMD, HWSIM_TM_CMD_GET_PS); + + nla_nest_end(msg, tmdata); + + register_handler(print_hwsim_ps_handler, NULL); + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(hwsim, getps, "", NL80211_CMD_TESTMODE, 0, CIB_PHY, handle_hwsim_getps, ""); + +static int handle_hwsim_setps(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nlattr *tmdata; + __u32 ps; + char *end; + + if (argc != 1) + return 1; + + ps = strtoul(argv[0], &end, 0); + if (*end) + return 1; + + tmdata = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!tmdata) + goto nla_put_failure; + + NLA_PUT_U32(msg, HWSIM_TM_ATTR_CMD, HWSIM_TM_CMD_SET_PS); + NLA_PUT_U32(msg, HWSIM_TM_ATTR_PS, ps); + + nla_nest_end(msg, tmdata); + + register_handler(print_hwsim_ps_handler, NULL); + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(hwsim, setps, "", NL80211_CMD_TESTMODE, 0, CIB_PHY, handle_hwsim_setps, ""); + +static int handle_hwsim_stop_queues(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nlattr *tmdata; + + if (argc != 0) + return 1; + + tmdata = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!tmdata) + goto nla_put_failure; + + NLA_PUT_U32(msg, HWSIM_TM_ATTR_CMD, HWSIM_TM_CMD_STOP_QUEUES); + + nla_nest_end(msg, tmdata); + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(hwsim, stopqueues, "", NL80211_CMD_TESTMODE, 0, CIB_PHY, handle_hwsim_stop_queues, ""); + +static int handle_hwsim_wake_queues(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nlattr *tmdata; + + if (argc != 0) + return 1; + + tmdata = nla_nest_start(msg, NL80211_ATTR_TESTDATA); + if (!tmdata) + goto nla_put_failure; + + NLA_PUT_U32(msg, HWSIM_TM_ATTR_CMD, HWSIM_TM_CMD_WAKE_QUEUES); + + nla_nest_end(msg, tmdata); + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(hwsim, wakequeues, "", NL80211_CMD_TESTMODE, 0, CIB_PHY, handle_hwsim_wake_queues, ""); diff --git a/ibss.c b/ibss.c new file mode 100644 index 0000000..84f1e95 --- /dev/null +++ b/ibss.c @@ -0,0 +1,143 @@ +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +SECTION(ibss); + +static int join_ibss(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + char *end; + struct chandef chandef; + unsigned char abssid[6]; + unsigned char rates[NL80211_MAX_SUPP_RATES]; + int n_rates = 0; + char *value = NULL, *sptr = NULL; + float rate; + int bintval; + int parsed, err; + + if (argc < 2) + return 1; + + /* SSID */ + NLA_PUT(msg, NL80211_ATTR_SSID, strlen(argv[0]), argv[0]); + argv++; + argc--; + + err = parse_freqchan(&chandef, false, argc, argv, &parsed); + if (err) + return err; + + argv += parsed; + argc -= parsed; + + put_chandef(msg, &chandef); + if (err) + return err; + + if (argc && strcmp(argv[0], "fixed-freq") == 0) { + NLA_PUT_FLAG(msg, NL80211_ATTR_FREQ_FIXED); + argv++; + argc--; + } + + if (argc) { + if (mac_addr_a2n(abssid, argv[0]) == 0) { + NLA_PUT(msg, NL80211_ATTR_MAC, 6, abssid); + argv++; + argc--; + } + } + + if (argc > 1 && strcmp(argv[0], "beacon-interval") == 0) { + argv++; + argc--; + bintval = strtoul(argv[0], &end, 10); + if (*end != '\0') + return 1; + NLA_PUT_U32(msg, NL80211_ATTR_BEACON_INTERVAL, bintval); + argv++; + argc--; + } + + /* basic rates */ + if (argc > 1 && strcmp(argv[0], "basic-rates") == 0) { + argv++; + argc--; + + value = strtok_r(argv[0], ",", &sptr); + + while (value && n_rates < NL80211_MAX_SUPP_RATES) { + rate = strtod(value, &end); + rates[n_rates] = rate * 2; + + /* filter out suspicious values */ + if (*end != '\0' || !rates[n_rates] || + rate*2 != rates[n_rates]) + return 1; + + n_rates++; + value = strtok_r(NULL, ",", &sptr); + } + + NLA_PUT(msg, NL80211_ATTR_BSS_BASIC_RATES, n_rates, rates); + + argv++; + argc--; + } + + /* multicast rate */ + if (argc > 1 && strcmp(argv[0], "mcast-rate") == 0) { + argv++; + argc--; + + rate = strtod(argv[0], &end); + if (*end != '\0') + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_MCAST_RATE, (int)(rate * 10)); + argv++; + argc--; + } + + if (!argc) + return 0; + + if (strcmp(*argv, "key") != 0 && strcmp(*argv, "keys") != 0) + return 1; + + argv++; + argc--; + + return parse_keys(msg, argv, argc); + nla_put_failure: + return -ENOSPC; +} + +static int leave_ibss(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + return 0; +} +COMMAND(ibss, leave, NULL, + NL80211_CMD_LEAVE_IBSS, 0, CIB_NETDEV, leave_ibss, + "Leave the current IBSS cell."); +COMMAND(ibss, join, + " [NOHT|HT20|HT40+|HT40-|5MHz|10MHz|80MHz] [fixed-freq] [] [beacon-interval ]" + " [basic-rates ] [mcast-rate ] " + "[key d:0:abcde]", + NL80211_CMD_JOIN_IBSS, 0, CIB_NETDEV, join_ibss, + "Join the IBSS cell with the given SSID, if it doesn't exist create\n" + "it on the given frequency. When fixed frequency is requested, don't\n" + "join/create a cell on a different frequency. When a fixed BSSID is\n" + "requested use that BSSID and do not adopt another cell's BSSID even\n" + "if it has higher TSF and the same SSID. If an IBSS is created, create\n" + "it with the specified basic-rates, multicast-rate and beacon-interval."); diff --git a/ieee80211.h b/ieee80211.h new file mode 100644 index 0000000..8745608 --- /dev/null +++ b/ieee80211.h @@ -0,0 +1,61 @@ +#ifndef __IEEE80211 +#define __IEEE80211 + +/* 802.11n HT capability AMPDU settings (for ampdu_params_info) */ +#define IEEE80211_HT_AMPDU_PARM_FACTOR 0x03 +#define IEEE80211_HT_AMPDU_PARM_DENSITY 0x1C + +#define IEEE80211_HT_CAP_SUP_WIDTH_20_40 0x0002 +#define IEEE80211_HT_CAP_SGI_40 0x0040 +#define IEEE80211_HT_CAP_MAX_AMSDU 0x0800 + +#define IEEE80211_HT_MCS_MASK_LEN 10 + +/** + * struct ieee80211_mcs_info - MCS information + * @rx_mask: RX mask + * @rx_highest: highest supported RX rate. If set represents + * the highest supported RX data rate in units of 1 Mbps. + * If this field is 0 this value should not be used to + * consider the highest RX data rate supported. + * @tx_params: TX parameters + */ +struct ieee80211_mcs_info { + __u8 rx_mask[IEEE80211_HT_MCS_MASK_LEN]; + __u16 rx_highest; + __u8 tx_params; + __u8 reserved[3]; +} __attribute__ ((packed)); + + +/** + * struct ieee80211_ht_cap - HT capabilities + * + * This structure is the "HT capabilities element" as + * described in 802.11n D5.0 7.3.2.57 + */ +struct ieee80211_ht_cap { + __u16 cap_info; + __u8 ampdu_params_info; + + /* 16 bytes MCS information */ + struct ieee80211_mcs_info mcs; + + __u16 extended_ht_cap_info; + __u32 tx_BF_cap_info; + __u8 antenna_selection_info; +} __attribute__ ((packed)); + +struct ieee80211_vht_mcs_info { + __u16 rx_vht_mcs; + __u16 rx_highest; + __u16 tx_vht_mcs; + __u16 tx_highest; +} __attribute__ ((packed)); + +struct ieee80211_vht_cap { + __u32 cap_info; + struct ieee80211_vht_mcs_info mcs; +} __attribute__ ((packed)); + +#endif /* __IEEE80211 */ diff --git a/info.c b/info.c new file mode 100644 index 0000000..1eba5c9 --- /dev/null +++ b/info.c @@ -0,0 +1,705 @@ +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +static void print_flag(const char *name, int *open) +{ + if (!*open) + printf(" ("); + else + printf(", "); + printf("%s", name); + *open = 1; +} + +static char *cipher_name(__u32 c) +{ + static char buf[20]; + + switch (c) { + case 0x000fac01: + return "WEP40 (00-0f-ac:1)"; + case 0x000fac05: + return "WEP104 (00-0f-ac:5)"; + case 0x000fac02: + return "TKIP (00-0f-ac:2)"; + case 0x000fac04: + return "CCMP-128 (00-0f-ac:4)"; + case 0x000fac06: + return "CMAC (00-0f-ac:6)"; + case 0x000fac08: + return "GCMP-128 (00-0f-ac:8)"; + case 0x000fac09: + return "GCMP-256 (00-0f-ac:9)"; + case 0x000fac0a: + return "CCMP-256 (00-0f-ac:10)"; + case 0x000fac0b: + return "GMAC-128 (00-0f-ac:11)"; + case 0x000fac0c: + return "GMAC-256 (00-0f-ac:12)"; + case 0x000fac0d: + return "CMAC-256 (00-0f-ac:13)"; + case 0x00147201: + return "WPI-SMS4 (00-14-72:1)"; + default: + sprintf(buf, "%.2x-%.2x-%.2x:%d", + c >> 24, (c >> 16) & 0xff, + (c >> 8) & 0xff, c & 0xff); + + return buf; + } +} + +static int ext_feature_isset(const unsigned char *ext_features, int ext_features_len, + enum nl80211_ext_feature_index ftidx) +{ + unsigned char ft_byte; + + if ((int) ftidx / 8 >= ext_features_len) + return 0; + + ft_byte = ext_features[ftidx / 8]; + return (ft_byte & BIT(ftidx % 8)) != 0; +} + +static int print_phy_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + + struct nlattr *tb_band[NL80211_BAND_ATTR_MAX + 1]; + + struct nlattr *tb_freq[NL80211_FREQUENCY_ATTR_MAX + 1]; + static struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = { + [NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 }, + [NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_NO_IR] = { .type = NLA_FLAG }, + [__NL80211_FREQUENCY_ATTR_NO_IBSS] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_RADAR] = { .type = NLA_FLAG }, + [NL80211_FREQUENCY_ATTR_MAX_TX_POWER] = { .type = NLA_U32 }, + }; + + struct nlattr *tb_rate[NL80211_BITRATE_ATTR_MAX + 1]; + static struct nla_policy rate_policy[NL80211_BITRATE_ATTR_MAX + 1] = { + [NL80211_BITRATE_ATTR_RATE] = { .type = NLA_U32 }, + [NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE] = { .type = NLA_FLAG }, + }; + + struct nlattr *nl_band; + struct nlattr *nl_freq; + struct nlattr *nl_rate; + struct nlattr *nl_mode; + struct nlattr *nl_cmd; + struct nlattr *nl_if, *nl_ftype; + int rem_band, rem_freq, rem_rate, rem_mode, rem_cmd, rem_ftype, rem_if; + int open; + /* + * static variables only work here, other applications need to use the + * callback pointer and store them there so they can be multithreaded + * and/or have multiple netlink sockets, etc. + */ + static int64_t phy_id = -1; + static int last_band = -1; + static bool band_had_freq = false; + bool print_name = true; + + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (tb_msg[NL80211_ATTR_WIPHY]) { + if (nla_get_u32(tb_msg[NL80211_ATTR_WIPHY]) == phy_id) + print_name = false; + else + last_band = -1; + phy_id = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY]); + } + if (print_name && tb_msg[NL80211_ATTR_WIPHY_NAME]) + printf("Wiphy %s\n", nla_get_string(tb_msg[NL80211_ATTR_WIPHY_NAME])); + + /* needed for split dump */ + if (tb_msg[NL80211_ATTR_WIPHY_BANDS]) { + nla_for_each_nested(nl_band, tb_msg[NL80211_ATTR_WIPHY_BANDS], rem_band) { + if (last_band != nl_band->nla_type) { + printf("\tBand %d:\n", nl_band->nla_type + 1); + band_had_freq = false; + } + last_band = nl_band->nla_type; + + nla_parse(tb_band, NL80211_BAND_ATTR_MAX, nla_data(nl_band), + nla_len(nl_band), NULL); + + if (tb_band[NL80211_BAND_ATTR_HT_CAPA]) { + __u16 cap = nla_get_u16(tb_band[NL80211_BAND_ATTR_HT_CAPA]); + print_ht_capability(cap); + } + if (tb_band[NL80211_BAND_ATTR_HT_AMPDU_FACTOR]) { + __u8 exponent = nla_get_u8(tb_band[NL80211_BAND_ATTR_HT_AMPDU_FACTOR]); + print_ampdu_length(exponent); + } + if (tb_band[NL80211_BAND_ATTR_HT_AMPDU_DENSITY]) { + __u8 spacing = nla_get_u8(tb_band[NL80211_BAND_ATTR_HT_AMPDU_DENSITY]); + print_ampdu_spacing(spacing); + } + if (tb_band[NL80211_BAND_ATTR_HT_MCS_SET] && + nla_len(tb_band[NL80211_BAND_ATTR_HT_MCS_SET]) == 16) + print_ht_mcs(nla_data(tb_band[NL80211_BAND_ATTR_HT_MCS_SET])); + if (tb_band[NL80211_BAND_ATTR_VHT_CAPA] && + tb_band[NL80211_BAND_ATTR_VHT_MCS_SET]) + print_vht_info(nla_get_u32(tb_band[NL80211_BAND_ATTR_VHT_CAPA]), + nla_data(tb_band[NL80211_BAND_ATTR_VHT_MCS_SET])); + + if (tb_band[NL80211_BAND_ATTR_FREQS]) { + if (!band_had_freq) { + printf("\t\tFrequencies:\n"); + band_had_freq = true; + } + nla_for_each_nested(nl_freq, tb_band[NL80211_BAND_ATTR_FREQS], rem_freq) { + uint32_t freq; + nla_parse(tb_freq, NL80211_FREQUENCY_ATTR_MAX, nla_data(nl_freq), + nla_len(nl_freq), freq_policy); + if (!tb_freq[NL80211_FREQUENCY_ATTR_FREQ]) + continue; + freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); + printf("\t\t\t* %d MHz [%d]", freq, ieee80211_frequency_to_channel(freq)); + + if (tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER] && + !tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) + printf(" (%.1f dBm)", 0.01 * nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_MAX_TX_POWER])); + + open = 0; + if (tb_freq[NL80211_FREQUENCY_ATTR_DISABLED]) { + print_flag("disabled", &open); + goto next; + } + + /* If both flags are set assume an new kernel */ + if (tb_freq[NL80211_FREQUENCY_ATTR_NO_IR] && tb_freq[__NL80211_FREQUENCY_ATTR_NO_IBSS]) { + print_flag("no IR", &open); + } else if (tb_freq[NL80211_FREQUENCY_ATTR_PASSIVE_SCAN]) { + print_flag("passive scan", &open); + } else if (tb_freq[__NL80211_FREQUENCY_ATTR_NO_IBSS]){ + print_flag("no ibss", &open); + } + + if (tb_freq[NL80211_FREQUENCY_ATTR_RADAR]) + print_flag("radar detection", &open); +next: + if (open) + printf(")"); + printf("\n"); + } + } + + if (tb_band[NL80211_BAND_ATTR_RATES]) { + printf("\t\tBitrates (non-HT):\n"); + nla_for_each_nested(nl_rate, tb_band[NL80211_BAND_ATTR_RATES], rem_rate) { + nla_parse(tb_rate, NL80211_BITRATE_ATTR_MAX, nla_data(nl_rate), + nla_len(nl_rate), rate_policy); + if (!tb_rate[NL80211_BITRATE_ATTR_RATE]) + continue; + printf("\t\t\t* %2.1f Mbps", 0.1 * nla_get_u32(tb_rate[NL80211_BITRATE_ATTR_RATE])); + open = 0; + if (tb_rate[NL80211_BITRATE_ATTR_2GHZ_SHORTPREAMBLE]) + print_flag("short preamble supported", &open); + if (open) + printf(")"); + printf("\n"); + } + } + } + } + + if (tb_msg[NL80211_ATTR_MAX_NUM_SCAN_SSIDS]) + printf("\tmax # scan SSIDs: %d\n", + nla_get_u8(tb_msg[NL80211_ATTR_MAX_NUM_SCAN_SSIDS])); + if (tb_msg[NL80211_ATTR_MAX_SCAN_IE_LEN]) + printf("\tmax scan IEs length: %d bytes\n", + nla_get_u16(tb_msg[NL80211_ATTR_MAX_SCAN_IE_LEN])); + if (tb_msg[NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS]) + printf("\tmax # sched scan SSIDs: %d\n", + nla_get_u8(tb_msg[NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS])); + if (tb_msg[NL80211_ATTR_MAX_MATCH_SETS]) + printf("\tmax # match sets: %d\n", + nla_get_u8(tb_msg[NL80211_ATTR_MAX_MATCH_SETS])); + if (tb_msg[NL80211_ATTR_MAX_NUM_SCHED_SCAN_PLANS]) + printf("\tmax # scan plans: %d\n", + nla_get_u32(tb_msg[NL80211_ATTR_MAX_NUM_SCHED_SCAN_PLANS])); + if (tb_msg[NL80211_ATTR_MAX_SCAN_PLAN_INTERVAL]) + printf("\tmax scan plan interval: %d\n", + nla_get_u32(tb_msg[NL80211_ATTR_MAX_SCAN_PLAN_INTERVAL])); + if (tb_msg[NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS]) + printf("\tmax scan plan iterations: %d\n", + nla_get_u32(tb_msg[NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS])); + + if (tb_msg[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]) { + unsigned int frag; + + frag = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]); + if (frag != (unsigned int)-1) + printf("\tFragmentation threshold: %d\n", frag); + } + + if (tb_msg[NL80211_ATTR_WIPHY_RTS_THRESHOLD]) { + unsigned int rts; + + rts = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_RTS_THRESHOLD]); + if (rts != (unsigned int)-1) + printf("\tRTS threshold: %d\n", rts); + } + + if (tb_msg[NL80211_ATTR_WIPHY_RETRY_SHORT] || + tb_msg[NL80211_ATTR_WIPHY_RETRY_LONG]) { + unsigned char retry_short = 0, retry_long = 0; + + if (tb_msg[NL80211_ATTR_WIPHY_RETRY_SHORT]) + retry_short = nla_get_u8(tb_msg[NL80211_ATTR_WIPHY_RETRY_SHORT]); + if (tb_msg[NL80211_ATTR_WIPHY_RETRY_LONG]) + retry_long = nla_get_u8(tb_msg[NL80211_ATTR_WIPHY_RETRY_LONG]); + if (retry_short == retry_long) { + printf("\tRetry short long limit: %d\n", retry_short); + } else { + printf("\tRetry short limit: %d\n", retry_short); + printf("\tRetry long limit: %d\n", retry_long); + } + } + + if (tb_msg[NL80211_ATTR_WIPHY_COVERAGE_CLASS]) { + unsigned char coverage; + + coverage = nla_get_u8(tb_msg[NL80211_ATTR_WIPHY_COVERAGE_CLASS]); + /* See handle_distance() for an explanation where the '450' comes from */ + printf("\tCoverage class: %d (up to %dm)\n", coverage, 450 * coverage); + } + + if (tb_msg[NL80211_ATTR_CIPHER_SUITES]) { + int num = nla_len(tb_msg[NL80211_ATTR_CIPHER_SUITES]) / sizeof(__u32); + int i; + __u32 *ciphers = nla_data(tb_msg[NL80211_ATTR_CIPHER_SUITES]); + if (num > 0) { + printf("\tSupported Ciphers:\n"); + for (i = 0; i < num; i++) + printf("\t\t* %s\n", + cipher_name(ciphers[i])); + } + } + + if (tb_msg[NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX] && + tb_msg[NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX]) + printf("\tAvailable Antennas: TX %#x RX %#x\n", + nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX]), + nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX])); + + if (tb_msg[NL80211_ATTR_WIPHY_ANTENNA_TX] && + tb_msg[NL80211_ATTR_WIPHY_ANTENNA_RX]) + printf("\tConfigured Antennas: TX %#x RX %#x\n", + nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_ANTENNA_TX]), + nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_ANTENNA_RX])); + + if (tb_msg[NL80211_ATTR_SUPPORTED_IFTYPES]) { + printf("\tSupported interface modes:\n"); + nla_for_each_nested(nl_mode, tb_msg[NL80211_ATTR_SUPPORTED_IFTYPES], rem_mode) + printf("\t\t * %s\n", iftype_name(nla_type(nl_mode))); + } + + if (tb_msg[NL80211_ATTR_SOFTWARE_IFTYPES]) { + printf("\tsoftware interface modes (can always be added):\n"); + nla_for_each_nested(nl_mode, tb_msg[NL80211_ATTR_SOFTWARE_IFTYPES], rem_mode) + printf("\t\t * %s\n", iftype_name(nla_type(nl_mode))); + } + + if (tb_msg[NL80211_ATTR_INTERFACE_COMBINATIONS]) { + struct nlattr *nl_combi; + int rem_combi; + bool have_combinations = false; + + nla_for_each_nested(nl_combi, tb_msg[NL80211_ATTR_INTERFACE_COMBINATIONS], rem_combi) { + static struct nla_policy iface_combination_policy[NUM_NL80211_IFACE_COMB] = { + [NL80211_IFACE_COMB_LIMITS] = { .type = NLA_NESTED }, + [NL80211_IFACE_COMB_MAXNUM] = { .type = NLA_U32 }, + [NL80211_IFACE_COMB_STA_AP_BI_MATCH] = { .type = NLA_FLAG }, + [NL80211_IFACE_COMB_NUM_CHANNELS] = { .type = NLA_U32 }, + [NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS] = { .type = NLA_U32 }, + }; + struct nlattr *tb_comb[NUM_NL80211_IFACE_COMB]; + static struct nla_policy iface_limit_policy[NUM_NL80211_IFACE_LIMIT] = { + [NL80211_IFACE_LIMIT_TYPES] = { .type = NLA_NESTED }, + [NL80211_IFACE_LIMIT_MAX] = { .type = NLA_U32 }, + }; + struct nlattr *tb_limit[NUM_NL80211_IFACE_LIMIT]; + struct nlattr *nl_limit; + int err, rem_limit; + bool comma = false; + + if (!have_combinations) { + printf("\tvalid interface combinations:\n"); + have_combinations = true; + } + + printf("\t\t * "); + + err = nla_parse_nested(tb_comb, MAX_NL80211_IFACE_COMB, + nl_combi, iface_combination_policy); + if (err || !tb_comb[NL80211_IFACE_COMB_LIMITS] || + !tb_comb[NL80211_IFACE_COMB_MAXNUM] || + !tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]) { + printf(" \n"); + goto broken_combination; + } + + nla_for_each_nested(nl_limit, tb_comb[NL80211_IFACE_COMB_LIMITS], rem_limit) { + bool ift_comma = false; + + err = nla_parse_nested(tb_limit, MAX_NL80211_IFACE_LIMIT, + nl_limit, iface_limit_policy); + if (err || !tb_limit[NL80211_IFACE_LIMIT_TYPES]) { + printf("\n"); + goto broken_combination; + } + + if (comma) + printf(", "); + comma = true; + printf("#{"); + + nla_for_each_nested(nl_mode, tb_limit[NL80211_IFACE_LIMIT_TYPES], rem_mode) { + printf("%s %s", ift_comma ? "," : "", + iftype_name(nla_type(nl_mode))); + ift_comma = true; + } + printf(" } <= %u", nla_get_u32(tb_limit[NL80211_IFACE_LIMIT_MAX])); + } + printf(",\n\t\t "); + + printf("total <= %d, #channels <= %d%s", + nla_get_u32(tb_comb[NL80211_IFACE_COMB_MAXNUM]), + nla_get_u32(tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]), + tb_comb[NL80211_IFACE_COMB_STA_AP_BI_MATCH] ? + ", STA/AP BI must match" : ""); + if (tb_comb[NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS]) { + unsigned long widths = nla_get_u32(tb_comb[NL80211_IFACE_COMB_RADAR_DETECT_WIDTHS]); + + if (widths) { + int width; + bool first = true; + + printf(", radar detect widths: {"); + for (width = 0; width < 32; width++) + if (widths & (1 << width)) { + printf("%s %s", + first ? "":",", + channel_width_name(width)); + first = false; + } + printf(" }\n"); + } + } + printf("\n"); +broken_combination: + ; + } + + if (!have_combinations) + printf("\tinterface combinations are not supported\n"); + } + + if (tb_msg[NL80211_ATTR_SUPPORTED_COMMANDS]) { + printf("\tSupported commands:\n"); + nla_for_each_nested(nl_cmd, tb_msg[NL80211_ATTR_SUPPORTED_COMMANDS], rem_cmd) + printf("\t\t * %s\n", command_name(nla_get_u32(nl_cmd))); + } + + if (tb_msg[NL80211_ATTR_TX_FRAME_TYPES]) { + printf("\tSupported TX frame types:\n"); + nla_for_each_nested(nl_if, tb_msg[NL80211_ATTR_TX_FRAME_TYPES], rem_if) { + bool printed = false; + nla_for_each_nested(nl_ftype, nl_if, rem_ftype) { + if (!printed) + printf("\t\t * %s:", iftype_name(nla_type(nl_if))); + printed = true; + printf(" 0x%.2x", nla_get_u16(nl_ftype)); + } + if (printed) + printf("\n"); + } + } + + if (tb_msg[NL80211_ATTR_RX_FRAME_TYPES]) { + printf("\tSupported RX frame types:\n"); + nla_for_each_nested(nl_if, tb_msg[NL80211_ATTR_RX_FRAME_TYPES], rem_if) { + bool printed = false; + nla_for_each_nested(nl_ftype, nl_if, rem_ftype) { + if (!printed) + printf("\t\t * %s:", iftype_name(nla_type(nl_if))); + printed = true; + printf(" 0x%.2x", nla_get_u16(nl_ftype)); + } + if (printed) + printf("\n"); + } + } + + if (tb_msg[NL80211_ATTR_SUPPORT_IBSS_RSN]) + printf("\tDevice supports RSN-IBSS.\n"); + + if (tb_msg[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]) { + struct nlattr *tb_wowlan[NUM_NL80211_WOWLAN_TRIG]; + static struct nla_policy wowlan_policy[NUM_NL80211_WOWLAN_TRIG] = { + [NL80211_WOWLAN_TRIG_ANY] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_DISCONNECT] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_MAGIC_PKT] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_PKT_PATTERN] = { .minlen = 12 }, + [NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_RFKILL_RELEASE] = { .type = NLA_FLAG }, + [NL80211_WOWLAN_TRIG_NET_DETECT] = { .type = NLA_U32 }, + [NL80211_WOWLAN_TRIG_TCP_CONNECTION] = { .type = NLA_NESTED }, + }; + struct nl80211_pattern_support *pat; + int err; + + err = nla_parse_nested(tb_wowlan, MAX_NL80211_WOWLAN_TRIG, + tb_msg[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED], + wowlan_policy); + printf("\tWoWLAN support:"); + if (err) { + printf(" \n"); + } else { + printf("\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_ANY]) + printf("\t\t * wake up on anything (device continues operating normally)\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_DISCONNECT]) + printf("\t\t * wake up on disconnect\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_MAGIC_PKT]) + printf("\t\t * wake up on magic packet\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_PKT_PATTERN]) { + unsigned int len = nla_len(tb_wowlan[NL80211_WOWLAN_TRIG_PKT_PATTERN]); + + pat = nla_data(tb_wowlan[NL80211_WOWLAN_TRIG_PKT_PATTERN]); + printf("\t\t * wake up on pattern match, up to %u patterns of %u-%u bytes,\n" + "\t\t maximum packet offset %u bytes\n", + pat->max_patterns, pat->min_pattern_len, pat->max_pattern_len, + len < sizeof(*pat) ? 0 : pat->max_pkt_offset); + } + if (tb_wowlan[NL80211_WOWLAN_TRIG_GTK_REKEY_SUPPORTED]) + printf("\t\t * can do GTK rekeying\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) + printf("\t\t * wake up on GTK rekey failure\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) + printf("\t\t * wake up on EAP identity request\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) + printf("\t\t * wake up on 4-way handshake\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) + printf("\t\t * wake up on rfkill release\n"); + if (tb_wowlan[NL80211_WOWLAN_TRIG_NET_DETECT]) + printf("\t\t * wake up on network detection, up to %d match sets\n", + nla_get_u32(tb_wowlan[NL80211_WOWLAN_TRIG_NET_DETECT])); + if (tb_wowlan[NL80211_WOWLAN_TRIG_TCP_CONNECTION]) + printf("\t\t * wake up on TCP connection\n"); + } + } + + if (tb_msg[NL80211_ATTR_ROAM_SUPPORT]) + printf("\tDevice supports roaming.\n"); + + if (tb_msg[NL80211_ATTR_SUPPORT_AP_UAPSD]) + printf("\tDevice supports AP-side u-APSD.\n"); + + if (tb_msg[NL80211_ATTR_HT_CAPABILITY_MASK]) { + struct ieee80211_ht_cap *cm; + unsigned int len = nla_len(tb_msg[NL80211_ATTR_HT_CAPABILITY_MASK]); + + printf("\tHT Capability overrides:\n"); + if (len >= sizeof(*cm)) { + cm = nla_data(tb_msg[NL80211_ATTR_HT_CAPABILITY_MASK]); + printf("\t\t * MCS: %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx" + " %02hhx %02hhx %02hhx %02hhx\n", + cm->mcs.rx_mask[0], cm->mcs.rx_mask[1], + cm->mcs.rx_mask[2], cm->mcs.rx_mask[3], + cm->mcs.rx_mask[4], cm->mcs.rx_mask[5], + cm->mcs.rx_mask[6], cm->mcs.rx_mask[7], + cm->mcs.rx_mask[8], cm->mcs.rx_mask[9]); + if (cm->cap_info & htole16(IEEE80211_HT_CAP_MAX_AMSDU)) + printf("\t\t * maximum A-MSDU length\n"); + if (cm->cap_info & htole16(IEEE80211_HT_CAP_SUP_WIDTH_20_40)) + printf("\t\t * supported channel width\n"); + if (cm->cap_info & htole16(IEEE80211_HT_CAP_SGI_40)) + printf("\t\t * short GI for 40 MHz\n"); + if (cm->ampdu_params_info & IEEE80211_HT_AMPDU_PARM_FACTOR) + printf("\t\t * max A-MPDU length exponent\n"); + if (cm->ampdu_params_info & IEEE80211_HT_AMPDU_PARM_DENSITY) + printf("\t\t * min MPDU start spacing\n"); + } else { + printf("\tERROR: capabilities mask is too short, expected: %d, received: %d\n", + (int)(sizeof(*cm)), + (int)(nla_len(tb_msg[NL80211_ATTR_HT_CAPABILITY_MASK]))); + } + } + + if (tb_msg[NL80211_ATTR_FEATURE_FLAGS]) { + unsigned int features = nla_get_u32(tb_msg[NL80211_ATTR_FEATURE_FLAGS]); + + if (features & NL80211_FEATURE_SK_TX_STATUS) + printf("\tDevice supports TX status socket option.\n"); + if (features & NL80211_FEATURE_HT_IBSS) + printf("\tDevice supports HT-IBSS.\n"); + if (features & NL80211_FEATURE_INACTIVITY_TIMER) + printf("\tDevice has client inactivity timer.\n"); + if (features & NL80211_FEATURE_CELL_BASE_REG_HINTS) + printf("\tDevice accepts cell base station regulatory hints.\n"); + if (features & NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL) + printf("\tP2P Device uses a channel (of the concurrent ones)\n"); + if (features & NL80211_FEATURE_SAE) + printf("\tDevice supports SAE with AUTHENTICATE command\n"); + if (features & NL80211_FEATURE_LOW_PRIORITY_SCAN) + printf("\tDevice supports low priority scan.\n"); + if (features & NL80211_FEATURE_SCAN_FLUSH) + printf("\tDevice supports scan flush.\n"); + if (features & NL80211_FEATURE_AP_SCAN) + printf("\tDevice supports AP scan.\n"); + if (features & NL80211_FEATURE_VIF_TXPOWER) + printf("\tDevice supports per-vif TX power setting\n"); + if (features & NL80211_FEATURE_NEED_OBSS_SCAN) + printf("\tUserspace should do OBSS scan and generate 20/40 coex reports\n"); + if (features & NL80211_FEATURE_P2P_GO_CTWIN) + printf("\tP2P GO supports CT window setting\n"); + if (features & NL80211_FEATURE_P2P_GO_OPPPS) + printf("\tP2P GO supports opportunistic powersave setting\n"); + if (features & NL80211_FEATURE_FULL_AP_CLIENT_STATE) + printf("\tDriver supports full state transitions for AP/GO clients\n"); + if (features & NL80211_FEATURE_USERSPACE_MPM) + printf("\tDriver supports a userspace MPM\n"); + if (features & NL80211_FEATURE_ACTIVE_MONITOR) + printf("\tDevice supports active monitor (which will ACK incoming frames)\n"); + if (features & NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE) + printf("\tDriver/device bandwidth changes during BSS lifetime (AP/GO mode)\n"); + if (features & NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES) + printf("\tDevice adds DS IE to probe requests\n"); + if (features & NL80211_FEATURE_WFA_TPC_IE_IN_PROBES) + printf("\tDevice adds WFA TPC Report IE to probe requests\n"); + if (features & NL80211_FEATURE_QUIET) + printf("\tDevice supports quiet requests from AP\n"); + if (features & NL80211_FEATURE_TX_POWER_INSERTION) + printf("\tDevice can update TPC Report IE\n"); + if (features & NL80211_FEATURE_ACKTO_ESTIMATION) + printf("\tDevice supports ACK timeout estimation.\n"); + if (features & NL80211_FEATURE_STATIC_SMPS) + printf("\tDevice supports static SMPS\n"); + if (features & NL80211_FEATURE_DYNAMIC_SMPS) + printf("\tDevice supports dynamic SMPS\n"); + if (features & NL80211_FEATURE_SUPPORTS_WMM_ADMISSION) + printf("\tDevice supports WMM-AC admission (TSPECs)\n"); + if (features & NL80211_FEATURE_MAC_ON_CREATE) + printf("\tDevice supports configuring vdev MAC-addr on create.\n"); + if (features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH) + printf("\tDevice supports TDLS channel switching\n"); + } + + if (tb_msg[NL80211_ATTR_EXT_FEATURES]) { + struct nlattr *tb = tb_msg[NL80211_ATTR_EXT_FEATURES]; + + if (ext_feature_isset(nla_data(tb), nla_len(tb), + NL80211_EXT_FEATURE_VHT_IBSS)) + printf("\tDevice supports VHT-IBSS.\n"); + } + + if (tb_msg[NL80211_ATTR_TDLS_SUPPORT]) + printf("\tDevice supports T-DLS.\n"); + + if (tb_msg[NL80211_ATTR_COALESCE_RULE]) { + struct nl80211_coalesce_rule_support *rule; + struct nl80211_pattern_support *pat; + + printf("\tCoalesce support:\n"); + rule = nla_data(tb_msg[NL80211_ATTR_COALESCE_RULE]); + pat = &rule->pat; + printf("\t\t * Maximum %u coalesce rules supported\n" + "\t\t * Each rule contains upto %u patterns of %u-%u bytes,\n" + "\t\t maximum packet offset %u bytes\n" + "\t\t * Maximum supported coalescing delay %u msecs\n", + rule->max_rules, pat->max_patterns, pat->min_pattern_len, + pat->max_pattern_len, pat->max_pkt_offset, rule->max_delay); + } + + return NL_SKIP; +} + +static bool nl80211_has_split_wiphy = false; + +static int handle_info(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + char *feat_args[] = { "features", "-q" }; + int err; + + err = handle_cmd(state, CIB_NONE, 2, feat_args); + if (!err && nl80211_has_split_wiphy) { + nla_put_flag(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP); + nlmsg_hdr(msg)->nlmsg_flags |= NLM_F_DUMP; + } + + register_handler(print_phy_handler, NULL); + + return 0; +} +__COMMAND(NULL, info, "info", NULL, NL80211_CMD_GET_WIPHY, 0, 0, CIB_PHY, handle_info, + "Show capabilities for the specified wireless device.", NULL); +TOPLEVEL(list, NULL, NL80211_CMD_GET_WIPHY, NLM_F_DUMP, CIB_NONE, handle_info, + "List all wireless devices and their capabilities."); +TOPLEVEL(phy, NULL, NL80211_CMD_GET_WIPHY, NLM_F_DUMP, CIB_NONE, handle_info, NULL); + +static int handle_commands(struct nl80211_state *state, struct nl_msg *msg, + int argc, char **argv, enum id_input id) +{ + int i; + for (i = 1; i <= NL80211_CMD_MAX; i++) + printf("%d (0x%x): %s\n", i, i, command_name(i)); + /* don't send netlink messages */ + return 2; +} +TOPLEVEL(commands, NULL, NL80211_CMD_GET_WIPHY, 0, CIB_NONE, handle_commands, + "list all known commands and their decimal & hex value"); + +static int print_feature_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + bool print = (unsigned long)arg; +#define maybe_printf(...) do { if (print) printf(__VA_ARGS__); } while (0) + + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (tb_msg[NL80211_ATTR_PROTOCOL_FEATURES]) { + uint32_t feat = nla_get_u32(tb_msg[NL80211_ATTR_PROTOCOL_FEATURES]); + + maybe_printf("nl80211 features: 0x%x\n", feat); + if (feat & NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP) { + maybe_printf("\t* split wiphy dump\n"); + nl80211_has_split_wiphy = true; + } + } + + return NL_SKIP; +} + +static int handle_features(struct nl80211_state *state, struct nl_msg *msg, + int argc, char **argv, enum id_input id) +{ + unsigned long print = argc == 0 || strcmp(argv[0], "-q"); + register_handler(print_feature_handler, (void *)print); + return 0; +} + +TOPLEVEL(features, "", NL80211_CMD_GET_PROTOCOL_FEATURES, 0, CIB_NONE, + handle_features, ""); diff --git a/interface.c b/interface.c new file mode 100644 index 0000000..a19c83f --- /dev/null +++ b/interface.c @@ -0,0 +1,717 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +#define VALID_FLAGS "none: no special flags\n"\ + "fcsfail: show frames with FCS errors\n"\ + "control: show control frames\n"\ + "otherbss: show frames from other BSSes\n"\ + "cook: use cooked mode\n"\ + "active: use active mode (ACK incoming unicast packets)\n"\ + "mumimo-groupid : use MUMIMO according to a group id\n"\ + "mumimo-follow-mac : use MUMIMO according to a MAC address" + +SECTION(interface); + +static char *mntr_flags[NL80211_MNTR_FLAG_MAX + 1] = { + "none", + "fcsfail", + "plcpfail", + "control", + "otherbss", + "cook", + "active", +}; + +static int parse_mumimo_options(int *_argc, char ***_argv, struct nl_msg *msg) +{ + uint8_t mumimo_group[VHT_MUMIMO_GROUP_LEN]; + unsigned char mac_addr[ETH_ALEN]; + char **argv = *_argv; + int argc = *_argc; + int i; + unsigned int val; + + if (strcmp(*argv, "mumimo-groupid") == 0) { + argc--; + argv++; + if (!argc || strlen(*argv) != VHT_MUMIMO_GROUP_LEN*2) { + fprintf(stderr, "Invalid groupID: %s\n", *argv); + return 1; + } + + for (i = 0; i < VHT_MUMIMO_GROUP_LEN; i++) { + if (sscanf((*argv) + i*2, "%2x", &val) != 1) { + fprintf(stderr, "Failed reading groupID\n"); + return 1; + } + mumimo_group[i] = val; + } + + NLA_PUT(msg, + NL80211_ATTR_MU_MIMO_GROUP_DATA, + VHT_MUMIMO_GROUP_LEN, + mumimo_group); + argc--; + argv++; + } else if (strcmp(*argv, "mumimo-follow-mac") == 0) { + argc--; + argv++; + if (!argc || mac_addr_a2n(mac_addr, *argv)) { + fprintf(stderr, "Invalid MAC address\n"); + return 1; + } + NLA_PUT(msg, NL80211_ATTR_MU_MIMO_FOLLOW_MAC_ADDR, + ETH_ALEN, mac_addr); + argc--; + argv++; + } + nla_put_failure: + *_argc = argc; + *_argv = argv; + return 0; +} + +static int parse_mntr_flags(int *_argc, char ***_argv, + struct nl_msg *msg) +{ + struct nl_msg *flags; + int err = -ENOBUFS; + enum nl80211_mntr_flags flag; + int argc = *_argc; + char **argv = *_argv; + + flags = nlmsg_alloc(); + if (!flags) + return -ENOMEM; + + while (argc) { + int ok = 0; + + /* parse MU-MIMO options */ + err = parse_mumimo_options(&argc, &argv, msg); + if (err) + goto out; + else if (!argc) + break; + + /* parse monitor flags */ + for (flag = __NL80211_MNTR_FLAG_INVALID; + flag <= NL80211_MNTR_FLAG_MAX; flag++) { + if (strcmp(*argv, mntr_flags[flag]) == 0) { + ok = 1; + /* + * This shouldn't be adding "flag" if that is + * zero, but due to a problem in the kernel's + * nl80211 code (using NLA_NESTED policy) it + * will reject an empty nested attribute but + * not one that contains an invalid attribute + */ + NLA_PUT_FLAG(flags, flag); + break; + } + } + if (!ok) { + err = -EINVAL; + goto out; + } + argc--; + argv++; + } + + nla_put_nested(msg, NL80211_ATTR_MNTR_FLAGS, flags); + err = 0; + nla_put_failure: + out: + nlmsg_free(flags); + + *_argc = argc; + *_argv = argv; + + return err; +} + +/* for help */ +#define IFACE_TYPES "Valid interface types are: managed, ibss, monitor, mesh, wds." + +/* return 0 if ok, internal error otherwise */ +static int get_if_type(int *argc, char ***argv, enum nl80211_iftype *type, + bool need_type) +{ + char *tpstr; + + if (*argc < 1 + !!need_type) + return 1; + + if (need_type && strcmp((*argv)[0], "type")) + return 1; + + tpstr = (*argv)[!!need_type]; + *argc -= 1 + !!need_type; + *argv += 1 + !!need_type; + + if (strcmp(tpstr, "adhoc") == 0 || + strcmp(tpstr, "ibss") == 0) { + *type = NL80211_IFTYPE_ADHOC; + return 0; + } else if (strcmp(tpstr, "ocb") == 0) { + *type = NL80211_IFTYPE_OCB; + return 0; + } else if (strcmp(tpstr, "monitor") == 0) { + *type = NL80211_IFTYPE_MONITOR; + return 0; + } else if (strcmp(tpstr, "master") == 0 || + strcmp(tpstr, "ap") == 0) { + *type = NL80211_IFTYPE_UNSPECIFIED; + fprintf(stderr, "You need to run a management daemon, e.g. hostapd,\n"); + fprintf(stderr, "see http://wireless.kernel.org/en/users/Documentation/hostapd\n"); + fprintf(stderr, "for more information on how to do that.\n"); + return 2; + } else if (strcmp(tpstr, "__ap") == 0) { + *type = NL80211_IFTYPE_AP; + return 0; + } else if (strcmp(tpstr, "__ap_vlan") == 0) { + *type = NL80211_IFTYPE_AP_VLAN; + return 0; + } else if (strcmp(tpstr, "wds") == 0) { + *type = NL80211_IFTYPE_WDS; + return 0; + } else if (strcmp(tpstr, "managed") == 0 || + strcmp(tpstr, "mgd") == 0 || + strcmp(tpstr, "station") == 0) { + *type = NL80211_IFTYPE_STATION; + return 0; + } else if (strcmp(tpstr, "mp") == 0 || + strcmp(tpstr, "mesh") == 0) { + *type = NL80211_IFTYPE_MESH_POINT; + return 0; + } else if (strcmp(tpstr, "__p2pcl") == 0) { + *type = NL80211_IFTYPE_P2P_CLIENT; + return 0; + } else if (strcmp(tpstr, "__p2pdev") == 0) { + *type = NL80211_IFTYPE_P2P_DEVICE; + return 0; + } else if (strcmp(tpstr, "__p2pgo") == 0) { + *type = NL80211_IFTYPE_P2P_GO; + return 0; + } else if (strcmp(tpstr, "__nan") == 0) { + *type = NL80211_IFTYPE_NAN; + return 0; + } + + fprintf(stderr, "invalid interface type %s\n", tpstr); + return 2; +} + +static int parse_4addr_flag(const char *value, struct nl_msg *msg) +{ + if (strcmp(value, "on") == 0) + NLA_PUT_U8(msg, NL80211_ATTR_4ADDR, 1); + else if (strcmp(value, "off") == 0) + NLA_PUT_U8(msg, NL80211_ATTR_4ADDR, 0); + else + return 1; + return 0; + +nla_put_failure: + return 1; +} + +static int handle_interface_add(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + char *name; + char *mesh_id = NULL; + enum nl80211_iftype type; + int tpset; + unsigned char mac_addr[ETH_ALEN]; + int found_mac = 0; + + if (argc < 1) + return 1; + + name = argv[0]; + argc--; + argv++; + + tpset = get_if_type(&argc, &argv, &type, true); + if (tpset) + return tpset; + +try_another: + if (argc) { + if (strcmp(argv[0], "mesh_id") == 0) { + argc--; + argv++; + + if (!argc) + return 1; + mesh_id = argv[0]; + argc--; + argv++; + } else if (strcmp(argv[0], "addr") == 0) { + argc--; + argv++; + if (mac_addr_a2n(mac_addr, argv[0])) { + fprintf(stderr, "Invalid MAC address\n"); + return 2; + } + argc--; + argv++; + found_mac = 1; + goto try_another; + } else if (strcmp(argv[0], "4addr") == 0) { + argc--; + argv++; + if (parse_4addr_flag(argv[0], msg)) { + fprintf(stderr, "4addr error\n"); + return 2; + } + argc--; + argv++; + } else if (strcmp(argv[0], "flags") == 0) { + argc--; + argv++; + if (parse_mntr_flags(&argc, &argv, msg)) { + fprintf(stderr, "flags error\n"); + return 2; + } + } else { + return 1; + } + } + + if (argc) + return 1; + + NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, name); + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, type); + if (mesh_id) + NLA_PUT(msg, NL80211_ATTR_MESH_ID, strlen(mesh_id), mesh_id); + if (found_mac) + NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); + + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(interface, add, " type [mesh_id ] [4addr on|off] [flags *] [addr ]", + NL80211_CMD_NEW_INTERFACE, 0, CIB_PHY, handle_interface_add, + "Add a new virtual interface with the given configuration.\n" + IFACE_TYPES "\n\n" + "The flags are only used for monitor interfaces, valid flags are:\n" + VALID_FLAGS "\n\n" + "The mesh_id is used only for mesh mode."); +COMMAND(interface, add, " type [mesh_id ] [4addr on|off] [flags *] [addr ]", + NL80211_CMD_NEW_INTERFACE, 0, CIB_NETDEV, handle_interface_add, NULL); + +static int handle_interface_del(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + return 0; +} +TOPLEVEL(del, NULL, NL80211_CMD_DEL_INTERFACE, 0, CIB_NETDEV, handle_interface_del, + "Remove this virtual interface"); +HIDDEN(interface, del, NULL, NL80211_CMD_DEL_INTERFACE, 0, CIB_NETDEV, handle_interface_del); + +static char *channel_type_name(enum nl80211_channel_type channel_type) +{ + switch (channel_type) { + case NL80211_CHAN_NO_HT: + return "NO HT"; + case NL80211_CHAN_HT20: + return "HT20"; + case NL80211_CHAN_HT40MINUS: + return "HT40-"; + case NL80211_CHAN_HT40PLUS: + return "HT40+"; + default: + return "unknown"; + } +} + +char *channel_width_name(enum nl80211_chan_width width) +{ + switch (width) { + case NL80211_CHAN_WIDTH_20_NOHT: + return "20 MHz (no HT)"; + case NL80211_CHAN_WIDTH_20: + return "20 MHz"; + case NL80211_CHAN_WIDTH_40: + return "40 MHz"; + case NL80211_CHAN_WIDTH_80: + return "80 MHz"; + case NL80211_CHAN_WIDTH_80P80: + return "80+80 MHz"; + case NL80211_CHAN_WIDTH_160: + return "160 MHz"; + case NL80211_CHAN_WIDTH_5: + return "5 MHz"; + case NL80211_CHAN_WIDTH_10: + return "10 MHz"; + default: + return "unknown"; + } +} + +static int print_iface_handler(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb_msg[NL80211_ATTR_MAX + 1]; + unsigned int *wiphy = arg; + const char *indent = ""; + + nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (wiphy && tb_msg[NL80211_ATTR_WIPHY]) { + unsigned int thiswiphy = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY]); + indent = "\t"; + if (*wiphy != thiswiphy) + printf("phy#%d\n", thiswiphy); + *wiphy = thiswiphy; + } + + if (tb_msg[NL80211_ATTR_IFNAME]) + printf("%sInterface %s\n", indent, nla_get_string(tb_msg[NL80211_ATTR_IFNAME])); + else + printf("%sUnnamed/non-netdev interface\n", indent); + if (tb_msg[NL80211_ATTR_IFINDEX]) + printf("%s\tifindex %d\n", indent, nla_get_u32(tb_msg[NL80211_ATTR_IFINDEX])); + if (tb_msg[NL80211_ATTR_WDEV]) + printf("%s\twdev 0x%llx\n", indent, + (unsigned long long)nla_get_u64(tb_msg[NL80211_ATTR_WDEV])); + if (tb_msg[NL80211_ATTR_MAC]) { + char mac_addr[20]; + mac_addr_n2a(mac_addr, nla_data(tb_msg[NL80211_ATTR_MAC])); + printf("%s\taddr %s\n", indent, mac_addr); + } + if (tb_msg[NL80211_ATTR_SSID]) { + printf("%s\tssid ", indent); + print_ssid_escaped(nla_len(tb_msg[NL80211_ATTR_SSID]), + nla_data(tb_msg[NL80211_ATTR_SSID])); + printf("\n"); + } + if (tb_msg[NL80211_ATTR_IFTYPE]) + printf("%s\ttype %s\n", indent, iftype_name(nla_get_u32(tb_msg[NL80211_ATTR_IFTYPE]))); + if (!wiphy && tb_msg[NL80211_ATTR_WIPHY]) + printf("%s\twiphy %d\n", indent, nla_get_u32(tb_msg[NL80211_ATTR_WIPHY])); + if (tb_msg[NL80211_ATTR_WIPHY_FREQ]) { + uint32_t freq = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_FREQ]); + + printf("%s\tchannel %d (%d MHz)", indent, + ieee80211_frequency_to_channel(freq), freq); + + if (tb_msg[NL80211_ATTR_CHANNEL_WIDTH]) { + printf(", width: %s", + channel_width_name(nla_get_u32(tb_msg[NL80211_ATTR_CHANNEL_WIDTH]))); + if (tb_msg[NL80211_ATTR_CENTER_FREQ1]) + printf(", center1: %d MHz", + nla_get_u32(tb_msg[NL80211_ATTR_CENTER_FREQ1])); + if (tb_msg[NL80211_ATTR_CENTER_FREQ2]) + printf(", center2: %d MHz", + nla_get_u32(tb_msg[NL80211_ATTR_CENTER_FREQ2])); + } else if (tb_msg[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) { + enum nl80211_channel_type channel_type; + + channel_type = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_CHANNEL_TYPE]); + printf(" %s", channel_type_name(channel_type)); + } + + printf("\n"); + } + + if (tb_msg[NL80211_ATTR_WIPHY_TX_POWER_LEVEL]) { + uint32_t txp = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_TX_POWER_LEVEL]); + + printf("%s\ttxpower %d.%.2d dBm\n", + indent, txp / 100, txp % 100); + } + + return NL_SKIP; +} + +static int handle_interface_info(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + register_handler(print_iface_handler, NULL); + return 0; +} +TOPLEVEL(info, NULL, NL80211_CMD_GET_INTERFACE, 0, CIB_NETDEV, handle_interface_info, + "Show information for this interface."); + +static int handle_interface_set(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + if (!argc) + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MONITOR); + + switch (parse_mntr_flags(&argc, &argv, msg)) { + case 0: + return 0; + case 1: + return 1; + case -ENOMEM: + fprintf(stderr, "failed to allocate flags\n"); + return 2; + case -EINVAL: + fprintf(stderr, "unknown flag %s\n", *argv); + return 2; + default: + return 2; + } + nla_put_failure: + return -ENOBUFS; +} +COMMAND(set, monitor, "*", + NL80211_CMD_SET_INTERFACE, 0, CIB_NETDEV, handle_interface_set, + "Set monitor flags. Valid flags are:\n" + VALID_FLAGS); + +static int handle_interface_meshid(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + char *mesh_id = NULL; + + if (argc != 1) + return 1; + + mesh_id = argv[0]; + + NLA_PUT(msg, NL80211_ATTR_MESH_ID, strlen(mesh_id), mesh_id); + + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(set, meshid, "", + NL80211_CMD_SET_INTERFACE, 0, CIB_NETDEV, handle_interface_meshid, NULL); + +static unsigned int dev_dump_wiphy; + +static int handle_dev_dump(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + dev_dump_wiphy = -1; + register_handler(print_iface_handler, &dev_dump_wiphy); + return 0; +} +TOPLEVEL(dev, NULL, NL80211_CMD_GET_INTERFACE, NLM_F_DUMP, CIB_NONE, handle_dev_dump, + "List all network interfaces for wireless hardware."); + +static int handle_interface_type(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + enum nl80211_iftype type; + int tpset; + + tpset = get_if_type(&argc, &argv, &type, false); + if (tpset) + return tpset; + + if (argc) + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, type); + + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(set, type, "", + NL80211_CMD_SET_INTERFACE, 0, CIB_NETDEV, handle_interface_type, + "Set interface type/mode.\n" + IFACE_TYPES); + +static int handle_interface_4addr(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + if (argc != 1) + return 1; + return parse_4addr_flag(argv[0], msg); +} +COMMAND(set, 4addr, "", + NL80211_CMD_SET_INTERFACE, 0, CIB_NETDEV, handle_interface_4addr, + "Set interface 4addr (WDS) mode."); + +static int handle_interface_noack_map(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + uint16_t noack_map; + char *end; + + if (argc != 1) + return 1; + + noack_map = strtoul(argv[0], &end, 16); + if (*end) + return 1; + + NLA_PUT_U16(msg, NL80211_ATTR_NOACK_MAP, noack_map); + + return 0; + nla_put_failure: + return -ENOBUFS; + +} +COMMAND(set, noack_map, "", + NL80211_CMD_SET_NOACK_MAP, 0, CIB_NETDEV, handle_interface_noack_map, + "Set the NoAck map for the TIDs. (0x0009 = BE, 0x0006 = BK, 0x0030 = VI, 0x00C0 = VO)"); + + +static int handle_interface_wds_peer(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + unsigned char mac_addr[ETH_ALEN]; + + if (argc < 1) + return 1; + + if (mac_addr_a2n(mac_addr, argv[0])) { + fprintf(stderr, "Invalid MAC address\n"); + return 2; + } + + argc--; + argv++; + + if (argc) + return 1; + + NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); + + return 0; + nla_put_failure: + return -ENOBUFS; +} +COMMAND(set, peer, "", + NL80211_CMD_SET_WDS_PEER, 0, CIB_NETDEV, handle_interface_wds_peer, + "Set interface WDS peer."); + +static int set_mcast_rate(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + float rate; + char *end; + + if (argc != 1) + return 1; + + rate = strtod(argv[0], &end); + if (*end != '\0') + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_MCAST_RATE, (int)(rate * 10)); + + return 0; +nla_put_failure: + return -ENOBUFS; +} + +COMMAND(set, mcast_rate, "", + NL80211_CMD_SET_MCAST_RATE, 0, CIB_NETDEV, set_mcast_rate, + "Set the multicast bitrate."); + + +static int handle_chanfreq(struct nl80211_state *state, struct nl_msg *msg, + bool chan, int argc, char **argv, + enum id_input id) +{ + struct chandef chandef; + int res; + int parsed; + char *end; + + res = parse_freqchan(&chandef, chan, argc, argv, &parsed); + if (res) + return res; + + argc -= parsed; + argv += parsed; + + while (argc) { + unsigned int beacons = 10; + + if (strcmp(argv[0], "beacons") == 0) { + if (argc < 2) + return 1; + + beacons = strtol(argv[1], &end, 10); + if (*end) + return 1; + + argc -= 2; + argv += 2; + + NLA_PUT_U32(msg, NL80211_ATTR_CH_SWITCH_COUNT, beacons); + } else if (strcmp(argv[0], "block-tx") == 0) { + argc -= 1; + argv += 1; + + NLA_PUT_FLAG(msg, NL80211_ATTR_CH_SWITCH_BLOCK_TX); + } else { + return 1; + } + } + + return put_chandef(msg, &chandef); + + nla_put_failure: + return -ENOBUFS; +} + +static int handle_freq(struct nl80211_state *state, struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + return handle_chanfreq(state, msg, false, argc, argv, id); +} + +static int handle_chan(struct nl80211_state *state, struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + return handle_chanfreq(state, msg, true, argc, argv, id); +} + +SECTION(switch); +COMMAND(switch, freq, + " [NOHT|HT20|HT40+|HT40-|5MHz|10MHz|80MHz] [beacons ] [block-tx]\n" + " [5|10|20|40|80|80+80|160] [ []] [beacons ] [block-tx]", + NL80211_CMD_CHANNEL_SWITCH, 0, CIB_NETDEV, handle_freq, + "Switch the operating channel by sending a channel switch announcement (CSA)."); +COMMAND(switch, channel, " [NOHT|HT20|HT40+|HT40-|5MHz|10MHz|80MHz] [beacons ] [block-tx]", + NL80211_CMD_CHANNEL_SWITCH, 0, CIB_NETDEV, handle_chan, NULL); diff --git a/iw.8 b/iw.8 new file mode 100644 index 0000000..d0fcb61 --- /dev/null +++ b/iw.8 @@ -0,0 +1,71 @@ +.TH IW 8 "7 June 2012" "iw" "Linux" +.SH NAME +iw \- show / manipulate wireless devices and their configuration +.SH SYNOPSIS + +.ad l +.in +8 +.ti -8 +.B iw +.RI [ " OPTIONS " ] " " { " +.BR help " [ " +.RI ""command " ]" +.BR "|" +.RI ""OBJECT " " COMMAND " }" +.sp + +.ti -8 +.IR OBJECT " := { " +.BR dev " | " phy " | " reg " }" +.sp + +.ti -8 +.IR OPTIONS " := { --version | --debug }" + +.SH OPTIONS + +.TP +.BR " --version" +print version information and exit. + +.TP +.BR " --debug" +enable netlink message debugging. + +.SH IW - COMMAND SYNTAX + +.SS +.I OBJECT + +.TP +.B dev +- network interface. + +.TP +.B phy +- wireless hardware device (by name). +.TP +.B phy# +- wireless hardware device (by index). + +.TP +.B reg +- regulatory agent. + +.SS +.I COMMAND + +Specifies the action to perform on the object. +The set of possible actions depends on the object type. +.B iw help +will print all supported commands, while +.B iw help command +will print the help for all matching commands. + +.SH SEE ALSO +.BR ip (8), +.BR crda (8), +.BR regdbdump (8), +.BR regulatory.bin (5) + +.BR http://wireless.kernel.org/en/users/Documentation/iw diff --git a/iw.c b/iw.c new file mode 100644 index 0000000..daa2a77 --- /dev/null +++ b/iw.c @@ -0,0 +1,619 @@ +/* + * nl80211 userspace tool + * + * Copyright 2007, 2008 Johannes Berg + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +/* libnl 1.x compatibility code */ +#if !defined(CONFIG_LIBNL20) && !defined(CONFIG_LIBNL30) +static inline struct nl_handle *nl_socket_alloc(void) +{ + return nl_handle_alloc(); +} + +static inline void nl_socket_free(struct nl_sock *h) +{ + nl_handle_destroy(h); +} + +static inline int nl_socket_set_buffer_size(struct nl_sock *sk, + int rxbuf, int txbuf) +{ + return nl_set_buffer_size(sk, rxbuf, txbuf); +} +#endif /* CONFIG_LIBNL20 && CONFIG_LIBNL30 */ + +int iw_debug = 0; + +static int nl80211_init(struct nl80211_state *state) +{ + int err; + + state->nl_sock = nl_socket_alloc(); + if (!state->nl_sock) { + fprintf(stderr, "Failed to allocate netlink socket.\n"); + return -ENOMEM; + } + + if (genl_connect(state->nl_sock)) { + fprintf(stderr, "Failed to connect to generic netlink.\n"); + err = -ENOLINK; + goto out_handle_destroy; + } + + nl_socket_set_buffer_size(state->nl_sock, 8192, 8192); + + /* try to set NETLINK_EXT_ACK to 1, ignoring errors */ + err = 1; + setsockopt(nl_socket_get_fd(state->nl_sock), SOL_NETLINK, + NETLINK_EXT_ACK, &err, sizeof(err)); + + state->nl80211_id = genl_ctrl_resolve(state->nl_sock, "nl80211"); + if (state->nl80211_id < 0) { + fprintf(stderr, "nl80211 not found.\n"); + err = -ENOENT; + goto out_handle_destroy; + } + + return 0; + + out_handle_destroy: + nl_socket_free(state->nl_sock); + return err; +} + +static void nl80211_cleanup(struct nl80211_state *state) +{ + nl_socket_free(state->nl_sock); +} + +static int cmd_size; + +extern struct cmd __start___cmd; +extern struct cmd __stop___cmd; + +#define for_each_cmd(_cmd) \ + for (_cmd = &__start___cmd; _cmd < &__stop___cmd; \ + _cmd = (const struct cmd *)((char *)_cmd + cmd_size)) + + +static void __usage_cmd(const struct cmd *cmd, char *indent, bool full) +{ + const char *start, *lend, *end; + + printf("%s", indent); + + switch (cmd->idby) { + case CIB_NONE: + break; + case CIB_PHY: + printf("phy "); + break; + case CIB_NETDEV: + printf("dev "); + break; + case CIB_WDEV: + printf("wdev "); + break; + } + if (cmd->parent && cmd->parent->name) + printf("%s ", cmd->parent->name); + printf("%s", cmd->name); + + if (cmd->args) { + /* print line by line */ + start = cmd->args; + end = strchr(start, '\0'); + printf(" "); + do { + lend = strchr(start, '\n'); + if (!lend) + lend = end; + if (start != cmd->args) { + printf("\t"); + switch (cmd->idby) { + case CIB_NONE: + break; + case CIB_PHY: + printf("phy "); + break; + case CIB_NETDEV: + printf("dev "); + break; + case CIB_WDEV: + printf("wdev "); + break; + } + if (cmd->parent && cmd->parent->name) + printf("%s ", cmd->parent->name); + printf("%s ", cmd->name); + } + printf("%.*s\n", (int)(lend - start), start); + start = lend + 1; + } while (end != lend); + } else + printf("\n"); + + if (!full || !cmd->help) + return; + + /* hack */ + if (strlen(indent)) + indent = "\t\t"; + else + printf("\n"); + + /* print line by line */ + start = cmd->help; + end = strchr(start, '\0'); + do { + lend = strchr(start, '\n'); + if (!lend) + lend = end; + printf("%s", indent); + printf("%.*s\n", (int)(lend - start), start); + start = lend + 1; + } while (end != lend); + + printf("\n"); +} + +static void usage_options(void) +{ + printf("Options:\n"); + printf("\t--debug\t\tenable netlink debugging\n"); +} + +static const char *argv0; + +static void usage(int argc, char **argv) +{ + const struct cmd *section, *cmd; + bool full = argc >= 0; + const char *sect_filt = NULL; + const char *cmd_filt = NULL; + + if (argc > 0) + sect_filt = argv[0]; + + if (argc > 1) + cmd_filt = argv[1]; + + printf("Usage:\t%s [options] command\n", argv0); + usage_options(); + printf("\t--version\tshow version (%s)\n", iw_version); + printf("Commands:\n"); + for_each_cmd(section) { + if (section->parent) + continue; + + if (sect_filt && strcmp(section->name, sect_filt)) + continue; + + if (section->handler && !section->hidden) + __usage_cmd(section, "\t", full); + + for_each_cmd(cmd) { + if (section != cmd->parent) + continue; + if (!cmd->handler || cmd->hidden) + continue; + if (cmd_filt && strcmp(cmd->name, cmd_filt)) + continue; + __usage_cmd(cmd, "\t", full); + } + } + printf("\nCommands that use the netdev ('dev') can also be given the\n" + "'wdev' instead to identify the device.\n"); + printf("\nYou can omit the 'phy' or 'dev' if " + "the identification is unique,\n" + "e.g. \"iw wlan0 info\" or \"iw phy0 info\". " + "(Don't when scripting.)\n\n" + "Do NOT screenscrape this tool, we don't " + "consider its output stable.\n\n"); +} + +static int print_help(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + exit(3); +} +TOPLEVEL(help, "[command]", 0, 0, CIB_NONE, print_help, + "Print usage for all or a specific command, e.g.\n" + "\"help wowlan\" or \"help wowlan enable\"."); + +static void usage_cmd(const struct cmd *cmd) +{ + printf("Usage:\t%s [options] ", argv0); + __usage_cmd(cmd, "", true); + usage_options(); +} + +static void version(void) +{ + printf("iw version %s\n", iw_version); +} + +static int phy_lookup(char *name) +{ + char buf[200]; + int fd, pos; + + snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", name); + + fd = open(buf, O_RDONLY); + if (fd < 0) + return -1; + pos = read(fd, buf, sizeof(buf) - 1); + if (pos < 0) { + close(fd); + return -1; + } + buf[pos] = '\0'; + close(fd); + return atoi(buf); +} + +static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, + void *arg) +{ + struct nlmsghdr *nlh = (struct nlmsghdr *)err - 1; + int len = nlh->nlmsg_len; + struct nlattr *attrs; + struct nlattr *tb[NLMSGERR_ATTR_MAX + 1]; + int *ret = arg; + int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh); + + *ret = err->error; + + if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS)) + return NL_STOP; + + if (!(nlh->nlmsg_flags & NLM_F_CAPPED)) + ack_len += err->msg.nlmsg_len - sizeof(*nlh); + + if (len <= ack_len) + return NL_STOP; + + attrs = (void *)((unsigned char *)nlh + ack_len); + len -= ack_len; + + nla_parse(tb, NLMSGERR_ATTR_MAX, attrs, len, NULL); + if (tb[NLMSGERR_ATTR_MSG]) { + len = strnlen((char *)nla_data(tb[NLMSGERR_ATTR_MSG]), + nla_len(tb[NLMSGERR_ATTR_MSG])); + fprintf(stderr, "kernel reports: %*s\n", len, + (char *)nla_data(tb[NLMSGERR_ATTR_MSG])); + } + + return NL_STOP; +} + +static int finish_handler(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + *ret = 0; + return NL_SKIP; +} + +static int ack_handler(struct nl_msg *msg, void *arg) +{ + int *ret = arg; + *ret = 0; + return NL_STOP; +} + +static int (*registered_handler)(struct nl_msg *, void *); +static void *registered_handler_data; + +void register_handler(int (*handler)(struct nl_msg *, void *), void *data) +{ + registered_handler = handler; + registered_handler_data = data; +} + +int valid_handler(struct nl_msg *msg, void *arg) +{ + if (registered_handler) + return registered_handler(msg, registered_handler_data); + + return NL_OK; +} + +static int __handle_cmd(struct nl80211_state *state, enum id_input idby, + int argc, char **argv, const struct cmd **cmdout) +{ + const struct cmd *cmd, *match = NULL, *sectcmd; + struct nl_cb *cb; + struct nl_cb *s_cb; + struct nl_msg *msg; + signed long long devidx = 0; + int err, o_argc; + const char *command, *section; + char *tmp, **o_argv; + enum command_identify_by command_idby = CIB_NONE; + + if (argc <= 1 && idby != II_NONE) + return 1; + + o_argc = argc; + o_argv = argv; + + switch (idby) { + case II_PHY_IDX: + command_idby = CIB_PHY; + devidx = strtoul(*argv + 4, &tmp, 0); + if (*tmp != '\0') + return 1; + argc--; + argv++; + break; + case II_PHY_NAME: + command_idby = CIB_PHY; + devidx = phy_lookup(*argv); + argc--; + argv++; + break; + case II_NETDEV: + command_idby = CIB_NETDEV; + devidx = if_nametoindex(*argv); + if (devidx == 0) + devidx = -1; + argc--; + argv++; + break; + case II_WDEV: + command_idby = CIB_WDEV; + devidx = strtoll(*argv, &tmp, 0); + if (*tmp != '\0') + return 1; + argc--; + argv++; + default: + break; + } + + if (devidx < 0) + return -errno; + + section = *argv; + argc--; + argv++; + + for_each_cmd(sectcmd) { + if (sectcmd->parent) + continue; + /* ok ... bit of a hack for the dupe 'info' section */ + if (match && sectcmd->idby != command_idby) + continue; + if (strcmp(sectcmd->name, section) == 0) + match = sectcmd; + } + + sectcmd = match; + match = NULL; + if (!sectcmd) + return 1; + + if (argc > 0) { + command = *argv; + + for_each_cmd(cmd) { + if (!cmd->handler) + continue; + if (cmd->parent != sectcmd) + continue; + /* + * ignore mismatch id by, but allow WDEV + * in place of NETDEV + */ + if (cmd->idby != command_idby && + !(cmd->idby == CIB_NETDEV && + command_idby == CIB_WDEV)) + continue; + if (strcmp(cmd->name, command)) + continue; + if (argc > 1 && !cmd->args) + continue; + match = cmd; + break; + } + + if (match) { + argc--; + argv++; + } + } + + if (match) + cmd = match; + else { + /* Use the section itself, if possible. */ + cmd = sectcmd; + if (argc && !cmd->args) + return 1; + if (cmd->idby != command_idby && + !(cmd->idby == CIB_NETDEV && command_idby == CIB_WDEV)) + return 1; + if (!cmd->handler) + return 1; + } + + if (cmd->selector) { + cmd = cmd->selector(argc, argv); + if (!cmd) + return 1; + } + + if (cmdout) + *cmdout = cmd; + + if (!cmd->cmd) { + argc = o_argc; + argv = o_argv; + return cmd->handler(state, NULL, argc, argv, idby); + } + + msg = nlmsg_alloc(); + if (!msg) { + fprintf(stderr, "failed to allocate netlink message\n"); + return 2; + } + + cb = nl_cb_alloc(iw_debug ? NL_CB_DEBUG : NL_CB_DEFAULT); + s_cb = nl_cb_alloc(iw_debug ? NL_CB_DEBUG : NL_CB_DEFAULT); + if (!cb || !s_cb) { + fprintf(stderr, "failed to allocate netlink callbacks\n"); + err = 2; + goto out; + } + + genlmsg_put(msg, 0, 0, state->nl80211_id, 0, + cmd->nl_msg_flags, cmd->cmd, 0); + + switch (command_idby) { + case CIB_PHY: + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, devidx); + break; + case CIB_NETDEV: + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, devidx); + break; + case CIB_WDEV: + NLA_PUT_U64(msg, NL80211_ATTR_WDEV, devidx); + break; + default: + break; + } + + err = cmd->handler(state, msg, argc, argv, idby); + if (err) + goto out; + + nl_socket_set_cb(state->nl_sock, s_cb); + + err = nl_send_auto_complete(state->nl_sock, msg); + if (err < 0) + goto out; + + err = 1; + + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, NULL); + + while (err > 0) + nl_recvmsgs(state->nl_sock, cb); + out: + nl_cb_put(cb); + nl_cb_put(s_cb); + nlmsg_free(msg); + return err; + nla_put_failure: + fprintf(stderr, "building message failed\n"); + return 2; +} + +int handle_cmd(struct nl80211_state *state, enum id_input idby, + int argc, char **argv) +{ + return __handle_cmd(state, idby, argc, argv, NULL); +} + +int main(int argc, char **argv) +{ + struct nl80211_state nlstate; + int err; + const struct cmd *cmd = NULL; + + /* calculate command size including padding */ + cmd_size = labs((long)&__section_set - (long)&__section_get); + /* strip off self */ + argc--; + argv0 = *argv++; + + if (argc > 0 && strcmp(*argv, "--debug") == 0) { + iw_debug = 1; + argc--; + argv++; + } + + if (argc > 0 && strcmp(*argv, "--version") == 0) { + version(); + return 0; + } + + /* need to treat "help" command specially so it works w/o nl80211 */ + if (argc == 0 || strcmp(*argv, "help") == 0) { + usage(argc - 1, argv + 1); + return 0; + } + + err = nl80211_init(&nlstate); + if (err) + return 1; + + if (strcmp(*argv, "dev") == 0 && argc > 1) { + argc--; + argv++; + err = __handle_cmd(&nlstate, II_NETDEV, argc, argv, &cmd); + } else if (strncmp(*argv, "phy", 3) == 0 && argc > 1) { + if (strlen(*argv) == 3) { + argc--; + argv++; + err = __handle_cmd(&nlstate, II_PHY_NAME, argc, argv, &cmd); + } else if (*(*argv + 3) == '#') + err = __handle_cmd(&nlstate, II_PHY_IDX, argc, argv, &cmd); + else + goto detect; + } else if (strcmp(*argv, "wdev") == 0 && argc > 1) { + argc--; + argv++; + err = __handle_cmd(&nlstate, II_WDEV, argc, argv, &cmd); + } else { + int idx; + enum id_input idby = II_NONE; + detect: + if ((idx = if_nametoindex(argv[0])) != 0) + idby = II_NETDEV; + else if ((idx = phy_lookup(argv[0])) >= 0) + idby = II_PHY_NAME; + err = __handle_cmd(&nlstate, idby, argc, argv, &cmd); + } + + if (err == HANDLER_RET_USAGE) { + if (cmd) + usage_cmd(cmd); + else + usage(0, NULL); + } else if (err == HANDLER_RET_DONE) { + err = 0; + } else if (err < 0) + fprintf(stderr, "command failed: %s (%d)\n", strerror(-err), err); + + nl80211_cleanup(&nlstate); + + return err; +} diff --git a/iw.h b/iw.h new file mode 100644 index 0000000..49c77cf --- /dev/null +++ b/iw.h @@ -0,0 +1,238 @@ +#ifndef __IW_H +#define __IW_H + +#include +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "ieee80211.h" + +/* support for extack if compilation headers are too old */ +#ifndef NETLINK_EXT_ACK +#define NETLINK_EXT_ACK 11 +enum nlmsgerr_attrs { + NLMSGERR_ATTR_UNUSED, + NLMSGERR_ATTR_MSG, + NLMSGERR_ATTR_OFFS, + NLMSGERR_ATTR_COOKIE, + + __NLMSGERR_ATTR_MAX, + NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 +}; +#endif +#ifndef NLM_F_CAPPED +#define NLM_F_CAPPED 0x100 +#endif +#ifndef NLM_F_ACK_TLVS +#define NLM_F_ACK_TLVS 0x200 +#endif +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#define ETH_ALEN 6 +#define VHT_MUMIMO_GROUP_LEN 24 + +/* libnl 1.x compatibility code */ +#if !defined(CONFIG_LIBNL20) && !defined(CONFIG_LIBNL30) +# define nl_sock nl_handle +#endif + +struct nl80211_state { + struct nl_sock *nl_sock; + int nl80211_id; +}; + +enum command_identify_by { + CIB_NONE, + CIB_PHY, + CIB_NETDEV, + CIB_WDEV, +}; + +enum id_input { + II_NONE, + II_NETDEV, + II_PHY_NAME, + II_PHY_IDX, + II_WDEV, +}; + +#define HANDLER_RET_USAGE 1 +#define HANDLER_RET_DONE 3 + +struct cmd { + const char *name; + const char *args; + const char *help; + const enum nl80211_commands cmd; + int nl_msg_flags; + int hidden; + const enum command_identify_by idby; + /* + * The handler should return a negative error code, + * zero on success, 1 if the arguments were wrong. + * Return 2 iff you provide the error message yourself. + */ + int (*handler)(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id); + const struct cmd *(*selector)(int argc, char **argv); + const struct cmd *parent; +}; + +struct chanmode { + const char *name; + unsigned int width; + int freq1_diff; + int chantype; /* for older kernel */ +}; + +struct chandef { + enum nl80211_chan_width width; + + unsigned int control_freq; + unsigned int center_freq1; + unsigned int center_freq2; +}; + +#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof(ar[0])) +#define DIV_ROUND_UP(x, y) (((x) + (y - 1)) / (y)) + +#define __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel)\ + static struct cmd \ + __cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden\ + __attribute__((used)) __attribute__((section("__cmd"))) = { \ + .name = (_name), \ + .args = (_args), \ + .cmd = (_nlcmd), \ + .nl_msg_flags = (_flags), \ + .hidden = (_hidden), \ + .idby = (_idby), \ + .handler = (_handler), \ + .help = (_help), \ + .parent = _section, \ + .selector = (_sel), \ + } +#define __ACMD(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel, _alias)\ + __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel);\ + static const struct cmd *_alias = &__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden +#define COMMAND(section, name, args, cmd, flags, idby, handler, help) \ + __COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, NULL) +#define COMMAND_ALIAS(section, name, args, cmd, flags, idby, handler, help, selector, alias)\ + __ACMD(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, selector, alias) +#define HIDDEN(section, name, args, cmd, flags, idby, handler) \ + __COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 1, idby, handler, NULL, NULL) + +#define TOPLEVEL(_name, _args, _nlcmd, _flags, _idby, _handler, _help) \ + struct cmd \ + __section ## _ ## _name \ + __attribute__((used)) __attribute__((section("__cmd"))) = { \ + .name = (#_name), \ + .args = (_args), \ + .cmd = (_nlcmd), \ + .nl_msg_flags = (_flags), \ + .idby = (_idby), \ + .handler = (_handler), \ + .help = (_help), \ + } +#define SECTION(_name) \ + struct cmd __section ## _ ## _name \ + __attribute__((used)) __attribute__((section("__cmd"))) = { \ + .name = (#_name), \ + .hidden = 1, \ + } + +#define DECLARE_SECTION(_name) \ + extern struct cmd __section ## _ ## _name; + +extern const char iw_version[]; + +extern int iw_debug; + +int handle_cmd(struct nl80211_state *state, enum id_input idby, + int argc, char **argv); + +struct print_event_args { + struct timeval ts; /* internal */ + bool have_ts; /* must be set false */ + bool frame, time, reltime; + bool continue_listening; +}; + +__u32 listen_events(struct nl80211_state *state, + const int n_waits, const __u32 *waits); +int __prepare_listen_events(struct nl80211_state *state); +__u32 __do_listen_events(struct nl80211_state *state, + const int n_waits, const __u32 *waits, + struct print_event_args *args); + +int valid_handler(struct nl_msg *msg, void *arg); +void register_handler(int (*handler)(struct nl_msg *, void *), void *data); + +int mac_addr_a2n(unsigned char *mac_addr, char *arg); +void mac_addr_n2a(char *mac_addr, unsigned char *arg); +int parse_hex_mask(char *hexmask, unsigned char **result, size_t *result_len, + unsigned char **mask); +unsigned char *parse_hex(char *hex, size_t *outlen); + +int parse_keys(struct nl_msg *msg, char **argv, int argc); +int parse_freqchan(struct chandef *chandef, bool chan, int argc, char **argv, int *parsed); +enum nl80211_chan_width str_to_bw(const char *str); +int put_chandef(struct nl_msg *msg, struct chandef *chandef); + +void print_ht_mcs(const __u8 *mcs); +void print_ampdu_length(__u8 exponent); +void print_ampdu_spacing(__u8 spacing); +void print_ht_capability(__u16 cap); +void print_vht_info(__u32 capa, const __u8 *mcs); + +char *channel_width_name(enum nl80211_chan_width width); +const char *iftype_name(enum nl80211_iftype iftype); +const char *command_name(enum nl80211_commands cmd); +int ieee80211_channel_to_frequency(int chan, enum nl80211_band band); +int ieee80211_frequency_to_channel(int freq); + +void print_ssid_escaped(const uint8_t len, const uint8_t *data); + +int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group); + +char *reg_initiator_to_string(__u8 initiator); + +const char *get_reason_str(uint16_t reason); +const char *get_status_str(uint16_t status); + +enum print_ie_type { + PRINT_SCAN, + PRINT_LINK, +}; + +#define BIT(x) (1ULL<<(x)) + +void print_ies(unsigned char *ie, int ielen, bool unknown, + enum print_ie_type ptype); + +void parse_bitrate(struct nlattr *bitrate_attr, char *buf, int buflen); +void iw_hexdump(const char *prefix, const __u8 *data, size_t len); + +int get_cf1(const struct chanmode *chanmode, unsigned long freq); + +#define SCHED_SCAN_OPTIONS "[interval | scan_plans [*] ] " \ + "[delay ] [freqs +] [matches [ssid ]+]] [active [ssid ]+|passive] " \ + "[randomise[=/]]" +int parse_sched_scan(struct nl_msg *msg, int *argc, char ***argv); + +DECLARE_SECTION(switch); +DECLARE_SECTION(set); +DECLARE_SECTION(get); + +char *hex2bin(const char *hex, char *buf); + +void iwl_parse_event(__u32 vendor_id, struct nlattr **attrs); + +#endif /* __IW_H */ diff --git a/link.c b/link.c new file mode 100644 index 0000000..0a32392 --- /dev/null +++ b/link.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +struct link_result { + uint8_t bssid[8]; + bool link_found; + bool anything_found; +}; + +static struct link_result lr = { .link_found = false }; + +static int link_bss_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *bss[NL80211_BSS_MAX + 1]; + static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = { + [NL80211_BSS_TSF] = { .type = NLA_U64 }, + [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 }, + [NL80211_BSS_BSSID] = { }, + [NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 }, + [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 }, + [NL80211_BSS_INFORMATION_ELEMENTS] = { }, + [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 }, + [NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 }, + [NL80211_BSS_STATUS] = { .type = NLA_U32 }, + }; + struct link_result *result = arg; + char mac_addr[20], dev[20]; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_BSS]) { + fprintf(stderr, "bss info missing!\n"); + return NL_SKIP; + } + if (nla_parse_nested(bss, NL80211_BSS_MAX, + tb[NL80211_ATTR_BSS], + bss_policy)) { + fprintf(stderr, "failed to parse nested attributes!\n"); + return NL_SKIP; + } + + if (!bss[NL80211_BSS_BSSID]) + return NL_SKIP; + + if (!bss[NL80211_BSS_STATUS]) + return NL_SKIP; + + mac_addr_n2a(mac_addr, nla_data(bss[NL80211_BSS_BSSID])); + if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), dev); + + switch (nla_get_u32(bss[NL80211_BSS_STATUS])) { + case NL80211_BSS_STATUS_ASSOCIATED: + printf("Connected to %s (on %s)\n", mac_addr, dev); + break; + case NL80211_BSS_STATUS_AUTHENTICATED: + printf("Authenticated with %s (on %s)\n", mac_addr, dev); + return NL_SKIP; + case NL80211_BSS_STATUS_IBSS_JOINED: + printf("Joined IBSS %s (on %s)\n", mac_addr, dev); + break; + default: + return NL_SKIP; + } + + result->anything_found = true; + + if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) + print_ies(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), + nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]), + false, PRINT_LINK); + + if (bss[NL80211_BSS_FREQUENCY]) + printf("\tfreq: %d\n", + nla_get_u32(bss[NL80211_BSS_FREQUENCY])); + + if (nla_get_u32(bss[NL80211_BSS_STATUS]) != NL80211_BSS_STATUS_ASSOCIATED) + return NL_SKIP; + + /* only in the assoc case do we want more info from station get */ + result->link_found = true; + memcpy(result->bssid, nla_data(bss[NL80211_BSS_BSSID]), 6); + return NL_SKIP; +} + +static int handle_scan_for_link(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + if (argc > 0) + return 1; + + register_handler(link_bss_handler, &lr); + return 0; +} + +static int print_link_sta(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1]; + struct nlattr *binfo[NL80211_STA_BSS_PARAM_MAX + 1]; + static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = { + [NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 }, + [NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 }, + [NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 }, + [NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 }, + [NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 }, + [NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 }, + [NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED }, + [NL80211_STA_INFO_LLID] = { .type = NLA_U16 }, + [NL80211_STA_INFO_PLID] = { .type = NLA_U16 }, + [NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 }, + }; + static struct nla_policy bss_policy[NL80211_STA_BSS_PARAM_MAX + 1] = { + [NL80211_STA_BSS_PARAM_CTS_PROT] = { .type = NLA_FLAG }, + [NL80211_STA_BSS_PARAM_SHORT_PREAMBLE] = { .type = NLA_FLAG }, + [NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME] = { .type = NLA_FLAG }, + [NL80211_STA_BSS_PARAM_DTIM_PERIOD] = { .type = NLA_U8 }, + [NL80211_STA_BSS_PARAM_BEACON_INTERVAL] = { .type = NLA_U16 }, + }; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[NL80211_ATTR_STA_INFO]) { + fprintf(stderr, "sta stats missing!\n"); + return NL_SKIP; + } + if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, + tb[NL80211_ATTR_STA_INFO], + stats_policy)) { + fprintf(stderr, "failed to parse nested attributes!\n"); + return NL_SKIP; + } + + if (sinfo[NL80211_STA_INFO_RX_BYTES] && sinfo[NL80211_STA_INFO_RX_PACKETS]) + printf("\tRX: %u bytes (%u packets)\n", + nla_get_u32(sinfo[NL80211_STA_INFO_RX_BYTES]), + nla_get_u32(sinfo[NL80211_STA_INFO_RX_PACKETS])); + if (sinfo[NL80211_STA_INFO_TX_BYTES] && sinfo[NL80211_STA_INFO_TX_PACKETS]) + printf("\tTX: %u bytes (%u packets)\n", + nla_get_u32(sinfo[NL80211_STA_INFO_TX_BYTES]), + nla_get_u32(sinfo[NL80211_STA_INFO_TX_PACKETS])); + if (sinfo[NL80211_STA_INFO_SIGNAL]) + printf("\tsignal: %d dBm\n", + (int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL])); + + if (sinfo[NL80211_STA_INFO_TX_BITRATE]) { + char buf[100]; + + parse_bitrate(sinfo[NL80211_STA_INFO_TX_BITRATE], buf, sizeof(buf)); + printf("\ttx bitrate: %s\n", buf); + } + + if (sinfo[NL80211_STA_INFO_BSS_PARAM]) { + if (nla_parse_nested(binfo, NL80211_STA_BSS_PARAM_MAX, + sinfo[NL80211_STA_INFO_BSS_PARAM], + bss_policy)) { + fprintf(stderr, "failed to parse nested bss parameters!\n"); + } else { + char *delim = ""; + printf("\n\tbss flags:\t"); + if (binfo[NL80211_STA_BSS_PARAM_CTS_PROT]) { + printf("CTS-protection"); + delim = " "; + } + if (binfo[NL80211_STA_BSS_PARAM_SHORT_PREAMBLE]) { + printf("%sshort-preamble", delim); + delim = " "; + } + if (binfo[NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME]) + printf("%sshort-slot-time", delim); + printf("\n\tdtim period:\t%d", + nla_get_u8(binfo[NL80211_STA_BSS_PARAM_DTIM_PERIOD])); + printf("\n\tbeacon int:\t%d", + nla_get_u16(binfo[NL80211_STA_BSS_PARAM_BEACON_INTERVAL])); + printf("\n"); + } + } + + return NL_SKIP; +} + +static int handle_link_sta(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + unsigned char mac_addr[ETH_ALEN]; + + if (argc < 1) + return 1; + + if (mac_addr_a2n(mac_addr, argv[0])) { + fprintf(stderr, "invalid mac address\n"); + return 2; + } + + argc--; + argv++; + + if (argc) + return 1; + + NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr); + + register_handler(print_link_sta, NULL); + + return 0; + nla_put_failure: + return -ENOBUFS; +} + +static int handle_link(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + char *link_argv[] = { + NULL, + "link", + "get_bss", + NULL, + }; + char *station_argv[] = { + NULL, + "link", + "get_sta", + NULL, + NULL, + }; + char bssid_buf[3*6]; + int err; + + link_argv[0] = argv[0]; + err = handle_cmd(state, id, 3, link_argv); + if (err) + return err; + + if (!lr.link_found) { + if (!lr.anything_found) + printf("Not connected.\n"); + return 0; + } + + mac_addr_n2a(bssid_buf, lr.bssid); + bssid_buf[17] = '\0'; + + station_argv[0] = argv[0]; + station_argv[3] = bssid_buf; + return handle_cmd(state, id, 4, station_argv); +} +TOPLEVEL(link, NULL, 0, 0, CIB_NETDEV, handle_link, + "Print information about the current link, if any."); +HIDDEN(link, get_sta, "", NL80211_CMD_GET_STATION, 0, + CIB_NETDEV, handle_link_sta); +HIDDEN(link, get_bss, NULL, NL80211_CMD_GET_SCAN, NLM_F_DUMP, + CIB_NETDEV, handle_scan_for_link); diff --git a/measurements.c b/measurements.c new file mode 100644 index 0000000..a69b213 --- /dev/null +++ b/measurements.c @@ -0,0 +1,329 @@ +#include + +#include "nl80211.h" +#include "iw.h" +#include + +SECTION(measurement); + + /** + * struct ftm_target - data for an FTM target (FTM responder) + * + * @freq: target's frequency + * @bw: target's bandwith. one of @enum nl80211_chan_width + * @center_freq1: target's center frequency, 1st segment + * @center_freq2: target's center frequency, 2nd segment(if relevant) + * @target: target's mac address. + * @samples_per_burst: number of FTM frames in a single burst. + * @one_sided: whether to perform a one-sided or two-sided measurement. + * @asap: Whether to perform the measurement in ASAP mode. Ignored if + * one-sided. + */ +struct ftm_target { + int freq; + char bw; + int center_freq1; + int center_freq2; + char target[ETH_ALEN]; + char samples_per_burst; + char one_sided; + char asap; + char num_of_bursts_exp; + unsigned short burst_period; + char retries; + char burst_duration; + char query_lci; + char query_civic; +}; + +static int ftm_put_target(struct nl_msg *msg, struct ftm_target *tgt) +{ + if (nla_put(msg, NL80211_FTM_TARGET_ATTR_BSSID, ETH_ALEN, + tgt->target) || + nla_put_u8(msg, NL80211_FTM_TARGET_ATTR_BW, tgt->bw) || + nla_put_u32(msg, NL80211_FTM_TARGET_ATTR_FREQ, tgt->freq) || + (tgt->center_freq1 && + nla_put_u32(msg, NL80211_FTM_TARGET_ATTR_CNTR_FREQ_1, + tgt->center_freq1)) || + (tgt->center_freq2 && + nla_put_u32(msg, NL80211_FTM_TARGET_ATTR_CNTR_FREQ_2, + tgt->center_freq2)) || + (tgt->samples_per_burst && + nla_put_u8(msg, NL80211_FTM_TARGET_ATTR_SAMPLES_PER_BURST, + tgt->samples_per_burst)) || + (tgt->num_of_bursts_exp && + nla_put_u8(msg, NL80211_FTM_TARGET_ATTR_NUM_OF_BURSTS_EXP, + tgt->num_of_bursts_exp)) || + (tgt->burst_period && + nla_put_u16(msg, NL80211_FTM_TARGET_ATTR_BURST_PERIOD, + tgt->burst_period)) || + (tgt->retries && + nla_put_u8(msg, NL80211_FTM_TARGET_ATTR_RETRIES, + tgt->retries)) || + (tgt->burst_duration && + nla_put_u8(msg, NL80211_FTM_TARGET_ATTR_BURST_DURATION, + tgt->burst_duration)) || + (tgt->query_lci && + nla_put_flag(msg, NL80211_FTM_TARGET_ATTR_QUERY_LCI)) || + (tgt->query_civic && + nla_put_flag(msg, NL80211_FTM_TARGET_ATTR_QUERY_CIVIC))) + return -ENOBUFS; + + if (tgt->one_sided) { + if (nla_put_flag(msg, NL80211_FTM_TARGET_ATTR_ONE_SIDED)) + return -ENOBUFS; + } else if (tgt->asap) { + if (nla_put_flag(msg, NL80211_FTM_TARGET_ATTR_ASAP)) + return -ENOBUFS; + } + + return 0; +} + +static int parse_ftm_target(char *str, struct ftm_target *target) +{ + int res, consumed; + char bw[6] = {0}, *pos, *tmp, *save_ptr, *delims = " \t\n"; + + res = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx bw=%5s cf=%d%n", + &target->target[0], &target->target[1], &target->target[2], + &target->target[3], &target->target[4], &target->target[5], + bw, &target->freq, &consumed); + + if (res != 8) + return -1; + + target->bw = str_to_bw(bw); + + str += consumed; + pos = strtok_r(str, delims, &save_ptr); + + while (pos) { + if (strncmp(pos, "cf1=", 4) == 0) { + target->center_freq1 = strtol(pos + 4, &tmp, 10); + if (*tmp) { + printf("Invalid cf1 value!\n"); + return -1; + } + } else if (strncmp(pos, "cf2=", 4) == 0) { + target->center_freq2 = strtol(pos + 4, &tmp, 10); + if (*tmp) { + printf("Invalid cf2 value!\n"); + return -1; + } + } else if (strncmp(pos, "bursts_exp=", 11) == 0) { + target->num_of_bursts_exp = strtol(pos + 11, &tmp, 10); + if (*tmp) { + printf("Invalid bursts_exp value!\n"); + return -1; + } + } else if (strncmp(pos, "burst_period=", 13) == 0) { + target->burst_period= strtol(pos + 13, &tmp, 10); + if (*tmp) { + printf("Invalid burst_period value!\n"); + return -1; + } + } else if (strncmp(pos, "retries=", 8) == 0) { + target->retries = strtol(pos + 8, &tmp, 10); + if (*tmp) { + printf("Invalid retries value!\n"); + return -1; + } + } else if (strncmp(pos, "burst_duration=", 15) == 0) { + target->burst_duration = strtol(pos + 15, &tmp, 10); + if (*tmp) { + printf("Invalid burst_duration value!\n"); + return -1; + } + } else if (strncmp(pos, "spb=", 4) == 0) { + target->samples_per_burst = strtol(pos + 4, &tmp, 10); + if (tmp - pos <= 4) { + printf("Invalid spb value!\n"); + return -1; + } + } else if (strcmp(pos, "one-sided") == 0) { + target->one_sided = 1; + } else if (strcmp(pos, "asap") == 0) { + target->asap = 1; + } else if (strcmp(pos, "civic") == 0) { + target->query_civic = 1; + } else if (strcmp(pos, "lci") == 0) { + target->query_lci = 1; + } else { + printf("Unknown parameter %s\n", pos); + return -1; + } + + pos = strtok_r(NULL, delims, &save_ptr); + } + + return 0; +} + +static int parse_ftm_mac_rand(char *str, __u8 *template, __u8 *mask) +{ + int res; + char macbuf[6 * 3]; + char macmask[6 * 3]; + + res = sscanf(str, "template=%s mask=%s", macbuf, macmask); + if (res != 2) + return 0; + + if (mac_addr_a2n(template, macbuf)) + return -1; + + if (mac_addr_a2n(mask, macmask)) + return -1; + + /* Don't randomize the MC bit */ + if (!(mask[0] & 0x01)) { + printf("The MC bit must not be randomized"); + return -1; + } + + return 1; +} + +static int parse_ftm_config(const char *conf_file, struct ftm_target **ptargets, + __u8 *template, __u8 *mask) +{ + FILE *input; + char line[256]; + int i, line_num; + struct ftm_target *targets = NULL, *tmp; + int res, mac_set = 0; + + input = fopen(conf_file, "r"); + if (!input) { + printf("The given path does not exist!\n"); + return -1; + } + + for (i = 0, line_num = 1; fgets(line, sizeof(line), input); line_num++) { + struct ftm_target tgt = {0}; + + if (strncmp(line, "#", 1) == 0) + continue; + + res = parse_ftm_mac_rand(line, template, mask); + if (res < 0 || (res > 0 && mac_set)) { + printf("Invalid FTM configuration at line %d!\n", + line_num); + free(targets); + return -1; + } + + if (res > 0) { + mac_set = 1; + continue; + } + + if (parse_ftm_target(line, &tgt)) { + printf("Invalid FTM configuration at line %d!\n", + line_num); + free(targets); + return -1; + } + + i++; + tmp = realloc(targets, i * sizeof(struct ftm_target)); + if (!tmp) { + printf("Failed to allocate targets\n"); + free(targets); + return -1; + } + targets = tmp; + targets[i - 1] = tgt; + } + + *ptargets = targets; + return i; +} + +static int handle_ftm_req(struct nl80211_state *state, struct nl_msg *msg, + int argc, char **argv, enum id_input id) +{ + int err; + static char *req_argv[4] = { + NULL, + "measurement", + "ftm_request_send", + NULL, + }; + static const __u32 cmds[] = { + NL80211_CMD_MSRMENT_REQUEST, + NL80211_CMD_MSRMENT_RESPONSE, + }; + struct print_event_args printargs = { }; + + if (argc != 4) + return 1; + + req_argv[0] = argv[0]; + req_argv[3] = argv[3]; + + err = handle_cmd(state, id, 4, req_argv); + if (err) + return err; + + __do_listen_events(state, ARRAY_SIZE(cmds), cmds, &printargs); + return 0; +} + +static int handle_ftm_req_send(struct nl80211_state *state, struct nl_msg *msg, + int argc, char **argv, enum id_input id) +{ + struct nlattr *nl_ftm_req, *nl_targets, *nl_target; + __u8 macaddr[ETH_ALEN] = {0}; + /* Dont randomize the MC bit */ + __u8 macaddr_mask[ETH_ALEN] = {0x01, }; + struct ftm_target *targets; + int i, num_targets = 0; + + if (argc != 1) + return 1; + + num_targets = parse_ftm_config(argv[0], &targets, macaddr, macaddr_mask); + if (num_targets <= 0) + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_MSRMENT_TYPE, NL80211_MSRMENT_TYPE_FTM); + + nl_ftm_req = nla_nest_start(msg, NL80211_ATTR_MSRMENT_FTM_REQUEST); + if (!nl_ftm_req) + goto nla_put_failure; + + NLA_PUT(msg, NL80211_FTM_REQ_ATTR_MACADDR_TEMPLATE, ETH_ALEN, macaddr); + NLA_PUT(msg, NL80211_FTM_REQ_ATTR_MACADDR_MASK, ETH_ALEN, macaddr_mask); + + nl_targets = nla_nest_start(msg, NL80211_FTM_REQ_ATTR_TARGETS); + if (!nl_targets) + goto nla_put_failure; + + for (i = 0; i < num_targets; i++) { + nl_target = nla_nest_start(msg, i); + if (!nl_target || ftm_put_target(msg, &targets[i])) + goto nla_put_failure; + nla_nest_end(msg, nl_target); + } + + nla_nest_end(msg, nl_targets); + nla_nest_end(msg, nl_ftm_req); + + free(targets); + return 0; + +nla_put_failure: + free(targets); + return -ENOBUFS; +} +COMMAND(measurement, ftm_request, "", 0, 0, + CIB_NETDEV, handle_ftm_req, + "Send an FTM request to the targets supplied in the config file.\n" + "Each line in the file represents a target, with the following format:\n" + " bw=<[20|40|80|80+80|160]> cf= [cf1=] [cf2=] [spb=] [one-sided] [asap] [bursts_exp=] [burst_period=] [retries=] [burst_duration=] [civic] [lci]\n" + "To set the MAC address randomization template and mask, add the line:\n" + "template= mask="); +HIDDEN(measurement, ftm_request_send, "", NL80211_CMD_MSRMENT_REQUEST, 0, + CIB_NETDEV, handle_ftm_req_send); diff --git a/mesh.c b/mesh.c new file mode 100644 index 0000000..97f236b --- /dev/null +++ b/mesh.c @@ -0,0 +1,585 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include "nl80211.h" +#include "iw.h" + +SECTION(mesh); + + +typedef struct _any_t { + union { + uint32_t as_32; + int32_t as_s32; + uint16_t as_16; + uint8_t as_8; + } u; +} _any; + +/* describes a mesh parameter */ +struct mesh_param_descr { + const char *name; + enum nl80211_meshconf_params mesh_param_num; + int (*nla_put_fn)(struct nl_msg*, int, _any*); + uint32_t (*parse_fn)(const char*, _any*); + void (*nla_print_fn)(struct nlattr *); +}; + +/* utility functions for manipulating and printing u8/u16/u32 values and + * timesouts. */ +static int _my_nla_put_u8(struct nl_msg *n, int mesh_param_num, _any *value) +{ + return nla_put(n, mesh_param_num, sizeof(uint8_t), &value->u.as_8); +} + +static int _my_nla_put_u16(struct nl_msg *n, int mesh_param_num, _any *value) +{ + return nla_put(n, mesh_param_num, sizeof(uint16_t), &value->u.as_16); +} + +static int _my_nla_put_u32(struct nl_msg *n, int mesh_param_num, _any *value) +{ + return nla_put(n, mesh_param_num, sizeof(uint32_t), &value->u.as_32); +} + +static uint32_t _parse_u8(const char *str, _any *ret) +{ + char *endptr = NULL; + unsigned long int v = strtoul(str, &endptr, 10); + if (*endptr != '\0') + return 0xff; + if (v > 0xff) + return 0xff; + ret->u.as_8 = (uint8_t)v; + return 0; +} + +static uint32_t _parse_u8_as_bool(const char *str, _any *ret) +{ + char *endptr = NULL; + unsigned long int v = strtoul(str, &endptr, 10); + if (*endptr != '\0') + return 0x1; + if (v > 0x1) + return 0x1; + ret->u.as_8 = (uint8_t)v; + return 0; +} + +static uint32_t _parse_u16(const char *str, _any *ret) +{ + char *endptr = NULL; + long int v = strtol(str, &endptr, 10); + if (*endptr != '\0') + return 0xffff; + if ((v < 0) || (v > 0xffff)) + return 0xffff; + ret->u.as_16 = (uint16_t)v; + return 0; +} + +static uint32_t _parse_u32(const char *str, _any *ret) +{ + char *endptr = NULL; + long long int v = strtoll(str, &endptr, 10); + if (*endptr != '\0') + return 0xffffffff; + if ((v < 0) || (v > 0xffffffff)) + return 0xffffffff; + ret->u.as_32 = (uint32_t)v; + return 0; +} + +static uint32_t _parse_s32(const char *str, _any *ret) +{ + char *endptr = NULL; + long int v = strtol(str, &endptr, 10); + if (*endptr != '\0') + return 0xffffffff; + if (v > 0xff) + return 0xffffffff; + ret->u.as_s32 = (int32_t)v; + return 0; +} + +static uint32_t _parse_u32_power_mode(const char *str, _any *ret) +{ + unsigned long int v; + + /* Parse attribute for the name of power mode */ + if (!strcmp(str, "active")) + v = NL80211_MESH_POWER_ACTIVE; + else if (!strcmp(str, "light")) + v = NL80211_MESH_POWER_LIGHT_SLEEP; + else if (!strcmp(str, "deep")) + v = NL80211_MESH_POWER_DEEP_SLEEP; + else + return 0xff; + + ret->u.as_32 = (uint32_t)v; + return 0; +} + +static void _print_u8(struct nlattr *a) +{ + printf("%d", nla_get_u8(a)); +} + +static void _print_u16(struct nlattr *a) +{ + printf("%d", nla_get_u16(a)); +} + +static void _print_u16_timeout(struct nlattr *a) +{ + printf("%d milliseconds", nla_get_u16(a)); +} + +static void _print_u16_in_TUs(struct nlattr *a) +{ + printf("%d TUs", nla_get_u16(a)); +} + +static void _print_u32(struct nlattr *a) +{ + printf("%d", nla_get_u32(a)); +} + +static void _print_u32_timeout(struct nlattr *a) +{ + printf("%u milliseconds", nla_get_u32(a)); +} + +static void _print_u32_in_seconds(struct nlattr *a) +{ + printf("%d seconds", nla_get_u32(a)); +} + +static void _print_u32_in_TUs(struct nlattr *a) +{ + printf("%d TUs", nla_get_u32(a)); +} + +static void _print_u32_power_mode(struct nlattr *a) +{ + unsigned long v = nla_get_u32(a); + + switch (v) { + case NL80211_MESH_POWER_ACTIVE: + printf("active"); + break; + case NL80211_MESH_POWER_LIGHT_SLEEP: + printf("light"); + break; + case NL80211_MESH_POWER_DEEP_SLEEP: + printf("deep"); + break; + default: + printf("undefined"); + break; + } +} + +static void _print_s32_in_dBm(struct nlattr *a) +{ + printf("%d dBm", (int32_t) nla_get_u32(a)); +} + + +/* The current mesh parameters */ +const static struct mesh_param_descr _mesh_param_descrs[] = +{ + {"mesh_retry_timeout", + NL80211_MESHCONF_RETRY_TIMEOUT, + _my_nla_put_u16, _parse_u16, _print_u16_timeout}, + {"mesh_confirm_timeout", + NL80211_MESHCONF_CONFIRM_TIMEOUT, + _my_nla_put_u16, _parse_u16, _print_u16_timeout}, + {"mesh_holding_timeout", + NL80211_MESHCONF_HOLDING_TIMEOUT, + _my_nla_put_u16, _parse_u16, _print_u16_timeout}, + {"mesh_max_peer_links", + NL80211_MESHCONF_MAX_PEER_LINKS, + _my_nla_put_u16, _parse_u16, _print_u16}, + {"mesh_max_retries", + NL80211_MESHCONF_MAX_RETRIES, + _my_nla_put_u8, _parse_u8, _print_u8}, + {"mesh_ttl", + NL80211_MESHCONF_TTL, + _my_nla_put_u8, _parse_u8, _print_u8}, + {"mesh_element_ttl", + NL80211_MESHCONF_ELEMENT_TTL, + _my_nla_put_u8, _parse_u8, _print_u8}, + {"mesh_auto_open_plinks", + NL80211_MESHCONF_AUTO_OPEN_PLINKS, + _my_nla_put_u8, _parse_u8_as_bool, _print_u8}, + {"mesh_hwmp_max_preq_retries", + NL80211_MESHCONF_HWMP_MAX_PREQ_RETRIES, + _my_nla_put_u8, _parse_u8, _print_u8}, + {"mesh_path_refresh_time", + NL80211_MESHCONF_PATH_REFRESH_TIME, + _my_nla_put_u32, _parse_u32, _print_u32_timeout}, + {"mesh_min_discovery_timeout", + NL80211_MESHCONF_MIN_DISCOVERY_TIMEOUT, + _my_nla_put_u16, _parse_u16, _print_u16_timeout}, + {"mesh_hwmp_active_path_timeout", + NL80211_MESHCONF_HWMP_ACTIVE_PATH_TIMEOUT, + _my_nla_put_u32, _parse_u32, _print_u32_in_TUs}, + {"mesh_hwmp_preq_min_interval", + NL80211_MESHCONF_HWMP_PREQ_MIN_INTERVAL, + _my_nla_put_u16, _parse_u16, _print_u16_in_TUs}, + {"mesh_hwmp_net_diameter_traversal_time", + NL80211_MESHCONF_HWMP_NET_DIAM_TRVS_TIME, + _my_nla_put_u16, _parse_u16, _print_u16_in_TUs}, + {"mesh_hwmp_rootmode", NL80211_MESHCONF_HWMP_ROOTMODE, + _my_nla_put_u8, _parse_u8, _print_u8}, + {"mesh_hwmp_rann_interval", NL80211_MESHCONF_HWMP_RANN_INTERVAL, + _my_nla_put_u16, _parse_u16, _print_u16_in_TUs}, + {"mesh_gate_announcements", NL80211_MESHCONF_GATE_ANNOUNCEMENTS, + _my_nla_put_u8, _parse_u8, _print_u8}, + {"mesh_fwding", NL80211_MESHCONF_FORWARDING, + _my_nla_put_u8, _parse_u8_as_bool, _print_u8}, + {"mesh_sync_offset_max_neighor", + NL80211_MESHCONF_SYNC_OFFSET_MAX_NEIGHBOR, + _my_nla_put_u32, _parse_u32, _print_u32}, + {"mesh_rssi_threshold", NL80211_MESHCONF_RSSI_THRESHOLD, + _my_nla_put_u32, _parse_s32, _print_s32_in_dBm}, + {"mesh_hwmp_active_path_to_root_timeout", + NL80211_MESHCONF_HWMP_PATH_TO_ROOT_TIMEOUT, + _my_nla_put_u32, _parse_u32, _print_u32_in_TUs}, + {"mesh_hwmp_root_interval", NL80211_MESHCONF_HWMP_ROOT_INTERVAL, + _my_nla_put_u16, _parse_u16, _print_u16_in_TUs}, + {"mesh_hwmp_confirmation_interval", + NL80211_MESHCONF_HWMP_CONFIRMATION_INTERVAL, + _my_nla_put_u16, _parse_u16, _print_u16_in_TUs}, + {"mesh_power_mode", NL80211_MESHCONF_POWER_MODE, + _my_nla_put_u32, _parse_u32_power_mode, _print_u32_power_mode}, + {"mesh_awake_window", NL80211_MESHCONF_AWAKE_WINDOW, + _my_nla_put_u16, _parse_u16, _print_u16_in_TUs}, + {"mesh_plink_timeout", NL80211_MESHCONF_PLINK_TIMEOUT, + _my_nla_put_u32, _parse_u32, _print_u32_in_seconds}, +}; + +static void print_all_mesh_param_descr(void) +{ + unsigned int i; + + printf("Possible mesh parameters are:\n"); + + for (i = 0; i < ARRAY_SIZE(_mesh_param_descrs); i++) + printf(" - %s\n", _mesh_param_descrs[i].name); +} + +static const struct mesh_param_descr *find_mesh_param(const char *name) +{ + unsigned int i; + + /* Find out what mesh parameter we want to change. */ + for (i = 0; i < ARRAY_SIZE(_mesh_param_descrs); i++) { + if (strcmp(_mesh_param_descrs[i].name, name) == 0) + return _mesh_param_descrs + i; + } + + return NULL; +} + +/* Setter */ +static int set_interface_meshparam(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + const struct mesh_param_descr *mdescr; + struct nlattr *container; + uint32_t ret; + int err; + + container = nla_nest_start(msg, NL80211_ATTR_MESH_PARAMS); + if (!container) + return -ENOBUFS; + + if (!argc) { + print_all_mesh_param_descr(); + return 1; + } + + while (argc) { + const char *name; + char *value; + _any any; + + memset(&any, 0, sizeof(_any)); + + name = argv[0]; + value = strchr(name, '='); + if (value) { + *value = '\0'; + value++; + argc--; + argv++; + } else { + /* backward compat -- accept w/o '=' */ + if (argc < 2) { + printf("Must specify a value for %s.\n", name); + return 2; + } + value = argv[1]; + argc -= 2; + argv += 2; + } + + mdescr = find_mesh_param(name); + if (!mdescr) { + printf("Could not find the parameter %s.\n", name); + print_all_mesh_param_descr(); + return 2; + } + + /* Parse the new value */ + ret = mdescr->parse_fn(value, &any); + if (ret != 0) { + if (mdescr->mesh_param_num + == NL80211_MESHCONF_POWER_MODE) + printf("%s must be set to active, light or " + "deep.\n", mdescr->name); + else + printf("%s must be set to a number " + "between 0 and %u\n", + mdescr->name, ret); + + return 2; + } + + err = mdescr->nla_put_fn(msg, mdescr->mesh_param_num, &any); + if (err) + return err; + } + nla_nest_end(msg, container); + + return err; +} + +COMMAND(set, mesh_param, "= [=]*", + NL80211_CMD_SET_MESH_PARAMS, 0, CIB_NETDEV, set_interface_meshparam, + "Set mesh parameter (run command without any to see available ones)."); + +/* Getter */ +static int print_mesh_param_handler(struct nl_msg *msg, void *arg) +{ + const struct mesh_param_descr *mdescr = arg; + struct nlattr *attrs[NL80211_ATTR_MAX + 1]; + struct nlattr *parent_attr; + struct nlattr *mesh_params[NL80211_MESHCONF_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + + /* locate NL80211_ATTR_MESH_PARAMS */ + nla_parse(attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + parent_attr = attrs[NL80211_ATTR_MESH_PARAMS]; + if (!parent_attr) + return -EINVAL; + + /* unpack the mesh parameters */ + if (nla_parse_nested(mesh_params, NL80211_MESHCONF_ATTR_MAX, + parent_attr, NULL)) + return -EINVAL; + + if (!mdescr) { + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(_mesh_param_descrs); i++) { + mdescr = &_mesh_param_descrs[i]; + printf("%s = ", mdescr->name); + mdescr->nla_print_fn(mesh_params[mdescr->mesh_param_num]); + printf("\n"); + } + return NL_SKIP; + } + + /* print out the mesh parameter */ + mdescr->nla_print_fn(mesh_params[mdescr->mesh_param_num]); + printf("\n"); + return NL_SKIP; +} + +static int get_interface_meshparam(struct nl80211_state *state, + struct nl_msg *msg, + int argc, char **argv, + enum id_input id) +{ + const struct mesh_param_descr *mdescr = NULL; + + if (argc == 0) { + print_all_mesh_param_descr(); + return 1; + } else if (argc == 1) { + mdescr = find_mesh_param(argv[0]); + if (!mdescr) { + printf("Could not find the parameter %s.\n", argv[0]); + print_all_mesh_param_descr(); + return 2; + } + } else { + return 1; + } + + register_handler(print_mesh_param_handler, (void *)mdescr); + return 0; +} + +COMMAND(get, mesh_param, "[]", + NL80211_CMD_GET_MESH_PARAMS, 0, CIB_NETDEV, get_interface_meshparam, + "Retrieve mesh parameter (run command without any to see available ones)."); + +static int join_mesh(struct nl80211_state *state, + struct nl_msg *msg, int argc, char **argv, + enum id_input id) +{ + struct nlattr *container; + float rate; + unsigned char rates[NL80211_MAX_SUPP_RATES]; + int bintval, dtim_period, n_rates = 0; + char *end, *value = NULL, *sptr = NULL; + + if (argc < 1) + return 1; + + NLA_PUT(msg, NL80211_ATTR_MESH_ID, strlen(argv[0]), argv[0]); + argc--; + argv++; + + /* freq */ + if (argc > 1 && strcmp(argv[0], "freq") == 0) { + struct chandef chandef; + int err, parsed; + + err = parse_freqchan(&chandef, false, argc - 1, argv + 1, + &parsed); + if (err) + return err; + + argv += parsed + 1; + argc -= parsed + 1; + + put_chandef(msg, &chandef); + if (err) + return err; + } + + /* basic rates */ + if (argc > 1 && strcmp(argv[0], "basic-rates") == 0) { + argv++; + argc--; + + value = strtok_r(argv[0], ",", &sptr); + + while (value && n_rates < NL80211_MAX_SUPP_RATES) { + rate = strtod(value, &end); + rates[n_rates] = rate * 2; + + /* filter out suspicious values */ + if (*end != '\0' || !rates[n_rates] || + rate*2 != rates[n_rates]) + return 1; + + n_rates++; + value = strtok_r(NULL, ",", &sptr); + } + + NLA_PUT(msg, NL80211_ATTR_BSS_BASIC_RATES, n_rates, rates); + argv++; + argc--; + } + + /* multicast rate */ + if (argc > 1 && strcmp(argv[0], "mcast-rate") == 0) { + argv++; + argc--; + + rate = strtod(argv[0], &end); + if (*end != '\0') + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_MCAST_RATE, (int)(rate * 10)); + argv++; + argc--; + } + + if (argc > 1 && strcmp(argv[0], "beacon-interval") == 0) { + argc--; + argv++; + + bintval = strtoul(argv[0], &end, 10); + if (*end != '\0') + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_BEACON_INTERVAL, bintval); + argv++; + argc--; + } + + if (argc > 1 && strcmp(argv[0], "dtim-period") == 0) { + argc--; + argv++; + + dtim_period = strtoul(argv[0], &end, 10); + if (*end != '\0') + return 1; + + NLA_PUT_U32(msg, NL80211_ATTR_DTIM_PERIOD, dtim_period); + argv++; + argc--; + } + + container = nla_nest_start(msg, NL80211_ATTR_MESH_SETUP); + if (!container) + return -ENOBUFS; + + if (argc > 1 && strcmp(argv[0], "vendor_sync") == 0) { + argv++; + argc--; + if (strcmp(argv[0], "on") == 0) + NLA_PUT_U8(msg, + NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC, 1); + else + NLA_PUT_U8(msg, + NL80211_MESH_SETUP_ENABLE_VENDOR_SYNC, 0); + argv++; + argc--; + } + /* parse and put other NL80211_ATTR_MESH_SETUP elements here */ + + nla_nest_end(msg, container); + + if (!argc) + return 0; + return set_interface_meshparam(state, msg, argc, argv, id); + nla_put_failure: + return -ENOBUFS; +} +COMMAND(mesh, join, " [[freq ]" + " [basic-rates ]], [mcast-rate ]" + " [beacon-interval