You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7819 lines
200 KiB
7819 lines
200 KiB
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2013-2019 Intel Corporation. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <net/ethernet.h>
|
|
#include <netinet/ether.h>
|
|
#include <linux/if.h>
|
|
#include <linux/if_packet.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/genetlink.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/filter.h>
|
|
#include <ell/ell.h>
|
|
|
|
#ifndef ARPHRD_NETLINK
|
|
#define ARPHRD_NETLINK 824
|
|
#endif
|
|
|
|
#include "linux/nl80211.h"
|
|
|
|
#include "ell/useful.h"
|
|
#include "src/ie.h"
|
|
#include "src/mpdu.h"
|
|
#include "src/eapol.h"
|
|
#include "src/util.h"
|
|
#include "src/p2putil.h"
|
|
#include "src/nl80211cmd.h"
|
|
#include "monitor/pcap.h"
|
|
#include "monitor/display.h"
|
|
#include "monitor/nlmon.h"
|
|
#include "src/anqputil.h"
|
|
|
|
#define COLOR_TIMESTAMP COLOR_YELLOW
|
|
|
|
#define COLOR_REQUEST COLOR_BLUE
|
|
#define COLOR_RESPONSE COLOR_MAGENTA
|
|
#define COLOR_COMPLETE COLOR_MAGENTA
|
|
#define COLOR_RESULT COLOR_MAGENTA
|
|
#define COLOR_EVENT COLOR_CYAN
|
|
|
|
/* BSS Capabilities */
|
|
#define BSS_CAPABILITY_ESS (1<<0)
|
|
#define BSS_CAPABILITY_IBSS (1<<1)
|
|
#define BSS_CAPABILITY_CF_POLLABLE (1<<2)
|
|
#define BSS_CAPABILITY_CF_POLL_REQUEST (1<<3)
|
|
#define BSS_CAPABILITY_PRIVACY (1<<4)
|
|
#define BSS_CAPABILITY_SHORT_PREAMBLE (1<<5)
|
|
#define BSS_CAPABILITY_PBCC (1<<6)
|
|
#define BSS_CAPABILITY_CHANNEL_AGILITY (1<<7)
|
|
#define BSS_CAPABILITY_SPECTRUM_MGMT (1<<8)
|
|
#define BSS_CAPABILITY_QOS (1<<9)
|
|
#define BSS_CAPABILITY_SHORT_SLOT_TIME (1<<10)
|
|
#define BSS_CAPABILITY_APSD (1<<11)
|
|
#define BSS_CAPABILITY_DSSS_OFDM (1<<13)
|
|
|
|
struct nlmon *cur_nlmon;
|
|
|
|
enum msg_type {
|
|
MSG_REQUEST,
|
|
MSG_RESPONSE,
|
|
MSG_COMPLETE,
|
|
MSG_RESULT,
|
|
MSG_EVENT,
|
|
};
|
|
|
|
struct nlmon {
|
|
uint16_t id;
|
|
struct l_io *io;
|
|
struct l_io *pae_io;
|
|
struct l_queue *req_list;
|
|
struct pcap *pcap;
|
|
bool nortnl;
|
|
bool nowiphy;
|
|
bool noscan;
|
|
bool noies;
|
|
};
|
|
|
|
struct nlmon_req {
|
|
uint32_t seq;
|
|
uint32_t pid;
|
|
uint16_t flags;
|
|
uint8_t cmd;
|
|
uint8_t version;
|
|
};
|
|
|
|
typedef void (*attr_func_t) (unsigned int level, const char *label,
|
|
const void *data, uint16_t size);
|
|
enum attr_type {
|
|
ATTR_UNSPEC,
|
|
ATTR_FLAG,
|
|
ATTR_U8,
|
|
ATTR_U16,
|
|
ATTR_U32,
|
|
ATTR_U64,
|
|
ATTR_S8,
|
|
ATTR_S32,
|
|
ATTR_S64,
|
|
ATTR_STRING,
|
|
ATTR_ADDRESS,
|
|
ATTR_BINARY,
|
|
ATTR_NESTED,
|
|
ATTR_ARRAY,
|
|
ATTR_FLAG_OR_U16,
|
|
ATTR_CUSTOM,
|
|
};
|
|
|
|
struct attr_entry {
|
|
uint16_t attr;
|
|
const char *str;
|
|
enum attr_type type;
|
|
union {
|
|
const struct attr_entry *nested;
|
|
enum attr_type array_type;
|
|
attr_func_t function;
|
|
};
|
|
};
|
|
|
|
static void print_attributes(int indent, const struct attr_entry *table,
|
|
const void *buf, uint32_t len);
|
|
|
|
struct flag_names {
|
|
uint16_t flag;
|
|
const char *name;
|
|
};
|
|
|
|
struct wlan_iface {
|
|
int index;
|
|
};
|
|
|
|
static struct l_hashmap *wlan_iface_list = NULL;
|
|
|
|
static void wlan_iface_list_free(void *data)
|
|
{
|
|
struct wlan_iface *iface = data;
|
|
|
|
l_free(iface);
|
|
}
|
|
|
|
static void nlmon_req_free(void *data)
|
|
{
|
|
struct nlmon_req *req = data;
|
|
|
|
l_free(req);
|
|
}
|
|
|
|
static time_t time_offset = ((time_t) -1);
|
|
|
|
static inline void update_time_offset(const struct timeval *tv)
|
|
{
|
|
if (tv && time_offset == ((time_t) -1))
|
|
time_offset = tv->tv_sec;
|
|
}
|
|
|
|
#define print_indent(indent, color1, prefix, title, color2, fmt, args...) \
|
|
do { \
|
|
printf("%*c%s%s%s%s" fmt "%s\n", (indent), ' ', \
|
|
use_color() ? (color1) : "", prefix, title, \
|
|
use_color() ? (color2) : "", ## args, \
|
|
use_color() ? COLOR_OFF : ""); \
|
|
} while (0)
|
|
|
|
#define print_text(color, fmt, args...) \
|
|
print_indent(4, COLOR_OFF, "", "", color, fmt, ## args)
|
|
|
|
#define print_field(fmt, args...) \
|
|
print_indent(4, COLOR_OFF, "", "", COLOR_OFF, fmt, ## args)
|
|
|
|
#define print_attr(level, fmt, args...) \
|
|
print_indent(4 + (level) * 4, COLOR_OFF, "", "", COLOR_OFF, \
|
|
fmt, ## args)
|
|
|
|
#define print_attr_color(level, color, fmt, args...) \
|
|
print_indent(4 + (level) * 4, COLOR_OFF, "", "", color, \
|
|
fmt, ## args)
|
|
|
|
#define print_space(x) printf("%*c", (x), ' ')
|
|
|
|
static void print_packet(const struct timeval *tv, char ident,
|
|
const char *color, const char *label,
|
|
const char *text, const char *extra)
|
|
{
|
|
int col = num_columns();
|
|
char line[256], ts_str[64];
|
|
int n, ts_len = 0, ts_pos = 0, len = 0, pos = 0;
|
|
|
|
if (tv) {
|
|
if (use_color()) {
|
|
n = sprintf(ts_str + ts_pos, "%s", COLOR_TIMESTAMP);
|
|
if (n > 0)
|
|
ts_pos += n;
|
|
}
|
|
|
|
n = sprintf(ts_str + ts_pos, " %" PRId64 ".%06" PRId64,
|
|
(int64_t)tv->tv_sec - time_offset,
|
|
(int64_t)tv->tv_usec);
|
|
if (n > 0) {
|
|
ts_pos += n;
|
|
ts_len += n;
|
|
}
|
|
}
|
|
|
|
if (use_color()) {
|
|
n = sprintf(ts_str + ts_pos, "%s", COLOR_OFF);
|
|
if (n > 0)
|
|
ts_pos += n;
|
|
}
|
|
|
|
if (use_color()) {
|
|
n = sprintf(line + pos, "%s", color);
|
|
if (n > 0)
|
|
pos += n;
|
|
}
|
|
|
|
n = sprintf(line + pos, "%c %s", ident, label);
|
|
if (n > 0) {
|
|
pos += n;
|
|
len += n;
|
|
}
|
|
|
|
if (text) {
|
|
int extra_len = extra ? strlen(extra) : 0;
|
|
int max_len = col - len - extra_len - ts_len - 3;
|
|
|
|
n = snprintf(line + pos, max_len + 1, ": %s", text);
|
|
if (n > max_len) {
|
|
line[pos + max_len - 1] = '.';
|
|
line[pos + max_len - 2] = '.';
|
|
if (line[pos + max_len - 3] == ' ')
|
|
line[pos + max_len - 3] = '.';
|
|
|
|
n = max_len;
|
|
}
|
|
|
|
if (n > 0) {
|
|
pos += n;
|
|
len += n;
|
|
}
|
|
}
|
|
|
|
if (use_color()) {
|
|
n = sprintf(line + pos, "%s", COLOR_OFF);
|
|
if (n > 0)
|
|
pos += n;
|
|
}
|
|
|
|
if (extra) {
|
|
n = sprintf(line + pos, " %s", extra);
|
|
if (n > 0) {
|
|
pos += n;
|
|
len += n;
|
|
}
|
|
}
|
|
|
|
if (ts_len > 0) {
|
|
printf("%s", line);
|
|
if (len < col)
|
|
print_space(col - len - ts_len - 1);
|
|
printf("%s%s\n", use_color() ? COLOR_TIMESTAMP : "", ts_str);
|
|
} else
|
|
printf("%s\n", line);
|
|
}
|
|
|
|
static void print_hexdump(unsigned int level,
|
|
const unsigned char *buf, uint16_t len)
|
|
{
|
|
static const char hexdigits[] = "0123456789abcdef";
|
|
char str[68];
|
|
uint16_t i;
|
|
|
|
if (!len)
|
|
return;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
str[((i % 16) * 3) + 0] = hexdigits[buf[i] >> 4];
|
|
str[((i % 16) * 3) + 1] = hexdigits[buf[i] & 0xf];
|
|
str[((i % 16) * 3) + 2] = ' ';
|
|
str[(i % 16) + 49] = isprint(buf[i]) ? buf[i] : '.';
|
|
|
|
if ((i + 1) % 16 == 0) {
|
|
str[47] = ' ';
|
|
str[48] = ' ';
|
|
str[65] = '\0';
|
|
print_attr_color(level, COLOR_WHITE, "%s", str);
|
|
str[0] = ' ';
|
|
}
|
|
}
|
|
|
|
if (i % 16 > 0) {
|
|
uint16_t j;
|
|
for (j = (i % 16); j < 16; j++) {
|
|
str[(j * 3) + 0] = ' ';
|
|
str[(j * 3) + 1] = ' ';
|
|
str[(j * 3) + 2] = ' ';
|
|
str[j + 49] = ' ';
|
|
}
|
|
str[47] = ' ';
|
|
str[48] = ' ';
|
|
str[65] = '\0';
|
|
print_attr_color(level, COLOR_WHITE, "%s", str);
|
|
}
|
|
}
|
|
|
|
static void print_address(unsigned int level, const char *label,
|
|
const unsigned char address[6])
|
|
{
|
|
char addr[18];
|
|
|
|
snprintf(addr, sizeof(addr), "%02X:%02X:%02X:%02X:%02X:%02X",
|
|
address[0], address[1], address[2],
|
|
address[3], address[4], address[5]);
|
|
|
|
print_attr(level, "%s %s", label, addr);
|
|
}
|
|
|
|
static const struct {
|
|
const uint8_t oui[3];
|
|
const char *str;
|
|
} oui_table[] = {
|
|
{ { 0x00, 0x03, 0x7f }, "Atheros" },
|
|
{ { 0x00, 0x03, 0x93 }, "Apple" },
|
|
{ { 0x00, 0x04, 0x0e }, "AVM" },
|
|
{ { 0x00, 0x0f, 0xac }, "IEEE 802.11" },
|
|
{ { 0x00, 0x10, 0x18 }, "Broadcom" },
|
|
{ { 0x00, 0x17, 0xf2 }, "Apple" },
|
|
{ { 0x00, 0x40, 0x96 }, "Cisco Systems" },
|
|
{ { 0x00, 0x50, 0xf2 }, "Microsoft" },
|
|
{ { 0x00, 0x90, 0x4c }, "Epigram" },
|
|
{ { 0x50, 0x6f, 0x9a }, "Wi-Fi Alliance" },
|
|
{ }
|
|
};
|
|
|
|
static void print_ie_error(unsigned int level, const char *label,
|
|
uint16_t len, int err)
|
|
{
|
|
print_attr(level, "Error decoding %s IE len %d: %s (%d)", label, len,
|
|
strerror(-err), err);
|
|
}
|
|
|
|
static void print_ie_ssid(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s: %s", label, util_ssid_to_utf8(size, data));
|
|
}
|
|
|
|
static void print_ie_rate(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t *rate = (uint8_t *)data;
|
|
int pos = 0, i = 0;
|
|
char str[128];
|
|
|
|
if (!size) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s:", label);
|
|
|
|
while (i < size) {
|
|
bool mandatory = (rate[i] & 0x80);
|
|
|
|
if (rate[i] == 0xff) {
|
|
print_attr(level + 1, "BSS membership HT_PHY");
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
pos += snprintf(&str[pos], sizeof(str) - pos, "%.1f%s ",
|
|
(rate[i] & 127) * 0.5, mandatory? "(B)": "");
|
|
|
|
i++;
|
|
|
|
if (i % 8 && i != size)
|
|
continue;
|
|
|
|
if (pos) {
|
|
pos += snprintf(&str[pos], sizeof(str) - pos, "Mbit/s");
|
|
print_attr(level + 1, "%s", str);
|
|
pos = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static void print_ie_ds(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t *channel = (uint8_t *)data;
|
|
|
|
if (!size) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: channel %d", label, *channel);
|
|
}
|
|
|
|
static void print_ie_tim(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const char *dtim = data;
|
|
int t, len = size - 3, pos = 0;
|
|
uint8_t bit;
|
|
char str[128];
|
|
|
|
if (size < 4) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s:", label);
|
|
print_attr(level + 1, "DTIM count %2d %s", dtim[0],
|
|
dtim[0] ? "beacon frame(s)" :
|
|
"this beacon frame is DTIM");
|
|
print_attr(level + 1, "DTIM period %2d beacon frame(s)", dtim[1]);
|
|
print_attr(level + 1, "Group buffered %d offset %d",
|
|
!!(dtim[2] & 0x01), dtim[2] >> 1);
|
|
|
|
len = size - 3;
|
|
|
|
for (t = 0; t < len ; t++) {
|
|
if (((t + 1) % 4) == 1) {
|
|
pos = 0;
|
|
pos += snprintf(&str[pos], sizeof(str) - pos,
|
|
"AID %4d - %4d ",
|
|
t * 8 + 1,
|
|
t + 4 > len ? len * 8 : (t + 4) * 8);
|
|
}
|
|
|
|
for (bit = 0x01; bit; bit <<= 1)
|
|
pos += snprintf(&str[pos], sizeof(str) - pos,
|
|
"%d", !!(dtim[t + 3] & bit));
|
|
|
|
pos += snprintf(&str[pos], sizeof(str) - pos, " ");
|
|
|
|
if ((t + 1) % 4 == 0 || t + 1 == len)
|
|
print_attr(level + 1, "%s", str);
|
|
}
|
|
|
|
}
|
|
|
|
static void print_ie_country(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t *code = (uint8_t *)data;
|
|
int i = 3;
|
|
|
|
if (size < 6 || size % 2) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %c%c%c", label, code[0], code[1], code[2]);
|
|
|
|
while (i < size) {
|
|
if (code[i] > 200) {
|
|
print_attr(level + 1, "Regulatory ID %3d class %3d "
|
|
"coverage class %3d",
|
|
code[i], code[i + 1], code[i + 2]);
|
|
|
|
if (code[i + 2] < 32)
|
|
print_attr(level + 1, "%27c (air propagation "
|
|
"time %2d µs)", ' ', 3 * code[i + 2]);
|
|
} else {
|
|
print_attr(level + 1, "First channel %3d number of "
|
|
"channels %2d max tx power %2d dBm",
|
|
code[i], code[i + 1], code[i + 2]);
|
|
}
|
|
|
|
i += 3;
|
|
}
|
|
}
|
|
|
|
static void print_ie_bss_load(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t stations, capacity;
|
|
uint8_t utilization;
|
|
const uint8_t *bytes = data;
|
|
|
|
if (size != 5) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
stations = bytes[0] | bytes[1] << 8;
|
|
utilization = bytes[2];
|
|
capacity = bytes[3] | bytes[4] << 8;
|
|
|
|
print_attr(level, "%s: %2d station(s) utilization %d/255 available "
|
|
"capacity %d 32µs/s units",
|
|
label, stations, utilization, capacity);
|
|
}
|
|
|
|
static void print_ie_power_constraint(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t *dB = (uint8_t *)data;
|
|
|
|
if (!size) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %2d dB", label, *dB);
|
|
}
|
|
|
|
static void print_ie_tpc(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
signed char *dB = (signed char*)data;
|
|
|
|
if (size != 2) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: transmit power %2d dB link margin %2d dB",
|
|
label, dB[0], dB[1]);
|
|
}
|
|
|
|
static void print_ie_erp(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t *flags = (uint8_t *)data;
|
|
|
|
if (!size) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s:", label);
|
|
print_attr(level + 1, "non-ERP present %d", !!(*flags & 0x01));
|
|
print_attr(level + 1, "use protection %d", !!(*flags & 0x02));
|
|
print_attr(level + 1, "Barker preamble mode %d", !!(*flags & 0x04));
|
|
}
|
|
|
|
struct cipher_suites {
|
|
uint32_t cipher;
|
|
const char *str;
|
|
};
|
|
|
|
static const struct cipher_suites rsn_cipher_selectors[] = {
|
|
{ 0x000fac00, "Use group cipher suite" },
|
|
{ 0x000fac01, "WEP-40" },
|
|
{ 0x000fac02, "TKIP" },
|
|
{ 0x000fac04, "CCMP" },
|
|
{ 0x000fac05, "WEP-104" },
|
|
{ 0x000fac06, "BIP" },
|
|
{ 0x000fac07, "Group traffic not allowed" },
|
|
{ 0x00147201, "WPI-SMS4" },
|
|
{ },
|
|
};
|
|
|
|
static const struct cipher_suites rsn_akm_selectors[] = {
|
|
{ 0x000fac01, "IEEE 802.1X/PMKSA; RSNA/PMKSA caching" },
|
|
{ 0x000fac02, "PSK; RSNA PSK" },
|
|
{ 0x000fac03, "IEEE 802.1X FT; FT" },
|
|
{ 0x000fac04, "PSK FT; FT" },
|
|
{ 0x000fac05, "IEEE 802.1X/PMKSA caching SHA256; RSNA/RSNA caching SHA256" },
|
|
{ 0x000fac06, "PSK SHA256; RSNA PSK SHA256" },
|
|
{ 0x000fac07, "TDLS; TPK" },
|
|
{ 0x000fac08, "SAE/PMKSA caching SHA256; RSNA PMKSA caching SHA256/mesh peering exchange" },
|
|
{ 0x000fac09, "FT SAE SHA256; FT" },
|
|
{ 0x000fac0e, "FILS SHA256" },
|
|
{ 0x000fac0f, "FILS SHA384" },
|
|
{ 0x000fac10, "FILS FT SHA256" },
|
|
{ 0x000fac11, "FILS FT SHA3854" },
|
|
{ 0x000fac12, "OWE" },
|
|
{ 0x506f9a01, "WFA OSEN" },
|
|
{ }
|
|
};
|
|
|
|
static const struct cipher_suites wpa_cipher_selectors[] = {
|
|
{ 0x0050f200, "Use group cipher suite" },
|
|
{ 0x0050f201, "WEP-40" },
|
|
{ 0x0050f202, "TKIP" },
|
|
{ 0x0050f204, "CCMP" },
|
|
{ 0x0050f205, "WEP-104" },
|
|
{ },
|
|
};
|
|
|
|
static const struct cipher_suites wpa_akm_selectors[] = {
|
|
{ 0x0050f201, "IEEE 802.1X/PMKSA; RSNA/PMKSA caching" },
|
|
{ 0x0050f202, "PSK; RSNA PSK" },
|
|
{ }
|
|
};
|
|
|
|
static void print_ie_cipher_suite(unsigned int level, const char *label,
|
|
const uint32_t cipher,
|
|
const struct cipher_suites cipher_table[])
|
|
{
|
|
const char *str = NULL;
|
|
unsigned int i;
|
|
unsigned char oui[] = {
|
|
(cipher & 0xff000000) >> 24,
|
|
(cipher & 0x00ff0000) >> 16,
|
|
(cipher & 0x0000ff00) >> 8,
|
|
};
|
|
char suite_value[32] = "";
|
|
|
|
for (i = 0; cipher_table[i].str; i++) {
|
|
if (cipher_table[i].cipher == cipher) {
|
|
str = cipher_table[i].str;
|
|
snprintf(suite_value, sizeof(suite_value), " %02x",
|
|
cipher & 0x000000ff);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!str) {
|
|
for (i = 0; oui_table[i].str; i++) {
|
|
if (!memcmp(oui_table[i].oui, oui, 3)) {
|
|
str = oui_table[i].str;
|
|
snprintf(suite_value, sizeof(suite_value),
|
|
" %02x (vendor specific)",
|
|
cipher & 0x000000ff);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!str) {
|
|
str = "unknown";
|
|
snprintf(suite_value, sizeof(suite_value), "%02x (unknown)",
|
|
cipher & 0x000000ff);
|
|
}
|
|
|
|
if (label)
|
|
print_attr(level, "%s: %s (%02x:%02x:%02x) suite %s",
|
|
label, str, oui[0], oui[1], oui[2], suite_value);
|
|
else
|
|
print_attr(level, "%s (%02x:%02x:%02x) suite %s",
|
|
str, oui[0], oui[1], oui[2], suite_value);
|
|
}
|
|
|
|
static void print_ie_cipher_suites(unsigned int level, const char *label,
|
|
const void *data, uint16_t size,
|
|
const struct cipher_suites cipher_table[])
|
|
{
|
|
uint32_t cipher;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
while (size >= 4) {
|
|
cipher = l_get_be32(data);
|
|
|
|
print_ie_cipher_suite(level + 1, NULL, cipher, cipher_table);
|
|
|
|
data += 4;
|
|
size -= 4;
|
|
}
|
|
}
|
|
|
|
static const char *rsn_capabilities_bitfield[] = {
|
|
"Preauthentication",
|
|
"No Pairwise",
|
|
"",
|
|
"",
|
|
"",
|
|
"",
|
|
"Management Frame Protection Required",
|
|
"Management Frame Protection Capable",
|
|
"Reserved",
|
|
"Peerkey Enabled",
|
|
"SPP A-MSDU Capable",
|
|
"SPP A-MSDU Required",
|
|
"PBAC",
|
|
"Extended Key ID for Individually Addressed Frames",
|
|
"Reserved",
|
|
"Reserved",
|
|
NULL
|
|
};
|
|
|
|
static void print_ie_bitfield(unsigned int level, const char *label,
|
|
const uint8_t *bytes, const uint8_t *mask, size_t len,
|
|
const char *bitfield_table[])
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < len * 8; i++) {
|
|
uint8_t byte = i / 8;
|
|
uint8_t bit = i % 8;
|
|
uint8_t masked = bytes[byte] & mask[byte];
|
|
|
|
if (!test_bit(&masked, bit))
|
|
continue;
|
|
|
|
print_attr(level, "%s: bit %2d: %s", label, i,
|
|
bitfield_table[i]);
|
|
}
|
|
}
|
|
|
|
static size_t print_ie_rsn_suites(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t count;
|
|
uint16_t orig_size = size;
|
|
|
|
print_ie_cipher_suites(level + 1, "Group Data Cipher Suite", data, 4,
|
|
rsn_cipher_selectors);
|
|
|
|
data += 4;
|
|
size -= 4;
|
|
|
|
if (size < 2)
|
|
goto end;
|
|
|
|
count = l_get_le16(data) * 4;
|
|
data += 2;
|
|
size -= 2;
|
|
|
|
if (size < count)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "Pairwise Cipher Suite", data,
|
|
count, rsn_cipher_selectors);
|
|
data += count;
|
|
size -= count;
|
|
|
|
if (size < 2)
|
|
goto end;
|
|
|
|
count = l_get_le16(data) * 4;
|
|
data += 2;
|
|
size -= 2;
|
|
|
|
if (size < count)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "AKM Suite", data, count,
|
|
rsn_akm_selectors);
|
|
data += count;
|
|
size -= count;
|
|
|
|
if (size < 2)
|
|
goto end;
|
|
|
|
return orig_size - size;
|
|
|
|
end:
|
|
if (size)
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
|
|
return orig_size - size;
|
|
}
|
|
|
|
static void print_ie_rsn(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const void *end = data + size;
|
|
|
|
uint16_t version, count;
|
|
uint8_t bytemask[2];
|
|
int i;
|
|
const char *rsn_capabilities_replay_counter[] = {
|
|
"1 replay counter",
|
|
"2 replay counters",
|
|
"4 replay counters",
|
|
"16 replay counters"
|
|
};
|
|
|
|
print_attr(level, "RSN:");
|
|
|
|
if (end - data < 2) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
version = l_get_le16(data);
|
|
if (version != 1) {
|
|
print_attr(level, "Unknown RSN version %d", version);
|
|
return;
|
|
}
|
|
|
|
data += 2;
|
|
|
|
if (end - data < 4)
|
|
goto end;
|
|
|
|
data += print_ie_rsn_suites(level, label, data, size);
|
|
|
|
bytemask[0] = 0x03;
|
|
bytemask[1] = 0x00;
|
|
print_ie_bitfield(level + 1, "RSN capabilities", data, bytemask,
|
|
sizeof(bytemask), rsn_capabilities_bitfield);
|
|
|
|
count = (*((uint8_t *)data) & 0x0c) >> 2;
|
|
print_attr(level + 1, "RSN capabilities: bits 2 - 3: %s per PTKSA",
|
|
rsn_capabilities_replay_counter[count]);
|
|
|
|
count = (*((uint8_t *)data) & 0x30) >> 4;
|
|
print_attr(level + 1, "RSN capabilities: bits 4 - 5: %s per GTKSA",
|
|
rsn_capabilities_replay_counter[count]);
|
|
|
|
bytemask[0] = 0xc0;
|
|
bytemask[1] = 0xff;
|
|
print_ie_bitfield(level + 1, "RSN capabilities", data, bytemask,
|
|
sizeof(bytemask), rsn_capabilities_bitfield);
|
|
|
|
data += 2;
|
|
|
|
if (end - data < 2)
|
|
goto end;
|
|
|
|
count = l_get_le16(data) * 16;
|
|
data += 2;
|
|
|
|
if (end - data < count)
|
|
goto end;
|
|
|
|
for (i = 0; i < count; i += 16) {
|
|
const uint8_t *bytes = data;
|
|
|
|
print_attr(level + 1, "PMKID: %02x:%02x:%02x:%02x:"
|
|
"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
|
|
"%02x:%02x:%02x:%02x",
|
|
bytes[i], bytes[i + 1],
|
|
bytes[i + 2], bytes[i + 3],
|
|
bytes[i + 4], bytes[i + 5],
|
|
bytes[i + 6], bytes[i + 7],
|
|
bytes[i + 8], bytes[i + 9],
|
|
bytes[i + 10], bytes[i + 11],
|
|
bytes[i + 12], bytes[i + 13],
|
|
bytes[i + 14], bytes[i + 15]);
|
|
}
|
|
|
|
data += count;
|
|
|
|
if (end - data < 4)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "Group Management Cipher Suite",
|
|
data, 4, rsn_cipher_selectors);
|
|
|
|
data += 4;
|
|
|
|
end:
|
|
if (end - data)
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
}
|
|
|
|
static void print_ie_wpa(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t offset;
|
|
uint16_t version, count;
|
|
|
|
if (size < 2)
|
|
return;
|
|
|
|
offset = 0;
|
|
version = l_get_le16(data + offset);
|
|
offset += 2;
|
|
|
|
if (version != 1)
|
|
return;
|
|
|
|
print_attr(level, "WPA:");
|
|
print_attr(level + 1, "Version: %d(%04x)", version, version);
|
|
|
|
if (offset + 4 > size)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "Group Data Cipher Suite",
|
|
data + offset, 4, wpa_cipher_selectors);
|
|
|
|
offset += 4;
|
|
if (offset + 2 > size)
|
|
goto end;
|
|
|
|
count = l_get_le16(data + offset) * 4;
|
|
offset += 2;
|
|
|
|
if (offset + count > size)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "Pairwise Cipher Suite",
|
|
data + offset, count, wpa_cipher_selectors);
|
|
offset += count;
|
|
if (offset + 2 > size)
|
|
goto end;
|
|
|
|
count = l_get_le16(data + offset) * 4;
|
|
offset += 2;
|
|
if (offset + count > size)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "AKM Suite", data + offset, count,
|
|
wpa_akm_selectors);
|
|
return;
|
|
|
|
end:
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
}
|
|
|
|
static void print_ie_wfa_hs20(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *ptr = data;
|
|
bool pps_mo_id_present;
|
|
bool anpq_domain_id_present;
|
|
|
|
if (size < 1)
|
|
return;
|
|
|
|
pps_mo_id_present = test_bit(ptr, 1);
|
|
anpq_domain_id_present = test_bit(ptr, 2);
|
|
|
|
print_attr(level + 1, "HS2.0 Indication Element:");
|
|
print_attr(level + 2, "DGAF Disabled: %u", test_bit(ptr, 0));
|
|
print_attr(level + 2, "PPS MO ID Present: %u", pps_mo_id_present);
|
|
print_attr(level + 2, "ANQP Domain ID Present: %u",
|
|
anpq_domain_id_present);
|
|
|
|
switch (bit_field(ptr[0], 4, 7)) {
|
|
case 0:
|
|
print_attr(level + 2, "Version Number: 1.x");
|
|
break;
|
|
case 1:
|
|
print_attr(level + 2, "Version Number: 2.x");
|
|
break;
|
|
case 2:
|
|
print_attr(level + 2, "Version Number: 3.x");
|
|
break;
|
|
}
|
|
|
|
ptr += 1;
|
|
size -= 1;
|
|
|
|
if (pps_mo_id_present) {
|
|
if (size < 2)
|
|
return;
|
|
|
|
print_attr(level + 2, "PPS MO ID: %02x %02x", ptr[0], ptr[1]);
|
|
ptr += 2;
|
|
size -= 2;
|
|
}
|
|
|
|
if (anpq_domain_id_present) {
|
|
if (size < 2)
|
|
return;
|
|
|
|
print_attr(level + 2, "ANQP Domain ID: %02x %02x",
|
|
ptr[0], ptr[1]);
|
|
}
|
|
}
|
|
|
|
static bool print_oui(unsigned int level, const uint8_t *oui)
|
|
{
|
|
const char *str = NULL;
|
|
unsigned int i;
|
|
|
|
for (i = 0; oui_table[i].str; i++) {
|
|
if (!memcmp(oui_table[i].oui, oui, 3)) {
|
|
str = oui_table[i].str;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!str) {
|
|
print_attr(level + 1, "OUI: %02x:%02x:%02x type:%02x",
|
|
oui[0], oui[1], oui[2],
|
|
oui[3]);
|
|
return false;
|
|
}
|
|
|
|
print_attr(level + 1, "%s (%02x:%02x:%02x) type: %02x", str,
|
|
oui[0], oui[1], oui[2],
|
|
oui[3]);
|
|
return true;
|
|
}
|
|
|
|
static void print_ipv4(unsigned int level, const char *label,
|
|
const uint8_t *addr)
|
|
{
|
|
print_attr(level, "%s: %u.%u.%u.%u", label,
|
|
addr[0], addr[1], addr[2], addr[3]);
|
|
}
|
|
|
|
static void print_ie_vendor(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *oui = data;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size < 4)
|
|
return;
|
|
|
|
if (!print_oui(level, oui))
|
|
return;
|
|
|
|
data += 4;
|
|
size -= 4;
|
|
|
|
if (!memcmp(oui, microsoft_oui, 3)) {
|
|
switch (oui[3]) {
|
|
case 1: /* MSoft WPA IE */
|
|
print_ie_wpa(level + 2, label, data, size);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
} else if (!memcmp(oui, wifi_alliance_oui, 3)) {
|
|
switch (oui[3]) {
|
|
case 0x04:
|
|
print_attr(level + 1, "IP Address Request KDE");
|
|
return;
|
|
case 0x05:
|
|
print_attr(level + 1, "IP Address Allocation KDE");
|
|
|
|
if (size < 12)
|
|
return;
|
|
|
|
print_ipv4(level + 2, "Client IP Address", data + 0);
|
|
print_ipv4(level + 2, "Subnet Mask", data + 4);
|
|
print_ipv4(level + 2, "GO IP Address", data + 8);
|
|
return;
|
|
case 0x10:
|
|
print_ie_wfa_hs20(level + 1, label, data, size);
|
|
return;
|
|
case 0x12:
|
|
print_ie_rsn_suites(level + 1, label, data, size);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
} else if (!memcmp(oui, ieee_oui, 3)) {
|
|
const char *kde;
|
|
|
|
/* EAPoL-Key KDEs */
|
|
switch (oui[3]) {
|
|
case 1:
|
|
kde = "GTK";
|
|
break;
|
|
case 3:
|
|
kde = "MAC address";
|
|
break;
|
|
case 4:
|
|
kde = "PMKID";
|
|
break;
|
|
case 5:
|
|
kde = "SMK";
|
|
break;
|
|
case 6:
|
|
kde = "Nonce";
|
|
break;
|
|
case 7:
|
|
kde = "Lifetime";
|
|
break;
|
|
case 8:
|
|
kde = "Error";
|
|
break;
|
|
case 9:
|
|
kde = "IGTK";
|
|
break;
|
|
case 10:
|
|
kde = "Key ID";
|
|
break;
|
|
case 11:
|
|
kde = "Multi-band GTK";
|
|
break;
|
|
case 12:
|
|
kde = "Multi-band Key ID";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
print_attr(level + 1, "%s KDE", kde);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void print_ie_mcs(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
int i;
|
|
uint8_t bytemask[16];
|
|
uint16_t data_rate;
|
|
const char *mcs_set[128] = {
|
|
[77] = "Reserved",
|
|
[78] = "Reserved",
|
|
[79] = "Reserved",
|
|
|
|
[90] = "Reserved",
|
|
[91] = "Reserved",
|
|
[92] = "Reserved",
|
|
[93] = "Reserved",
|
|
[94] = "Reserved",
|
|
[95] = "Reserved",
|
|
[96] = "Tx MCS set defined",
|
|
|
|
[97] = "Tx Rx MCS set not equal",
|
|
[100] = "Tx unequal modulation supported",
|
|
[101] = "Reserved",
|
|
[102] = "Reserved",
|
|
[103] = "Reserved",
|
|
|
|
[104] = "Reserved",
|
|
[105] = "Reserved",
|
|
[106] = "Reserved",
|
|
[107] = "Reserved",
|
|
[108] = "Reserved",
|
|
[109] = "Reserved",
|
|
[110] = "Reserved",
|
|
[111] = "Reserved",
|
|
|
|
[112] = "Reserved",
|
|
[113] = "Reserved",
|
|
[114] = "Reserved",
|
|
[115] = "Reserved",
|
|
[116] = "Reserved",
|
|
[117] = "Reserved",
|
|
[118] = "Reserved",
|
|
[119] = "Reserved",
|
|
|
|
[120] = "Reserved",
|
|
[121] = "Reserved",
|
|
[122] = "Reserved",
|
|
[123] = "Reserved",
|
|
[124] = "Reserved",
|
|
[125] = "Reserved",
|
|
[126] = "Reserved",
|
|
[127] = "Reserved",
|
|
};
|
|
|
|
if (size != 16)
|
|
return print_ie_error(level, label, size, -EINVAL);
|
|
|
|
for (i = 0; i < 77; i++) {
|
|
if (test_bit(bytes, i))
|
|
print_attr(level, "%s: MCS %d", label, i);
|
|
}
|
|
|
|
memset(bytemask, 0, sizeof(bytemask));
|
|
|
|
bytemask[9] = 0xe0;
|
|
|
|
print_ie_bitfield(level, "MCS set", bytes, bytemask, sizeof(bytemask),
|
|
mcs_set);
|
|
|
|
data_rate = l_get_le16(&bytes[10]) & 0x3ff;
|
|
|
|
if (data_rate)
|
|
print_attr(level, "MCS set: Rx Highest data rate: %d Mbit/s",
|
|
data_rate);
|
|
|
|
bytemask[9] = 0x00;
|
|
bytemask[11] = 0xfc;
|
|
bytemask[12] = 0x03;
|
|
|
|
print_ie_bitfield(level, "MCS Set", bytes, bytemask, sizeof(bytemask),
|
|
mcs_set);
|
|
|
|
if (bytes[12] & 0x0c)
|
|
print_attr(level,
|
|
"MCS set: Tx max spatial streams supported: %d",
|
|
((bytes[12] & 0x0c) >> 2) + 1);
|
|
|
|
bytemask[11] = 0x00;
|
|
bytemask[12] = 0xf0;
|
|
bytemask[13] = 0xff;
|
|
bytemask[14] = 0xff;
|
|
bytemask[15] = 0xff;
|
|
|
|
print_ie_bitfield(level, "MCS set", bytes, bytemask, sizeof(bytemask),
|
|
mcs_set);
|
|
}
|
|
|
|
static void print_ie_ht_operation(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const char *secondary_offset[] = {
|
|
"no secondary channel",
|
|
"above primary channel",
|
|
"reserved",
|
|
"below primary channel"
|
|
};
|
|
const char *channel_width[] = {
|
|
"20 MHz channel width",
|
|
"Any supported channel width"
|
|
};
|
|
const char *ht_protection[] = {
|
|
"No protection",
|
|
"Nonmember protection mode",
|
|
"20 MHz protection mode",
|
|
"non-HT mixed mode"
|
|
};
|
|
const char *ht_ops_bitfield[] = {
|
|
"",
|
|
"",
|
|
"",
|
|
"RIFS permitted",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
|
|
"",
|
|
"",
|
|
"Non-greenfield HT STAs present",
|
|
"Reserved",
|
|
"OBSS non-HT STAs present",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Dual beacon",
|
|
"Dual CTS protection",
|
|
|
|
"STBC beacon",
|
|
"L-SIG TXOP protection full support",
|
|
"PCO active",
|
|
"PCO Phase",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
"Reserved",
|
|
|
|
NULL
|
|
};
|
|
uint8_t *bytes = (uint8_t *) data;
|
|
uint8_t bytemask[5];
|
|
int i;
|
|
|
|
if (size < 22) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr (level, "%s:", label);
|
|
print_attr (level + 1, "Primary channel %d", bytes[0]);
|
|
|
|
i = bytes[1] & 0x03;
|
|
print_attr (level + 1,
|
|
"Information: Secondary Channel Offset: %s",
|
|
secondary_offset[i]);
|
|
|
|
i = (bytes[1] & 0x04) >> 2;
|
|
print_attr (level + 1,
|
|
"Information: Channel width: bit 2: %s",
|
|
channel_width[i]);
|
|
|
|
memset(bytemask, 0, sizeof(bytemask));
|
|
bytemask[0] = 0xf8;
|
|
|
|
print_ie_bitfield(level + 1,
|
|
"Information", &bytes[1], bytemask,
|
|
sizeof(bytemask), ht_ops_bitfield);
|
|
|
|
i = bytes[2] & 0x03;
|
|
print_attr(level + 1,
|
|
"Information: HT Protection: bits 8 - 9: %s",
|
|
ht_protection[i]);
|
|
|
|
bytemask[0] = 0x00;
|
|
bytemask[1] = 0xfc;
|
|
bytemask[2] = 0xff;
|
|
bytemask[3] = 0xff;
|
|
bytemask[4] = 0xff;
|
|
print_ie_bitfield(level + 1, "Information", &bytes[1],
|
|
bytemask, sizeof(bytemask), ht_ops_bitfield);
|
|
|
|
print_ie_mcs(level + 1, "Basic MCS set", &bytes[6], 16);
|
|
}
|
|
|
|
static const char *extended_capabilities_bitfield[80] = {
|
|
[0] = "20/40 BSS coexistence management support",
|
|
[1] = "Reserved",
|
|
[2] = "Extended channel switching",
|
|
[3] = "Reserved",
|
|
[4] = "PSMP capability",
|
|
[5] = "Reserved",
|
|
[6] = "S-PSMP support",
|
|
[7] = "Event",
|
|
[8] = "Diagnostics",
|
|
[9] = "Multicast diagnostics",
|
|
[10] = "Location tracking",
|
|
[11] = "FMS",
|
|
[12] = "Proxy ARP service",
|
|
[13] = "Collocated interference reporting",
|
|
[14] = "Civic location",
|
|
[15] = "Geospatial location",
|
|
[16] = "TFS",
|
|
[17] = "WNM-Sleep mode",
|
|
[18] = "TIM broadcast",
|
|
[19] = "BSS transition",
|
|
[20] = "QoS traffic capability",
|
|
[21] = "AC station count",
|
|
[22] = "Multiple BSSID",
|
|
[23] = "Timing measurement",
|
|
[24] = "Channel usage",
|
|
[25] = "SSID list",
|
|
[26] = "DMS",
|
|
[27] = "UTC TSF offset",
|
|
[28] = "TDLS Peer U-APSD buffer STA support",
|
|
[29] = "TDLS Peer PSM support",
|
|
[30] = "TDLS channel switching",
|
|
[31] = "Interworking",
|
|
[32] = "QoS Map",
|
|
[33] = "EBR",
|
|
[34] = "SSPN Interface",
|
|
[35] = "Reserved",
|
|
[36] = "MSGCF Capability",
|
|
[37] = "TDLS Support",
|
|
[38] = "TDLS Prohibited",
|
|
[39] = "TDLS Channel Switching Prohibited",
|
|
[40] = "Reject Unadmitted Frame",
|
|
[41 ... 43] = "Reserved",
|
|
[44] = "Identifier Location",
|
|
[45] = "U-APSD Coexistence",
|
|
[46] = "WNM- Notification",
|
|
[47] = "Reserved",
|
|
[48] = "UTF-8 SSID",
|
|
[49] = "QMF Activated",
|
|
[50] = "QMF Reconfiguration Activated",
|
|
[51] = "Robust AV Streaming",
|
|
[52] = "Advanced GCR",
|
|
[53] = "Mesh GCR",
|
|
[54] = "SCS",
|
|
[55] = "QLoad Report",
|
|
[56] = "Alternate EDCA",
|
|
[57] = "Unprotected TXOP Negotiation",
|
|
[58] = "Protected TXOP Negotiation",
|
|
[59] = "Reserved",
|
|
[60] = "Protected QLoad Report",
|
|
[61] = "TDLS Wider Bandwidth",
|
|
[62] = "Opmode Notification",
|
|
[65] = "Channel Schedule Management",
|
|
[66] = "Geodatabase Inband Enabling Signal",
|
|
[67] = "Network Channel Control",
|
|
[68] = "White Space Map",
|
|
[69] = "Channel Availability Query",
|
|
[70] = "Fine Timing Measurement Responder",
|
|
[71] = "Fine Timing Measurement Initiator",
|
|
[72] = "FILS Capability",
|
|
[73] = "Extended Spectrum Management Capable",
|
|
[74] = "Future Channel Guidance",
|
|
};
|
|
|
|
static void print_ie_extended_capabilities(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t bytemask1[] = { 0xff, 0xff, 0xff, 0xff,
|
|
0xff, 0x01 };
|
|
uint8_t bytemask2[] = { 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0xf0, 0xff, 0xff };
|
|
uint8_t interval;
|
|
size_t bytes;
|
|
bool spsmp;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size == 0)
|
|
return;
|
|
|
|
spsmp = test_bit(data, 6);
|
|
|
|
bytes = size < sizeof(bytemask1) ? size : sizeof(bytemask1);
|
|
|
|
/* Print first 40 bits */
|
|
print_ie_bitfield(level + 1, "Capability", data, bytemask1,
|
|
bytes, extended_capabilities_bitfield);
|
|
|
|
if (size <= bytes)
|
|
return;
|
|
|
|
/* Print Service Interval Granularity */
|
|
if (spsmp) {
|
|
interval = bit_field(*((uint8_t *) data + 5), 1, 3);
|
|
print_attr(level + 1,
|
|
"Shortest Service Interval Granularity: %d ms",
|
|
interval * 5 + 5);
|
|
}
|
|
|
|
bytes = size < sizeof(bytemask2) ? size : sizeof(bytemask2);
|
|
|
|
/* Print remainder */
|
|
print_ie_bitfield(level + 1, "Capability", data, bytemask2,
|
|
bytes, extended_capabilities_bitfield);
|
|
}
|
|
|
|
static void print_ie_ht_capabilities(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
static const char *ht_capabilities_info_bitfield[16] = {
|
|
[0] = "LDPC Coding Capability",
|
|
[1] = "Supported Channel Width Set",
|
|
[2] = "SM Power Save",
|
|
[3] = "SM Power Save",
|
|
[4] = "HT-Greenfield",
|
|
[5] = "Short GI for 20Mhz",
|
|
[6] = "Short GI for 40Mhz",
|
|
[7] = "Tx STBC",
|
|
[8] = "Rx STBC",
|
|
[9] = "Rx STBC",
|
|
[10] = "HT-Delayed Block Ack",
|
|
[11] = "Maximum A-MSDU Length",
|
|
[12] = "DSSS/CCK Mode in 40Mhz",
|
|
[13] = "Reserved",
|
|
[14] = "40 Mhz Intolerant",
|
|
[15] = "L-SIG TXOP Protection Support",
|
|
};
|
|
static const char *ht_capabilities_sm_power_save[4] = {
|
|
"Static", "Dynamic", "Reserved", "Disabled",
|
|
};
|
|
static const char *ht_capabilities_rx_stbc[4] = {
|
|
"Disabled", "One spatial stream", "One and two spatial streams",
|
|
"One, two and three spatial streams"
|
|
};
|
|
static const char *ht_capabilities_min_mpdu_start_spacing[8] = {
|
|
"No restriction", "1/4 us", "1/2 us", "1 us", "2 us",
|
|
"4 us", "8 us", "16 us",
|
|
};
|
|
static const char *ht_capabilities_pco_transition_time[4] = {
|
|
"No transition", "400 us", "1.5 ms", "5 ms",
|
|
};
|
|
static const char *ht_capabilities_mcs_feedback[4] = {
|
|
"No feedback", "Reserved", "Unsolicited", "Both",
|
|
};
|
|
uint8_t info_mask[] = { 0x03, 0xfc };
|
|
const uint8_t *htc = data;
|
|
uint8_t sm_power_save;
|
|
uint8_t rx_stbc;
|
|
uint8_t ampdu_exponent;
|
|
bool pco;
|
|
bool plus_htc;
|
|
bool rd_responder;
|
|
uint8_t bits;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size != 26)
|
|
return;
|
|
|
|
/* Print bits 0-1 */
|
|
print_ie_bitfield(level + 1, "HT Capabilities Info", data, info_mask,
|
|
1, ht_capabilities_info_bitfield);
|
|
|
|
/* Print SM Power Save */
|
|
sm_power_save = bit_field(htc[0], 2, 2);
|
|
print_attr(level + 1, "HT Capabilities Info: bits 2-3: %s",
|
|
ht_capabilities_sm_power_save[sm_power_save]);
|
|
|
|
/* Print bits 4-7 */
|
|
info_mask[0] = 0xf0;
|
|
print_ie_bitfield(level + 1, "HT Capabilities Info", data, info_mask,
|
|
1, ht_capabilities_info_bitfield);
|
|
|
|
rx_stbc = bit_field(htc[1], 0, 2);
|
|
print_attr(level + 1, "HT Capabilities Info: bits 8-9: %s",
|
|
ht_capabilities_rx_stbc[rx_stbc]);
|
|
|
|
/* Print bits 10-15 */
|
|
info_mask[0] = 0x00;
|
|
print_ie_bitfield(level + 1, "HT Capabilities Info", data, info_mask,
|
|
2, ht_capabilities_info_bitfield);
|
|
|
|
ampdu_exponent = bit_field(htc[2], 0, 2);
|
|
print_attr(level + 1, "A-MPDU Parameters: "
|
|
"Maximum A-MPDU Length Exponent: %d", ampdu_exponent);
|
|
|
|
bits = bit_field(htc[2], 2, 3);
|
|
print_attr(level + 1, "A-MPDU Parameters: "
|
|
"Minimum MPDU Start Spacing: %s",
|
|
ht_capabilities_min_mpdu_start_spacing[bits]);
|
|
|
|
print_ie_mcs(level + 1, "Supported MCS", htc + 3, 16);
|
|
|
|
pco = test_bit(htc + 18, 0);
|
|
print_attr(level + 1, "HT Extended Capabilities: PCO: %s",
|
|
bits ? "supported" : "not supported");
|
|
|
|
if (pco) {
|
|
bits = bit_field(htc[18], 1, 2);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"PCO Transition Time: %s",
|
|
ht_capabilities_pco_transition_time[bits]);
|
|
}
|
|
|
|
bits = bit_field(htc[19], 0, 2);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"MCS Feedback: %s", ht_capabilities_mcs_feedback[bits]);
|
|
|
|
plus_htc = test_bit(htc + 19, 2);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"+HTC: %s", plus_htc ? "supported" : "not supported");
|
|
|
|
rd_responder = test_bit(htc + 19, 3);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"RD Responder: %s",
|
|
rd_responder ? "supported" : "not supported");
|
|
|
|
/* TODO: Transmit Beamforming Capabilities field */
|
|
/* TODO: ASEL Capability field */
|
|
}
|
|
|
|
static void print_ie_rm_enabled_caps(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
static const char *capabilities[40] = {
|
|
[0] = "Link Measurement",
|
|
[1] = "Neighbor Report",
|
|
[2] = "Parallel Measurements",
|
|
[3] = "Repeated Measurements",
|
|
[4] = "Beacon Passive Measurement",
|
|
[5] = "Beacon Active Measurement",
|
|
[6] = "Beacon Table Measurement",
|
|
[7] = "Beacon Measurement Reporting Conditions",
|
|
[8] = "Frame Measurement",
|
|
[9] = "Channel Load Measurement",
|
|
[10] = "Noise Histogram Measurement",
|
|
[11] = "Statistics Measurement",
|
|
[12] = "LCI Measurement",
|
|
[13] = "LCI Azimuth",
|
|
[14] = "Transmit Stream / Category Measurement",
|
|
[15] = "Triggered Transmit Stream / Category Measurement",
|
|
[16] = "AP Channel Report",
|
|
[17] = "RM MIB",
|
|
[27] = "Measurement Pilot Transmission Information",
|
|
[28] = "Neighbor Report TSF Offset",
|
|
[29] = "RCPI Measurement capability enabled",
|
|
[30] = "RSNI Measurement",
|
|
[31] = "BSS Average Access Delay",
|
|
[32] = "BSS Available Admission Capacity",
|
|
[33] = "Antenna capability",
|
|
};
|
|
const uint8_t *bytes;
|
|
uint8_t bytemask1[3] = { 0xff, 0xff, 0x03 };
|
|
uint8_t bytemask2[2] = { 0xf8, 0x03 };
|
|
uint8_t byte;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size != 5)
|
|
return;
|
|
|
|
bytes = data;
|
|
|
|
print_ie_bitfield(level + 1, "Enabled", bytes,
|
|
bytemask1, sizeof(bytemask1), capabilities);
|
|
|
|
byte = bit_field(bytes[2], 2, 3);
|
|
print_attr(level + 1, "Operating Channel Max Measurement Duration: %u",
|
|
byte);
|
|
|
|
byte = bit_field(bytes[2], 5, 3);
|
|
print_attr(level + 1, "Non-Operating Channel Max Measurement "
|
|
"Duration: %u", byte);
|
|
|
|
byte = bit_field(bytes[3], 0, 3);
|
|
print_attr(level + 1, "Measurement Pilot Capability: %u", byte);
|
|
|
|
print_ie_bitfield(level + 1, "Enabled", bytes + sizeof(bytemask1),
|
|
bytemask2, sizeof(bytemask2), capabilities);
|
|
}
|
|
|
|
static void print_ie_interworking(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *ptr = data;
|
|
const char *msg;
|
|
uint8_t type;
|
|
bool venue = false;
|
|
bool hessid = false;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
type = bit_field(ptr[0], 0, 3);
|
|
|
|
switch (type) {
|
|
case 0:
|
|
msg = "Private network";
|
|
break;
|
|
case 1:
|
|
msg = "Private network w/ guest access";
|
|
break;
|
|
case 2:
|
|
msg = "Chargeable public network";
|
|
break;
|
|
case 3:
|
|
msg = "Free public network";
|
|
break;
|
|
case 4:
|
|
msg = "Personal device network";
|
|
break;
|
|
case 5:
|
|
msg = "Emergency services only network";
|
|
break;
|
|
case 14:
|
|
msg = "Test/Experimental";
|
|
break;
|
|
case 15:
|
|
msg = "Wildcard";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
print_attr(level + 1, "Network Type: %s", msg);
|
|
print_attr(level + 1, "Internet: %u", test_bit(ptr, 4));
|
|
print_attr(level + 1, "ASRA: %u", test_bit(ptr, 5));
|
|
print_attr(level + 1, "ESR: %u", test_bit(ptr, 6));
|
|
print_attr(level + 1, "UESA: %u", test_bit(ptr, 7));
|
|
|
|
size--;
|
|
ptr++;
|
|
|
|
if (!size)
|
|
return;
|
|
|
|
/*
|
|
* There is venue/hessid info optionally, and no way of determining if
|
|
* they exist except looking at the length.
|
|
*/
|
|
if (size == 2)
|
|
venue = true;
|
|
else if (size == 6)
|
|
hessid = true;
|
|
else if (size == 8) {
|
|
venue = true;
|
|
hessid = true;
|
|
}
|
|
|
|
if (venue) {
|
|
switch (ptr[0]) {
|
|
case 0:
|
|
msg = "Unspecified";
|
|
break;
|
|
case 1:
|
|
msg = "Assembly";
|
|
break;
|
|
case 2:
|
|
msg = "Business";
|
|
break;
|
|
case 3:
|
|
msg = "Educational";
|
|
break;
|
|
case 5:
|
|
msg = "Factory and Industrial";
|
|
break;
|
|
case 6:
|
|
msg = "Institutional";
|
|
break;
|
|
case 7:
|
|
msg = "Mercantile";
|
|
break;
|
|
case 8:
|
|
msg = "Residential";
|
|
break;
|
|
case 9:
|
|
msg = "Utility and Miscellaneous";
|
|
break;
|
|
case 10:
|
|
msg = "Vehicular";
|
|
break;
|
|
case 11:
|
|
msg = "Outdoor";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Each of the above groups have many group types, but if
|
|
* anyone really cares they can cross reference the integer
|
|
* type with IEEE 802.11-2016 Table 9-62
|
|
*/
|
|
print_attr(level + 1, "Venue: %s, type: %u", msg, ptr[1]);
|
|
|
|
ptr += 2;
|
|
size -= 2;
|
|
}
|
|
|
|
if (hessid)
|
|
print_attr(level + 1, "HESSID: "MAC, MAC_STR(ptr));
|
|
}
|
|
|
|
static void print_ie_advertisement(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *ptr = data;
|
|
const char *msg = NULL;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
while (size) {
|
|
uint8_t qr_len = bit_field(ptr[0], 0, 7);
|
|
uint8_t id = ptr[1];
|
|
|
|
switch (id) {
|
|
case IE_ADVERTISEMENT_ANQP:
|
|
msg = "ANQP";
|
|
break;
|
|
case IE_ADVERTISEMENT_MIH_SERVICE:
|
|
msg = "MIH Information Service";
|
|
break;
|
|
case IE_ADVERTISEMENT_MIH_DISCOVERY:
|
|
msg = "MIH Command and Event Services";
|
|
break;
|
|
case IE_ADVERTISEMENT_EAS:
|
|
msg = "EAS";
|
|
break;
|
|
case IE_ADVERTISEMENT_RLQP:
|
|
msg = "RLQP";
|
|
break;
|
|
case IE_ADVERTISEMENT_VENDOR_SPECIFIC:
|
|
msg = "Vendor Specific";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (id == IE_ADVERTISEMENT_VENDOR_SPECIFIC) {
|
|
size -= ptr[3];
|
|
ptr += ptr[3];
|
|
} else {
|
|
size -= 2;
|
|
ptr += 2;
|
|
}
|
|
|
|
print_attr(level + 1, "Protocol: %s, Query Resp Limit: %u",
|
|
msg, qr_len);
|
|
}
|
|
}
|
|
|
|
static void print_ie_owe(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t group;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
group = l_get_le16(data);
|
|
|
|
print_attr(level + 1, "ECC Group: %u", group);
|
|
print_attr(level + 1, "Public Key:");
|
|
print_hexdump(level + 2, data + 2, size - 2);
|
|
}
|
|
|
|
static void print_fils_indication(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
|
|
print_attr(level, "FILS Indication: len %u", size);
|
|
|
|
print_attr(level + 1, "Num Public Key Identifiers: %u",
|
|
bit_field(*bytes, 0, 3));
|
|
print_attr(level + 1, "Num Realm Identifiers: %u",
|
|
bit_field(*bytes, 3, 3));
|
|
print_attr(level + 1, "IP configuration: %u", test_bit(bytes, 6));
|
|
print_attr(level + 1, "Cache Identifier Included: %u",
|
|
test_bit(bytes, 7));
|
|
|
|
bytes++;
|
|
|
|
print_attr(level + 1, "HES-SID Included: %u", test_bit(bytes, 0));
|
|
print_attr(level + 1, "SK Auth without PFS supported: %u",
|
|
test_bit(bytes, 1));
|
|
print_attr(level + 1, "SK Auth with PFS supported: %u",
|
|
test_bit(bytes, 2));
|
|
print_attr(level + 1, "PK Auth supported: %u", test_bit(bytes, 3));
|
|
|
|
bytes++;
|
|
|
|
print_hexdump(level + 1, bytes, size - 2);
|
|
}
|
|
|
|
static void print_fils_key_confirmation(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "FILS Key Confirmation (KeyAuth): len %u", size);
|
|
}
|
|
|
|
static void print_fils_session(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "FILS Session: len %u", size);
|
|
}
|
|
|
|
static void print_ie_supported_operating_classes(unsigned int level,
|
|
const char *label,
|
|
const void *data,
|
|
uint16_t size)
|
|
{
|
|
const void *end = data + size;
|
|
|
|
if (size < 1) {
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "Current Operating Class: %u", l_get_u8(data));
|
|
|
|
data += 1;
|
|
|
|
while (end - data) {
|
|
uint8_t cls = l_get_u8(data);
|
|
|
|
/*
|
|
* TODO: Support Current Operating Class Extension Sequence
|
|
* and Operating Class Duple Sequence
|
|
*/
|
|
if (cls == 130 || cls == 0) {
|
|
data = end;
|
|
break;
|
|
}
|
|
|
|
print_attr(level, "Supported Operating Class: %u", cls);
|
|
data += 1;
|
|
}
|
|
|
|
if (end - data)
|
|
print_ie_error(level, label, size, -EINVAL);
|
|
}
|
|
|
|
static void print_qos_map(unsigned int level, const char *label,
|
|
const void *data,
|
|
uint16_t size)
|
|
{
|
|
print_attr(level, "QoS Map");
|
|
}
|
|
|
|
static void print_measurement_request_beacon(unsigned int level,
|
|
const void *data,
|
|
uint16_t size)
|
|
{
|
|
uint8_t mode;
|
|
|
|
if (size < 13)
|
|
return;
|
|
|
|
print_attr(level, "Operating Class: %u", l_get_u8(data));
|
|
print_attr(level, "Channel: %u", l_get_u8(data + 1));
|
|
print_attr(level, "Randomization Interval: %u", l_get_le16(data + 2));
|
|
print_attr(level, "Duration: %u", l_get_le16(data + 4));
|
|
|
|
mode = l_get_u8(data + 6);
|
|
|
|
switch (mode) {
|
|
case 0:
|
|
print_attr(level, "Measurement: Passive");
|
|
break;
|
|
case 1:
|
|
print_attr(level, "Measurement: Active");
|
|
break;
|
|
case 2:
|
|
print_attr(level, "Measurement: Table");
|
|
break;
|
|
default:
|
|
print_attr(level, "Measurement: Invalid (%u)", mode);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "BSSID: "MAC, MAC_STR(((const uint8_t *)data + 7)));
|
|
}
|
|
|
|
static const char *rrm_measurement_types[] = {
|
|
[0] = "Basic",
|
|
[1] = "Clear Channel Assessment",
|
|
[2] = "Receive Power Indication",
|
|
[3] = "Channel Load",
|
|
[4] = "Noise Histogram",
|
|
[5] = "Beacon",
|
|
[6] = "Frame",
|
|
[7] = "STA Statistics",
|
|
[8] = "LCI",
|
|
[9] = "Transmit Stream/Category Measurement",
|
|
[10] = "Multicast Diagnostics",
|
|
[11] = "Location Civic",
|
|
[12] = "Location Identifier",
|
|
[13] = "Directional Channel Quality",
|
|
[14] = "Directional Measurement",
|
|
[15] = "Directional Statistics",
|
|
[16] = "Fine Timing Measurement Range",
|
|
[255] = "Measurement Pause"
|
|
};
|
|
|
|
static void print_measurement_request(unsigned int level, const char *label,
|
|
const void *data,
|
|
uint16_t size)
|
|
{
|
|
uint8_t mode;
|
|
uint8_t type;
|
|
|
|
print_attr(level, "Measurement Request");
|
|
|
|
if (size < 3)
|
|
return;
|
|
|
|
print_attr(level + 1, "Token: %u", l_get_u8(data));
|
|
|
|
mode = l_get_u8(data + 1);
|
|
print_attr(level + 1, "Request Mode: %u", mode);
|
|
|
|
if (test_bit(&mode, 0))
|
|
print_attr(level + 2, "Parallel bit set");
|
|
|
|
if (test_bit(&mode, 1))
|
|
print_attr(level + 2, "Enable bit set");
|
|
|
|
if (test_bit(&mode, 2))
|
|
print_attr(level + 2, "Request bit set");
|
|
|
|
if (test_bit(&mode, 3))
|
|
print_attr(level + 2, "Report bit set");
|
|
|
|
if (test_bit(&mode, 4))
|
|
print_attr(level + 2, "Duration Mandatory set");
|
|
|
|
type = l_get_u8(data + 2);
|
|
|
|
if (type > 16 && type != 255) {
|
|
print_attr(level + 1, "Type: Invalid (%u)", type);
|
|
return;
|
|
}
|
|
|
|
print_attr(level + 1, "Type: %s", rrm_measurement_types[type]);
|
|
|
|
switch (type) {
|
|
case 5:
|
|
print_measurement_request_beacon(level + 1, data + 3, size - 3);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void print_measurement_report_beacon(unsigned int level,
|
|
const void *data,
|
|
uint16_t size)
|
|
{
|
|
uint8_t frame_info;
|
|
|
|
if (size < 26)
|
|
return;
|
|
|
|
print_attr(level, "Operating Class: %u", l_get_u8(data));
|
|
print_attr(level, "Channel: %u", l_get_u8(data + 1));
|
|
print_attr(level, "Start Time: %"PRIu64, l_get_le64(data + 2));
|
|
print_attr(level, "Duration: %u", l_get_le16(data + 10));
|
|
|
|
frame_info = l_get_u8(data + 12);
|
|
|
|
print_attr(level, "PHY Type: %u", bit_field(frame_info, 0, 7));
|
|
print_attr(level, "Frame Type: %u", test_bit(&frame_info, 7));
|
|
print_attr(level, "RCPI: %u", l_get_u8(data + 13));
|
|
print_attr(level, "RSNI: %u", l_get_u8(data + 14));
|
|
print_attr(level, "BSSID: "MAC, MAC_STR(((const uint8_t *)data + 15)));
|
|
print_attr(level, "Antenna ID: %u", l_get_u8(data + 21));
|
|
print_attr(level, "Parent TSF: %u", l_get_le32(data + 22));
|
|
}
|
|
|
|
static void print_measurement_report(unsigned int level, const char *label,
|
|
const void *data,
|
|
uint16_t size)
|
|
{
|
|
uint8_t mode;
|
|
uint8_t type;
|
|
|
|
print_attr(level, "Measurement Report");
|
|
|
|
if (size < 3)
|
|
return;
|
|
|
|
print_attr(level + 1, "Token: %u", l_get_u8(data));
|
|
|
|
mode = l_get_u8(data + 1);
|
|
print_attr(level + 1, "Report Mode: %u", mode);
|
|
|
|
if (test_bit(&mode, 0))
|
|
print_attr(level + 2, "Late bit set");
|
|
|
|
if (test_bit(&mode, 1))
|
|
print_attr(level + 2, "Incapable bit set");
|
|
|
|
if (test_bit(&mode, 2))
|
|
print_attr(level + 2, "Refused bit set");
|
|
|
|
type = l_get_u8(data + 2);
|
|
|
|
if (type > 16 && type != 255) {
|
|
print_attr(level + 1, "Type: Invalid (%u)", type);
|
|
return;
|
|
}
|
|
|
|
print_attr(level + 1, "Type: %s", rrm_measurement_types[type]);
|
|
|
|
switch (type) {
|
|
case 5:
|
|
print_measurement_report_beacon(level + 1, data + 3, size - 3);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct attr_entry ie_entry[] = {
|
|
{ IE_TYPE_SSID, "SSID",
|
|
ATTR_CUSTOM, { .function = print_ie_ssid } },
|
|
{ IE_TYPE_SUPPORTED_RATES, "Supported rates",
|
|
ATTR_CUSTOM, { .function = print_ie_rate } },
|
|
{ IE_TYPE_DSSS_PARAMETER_SET, "DSSS parameter set",
|
|
ATTR_CUSTOM, { .function = print_ie_ds } },
|
|
{ IE_TYPE_TIM, "TIM",
|
|
ATTR_CUSTOM, { .function = print_ie_tim } },
|
|
{ IE_TYPE_COUNTRY, "Country",
|
|
ATTR_CUSTOM, { .function = print_ie_country } },
|
|
{ IE_TYPE_BSS_LOAD, "BSS load",
|
|
ATTR_CUSTOM, { .function = print_ie_bss_load } },
|
|
{ IE_TYPE_POWER_CONSTRAINT, "Power constraint",
|
|
ATTR_CUSTOM, { .function = print_ie_power_constraint } },
|
|
{ IE_TYPE_TPC_REPORT, "TPC report",
|
|
ATTR_CUSTOM, { .function = print_ie_tpc } },
|
|
{ IE_TYPE_ERP, "ERP Information",
|
|
ATTR_CUSTOM, { .function = print_ie_erp } },
|
|
{ IE_TYPE_RSN, "RSN",
|
|
ATTR_CUSTOM, { .function = print_ie_rsn } },
|
|
{ IE_TYPE_EXTENDED_SUPPORTED_RATES, "Extended supported rates",
|
|
ATTR_CUSTOM, { .function = print_ie_rate } },
|
|
{ IE_TYPE_HT_OPERATION, "HT Operation",
|
|
ATTR_CUSTOM, { .function = print_ie_ht_operation } },
|
|
{ IE_TYPE_VENDOR_SPECIFIC, "Vendor specific",
|
|
ATTR_CUSTOM, { .function = print_ie_vendor } },
|
|
{ IE_TYPE_EXTENDED_CAPABILITIES, "Extended Capabilities",
|
|
ATTR_CUSTOM, { .function = print_ie_extended_capabilities } },
|
|
{ IE_TYPE_HT_CAPABILITIES, "HT Capabilities",
|
|
ATTR_CUSTOM, { .function = print_ie_ht_capabilities } },
|
|
{ IE_TYPE_RM_ENABLED_CAPABILITIES, "RM Enabled Capabilities",
|
|
ATTR_CUSTOM, { .function = print_ie_rm_enabled_caps } },
|
|
{ IE_TYPE_INTERWORKING, "Interworking",
|
|
ATTR_CUSTOM, { .function = print_ie_interworking } },
|
|
{ IE_TYPE_ADVERTISEMENT_PROTOCOL, "Advertisement Protocol",
|
|
ATTR_CUSTOM, { .function = print_ie_advertisement } },
|
|
{ IE_TYPE_OWE_DH_PARAM, "OWE Diffie-Hellman Parameter",
|
|
ATTR_CUSTOM, { .function = print_ie_owe } },
|
|
{ IE_TYPE_FILS_INDICATION, "FILS Indication",
|
|
ATTR_CUSTOM, { .function = print_fils_indication } },
|
|
{ IE_TYPE_FILS_KEY_CONFIRMATION, "FILS Key Confirmation",
|
|
ATTR_CUSTOM, { .function = print_fils_key_confirmation } },
|
|
{ IE_TYPE_FILS_SESSION, "FILS Session",
|
|
ATTR_CUSTOM, { .function = print_fils_session } },
|
|
{ IE_TYPE_SUPPORTED_OPERATING_CLASSES, "Supported Operating Classes",
|
|
ATTR_CUSTOM,
|
|
{ .function = print_ie_supported_operating_classes } },
|
|
{ IE_TYPE_QOS_MAP_SET, "QoS Map",
|
|
ATTR_CUSTOM, { .function = print_qos_map } },
|
|
{ IE_TYPE_MEASUREMENT_REQUEST, "Measurement Request",
|
|
ATTR_CUSTOM, { .function = print_measurement_request } },
|
|
{ IE_TYPE_MEASUREMENT_REPORT, "Measurement Report",
|
|
ATTR_CUSTOM, { .function = print_measurement_report } },
|
|
{ },
|
|
};
|
|
|
|
static void print_ie(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
struct ie_tlv_iter iter;
|
|
int i;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
ie_tlv_iter_init(&iter, data, size);
|
|
|
|
while (ie_tlv_iter_next(&iter)) {
|
|
uint16_t tag = ie_tlv_iter_get_tag(&iter);
|
|
struct attr_entry *entry = NULL;
|
|
|
|
for (i = 0; ie_entry[i].str; i++) {
|
|
if (ie_entry[i].attr == tag) {
|
|
entry = &ie_entry[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cur_nlmon && cur_nlmon->noies && tag != IE_TYPE_SSID)
|
|
continue;
|
|
|
|
if (entry && entry->function)
|
|
entry->function(level + 1, entry->str,
|
|
iter.data, iter.len);
|
|
else
|
|
print_attr(level + 1, "Tag %u: len %u", tag,
|
|
iter.len);
|
|
|
|
print_hexdump(level + 2, iter.data, iter.len);
|
|
}
|
|
}
|
|
|
|
static void print_wsc_byte(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
|
|
if (size != 1) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %u", label, bytes[0]);
|
|
}
|
|
|
|
static void print_wsc_bytes(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s: (len: %d)", label, size);
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static void print_wsc_bool(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
|
|
if (size != 1) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %s", label, bytes[0] ? "True" : "False");
|
|
}
|
|
|
|
static void print_wsc_ascii_string(unsigned int level, const char *label,
|
|
const void *data, uint16_t size,
|
|
uint16_t max_len)
|
|
{
|
|
const char *p = data;
|
|
unsigned int i;
|
|
|
|
if (size >= max_len) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (!p[i])
|
|
break;
|
|
|
|
if (!l_ascii_isprint(p[i]))
|
|
goto invalid_ascii;
|
|
}
|
|
|
|
print_attr(level, "%s: %.*s", label, i, p);
|
|
return;
|
|
|
|
invalid_ascii:
|
|
print_attr(level, "%s: (Non-Ascii, len: %d)", label, size);
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static void print_wsc_utf8_string(unsigned int level, const char *label,
|
|
const void *data, uint16_t size,
|
|
uint16_t max_len)
|
|
{
|
|
const char *p = data;
|
|
unsigned int i;
|
|
|
|
if (size >= max_len) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (!p[i])
|
|
break;
|
|
}
|
|
|
|
if (!l_utf8_validate((const char *) p, i, NULL))
|
|
goto invalid_utf8;
|
|
|
|
print_attr(level, "%s: %.*s", label, i, p);
|
|
return;
|
|
|
|
invalid_utf8:
|
|
print_attr(level, "%s: (Non-utf8, len: %d)", label, size);
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static void print_wsc_uuid(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
|
|
if (size != 16) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %02x%02x%02x%02x-%02x%02x-%02x%02x-"
|
|
"%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
label,
|
|
bytes[0], bytes[1], bytes[2], bytes[3],
|
|
bytes[4], bytes[5], bytes[6], bytes[7],
|
|
bytes[8], bytes[9], bytes[10], bytes[11],
|
|
bytes[12], bytes[13], bytes[14], bytes[15]);
|
|
}
|
|
|
|
static void print_wsc_association_state(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t state;
|
|
static const char *state_table[] = {
|
|
"Not Associated",
|
|
"Connection Success",
|
|
"Configuration Failure",
|
|
"Association Failure",
|
|
"IP Failure",
|
|
};
|
|
|
|
if (size != 2) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
state = l_get_be16(data);
|
|
|
|
if (state > 4)
|
|
print_attr(level, "%s: Reserved", label);
|
|
else
|
|
print_attr(level, "%s: %s", label, state_table[state]);
|
|
}
|
|
|
|
static void print_wsc_auth_type_flags(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t v;
|
|
|
|
if (size != 2)
|
|
return;
|
|
|
|
v = l_get_be16(data);
|
|
print_attr(level, "%s:", label);
|
|
|
|
if (v & WSC_AUTHENTICATION_TYPE_OPEN)
|
|
print_attr(level + 1, "Open");
|
|
|
|
if (v & WSC_AUTHENTICATION_TYPE_WPA_PERSONAL)
|
|
print_attr(level + 1, "WPA-Personal");
|
|
|
|
if (v & WSC_AUTHENTICATION_TYPE_SHARED)
|
|
print_attr(level + 1, "Shared");
|
|
|
|
if (v & WSC_AUTHENTICATION_TYPE_WPA_ENTERPRISE)
|
|
print_attr(level + 1, "WPA-Enterprise");
|
|
|
|
if (v & WSC_AUTHENTICATION_TYPE_WPA2_ENTERPRISE)
|
|
print_attr(level + 1, "WPA2-Enterprise");
|
|
|
|
if (v & WSC_AUTHENTICATION_TYPE_WPA2_PERSONAL)
|
|
print_attr(level + 1, "WPA2-Personal");
|
|
|
|
if (v & 0xffc0)
|
|
print_attr(level + 1, "Unknown: %04x", v & 0xffc0);
|
|
}
|
|
|
|
static void print_wsc_authenticator(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *a = data;
|
|
|
|
if (size != 8)
|
|
return;
|
|
|
|
print_attr(level, "%s: %02x%02x%02x%02x%02x%02x%02x%02x",
|
|
label, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
|
|
}
|
|
|
|
static void print_wsc_configuration_error(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t error;
|
|
static const char *error_table[] = {
|
|
"No Error",
|
|
"OOB Interface Read Error",
|
|
"Decryption CRC Failure",
|
|
"2.4 channel not supported",
|
|
"5.0 channel not supported",
|
|
"Signal too weak",
|
|
"Network auth failure",
|
|
"Network association failure",
|
|
"No DHCP response",
|
|
"Failed DHCP config",
|
|
"IP Address conflict",
|
|
"Couldn't connect to Registrar",
|
|
"Multiple PBC sessions detected",
|
|
"Rogue activity suspected",
|
|
"Device busy",
|
|
"Setup locked",
|
|
"Message timeout",
|
|
"Registration session timeout",
|
|
"Device Password Auth Failure",
|
|
"60 Ghz channel not supported",
|
|
"Public Key Hash Mismatch",
|
|
};
|
|
|
|
if (size != 2) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
error = l_get_be16(data);
|
|
|
|
if (error > 20)
|
|
print_attr(level, "%s: Reserved", label);
|
|
else
|
|
print_attr(level, "%s: %s", label, error_table[error]);
|
|
}
|
|
|
|
static void print_wsc_config_methods(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t v;
|
|
uint16_t flags;
|
|
|
|
if (size != 2) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
v = l_get_be16(data);
|
|
print_attr(level, "%s:", label);
|
|
|
|
if ((v & WSC_CONFIGURATION_METHOD_PHYSICAL_DISPLAY_PIN) ==
|
|
WSC_CONFIGURATION_METHOD_PHYSICAL_DISPLAY_PIN)
|
|
print_attr(level + 1, "Physical Display PIN");
|
|
|
|
if ((v & WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN) ==
|
|
WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN)
|
|
print_attr(level + 1, "Virtual Display PIN");
|
|
|
|
flags = WSC_CONFIGURATION_METHOD_PHYSICAL_DISPLAY_PIN |
|
|
WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN;
|
|
if (v & flags)
|
|
v &= ~flags;
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_P2P) {
|
|
print_attr(level + 1, "P2P");
|
|
v &= ~WSC_CONFIGURATION_METHOD_P2P;
|
|
}
|
|
|
|
if ((v & WSC_CONFIGURATION_METHOD_PHYSICAL_PUSH_BUTTON) ==
|
|
WSC_CONFIGURATION_METHOD_PHYSICAL_PUSH_BUTTON)
|
|
print_attr(level + 1, "Physical PushButton");
|
|
|
|
if ((v & WSC_CONFIGURATION_METHOD_VIRTUAL_PUSH_BUTTON) ==
|
|
WSC_CONFIGURATION_METHOD_VIRTUAL_PUSH_BUTTON)
|
|
print_attr(level + 1, "Virtual PushButton");
|
|
|
|
flags = WSC_CONFIGURATION_METHOD_PHYSICAL_PUSH_BUTTON |
|
|
WSC_CONFIGURATION_METHOD_VIRTUAL_PUSH_BUTTON;
|
|
if (v & flags)
|
|
v &= ~flags;
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_KEYPAD) {
|
|
print_attr(level + 1, "Keypad");
|
|
v &= ~WSC_CONFIGURATION_METHOD_KEYPAD;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_PUSH_BUTTON) {
|
|
print_attr(level + 1, "PushButton");
|
|
v &= ~WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_NFC_INTERFACE) {
|
|
print_attr(level + 1, "NFC Interface");
|
|
v &= ~WSC_CONFIGURATION_METHOD_NFC_INTERFACE;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_INTEGRATED_NFC_TOKEN) {
|
|
print_attr(level + 1, "Integrated NFC Token");
|
|
v &= ~WSC_CONFIGURATION_METHOD_INTEGRATED_NFC_TOKEN;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_EXTERNAL_NFC_TOKEN) {
|
|
print_attr(level + 1, "External NFC Token");
|
|
v &= ~WSC_CONFIGURATION_METHOD_EXTERNAL_NFC_TOKEN;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_DISPLAY) {
|
|
print_attr(level + 1, "Display");
|
|
v &= ~WSC_CONFIGURATION_METHOD_DISPLAY;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_LABEL) {
|
|
print_attr(level + 1, "Label");
|
|
v &= ~WSC_CONFIGURATION_METHOD_LABEL;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_ETHERNET) {
|
|
print_attr(level + 1, "Ethernet");
|
|
v &= ~WSC_CONFIGURATION_METHOD_ETHERNET;
|
|
}
|
|
|
|
if (v & WSC_CONFIGURATION_METHOD_USBA) {
|
|
print_attr(level + 1, "USBA");
|
|
v &= ~WSC_CONFIGURATION_METHOD_USBA;
|
|
}
|
|
|
|
if (v)
|
|
print_attr(level + 1, "Unknown: %04x", v);
|
|
}
|
|
|
|
static void print_wsc_connection_type_flags(unsigned int level,
|
|
const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint8_t v;
|
|
|
|
if (size != 1)
|
|
return;
|
|
|
|
v = *((uint8_t *) data);
|
|
print_attr(level, "%s:", label);
|
|
|
|
if (v & WSC_CONNECTION_TYPE_ESS)
|
|
print_attr(level + 1, "ESS");
|
|
|
|
if (v & WSC_CONNECTION_TYPE_IBSS)
|
|
print_attr(level + 1, "IBSS");
|
|
|
|
if (v & 0xfffc)
|
|
print_attr(level + 1, "Unknown: %04x", v & 0xfffc);
|
|
}
|
|
|
|
static void print_wsc_device_name(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_wsc_utf8_string(level, label, data, size, 32);
|
|
}
|
|
|
|
static void print_wsc_device_password_id(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t v;
|
|
static const char *device_password_id_table[] = {
|
|
"Default (PIN)",
|
|
"User-specified",
|
|
"Machine-specified",
|
|
"Rekey",
|
|