mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-18 01:59:31 +01:00
296eda2ee8
Previously we only printed EAPoL packets that were of type 'Key'. With this change, iwmon now prints all EAPoL packet headers.
5021 lines
131 KiB
C
5021 lines
131 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2013-2014 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
|
|
|
|
#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 <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 <net/ethernet.h>
|
|
#include <netinet/ether.h>
|
|
#include <ell/ell.h>
|
|
|
|
#ifndef ARPHRD_NETLINK
|
|
#define ARPHRD_NETLINK 824
|
|
#endif
|
|
|
|
#include "linux/nl80211.h"
|
|
#include "src/ie.h"
|
|
#include "src/mpdu.h"
|
|
#include "src/eapol.h"
|
|
#include "src/util.h"
|
|
#include "src/wscutil.h"
|
|
#include "monitor/pcap.h"
|
|
#include "monitor/display.h"
|
|
#include "monitor/nlmon.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)
|
|
|
|
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;
|
|
};
|
|
|
|
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, " %lu.%06lu",
|
|
tv->tv_sec - time_offset, 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 const struct {
|
|
const uint8_t oui[3];
|
|
const char *str;
|
|
} oui_table[] = {
|
|
{ { 0x00, 0x03, 0x7f }, "Atheros" },
|
|
{ { 0x00, 0x03, 0x93 }, "Apple" },
|
|
{ { 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" },
|
|
{ }
|
|
};
|
|
|
|
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;
|
|
|
|
if (!util_is_bit_set(bytes[byte] & mask[byte], bit))
|
|
continue;
|
|
|
|
print_attr(level, "%s: bit %2d: %s", label, i,
|
|
bitfield_table[i]);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
print_ie_cipher_suites(level + 1, "Group Data Cipher Suite", data, 4,
|
|
rsn_cipher_selectors);
|
|
|
|
data += 4;
|
|
|
|
if (end - data < 2)
|
|
goto end;
|
|
|
|
count = l_get_le16(data) * 4;
|
|
data += 2;
|
|
|
|
if (end - data < count)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "Pairwise Cipher Suite", data,
|
|
count, rsn_cipher_selectors);
|
|
data += count;
|
|
|
|
if (end - data < 2)
|
|
goto end;
|
|
|
|
count = l_get_le16(data) * 4;
|
|
data += 2;
|
|
|
|
if (end - data < count)
|
|
goto end;
|
|
|
|
print_ie_cipher_suites(level + 1, "AKM Suite", data, count,
|
|
rsn_akm_selectors);
|
|
data += count;
|
|
|
|
if (end - data < 2)
|
|
goto end;
|
|
|
|
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 3 - 4: %s per PTKSA",
|
|
rsn_capabilities_replay_counter[count]);
|
|
|
|
count = (*((uint8_t *)data) & 0x30) >> 4;
|
|
print_attr(level + 1, "RSN capabilities: bits 5 - 6: %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 char *bytes = data;
|
|
|
|
print_attr(level + 1, "PKMKID: %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_vendor(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
static const unsigned char wfa_oui[3] = { 0x00, 0x50, 0xf2 };
|
|
const uint8_t *oui = data;
|
|
const char *str = NULL;
|
|
unsigned int i;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size < 4)
|
|
return;
|
|
|
|
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;
|
|
}
|
|
|
|
print_attr(level + 1, "%s (%02x:%02x:%02x) type: %02x", str,
|
|
oui[0], oui[1], oui[2],
|
|
oui[3]);
|
|
|
|
data += 4;
|
|
size -= 4;
|
|
|
|
if (!memcmp(oui, wfa_oui, 3)) {
|
|
switch (oui[3]) {
|
|
case 1: /* WFA WPA IE */
|
|
print_ie_wpa(level + 2, label, data, size);
|
|
return;
|
|
default:
|
|
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++) {
|
|
uint8_t byte = i / 8;
|
|
uint8_t bit = i % 8;
|
|
|
|
if (util_is_bit_set(bytes[byte], bit))
|
|
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[64] = {
|
|
[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 ... 61] = "Reserved",
|
|
[62] = "Opmode Notification",
|
|
[63] = "TDLS Wide Bandwidth support",
|
|
};
|
|
|
|
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 = util_is_bit_set(*((uint8_t *) 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 = util_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 = util_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 = util_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 = util_bit_field(htc[2], 0, 2);
|
|
print_attr(level + 1, "A-MPDU Parameters: "
|
|
"Maximum A-MPDU Length Exponent: %d", ampdu_exponent);
|
|
|
|
bits = util_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, "Suppored MCS", htc + 3, 16);
|
|
|
|
pco = util_is_bit_set(htc[18], 0);
|
|
print_attr(level + 1, "HT Extended Capabilities: PCO: %s",
|
|
bits ? "supported" : "not supported");
|
|
|
|
if (pco) {
|
|
bits = util_bit_field(htc[18], 1, 2);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"PCO Transition Time: %s",
|
|
ht_capabilities_pco_transition_time[bits]);
|
|
}
|
|
|
|
bits = util_bit_field(htc[19], 0, 2);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"MCS Feedback: %s", ht_capabilities_mcs_feedback[bits]);
|
|
|
|
plus_htc = util_is_bit_set(htc[19], 2);
|
|
print_attr(level + 1, "HT Extended Capabilities: "
|
|
"+HTC: %s", plus_htc ? "supported" : "not supported");
|
|
|
|
rd_responder = util_is_bit_set(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 = util_bit_field(bytes[2], 2, 3);
|
|
print_attr(level + 1, "Operating Channel Max Measurement Duration: %u",
|
|
byte);
|
|
|
|
byte = util_bit_field(bytes[2], 5, 3);
|
|
print_attr(level + 1, "Non-Operating Channel Max Measurement "
|
|
"Duration: %u", byte);
|
|
|
|
byte = util_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 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 } },
|
|
{ },
|
|
};
|
|
|
|
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)) {
|
|
uint8_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 (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_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_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_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",
|
|
"PushButton",
|
|
"Registrar-specified",
|
|
"Reserved (for IBSS with WPS)",
|
|
"NFC-Connection-Handover",
|
|
"P2Ps (Reserved for WPS P2P Services Specification",
|
|
};
|
|
|
|
if (size != 2) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
v = l_get_be16(data);
|
|
if (v <= 0x0008)
|
|
print_attr(level, "%s: %s", label, device_password_id_table[v]);
|
|
else if (v <= 0x000F)
|
|
print_attr(level, "%s: Reserved (%02x)", label, v);
|
|
else
|
|
print_attr(level, "%s: Random via OOB (%02x)", label, v);
|
|
}
|
|
|
|
static void print_wsc_manufacturer(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_wsc_ascii_string(level, label, data, size, 64);
|
|
}
|
|
|
|
static void print_wsc_model_name(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_wsc_ascii_string(level, label, data, size, 32);
|
|
}
|
|
|
|
static void print_wsc_model_number(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_wsc_ascii_string(level, label, data, size, 32);
|
|
}
|
|
|
|
static void print_wsc_primary_device_type(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
uint16_t category;
|
|
uint16_t subcategory;
|
|
uint8_t oui[4];
|
|
static const char *category_table[256] = {
|
|
[0] = "Reserved",
|
|
[1] = "Computer",
|
|
[2] = "Input Device",
|
|
[3] = "Printer/Scanner/Fax/Copier",
|
|
[4] = "Camera",
|
|
[5] = "Storage",
|
|
[6] = "Network Infrastructure",
|
|
[7] = "Displays",
|
|
[8] = "Multimedia Devices",
|
|
[9] = "Gaming Devices",
|
|
[10] = "Telephony",
|
|
[11] = "Audio Devices",
|
|
[12] = "Docking Devices",
|
|
[13 ... 254] = "Reserved",
|
|
[255] = "Others",
|
|
};
|
|
|
|
if (size != 8) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
category = l_get_be16(bytes);
|
|
subcategory = l_get_be16(bytes + 6);
|
|
memcpy(oui, bytes + 2, 4);
|
|
|
|
if (category > 255) {
|
|
print_attr(level, "%s: %04x-%02x%02x%02x%02x-%04x",
|
|
label, category, oui[0], oui[1], oui[2], oui[3],
|
|
subcategory);
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %s", label, category_table[category]);
|
|
print_attr(level + 1, "OUI: %02x%02x%02x%02x",
|
|
oui[0], oui[1], oui[2], oui[3]);
|
|
print_attr(level + 1, "SubCategory: %02x", subcategory);
|
|
}
|
|
|
|
static void print_wsc_request_type(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
static const char *request_type_table[] = {
|
|
"Enrollee Info",
|
|
"Enrollee Open 802.1X",
|
|
"Registrar",
|
|
"WLAN Manager Registrar",
|
|
};
|
|
|
|
if (size != 1 || bytes[0] > 3) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %s", label, request_type_table[bytes[0]]);
|
|
}
|
|
|
|
static void print_wsc_response_type(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
static const char *response_type_table[] = {
|
|
"Enrollee Info",
|
|
"Enrollee Open 802.1X",
|
|
"Registrar",
|
|
"AP",
|
|
};
|
|
|
|
if (size != 1 || bytes[0] > 3) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %s", label, response_type_table[bytes[0]]);
|
|
}
|
|
|
|
static void print_wsc_rf_bands(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
char bands[256];
|
|
uint16_t pos = 0;
|
|
|
|
if (size != 1) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
if (bytes[0] >= 0x08) {
|
|
print_attr(level, "%s: %02x", label, bytes[0]);
|
|
return;
|
|
}
|
|
|
|
if (bytes[0] & WSC_RF_BAND_2_4_GHZ)
|
|
pos += sprintf(bands + pos, " 2.4 GHz,");
|
|
|
|
if (bytes[0] & WSC_RF_BAND_5_0_GHZ)
|
|
pos += sprintf(bands + pos, " 5 GHz,");
|
|
|
|
if (bytes[0] & WSC_RF_BAND_60_GHZ)
|
|
pos += sprintf(bands + pos, " 60 GHz,");
|
|
|
|
bands[pos - 1] = '\0';
|
|
print_attr(level, "%s: %s", label, bands);
|
|
}
|
|
|
|
static void print_wsc_serial_number(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_wsc_ascii_string(level, label, data, size, 32);
|
|
}
|
|
|
|
static void print_wsc_version(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: %x", label, bytes[0]);
|
|
}
|
|
|
|
static void print_wsc_state(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const uint8_t *bytes = data;
|
|
static const char *state_table[3] = {
|
|
"Reserved",
|
|
"Not Configured",
|
|
"Configured"
|
|
};
|
|
|
|
if (size != 1 || bytes[0] == 0 || bytes[0] > 2) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
print_attr(level, "%s: %s", label, state_table[bytes[0]]);
|
|
}
|
|
|
|
static void print_wsc_wfa_ext_version2(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: %x.%x", label, bytes[0] >> 4, bytes[0] & 0xf);
|
|
}
|
|
|
|
static struct attr_entry wsc_wfa_ext_attr_entry[] = {
|
|
{ WSC_WFA_EXTENSION_VERSION2, "Version2",
|
|
ATTR_CUSTOM, { .function = print_wsc_wfa_ext_version2 } },
|
|
{ WSC_WFA_EXTENSION_NETWORK_KEY_SHAREABLE,
|
|
"Network Key Shareable",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_WFA_EXTENSION_REQUEST_TO_ENROLL, "Request to Enroll",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ },
|
|
};
|
|
|
|
static void print_wsc_wfa_ext_attributes(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
struct wsc_wfa_ext_iter iter;
|
|
int i;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
wsc_wfa_ext_iter_init(&iter, data, size);
|
|
|
|
while (wsc_wfa_ext_iter_next(&iter)) {
|
|
uint8_t type = wsc_wfa_ext_iter_get_type(&iter);
|
|
uint8_t len = wsc_wfa_ext_iter_get_length(&iter);
|
|
const void *attr = wsc_wfa_ext_iter_get_data(&iter);
|
|
struct attr_entry *entry = NULL;
|
|
|
|
for (i = 0; wsc_wfa_ext_attr_entry[i].str; i++) {
|
|
if (wsc_wfa_ext_attr_entry[i].attr == type) {
|
|
entry = &wsc_wfa_ext_attr_entry[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entry && entry->function)
|
|
entry->function(level + 1, entry->str, attr, len);
|
|
else {
|
|
print_attr(level + 1, "Type: 0x%02x: len %u",
|
|
type, len);
|
|
print_hexdump(level + 2, attr, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_wsc_vendor_extension(unsigned int level, const char *label,
|
|
const void *data, uint16_t size){
|
|
static const unsigned char wfa_ext[3] = { 0x00, 0x37, 0x2a };
|
|
const uint8_t *bytes = data;
|
|
|
|
if (size < 3) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
if (memcmp(data, wfa_ext, sizeof(wfa_ext))) {
|
|
print_attr(level, "%s: OUI: 0x%02x 0x%02x 0x%02x: len %u",
|
|
label, bytes[0], bytes[1], bytes[2], size);
|
|
print_hexdump(level + 1, data + 3, size - 3);
|
|
return;
|
|
}
|
|
|
|
print_wsc_wfa_ext_attributes(level, "WFA Vendor Extension",
|
|
data + 3, size - 3);
|
|
}
|
|
|
|
static struct attr_entry wsc_attr_entry[] = {
|
|
{ WSC_ATTR_8021X_ENABLED, "802.1X Enabled",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_AP_SETUP_LOCKED, "AP Setup Locked",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_ASSOCIATION_STATE, "Association State",
|
|
ATTR_CUSTOM, { .function = print_wsc_association_state } },
|
|
{ WSC_ATTR_CONFIGURATION_ERROR, "Configuration Error",
|
|
ATTR_CUSTOM, { .function = print_wsc_configuration_error } },
|
|
{ WSC_ATTR_CONFIGURATION_METHODS, "Configuration Methods",
|
|
ATTR_CUSTOM, { .function = print_wsc_config_methods } },
|
|
{ WSC_ATTR_DEVICE_NAME, "Device Name",
|
|
ATTR_CUSTOM, { .function = print_wsc_device_name } },
|
|
{ WSC_ATTR_DEVICE_PASSWORD_ID, "Device Password Id",
|
|
ATTR_CUSTOM, { .function = print_wsc_device_password_id } },
|
|
{ WSC_ATTR_KEY_PROVIDED_AUTOMATICALLY, "Key Provided Automatically",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_MANUFACTURER, "Manufacturer",
|
|
ATTR_CUSTOM, { .function = print_wsc_manufacturer } },
|
|
{ WSC_ATTR_MODEL_NAME, "Model Name",
|
|
ATTR_CUSTOM, { .function = print_wsc_model_name } },
|
|
{ WSC_ATTR_MODEL_NUMBER, "Model Number",
|
|
ATTR_CUSTOM, { .function = print_wsc_model_number } },
|
|
{ WSC_ATTR_NETWORK_INDEX, "Network Index",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_NETWORK_KEY_INDEX, "Network Key Index (Reserved)",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_PORTABLE_DEVICE, "Portable Device",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_PRIMARY_DEVICE_TYPE, "Primary Device Type",
|
|
ATTR_CUSTOM, { .function = print_wsc_primary_device_type } },
|
|
{ WSC_ATTR_PSK_CURRENT, "PSK Current",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_PSK_MAX, "PSK Max",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_RADIO_ENABLED, "Radio Enabled",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_REBOOT, "Reboot",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_REGISTRAR_CURRENT, "Registrar Current",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_REGISTRAR_ESTABLISHED, "Registrar Established",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_REGISTRAR_MAX, "Registrar Max",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_REQUEST_TYPE, "Request Type",
|
|
ATTR_CUSTOM, { .function = print_wsc_request_type } },
|
|
{ WSC_ATTR_RESPONSE_TYPE, "Response Type",
|
|
ATTR_CUSTOM, { .function = print_wsc_response_type } },
|
|
{ WSC_ATTR_RF_BANDS, "RF Bands",
|
|
ATTR_CUSTOM, { .function = print_wsc_rf_bands } },
|
|
{ WSC_ATTR_SELECTED_REGISTRAR, "Selected Registrar",
|
|
ATTR_CUSTOM, { .function = print_wsc_bool } },
|
|
{ WSC_ATTR_SERIAL_NUMBER, "Serial Number",
|
|
ATTR_CUSTOM, { .function = print_wsc_serial_number } },
|
|
{ WSC_ATTR_TOTAL_NETWORKS, "Total Networks",
|
|
ATTR_CUSTOM, { .function = print_wsc_byte } },
|
|
{ WSC_ATTR_UUID_E, "UUID-E",
|
|
ATTR_CUSTOM, { .function = print_wsc_uuid } },
|
|
{ WSC_ATTR_UUID_R, "UUID-R",
|
|
ATTR_CUSTOM, { .function = print_wsc_uuid } },
|
|
{ WSC_ATTR_VENDOR_EXTENSION, "Vendor Extension",
|
|
ATTR_CUSTOM, { .function = print_wsc_vendor_extension } },
|
|
{ WSC_ATTR_VERSION, "Version",
|
|
ATTR_CUSTOM, { .function = print_wsc_version } },
|
|
{ WSC_ATTR_WSC_STATE, "WSC State",
|
|
ATTR_CUSTOM, { .function = print_wsc_state } },
|
|
{ },
|
|
};
|
|
|
|
static void print_wsc_attributes(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
struct wsc_attr_iter iter;
|
|
int i;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
wsc_attr_iter_init(&iter, data, size);
|
|
|
|
while (wsc_attr_iter_next(&iter)) {
|
|
uint16_t type = wsc_attr_iter_get_type(&iter);
|
|
uint16_t len = wsc_attr_iter_get_length(&iter);
|
|
const void *attr = wsc_attr_iter_get_data(&iter);
|
|
struct attr_entry *entry = NULL;
|
|
|
|
for (i = 0; wsc_attr_entry[i].str; i++) {
|
|
if (wsc_attr_entry[i].attr == type) {
|
|
entry = &wsc_attr_entry[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entry && entry->function)
|
|
entry->function(level + 1, entry->str, attr, len);
|
|
else {
|
|
print_attr(level + 1, "Type: 0x%02x: len %u",
|
|
type, len);
|
|
print_hexdump(level + 2, attr, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_management_ies(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
void *wsc_data;
|
|
ssize_t wsc_len;
|
|
|
|
print_ie(level, label, data, size);
|
|
|
|
wsc_data = ie_tlv_extract_wsc_payload(data, size, &wsc_len);
|
|
if (!wsc_data)
|
|
return;
|
|
|
|
print_wsc_attributes(level + 1, "WSC Payload", wsc_data, wsc_len);
|
|
l_free(wsc_data);
|
|
}
|
|
|
|
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 void print_reason_code(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t rc;
|
|
/* 802.11-2012, Table 8-36 */
|
|
static const char *reason_code_table[] = {
|
|
[0] = "Reserved",
|
|
[1] = "Unspecified reason",
|
|
[2] = "Previous authentication no longer valid",
|
|
[3] = "Leaving",
|
|
[4] = "Disassociation due to inactivity",
|
|
[5] = "Disassociated because AP is unable to handle all"
|
|
" currently associated STAs",
|
|
[6] = "Class 2 frame received from nonauthenticated STA",
|
|
[7] = "Class 3 frame received from nonassociated STA",
|
|
[8] = "Disassociated because sending STA is leaving",
|
|
[9] = "STA requesting (re)association is not authenticated "
|
|
"with responding STA",
|
|
[10] = "Disassociated because the information in the Power "
|
|
"Capability element is unacceptable",
|
|
[11] = "Disassociated because the information in the Supported "
|
|
"Channels element is unacceptable",
|
|
[12] = "Disassociated due to BSS Transition Management",
|
|
[13] = "Invalid element",
|
|
[14] = "MIC failure",
|
|
[15] = "4-Way Handshake timeout",
|
|
[16] = "Group Key Handshake timeout",
|
|
[17] = "Element in 4-Way Handshake different from "
|
|
"(Re)Association Request/Probe Response/Beacon frame",
|
|
[18] = "Invalid group cipher",
|
|
[19] = "Invalid pairwise cipher",
|
|
[20] = "Invalid AKMP",
|
|
[21] = "Unsupported RSNE version",
|
|
[22] = "Invalid RSNE capabilities",
|
|
[23] = "IEEE 802.1X authentication failed",
|
|
[24] = "Cipher suite rejected because of the security policy",
|
|
[25] = "TDLS direct-link teardown due to TDLS peer STA "
|
|
"unreachable via the TDLS direct link",
|
|
[26] = "TDLS direct-link teardown for unspecified reason",
|
|
[27] = "Disassociated because session terminated by SSP "
|
|
"request",
|
|
[28] = "Disassociated because of lack of SSP roaming agreement",
|
|
[29] = "Requested service rejected because of SSP cipher "
|
|
"suite or AKM requirement",
|
|
[30] = "Requested service not authorized in this location",
|
|
[31] = "TS deleted because QoS AP lacks sufficient bandwidth "
|
|
"for this QoS STA due to a change in BSS service "
|
|
"characteristics or operational mode",
|
|
[32] = "Disassociated for unspecified, QoS-related reason",
|
|
[33] = "Disassociated because QoS AP lacks sufficient "
|
|
"bandwidth for this QoS STA",
|
|
[34] = "Disassociated because excessive number of frames need "
|
|
"to be acknowledged, but are not acknowledged due to "
|
|
"AP transmissions and/or poor channel conditions",
|
|
[35] = "Disassociated because STA is transmitting outside the "
|
|
"limits of its TXOPs",
|
|
[36] = "Requested from peer STA as the STA is leaving",
|
|
[37] = "Requested from peer STA as it does not want to use the "
|
|
"mechanism",
|
|
[38] = "Requested from peer STA as the STA received frames "
|
|
"using the mechanism for which a setup is required",
|
|
[39] = "Requested from peer STA due to timeout",
|
|
[40 ... 44] = "Reserved",
|
|
[45] = "Peer STA does not support the requested cipher suite",
|
|
[46] = "The teardown was initiated by the DLS peer | "
|
|
"Disassociated because authorized access limit reached",
|
|
[47] = "The teardown was initiated by the AP | "
|
|
"Disassociated due to external service requirements",
|
|
[48] = "Invalid FT Action frame count",
|
|
[49] = "Invalid PMKI",
|
|
[50] = "Invalid MDE",
|
|
[51] = "Invalid FTE",
|
|
[52] = "SME cancels the mesh peering instance with the reason "
|
|
"other than reaching the maximum number of peer mesh "
|
|
"STAs",
|
|
[53] = "The mesh STA has reached the supported maximum number "
|
|
"of peer mesh STAs",
|
|
[54] = "The received information violates the Mesh "
|
|
"Configuration policy configured in the mesh STA "
|
|
"profile",
|
|
[55] = "The mesh STA has received a Mesh Peering Close message "
|
|
"requesting to close the mesh peering.",
|
|
[56] = "The mesh STA has resent dot11MeshMaxRetries Mesh "
|
|
"Peering Open messages, without receiving a Mesh "
|
|
"Peering Confirm message.",
|
|
[57] = "The confirmTimer for the mesh peering instance times "
|
|
"out.",
|
|
[58] = "The mesh STA fails to unwrap the GTK or the values in "
|
|
"the wrapped contents do not match",
|
|
[59] = "The mesh STA receives inconsistent information about "
|
|
"the mesh parameters between Mesh Peering Management "
|
|
"frames",
|
|
[60] = "The mesh STA fails the authenticated mesh peering "
|
|
"exchange because due to failure in selecting either "
|
|
"the pairwise ciphersuite or group ciphersuite",
|
|
[61] = "The mesh STA does not have proxy information for this "
|
|
"external destination.",
|
|
[62] = "The mesh STA does not have forwarding information for "
|
|
"this destination.",
|
|
[63] = "The mesh STA determines that the link to the next hop "
|
|
"of an active path in its forwarding information is "
|
|
"no longer usable.",
|
|
[64] = "The Deauthentication frame was sent because the MAC "
|
|
"address of the STA already exists in the mesh BSS. "
|
|
"See 10.3.6.",
|
|
[65] = "The mesh STA performs channel switch to meet "
|
|
"regulatory requirements.",
|
|
[66] = "The mesh STA performs channel switch with unspecified "
|
|
"reason.",
|
|
};
|
|
|
|
if (size != 2)
|
|
return;
|
|
|
|
rc = *((uint16_t *) data);
|
|
|
|
if (rc >= L_ARRAY_SIZE(reason_code_table))
|
|
print_attr(level, "%s: Reserved", label);
|
|
else
|
|
print_attr(level, "%s: %s", label, reason_code_table[rc]);
|
|
}
|
|
|
|
static void print_mpdu_frame_control(unsigned int level,
|
|
const struct mpdu_fc *fc)
|
|
{
|
|
print_attr(level, "Frame Control: protocol: %02u type: %02u "
|
|
"subtype: %02u to: %02u from: %02u more_frags: %02u",
|
|
fc->protocol_version, fc->type, fc->subtype,
|
|
fc->to_ds, fc->from_ds, fc->more_fragments);
|
|
print_attr(level + 1, "retry: %02u power_mgmt: %02u more_data: %02u "
|
|
"protected: %02u order: %02u",
|
|
fc->retry, fc->power_mgmt, fc->more_data,
|
|
fc->protected_frame, fc->order);
|
|
}
|
|
|
|
static void print_mpdu_mgmt_header(unsigned int level, const struct mpdu *mpdu)
|
|
{
|
|
print_attr(level, "Duration: %u",
|
|
L_LE16_TO_CPU(mpdu->mgmt_hdr.duration));
|
|
|
|
print_address(level, "Address 1 (RA):", mpdu->mgmt_hdr.address_1);
|
|
print_address(level, "Address 2 (TA):", mpdu->mgmt_hdr.address_2);
|
|
print_address(level, "Address 3:", mpdu->mgmt_hdr.address_3);
|
|
|
|
print_attr(level, "Fragment Number: %u",
|
|
mpdu->mgmt_hdr.fragment_number);
|
|
print_attr(level, "Sequence Number: %u",
|
|
MPDU_MGMT_SEQUENCE_NUMBER(mpdu->mgmt_hdr));
|
|
}
|
|
|
|
static void print_authentication_mgmt_frame(unsigned int level,
|
|
const struct mpdu *mpdu)
|
|
{
|
|
const char *str;
|
|
|
|
if (!mpdu)
|
|
return;
|
|
|
|
print_attr(level, "Authentication:");
|
|
|
|
print_mpdu_frame_control(level + 1, &mpdu->fc);
|
|
print_mpdu_mgmt_header(level + 1, mpdu);
|
|
|
|
switch (L_LE16_TO_CPU(mpdu->auth.algorithm)) {
|
|
case MPDU_AUTH_ALGO_OPEN_SYSTEM:
|
|
str = "Open";
|
|
break;
|
|
case MPDU_AUTH_ALGO_SHARED_KEY:
|
|
str = "Shared key";
|
|
break;
|
|
default:
|
|
str = "Reserved";
|
|
break;
|
|
}
|
|
|
|
print_attr(level + 1, "Algorithm: %s (seq: %u, status: %u)", str,
|
|
L_LE16_TO_CPU(mpdu->auth.transaction_sequence),
|
|
L_LE16_TO_CPU(mpdu->auth.status));
|
|
|
|
if (L_LE16_TO_CPU(mpdu->auth.algorithm) != MPDU_AUTH_ALGO_SHARED_KEY)
|
|
return;
|
|
|
|
if (L_LE16_TO_CPU(mpdu->auth.transaction_sequence) < 2 ||
|
|
L_LE16_TO_CPU(mpdu->auth.transaction_sequence) > 3)
|
|
return;
|
|
|
|
print_attr(level + 1, "Challenge text: \"%s\" (%u)",
|
|
mpdu->auth.shared_key_23.challenge_text,
|
|
mpdu->auth.shared_key_23.challenge_text_len);
|
|
}
|
|
|
|
static void print_deauthentication_mgmt_frame(unsigned int level,
|
|
const struct mpdu *mpdu)
|
|
{
|
|
if (!mpdu)
|
|
return;
|
|
|
|
print_attr(level, "Deauthentication:");
|
|
|
|
print_mpdu_frame_control(level + 1, &mpdu->fc);
|
|
print_mpdu_mgmt_header(level + 1, mpdu);
|
|
|
|
print_attr(level + 1, "Reason code: %u",
|
|
L_LE16_TO_CPU(mpdu->deauth.reason_code));
|
|
}
|
|
|
|
static void print_frame_type(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t frame_type = *((uint16_t *) data);
|
|
uint8_t type = frame_type & 0x000c;
|
|
uint8_t subtype = (frame_type & 0x00f0) >> 4;
|
|
const struct mpdu *mpdu = NULL;
|
|
const char *str;
|
|
|
|
print_attr(level, "%s: 0x%04x", label, frame_type);
|
|
|
|
switch (type) {
|
|
case 0x00:
|
|
str = "Management";
|
|
mpdu = mpdu_validate(data, size);
|
|
break;
|
|
default:
|
|
str = "Reserved";
|
|
break;
|
|
}
|
|
|
|
print_attr(level + 1, "Type: %s (%u)", str, type);
|
|
|
|
switch (subtype) {
|
|
case 0x00:
|
|
str = "Association request";
|
|
break;
|
|
case 0x01:
|
|
str = "Association response";
|
|
break;
|
|
case 0x02:
|
|
str = "Reassociation request";
|
|
break;
|
|
case 0x03:
|
|
str = "Reassociation response";
|
|
break;
|
|
case 0x04:
|
|
str = "Probe request";
|
|
break;
|
|
case 0x05:
|
|
str = "Probe response";
|
|
break;
|
|
case 0x06:
|
|
str = "Timing Advertisement";
|
|
break;
|
|
case 0x08:
|
|
str = "Beacon";
|
|
break;
|
|
case 0x09:
|
|
str = "ATIM";
|
|
break;
|
|
case 0x0a:
|
|
str = "Disassociation";
|
|
break;
|
|
case 0x0b:
|
|
str = "Authentication";
|
|
print_authentication_mgmt_frame(level + 1, mpdu);
|
|
break;
|
|
case 0x0c:
|
|
str = "Deauthentication";
|
|
print_deauthentication_mgmt_frame(level + 1, mpdu);
|
|
break;
|
|
case 0x0d:
|
|
str = "Action";
|
|
break;
|
|
case 0x0e:
|
|
str = "Action No Ack";
|
|
break;
|
|
default:
|
|
str = "Reserved";
|
|
break;
|
|
}
|
|
|
|
if (!mpdu)
|
|
print_attr(level + 1, "Subtype: %s (%u)", str, subtype);
|
|
}
|
|
|
|
static void print_frame(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s: len %u", label, size);
|
|
print_frame_type(level + 1, "Frame Type", data, size);
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static void print_cipher_suite(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint32_t cipher = *((uint32_t *) data);
|
|
|
|
if (size != 4)
|
|
return;
|
|
|
|
print_ie_cipher_suite(level, label, cipher, rsn_cipher_selectors);
|
|
}
|
|
|
|
static void print_cipher_suites(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s:", label);
|
|
|
|
while (size >= 4) {
|
|
print_cipher_suite(level + 1, NULL, data, 4);
|
|
data += 4;
|
|
size -= 4;
|
|
}
|
|
}
|
|
|
|
static void print_akm_suites(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s:", label);
|
|
|
|
while (size >= 4) {
|
|
uint32_t akm = *((uint32_t *) data);
|
|
|
|
print_ie_cipher_suite(level, NULL, akm, rsn_akm_selectors);
|
|
data += 4;
|
|
size -= 4;
|
|
}
|
|
}
|
|
|
|
static const struct attr_entry iftype_table[] = {
|
|
{ NL80211_IFTYPE_ADHOC, "Ad-hoc", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_STATION, "Station", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_AP, "AP", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_AP_VLAN, "AP-VLAN", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_WDS, "WDS", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_MONITOR, "Monitor", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_MESH_POINT, "Mesh-point", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_P2P_CLIENT, "P2P-Client", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_P2P_GO, "P2P-GO", ATTR_FLAG },
|
|
{ NL80211_IFTYPE_P2P_DEVICE, "P2P-Device", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry bss_param_table[] = {
|
|
{ NL80211_STA_BSS_PARAM_CTS_PROT, "CTS protection", ATTR_FLAG },
|
|
{ NL80211_STA_BSS_PARAM_SHORT_PREAMBLE, "Short Preamble", ATTR_FLAG },
|
|
{ NL80211_STA_BSS_PARAM_SHORT_SLOT_TIME,"Short Slot Time", ATTR_FLAG },
|
|
{ NL80211_STA_BSS_PARAM_DTIM_PERIOD, "DTIM Period", ATTR_U8 },
|
|
{ NL80211_STA_BSS_PARAM_BEACON_INTERVAL,"Beacon Interval", ATTR_U16 },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry sta_flag_table[] = {
|
|
{ NL80211_STA_FLAG_AUTHORIZED, "Authorized", ATTR_FLAG },
|
|
{ NL80211_STA_FLAG_SHORT_PREAMBLE, "ShortPreamble",ATTR_FLAG },
|
|
{ NL80211_STA_FLAG_WME, "WME", ATTR_FLAG },
|
|
{ NL80211_STA_FLAG_MFP, "MFP", ATTR_FLAG },
|
|
{ NL80211_STA_FLAG_AUTHENTICATED, "Authenticated",ATTR_FLAG },
|
|
{ NL80211_STA_FLAG_TDLS_PEER, "TDLS-Peer", ATTR_FLAG },
|
|
{ NL80211_STA_FLAG_ASSOCIATED, "Associated", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static void print_sta_flag_update(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const struct nl80211_sta_flag_update *flags = data;
|
|
unsigned int i;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
print_attr(level + 1, "Mask: 0x%08x", flags->mask);
|
|
for (i = 0; sta_flag_table[i].str; i++) {
|
|
if (flags->mask & (1 << sta_flag_table[i].attr))
|
|
print_attr(level + 2, "%s", sta_flag_table[i].str);
|
|
}
|
|
|
|
print_attr(level + 1, "Set: 0x%08x", flags->set);
|
|
for (i = 0; sta_flag_table[i].str; i++) {
|
|
if (flags->set & (1 << sta_flag_table[i].attr))
|
|
print_attr(level + 2, "%s", sta_flag_table[i].str);
|
|
}
|
|
}
|
|
|
|
static const struct attr_entry rate_info_table[] = {
|
|
{ NL80211_RATE_INFO_BITRATE, "Bit Rate (Legacy)", ATTR_U16 },
|
|
{ NL80211_RATE_INFO_MCS, "MCS Index", ATTR_U8 },
|
|
{ NL80211_RATE_INFO_40_MHZ_WIDTH, "40 Mhz Width", ATTR_FLAG },
|
|
{ NL80211_RATE_INFO_SHORT_GI, "Short GI", ATTR_FLAG },
|
|
{ NL80211_RATE_INFO_BITRATE32, "Bit Rate", ATTR_U32 },
|
|
{ NL80211_RATE_INFO_VHT_MCS, "VHT MCS Index", ATTR_U8 },
|
|
{ NL80211_RATE_INFO_VHT_NSS, "# VHT Streams", ATTR_U8 },
|
|
{ NL80211_RATE_INFO_80_MHZ_WIDTH, "80 Mhz Width", ATTR_FLAG },
|
|
{ NL80211_RATE_INFO_80P80_MHZ_WIDTH, "80P80 Mhz Width", ATTR_FLAG },
|
|
{ NL80211_RATE_INFO_160_MHZ_WIDTH, "160 Mhz Width", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry sta_info_table[] = {
|
|
{ NL80211_STA_INFO_INACTIVE_TIME,
|
|
"Inactivity time", ATTR_U32 },
|
|
{ NL80211_STA_INFO_RX_BYTES, "Total RX bytes", ATTR_U32 },
|
|
{ NL80211_STA_INFO_TX_BYTES, "Total TX bytes", ATTR_U32 },
|
|
{ NL80211_STA_INFO_RX_BYTES64, "Total RX bytes", ATTR_U64 },
|
|
{ NL80211_STA_INFO_TX_BYTES64, "Total TX bytes", ATTR_U64 },
|
|
{ NL80211_STA_INFO_SIGNAL, "Signal strength", ATTR_S8 },
|
|
{ NL80211_STA_INFO_TX_BITRATE, "TX bitrate",
|
|
ATTR_NESTED, { rate_info_table } },
|
|
{ NL80211_STA_INFO_RX_PACKETS, "RX packets", ATTR_U32 },
|
|
{ NL80211_STA_INFO_TX_PACKETS, "TX packets", ATTR_U32 },
|
|
{ NL80211_STA_INFO_TX_RETRIES, "TX retries", ATTR_U32 },
|
|
{ NL80211_STA_INFO_TX_FAILED, "TX failed", ATTR_U32 },
|
|
{ NL80211_STA_INFO_SIGNAL_AVG, "Signal strength average",
|
|
ATTR_S8 },
|
|
{ NL80211_STA_INFO_LLID, "Mesh LLID", ATTR_U16 },
|
|
{ NL80211_STA_INFO_PLID, "Mesh PLID", ATTR_U16 },
|
|
{ NL80211_STA_INFO_PLINK_STATE, "P-Link state" },
|
|
{ NL80211_STA_INFO_RX_BITRATE, "RX bitrate",
|
|
ATTR_NESTED, { rate_info_table } },
|
|
{ NL80211_STA_INFO_BSS_PARAM, "BSS parameters",
|
|
ATTR_NESTED, { bss_param_table } },
|
|
{ NL80211_STA_INFO_CONNECTED_TIME,
|
|
"Connected time", ATTR_U32 },
|
|
{ NL80211_STA_INFO_STA_FLAGS, "Station flags",
|
|
ATTR_CUSTOM, { .function = print_sta_flag_update } },
|
|
{ NL80211_STA_INFO_BEACON_LOSS, "Beacon loss", ATTR_U32 },
|
|
{ NL80211_STA_INFO_T_OFFSET, "Timing offset", ATTR_S64 },
|
|
{ NL80211_STA_INFO_LOCAL_PM, "Local mesh PM", ATTR_U32 },
|
|
{ NL80211_STA_INFO_PEER_PM, "Peer mesh PM", ATTR_U32 },
|
|
{ NL80211_STA_INFO_NONPEER_PM, "Neighbor mesh PM", ATTR_U32 },
|
|
{ NL80211_STA_INFO_CHAIN_SIGNAL,
|
|
"Per-chain signal strength" },
|
|
{ NL80211_STA_INFO_CHAIN_SIGNAL_AVG,
|
|
"Per-chain signal strength average" },
|
|
{ }
|
|
};
|
|
|
|
static void print_bss_capability(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
uint16_t cap = *(uint16_t *) data;
|
|
|
|
print_attr(level, "%s: %"PRIu16" (0x%04"PRIx16")", label, cap, cap);
|
|
|
|
if (cap & BSS_CAPABILITY_ESS)
|
|
print_attr(level + 1, "ESS");
|
|
if (cap & BSS_CAPABILITY_IBSS)
|
|
print_attr(level + 1, "IBSS");
|
|
if (cap & BSS_CAPABILITY_PRIVACY)
|
|
print_attr(level + 1, "Privacy");
|
|
if (cap & BSS_CAPABILITY_SHORT_PREAMBLE)
|
|
print_attr(level + 1, "ShortPreamble");
|
|
if (cap & BSS_CAPABILITY_PBCC)
|
|
print_attr(level + 1, "PBCC");
|
|
if (cap & BSS_CAPABILITY_CHANNEL_AGILITY)
|
|
print_attr(level + 1, "ChannelAgility");
|
|
if (cap & BSS_CAPABILITY_SPECTRUM_MGMT)
|
|
print_attr(level + 1, "SpectrumMgmt");
|
|
if (cap & BSS_CAPABILITY_QOS)
|
|
print_attr(level + 1, "QoS");
|
|
if (cap & BSS_CAPABILITY_SHORT_SLOT_TIME)
|
|
print_attr(level + 1, "ShortSlotTime");
|
|
if (cap & BSS_CAPABILITY_APSD)
|
|
print_attr(level + 1, "APSD");
|
|
if (cap & BSS_CAPABILITY_DSSS_OFDM)
|
|
print_attr(level + 1, "DSSS-OFDM");
|
|
}
|
|
|
|
static const struct attr_entry bss_table[] = {
|
|
{ NL80211_BSS_BSSID, "BSSID", ATTR_ADDRESS },
|
|
{ NL80211_BSS_FREQUENCY, "Frequency", ATTR_U32 },
|
|
{ NL80211_BSS_TSF, "TSF", ATTR_U64 },
|
|
{ NL80211_BSS_BEACON_INTERVAL, "Beacon Interval",
|
|
ATTR_U16 },
|
|
{ NL80211_BSS_CAPABILITY, "Capability", ATTR_CUSTOM,
|
|
{ .function = print_bss_capability } },
|
|
{ NL80211_BSS_INFORMATION_ELEMENTS, "IEs",
|
|
ATTR_CUSTOM, { .function = print_management_ies } },
|
|
{ NL80211_BSS_SIGNAL_MBM, "Signal mBm", ATTR_S32 },
|
|
{ NL80211_BSS_SIGNAL_UNSPEC, "Signal Unspec",ATTR_U8 },
|
|
{ NL80211_BSS_STATUS, "Status", ATTR_U32 },
|
|
{ NL80211_BSS_SEEN_MS_AGO, "Seen ms ago", ATTR_U32 },
|
|
{ NL80211_BSS_BEACON_IES, "Beacon IEs",
|
|
ATTR_CUSTOM, { .function = print_management_ies } },
|
|
{ NL80211_BSS_CHAN_WIDTH, "Chan Width", ATTR_U32 },
|
|
{ NL80211_BSS_BEACON_TSF, "Beacon TSF", ATTR_U64 },
|
|
{ NL80211_BSS_PRESP_DATA, "Probe Response", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry frame_types_field_table[] = {
|
|
{ NL80211_ATTR_FRAME_TYPE,
|
|
"Frame Type", ATTR_CUSTOM,
|
|
{ .function = print_frame_type } },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry frame_types_table[] = {
|
|
{ NL80211_IFTYPE_ADHOC, "Ad-hoc", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_STATION, "Station", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_AP, "AP", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_AP_VLAN, "AP-VLAN", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_WDS, "WDS", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_MONITOR, "Monitor", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_MESH_POINT, "Mesh-point", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_P2P_CLIENT, "P2P-Client", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_P2P_GO, "P2P-GO", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ NL80211_IFTYPE_P2P_DEVICE, "P2P-Device", ATTR_NESTED,
|
|
{ frame_types_field_table } },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry cqm_table[] = {
|
|
{ NL80211_ATTR_CQM_RSSI_THOLD, "RSSI threshold", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_RSSI_HYST, "RSSI hysteresis", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT,
|
|
"RSSI threshold event", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_PKT_LOSS_EVENT,
|
|
"Packet loss event", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_TXE_RATE, "TX error rate", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_TXE_PKTS, "TX error packets", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_TXE_INTVL, "TX error interval", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM_BEACON_LOSS_EVENT, "Beacon Loss Event", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static const struct attr_entry key_default_type_table[] = {
|
|
{ NL80211_KEY_DEFAULT_TYPE_UNICAST, "Unicast", ATTR_FLAG },
|
|
{ NL80211_KEY_DEFAULT_TYPE_MULTICAST, "Multicast", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static void print_rekey_kek(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size != NL80211_KEK_LEN)
|
|
printf("malformed packet");
|
|
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static void print_rekey_kck(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size != NL80211_KCK_LEN)
|
|
printf("malformed packet");
|
|
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static void print_rekey_replay_ctr(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
if (size != NL80211_REPLAY_CTR_LEN)
|
|
printf("malformed packet");
|
|
|
|
print_hexdump(level + 1, data, size);
|
|
}
|
|
|
|
static const struct attr_entry rekey_table[] = {
|
|
{ NL80211_REKEY_DATA_KEK, "KEK", ATTR_CUSTOM,
|
|
{ .function = print_rekey_kek } },
|
|
{ NL80211_REKEY_DATA_KCK, "KCK", ATTR_CUSTOM,
|
|
{ .function = print_rekey_kck } },
|
|
{ NL80211_REKEY_DATA_REPLAY_CTR, "Replay CTR", ATTR_CUSTOM,
|
|
{ .function = print_rekey_replay_ctr } },
|
|
{ }
|
|
};
|
|
|
|
#define NLA_OK(nla,len) ((len) >= (int) sizeof(struct nlattr) && \
|
|
(nla)->nla_len >= sizeof(struct nlattr) && \
|
|
(nla)->nla_len <= (len))
|
|
#define NLA_NEXT(nla,attrlen) ((attrlen) -= NLA_ALIGN((nla)->nla_len), \
|
|
(struct nlattr*)(((char*)(nla)) + \
|
|
NLA_ALIGN((nla)->nla_len)))
|
|
|
|
#define NLA_LENGTH(len) (NLA_ALIGN(sizeof(struct nlattr)) + (len))
|
|
#define NLA_DATA(nla) ((void*)(((char*)(nla)) + NLA_LENGTH(0)))
|
|
#define NLA_PAYLOAD(nla) ((int)((nla)->nla_len - NLA_LENGTH(0)))
|
|
|
|
static const struct {
|
|
uint8_t cmd;
|
|
const char *str;
|
|
} cmd_table[] = {
|
|
{ NL80211_CMD_GET_WIPHY, "Get Wiphy" },
|
|
{ NL80211_CMD_SET_WIPHY, "Set Wiphy" },
|
|
{ NL80211_CMD_NEW_WIPHY, "New Wiphy" },
|
|
{ NL80211_CMD_DEL_WIPHY, "Del Wiphy" },
|
|
{ NL80211_CMD_GET_INTERFACE, "Get Interface" },
|
|
{ NL80211_CMD_SET_INTERFACE, "Set Interface" },
|
|
{ NL80211_CMD_NEW_INTERFACE, "New Interface" },
|
|
{ NL80211_CMD_DEL_INTERFACE, "Del Interface" },
|
|
{ NL80211_CMD_GET_KEY, "Get Key" },
|
|
{ NL80211_CMD_SET_KEY, "Set Key" },
|
|
{ NL80211_CMD_NEW_KEY, "New Key" },
|
|
{ NL80211_CMD_DEL_KEY, "Del Key" },
|
|
{ NL80211_CMD_GET_BEACON, "Get Beacon" },
|
|
{ NL80211_CMD_SET_BEACON, "Set Beacon" },
|
|
{ NL80211_CMD_START_AP, "Start AP" },
|
|
{ NL80211_CMD_STOP_AP, "Stop AP" },
|
|
{ NL80211_CMD_GET_STATION, "Get Station" },
|
|
{ NL80211_CMD_SET_STATION, "Set Station" },
|
|
{ NL80211_CMD_NEW_STATION, "New Station" },
|
|
{ NL80211_CMD_DEL_STATION, "Del Station" },
|
|
{ NL80211_CMD_GET_MPATH, "Get Mesh Path" },
|
|
{ NL80211_CMD_SET_MPATH, "Set Mesh Path" },
|
|
{ NL80211_CMD_NEW_MPATH, "New Mesh Path" },
|
|
{ NL80211_CMD_DEL_MPATH, "Del Mesh Path" },
|
|
{ NL80211_CMD_SET_BSS, "Set BSS" },
|
|
{ NL80211_CMD_SET_REG, "Set Reg" },
|
|
{ NL80211_CMD_REQ_SET_REG, "Req Set Reg" },
|
|
{ NL80211_CMD_GET_MESH_CONFIG, "Get Mesh Config" },
|
|
{ NL80211_CMD_SET_MESH_CONFIG, "Set Mesh Config" },
|
|
{ NL80211_CMD_SET_MGMT_EXTRA_IE, "Mgmt Extra IE" },
|
|
{ NL80211_CMD_GET_REG, "Get Reg" },
|
|
{ NL80211_CMD_GET_SCAN, "Get Scan" },
|
|
{ NL80211_CMD_TRIGGER_SCAN, "Trigger Scan" },
|
|
{ NL80211_CMD_NEW_SCAN_RESULTS, "New Scan Results" },
|
|
{ NL80211_CMD_SCAN_ABORTED, "Scan Aborted" },
|
|
{ NL80211_CMD_REG_CHANGE, "Reg Change" },
|
|
{ NL80211_CMD_AUTHENTICATE, "Authenticate" },
|
|
{ NL80211_CMD_ASSOCIATE, "Associate" },
|
|
{ NL80211_CMD_DEAUTHENTICATE, "Deauthenticate" },
|
|
{ NL80211_CMD_DISASSOCIATE, "Disassociate" },
|
|
{ NL80211_CMD_MICHAEL_MIC_FAILURE, "Michael MIC Failure" },
|
|
{ NL80211_CMD_REG_BEACON_HINT, "Reg Beacon Hint" },
|
|
{ NL80211_CMD_JOIN_IBSS, "Join IBSS" },
|
|
{ NL80211_CMD_LEAVE_IBSS, "Leave IBSS" },
|
|
{ NL80211_CMD_TESTMODE, "Test Mode" },
|
|
{ NL80211_CMD_CONNECT, "Connect" },
|
|
{ NL80211_CMD_ROAM, "Roam" },
|
|
{ NL80211_CMD_DISCONNECT, "Disconnect" },
|
|
{ NL80211_CMD_SET_WIPHY_NETNS, "Set Wiphy Netns" },
|
|
{ NL80211_CMD_GET_SURVEY, "Get Survey" },
|
|
{ NL80211_CMD_NEW_SURVEY_RESULTS, "New Survey Results" },
|
|
{ NL80211_CMD_SET_PMKSA, "Set PMKSA" },
|
|
{ NL80211_CMD_DEL_PMKSA, "Del PMKSA" },
|
|
{ NL80211_CMD_FLUSH_PMKSA, "Flush PMKSA" },
|
|
{ NL80211_CMD_REMAIN_ON_CHANNEL, "Remain on Channel" },
|
|
{ NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL, "Cancel Remain on Channel"},
|
|
{ NL80211_CMD_SET_TX_BITRATE_MASK, "Set TX Bitrate Mask" },
|
|
{ NL80211_CMD_REGISTER_FRAME, "Register Frame" },
|
|
{ NL80211_CMD_FRAME, "Frame" },
|
|
{ NL80211_CMD_FRAME_TX_STATUS, "Frame TX Status" },
|
|
{ NL80211_CMD_SET_POWER_SAVE, "Set Power Save" },
|
|
{ NL80211_CMD_GET_POWER_SAVE, "Get Power Save" },
|
|
{ NL80211_CMD_SET_CQM, "Set CQM" },
|
|
{ NL80211_CMD_NOTIFY_CQM, "Notify CQM" },
|
|
{ NL80211_CMD_SET_CHANNEL, "Set Channel" },
|
|
{ NL80211_CMD_SET_WDS_PEER, "Set WDS Peer" },
|
|
{ NL80211_CMD_FRAME_WAIT_CANCEL, "Frame Wait Cancel" },
|
|
{ NL80211_CMD_JOIN_MESH, "Join Mesh" },
|
|
{ NL80211_CMD_LEAVE_MESH, "Leave Mesh" },
|
|
{ NL80211_CMD_UNPROT_DEAUTHENTICATE, "Unprot Deauthenticate" },
|
|
{ NL80211_CMD_UNPROT_DISASSOCIATE, "Unprot Disassociate" },
|
|
{ NL80211_CMD_NEW_PEER_CANDIDATE, "New Peer Candidate" },
|
|
{ NL80211_CMD_GET_WOWLAN, "Get WoWLAN" },
|
|
{ NL80211_CMD_SET_WOWLAN, "Set WoWLAN" },
|
|
{ NL80211_CMD_START_SCHED_SCAN, "Start Sched Scan" },
|
|
{ NL80211_CMD_STOP_SCHED_SCAN, "Stop Sched Scan" },
|
|
{ NL80211_CMD_SCHED_SCAN_RESULTS, "Sched Scan Results" },
|
|
{ NL80211_CMD_SCHED_SCAN_STOPPED, "Sched Scan Stopped" },
|
|
{ NL80211_CMD_SET_REKEY_OFFLOAD, "Set Rekey Offload" },
|
|
{ NL80211_CMD_PMKSA_CANDIDATE, "PMKSA Candidate" },
|
|
{ NL80211_CMD_TDLS_OPER, "TDLS Oper" },
|
|
{ NL80211_CMD_TDLS_MGMT, "TDLS Mgmt" },
|
|
{ NL80211_CMD_UNEXPECTED_FRAME, "Unexpected Frame" },
|
|
{ NL80211_CMD_PROBE_CLIENT, "Probe Client" },
|
|
{ NL80211_CMD_REGISTER_BEACONS, "Register Beacons" },
|
|
{ NL80211_CMD_UNEXPECTED_4ADDR_FRAME, "Unexpected 4addr Frame"},
|
|
{ NL80211_CMD_SET_NOACK_MAP, "Set NoAck Map" },
|
|
{ NL80211_CMD_CH_SWITCH_NOTIFY, "Channel Switch Notify" },
|
|
{ NL80211_CMD_START_P2P_DEVICE, "Start P2P Device" },
|
|
{ NL80211_CMD_STOP_P2P_DEVICE, "Stop P2P Device" },
|
|
{ NL80211_CMD_CONN_FAILED, "Conn Failed" },
|
|
{ NL80211_CMD_SET_MCAST_RATE, "Set Mcast Rate" },
|
|
{ NL80211_CMD_SET_MAC_ACL, "Set MAC ACL" },
|
|
{ NL80211_CMD_RADAR_DETECT, "Radar Detect" },
|
|
{ NL80211_CMD_GET_PROTOCOL_FEATURES, "Get Protocol Features" },
|
|
{ NL80211_CMD_UPDATE_FT_IES, "Update FT IEs" },
|
|
{ NL80211_CMD_FT_EVENT, "FT Event" },
|
|
{ NL80211_CMD_CRIT_PROTOCOL_START, "Crit Protocol Start" },
|
|
{ NL80211_CMD_CRIT_PROTOCOL_STOP, "Crit Protocol Stop" },
|
|
{ NL80211_CMD_GET_COALESCE, "Get Coalesce" },
|
|
{ NL80211_CMD_SET_COALESCE, "Set Coalesce" },
|
|
{ NL80211_CMD_CHANNEL_SWITCH, "Channel Switch" },
|
|
{ NL80211_CMD_VENDOR, "Vendor" },
|
|
{ NL80211_CMD_SET_QOS_MAP, "Set QoS Map" },
|
|
{ NL80211_CMD_ADD_TX_TS, "Add Traffic Stream" },
|
|
{ NL80211_CMD_DEL_TX_TS, "Delete Traffic Stream" },
|
|
{ NL80211_CMD_GET_MPP, "Get Mesh Proxy Path" },
|
|
{ NL80211_CMD_JOIN_OCB, "Join OCB Network" },
|
|
{ NL80211_CMD_LEAVE_OCB, "Leave OCB Network" },
|
|
{ NL80211_CMD_CH_SWITCH_STARTED_NOTIFY, "Channel Switch Notify" },
|
|
{ NL80211_CMD_TDLS_CHANNEL_SWITCH, "TDLS Channel Switch" },
|
|
{ NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH,
|
|
"Cancel TLDS Channel Switch" },
|
|
{ NL80211_CMD_WIPHY_REG_CHANGE, "Wiphy Reg Change" },
|
|
{ }
|
|
};
|
|
|
|
static void print_supported_commands(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const struct nlattr *nla;
|
|
|
|
print_attr(level, "%s:", label);
|
|
|
|
for (nla = data; NLA_OK(nla, size); nla = NLA_NEXT(nla, size)) {
|
|
const char *cmd_str = "Reserved";
|
|
uint32_t cmd = *((uint32_t *) NLA_DATA(nla));
|
|
unsigned int i;
|
|
|
|
for (i = 0; cmd_table[i].str; i++) {
|
|
if (cmd_table[i].cmd == cmd) {
|
|
cmd_str = cmd_table[i].str;
|
|
break;
|
|
}
|
|
}
|
|
|
|
print_attr(level + 1, "%s [%d]", cmd_str, cmd);
|
|
}
|
|
}
|
|
|
|
static const struct attr_entry frequency_attr_table[] = {
|
|
{ NL80211_FREQUENCY_ATTR_FREQ, "Frequency", ATTR_U32 },
|
|
{ NL80211_FREQUENCY_ATTR_DISABLED, "Disabled", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_NO_IR, "No IR", ATTR_FLAG },
|
|
{ __NL80211_FREQUENCY_ATTR_NO_IBSS, "No IBSS", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_RADAR, "Radar Detection", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_MAX_TX_POWER, "Max TX Power", ATTR_U32 },
|
|
{ NL80211_FREQUENCY_ATTR_DFS_STATE, "DFS State", ATTR_U32 },
|
|
{ NL80211_FREQUENCY_ATTR_DFS_TIME, "DFS Time", ATTR_U32 },
|
|
{ NL80211_FREQUENCY_ATTR_NO_HT40_MINUS, "No HT40-", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_NO_HT40_PLUS, "No HT40+", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_NO_80MHZ, "No 80 Mhz", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_NO_160MHZ, "No 160 Mhz", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_DFS_CAC_TIME, "DFS CAC Time", ATTR_U32 },
|
|
{ NL80211_FREQUENCY_ATTR_INDOOR_ONLY, "Indoor only", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_GO_CONCURRENT, "Go Concurrent", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_NO_20MHZ, "No 20 Mhz", ATTR_FLAG },
|
|
{ NL80211_FREQUENCY_ATTR_NO_10MHZ, "No 10 Mhz", ATTR_FLAG },
|
|
{ }
|
|
};
|
|
|
|
static void print_band_frequencies(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const struct nlattr *nla;
|
|
uint16_t nla_type;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
for (nla = data; NLA_OK(nla, size); nla = NLA_NEXT(nla, size)) {
|
|
nla_type = nla->nla_type & NLA_TYPE_MASK;
|
|
print_attr(level + 1, "Frequency %u: len %u", nla_type,
|
|
NLA_PAYLOAD(nla));
|
|
|
|
print_attributes(level + 2, frequency_attr_table,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
}
|
|
}
|
|
|
|
static const struct attr_entry wiphy_bands_table[] = {
|
|
{ NL80211_BAND_ATTR_FREQS, "Frequencies",
|
|
ATTR_CUSTOM, { .function = print_band_frequencies } },
|
|
{ NL80211_BAND_ATTR_RATES, "Rates" },
|
|
{ NL80211_BAND_ATTR_HT_MCS_SET, "HT MCS Set" },
|
|
{ NL80211_BAND_ATTR_HT_CAPA, "HT Capabilities" },
|
|
{ NL80211_BAND_ATTR_HT_AMPDU_FACTOR, "AMPDU Factor" },
|
|
{ NL80211_BAND_ATTR_HT_AMPDU_DENSITY, "AMPDU Density" },
|
|
{ NL80211_BAND_ATTR_VHT_MCS_SET, "VHT MCS Set" },
|
|
{ NL80211_BAND_ATTR_VHT_CAPA, "VHT Capabilities" },
|
|
{ }
|
|
};
|
|
|
|
static void print_wiphy_bands(unsigned int level, const char *label,
|
|
const void *data, uint16_t size)
|
|
{
|
|
const struct nlattr *nla;
|
|
uint16_t nla_type;
|
|
|
|
print_attr(level, "%s: len %u", label, size);
|
|
|
|
for (nla = data; NLA_OK(nla, size); nla = NLA_NEXT(nla, size)) {
|
|
nla_type = nla->nla_type & NLA_TYPE_MASK;
|
|
print_attr(level + 1, "Band %u: len %u", nla_type,
|
|
NLA_PAYLOAD(nla));
|
|
|
|
print_attributes(level + 2, wiphy_bands_table,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
}
|
|
}
|
|
|
|
static const struct attr_entry attr_table[] = {
|
|
{ NL80211_ATTR_WIPHY,
|
|
"Wiphy", ATTR_U32 },
|
|
{ NL80211_ATTR_WIPHY_NAME,
|
|
"Wiphy Name", ATTR_STRING },
|
|
{ NL80211_ATTR_IFINDEX,
|
|
"Interface Index", ATTR_U32 },
|
|
{ NL80211_ATTR_IFNAME,
|
|
"Interface Name", ATTR_STRING },
|
|
{ NL80211_ATTR_IFTYPE,
|
|
"Interface Type", ATTR_U32 },
|
|
{ NL80211_ATTR_MAC,
|
|
"MAC Address", ATTR_ADDRESS },
|
|
{ NL80211_ATTR_KEY_DATA,
|
|
"Key Data", ATTR_BINARY },
|
|
{ NL80211_ATTR_KEY_IDX,
|
|
"Key Index", ATTR_U8 },
|
|
{ NL80211_ATTR_KEY_CIPHER,
|
|
"Key Cipher", ATTR_CUSTOM,
|
|
{ .function = print_cipher_suite } },
|
|
{ NL80211_ATTR_KEY_SEQ,
|
|
"Key Sequence", ATTR_BINARY },
|
|
{ NL80211_ATTR_KEY_DEFAULT,
|
|
"Key Default", ATTR_FLAG },
|
|
{ NL80211_ATTR_BEACON_INTERVAL,
|
|
"Beacon Interval", ATTR_U32 },
|
|
{ NL80211_ATTR_DTIM_PERIOD,
|
|
"DTIM Period", ATTR_U32 },
|
|
{ NL80211_ATTR_BEACON_HEAD,
|
|
"Beacon Head", ATTR_BINARY },
|
|
{ NL80211_ATTR_BEACON_TAIL,
|
|
"Beacon Tail", ATTR_BINARY },
|
|
{ NL80211_ATTR_STA_AID,
|
|
"Station AID", ATTR_U16 },
|
|
{ NL80211_ATTR_STA_FLAGS,
|
|
"Station Flags", ATTR_NESTED, { sta_flag_table } },
|
|
{ NL80211_ATTR_STA_LISTEN_INTERVAL,
|
|
"Station Listen Interval", ATTR_U16 },
|
|
{ NL80211_ATTR_STA_SUPPORTED_RATES,
|
|
"Station Supported Rates", ATTR_BINARY },
|
|
{ NL80211_ATTR_STA_VLAN,
|
|
"Station VLAN", ATTR_U32 },
|
|
{ NL80211_ATTR_STA_INFO,
|
|
"Station Info", ATTR_NESTED, { sta_info_table } },
|
|
{ NL80211_ATTR_WIPHY_BANDS,
|
|
"Wiphy Bands", ATTR_CUSTOM,
|
|
{ .function = print_wiphy_bands } },
|
|
{ NL80211_ATTR_MNTR_FLAGS,
|
|
"MNTR Flags" },
|
|
{ NL80211_ATTR_MESH_ID,
|
|
"Mesh ID", ATTR_BINARY },
|
|
{ NL80211_ATTR_STA_PLINK_ACTION,
|
|
"Station P-Link Action", ATTR_U8 },
|
|
{ NL80211_ATTR_MPATH_NEXT_HOP,
|
|
"Mesh Path Next Hop", ATTR_U32 },
|
|
{ NL80211_ATTR_MPATH_INFO,
|
|
"Mesh Path Info" },
|
|
{ NL80211_ATTR_BSS_CTS_PROT,
|
|
"BSS CTS Protection", ATTR_U8 },
|
|
{ NL80211_ATTR_BSS_SHORT_PREAMBLE,
|
|
"BSS Short Preamble", ATTR_U8 },
|
|
{ NL80211_ATTR_BSS_SHORT_SLOT_TIME,
|
|
"BSS Short Slot Time", ATTR_U8 },
|
|
{ NL80211_ATTR_HT_CAPABILITY, "HT Capability",
|
|
ATTR_CUSTOM, { .function = print_ie_ht_capabilities } },
|
|
{ NL80211_ATTR_SUPPORTED_IFTYPES,
|
|
"Supported Interface Types", ATTR_NESTED,
|
|
{ iftype_table } },
|
|
{ NL80211_ATTR_REG_ALPHA2,
|
|
"Regulatory Alpha2", ATTR_STRING },
|
|
{ NL80211_ATTR_REG_RULES,
|
|
"Regulatory Rules" },
|
|
{ NL80211_ATTR_MESH_CONFIG,
|
|
"Mesh Configuration" },
|
|
{ NL80211_ATTR_BSS_BASIC_RATES,
|
|
"BSS Basic Rates", ATTR_BINARY },
|
|
{ NL80211_ATTR_WIPHY_TXQ_PARAMS,
|
|
"Wiphy TXQ Parameters" },
|
|
{ NL80211_ATTR_WIPHY_FREQ,
|
|
"Wiphy Frequency", ATTR_U32 },
|
|
{ NL80211_ATTR_WIPHY_CHANNEL_TYPE,
|
|
"Wiphy Channel Type", ATTR_U32 },
|
|
{ NL80211_ATTR_KEY_DEFAULT_MGMT,
|
|
"Key Default Management", ATTR_FLAG },
|
|
{ NL80211_ATTR_MGMT_SUBTYPE,
|
|
"Management Subtype", ATTR_U8 },
|
|
{ NL80211_ATTR_IE,
|
|
"Information Elements", ATTR_CUSTOM,
|
|
{ .function = print_management_ies } },
|
|
{ NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
|
|
"Max Number Scan SSIDs", ATTR_U8 },
|
|
{ NL80211_ATTR_SCAN_FREQUENCIES,
|
|
"Scan Frequencies", ATTR_ARRAY,
|
|
{ .array_type = ATTR_U32 } },
|
|
{ NL80211_ATTR_SCAN_SSIDS,
|
|
"Scan SSIDs", ATTR_ARRAY,
|
|
{ .array_type = ATTR_BINARY } },
|
|
{ NL80211_ATTR_GENERATION,
|
|
"Generation", ATTR_U32 },
|
|
{ NL80211_ATTR_BSS,
|
|
"BSS", ATTR_NESTED, { bss_table } },
|
|
{ NL80211_ATTR_REG_INITIATOR,
|
|
"Regulatory Initiator" },
|
|
{ NL80211_ATTR_REG_TYPE,
|
|
"Regulatory Type" },
|
|
{ NL80211_ATTR_SUPPORTED_COMMANDS,
|
|
"Supported Commands", ATTR_CUSTOM,
|
|
{ .function = print_supported_commands } },
|
|
{ NL80211_ATTR_FRAME,
|
|
"Frame", ATTR_CUSTOM, { .function = print_frame } },
|
|
{ NL80211_ATTR_SSID,
|
|
"SSID", ATTR_BINARY },
|
|
{ NL80211_ATTR_AUTH_TYPE,
|
|
"Auth Type", ATTR_U32 },
|
|
{ NL80211_ATTR_REASON_CODE,
|
|
"Reason Code", ATTR_CUSTOM,
|
|
{ .function = print_reason_code } },
|
|
{ NL80211_ATTR_KEY_TYPE,
|
|
"Key Type", ATTR_U32 },
|
|
{ NL80211_ATTR_MAX_SCAN_IE_LEN,
|
|
"Max Scan IE Length", ATTR_U16 },
|
|
{ NL80211_ATTR_CIPHER_SUITES,
|
|
"Cipher Suites", ATTR_CUSTOM,
|
|
{ .function = print_cipher_suites } },
|
|
{ NL80211_ATTR_FREQ_BEFORE,
|
|
"Frequency Before" },
|
|
{ NL80211_ATTR_FREQ_AFTER,
|
|
"Frequency After" },
|
|
{ NL80211_ATTR_FREQ_FIXED,
|
|
"Frequency Fixed", ATTR_FLAG },
|
|
{ NL80211_ATTR_WIPHY_RETRY_SHORT,
|
|
"Wiphy Retry Short", ATTR_U8 },
|
|
{ NL80211_ATTR_WIPHY_RETRY_LONG,
|
|
"Wiphy Retry Long", ATTR_U8 },
|
|
{ NL80211_ATTR_WIPHY_FRAG_THRESHOLD,
|
|
"Wiphy Frag Threshold", ATTR_U32 },
|
|
{ NL80211_ATTR_WIPHY_RTS_THRESHOLD,
|
|
"Wiphy RTS Threshold", ATTR_U32 },
|
|
{ NL80211_ATTR_TIMED_OUT,
|
|
"Timed Out", ATTR_FLAG },
|
|
{ NL80211_ATTR_USE_MFP,
|
|
"Use MFP", ATTR_U32 },
|
|
{ NL80211_ATTR_STA_FLAGS2,
|
|
"Station Flags 2", ATTR_CUSTOM,
|
|
{ .function = print_sta_flag_update } },
|
|
{ NL80211_ATTR_CONTROL_PORT,
|
|
"Control Port", ATTR_FLAG },
|
|
{ NL80211_ATTR_TESTDATA,
|
|
"Test Data" },
|
|
{ NL80211_ATTR_PRIVACY,
|
|
"Privacy", ATTR_FLAG },
|
|
{ NL80211_ATTR_DISCONNECTED_BY_AP,
|
|
"Disconnect by AP", ATTR_FLAG },
|
|
{ NL80211_ATTR_STATUS_CODE,
|
|
"Status Code", ATTR_U16 },
|
|
{ NL80211_ATTR_CIPHER_SUITES_PAIRWISE,
|
|
"Cipher Suites Pairwise", ATTR_CUSTOM,
|
|
{ .function = print_cipher_suites } },
|
|
{ NL80211_ATTR_CIPHER_SUITE_GROUP,
|
|
"Cipher Suite Group", ATTR_CUSTOM,
|
|
{ .function = print_cipher_suite } },
|
|
{ NL80211_ATTR_WPA_VERSIONS,
|
|
"WPA Versions", ATTR_U32 },
|
|
{ NL80211_ATTR_AKM_SUITES,
|
|
"AKM Suites", ATTR_CUSTOM,
|
|
{ .function = print_akm_suites } },
|
|
{ NL80211_ATTR_REQ_IE,
|
|
"Request IE", ATTR_CUSTOM,
|
|
{ .function = print_management_ies } },
|
|
{ NL80211_ATTR_RESP_IE,
|
|
"Response IE", ATTR_CUSTOM,
|
|
{.function = print_management_ies } },
|
|
{ NL80211_ATTR_PREV_BSSID,
|
|
"Previous BSSID", ATTR_ADDRESS },
|
|
{ NL80211_ATTR_KEY,
|
|
"Key" },
|
|
{ NL80211_ATTR_KEYS,
|
|
"Keys" },
|
|
{ NL80211_ATTR_PID,
|
|
"PID", ATTR_U32 },
|
|
{ NL80211_ATTR_4ADDR,
|
|
"4-Address", ATTR_U8 },
|
|
{ NL80211_ATTR_SURVEY_INFO,
|
|
"Survey Info" },
|
|
{ NL80211_ATTR_PMKID,
|
|
"PMKID", ATTR_BINARY },
|
|
{ NL80211_ATTR_MAX_NUM_PMKIDS,
|
|
"Max Number PMKIDs", ATTR_U8 },
|
|
{ NL80211_ATTR_DURATION,
|
|
"Duration", ATTR_U32 },
|
|
{ NL80211_ATTR_COOKIE,
|
|
"Cookie", ATTR_U64 },
|
|
{ NL80211_ATTR_WIPHY_COVERAGE_CLASS,
|
|
"Wiphy Coverage Class", ATTR_U8 },
|
|
{ NL80211_ATTR_TX_RATES,
|
|
"TX Rates" },
|
|
{ NL80211_ATTR_FRAME_MATCH,
|
|
"Frame Match", ATTR_BINARY },
|
|
{ NL80211_ATTR_ACK,
|
|
"ACK", ATTR_FLAG },
|
|
{ NL80211_ATTR_PS_STATE,
|
|
"PS State", ATTR_U32 },
|
|
{ NL80211_ATTR_CQM,
|
|
"CQM", ATTR_NESTED, { cqm_table } },
|
|
{ NL80211_ATTR_LOCAL_STATE_CHANGE,
|
|
"Local State Change", ATTR_FLAG },
|
|
{ NL80211_ATTR_AP_ISOLATE,
|
|
"AP Isolate", ATTR_U8 },
|
|
{ NL80211_ATTR_WIPHY_TX_POWER_SETTING,
|
|
"Wiphy TX Power Setting", ATTR_U32 },
|
|
{ NL80211_ATTR_WIPHY_TX_POWER_LEVEL,
|
|
"Wiphy TX Power Level", ATTR_U32 },
|
|
{ NL80211_ATTR_TX_FRAME_TYPES,
|
|
"TX Frame Types", ATTR_NESTED,
|
|
{ frame_types_table } },
|
|
{ NL80211_ATTR_RX_FRAME_TYPES,
|
|
"RX Frame Types", ATTR_NESTED,
|
|
{ frame_types_table } },
|
|
{ NL80211_ATTR_FRAME_TYPE,
|
|
"Frame Type", ATTR_CUSTOM,
|
|
{ .function = print_frame_type } },
|
|
{ NL80211_ATTR_CONTROL_PORT_ETHERTYPE,
|
|
"Control Port Ethertype", ATTR_FLAG_OR_U16 },
|
|
{ NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT,
|
|
"Control Port No Encrypt", ATTR_FLAG },
|
|
{ NL80211_ATTR_SUPPORT_IBSS_RSN,
|
|
"Support IBSS RSN", ATTR_FLAG },
|
|
{ NL80211_ATTR_WIPHY_ANTENNA_TX,
|
|
"Wiphy Antenna TX", ATTR_U32 },
|
|
{ NL80211_ATTR_WIPHY_ANTENNA_RX,
|
|
"Wiphy Antenna RX", ATTR_U32 },
|
|
{ NL80211_ATTR_MCAST_RATE,
|
|
"Multicast Rate", ATTR_U32 },
|
|
{ NL80211_ATTR_OFFCHANNEL_TX_OK,
|
|
"Offchannel TX OK", ATTR_FLAG },
|
|
{ NL80211_ATTR_BSS_HT_OPMODE,
|
|
"BSS HT Operation Mode", ATTR_U16 },
|
|
{ NL80211_ATTR_KEY_DEFAULT_TYPES,
|
|
"Key Default Types", ATTR_NESTED,
|
|
{ key_default_type_table } },
|
|
{ NL80211_ATTR_MAX_REMAIN_ON_CHANNEL_DURATION,
|
|
"Max Remain on Channel Duration ", ATTR_U32 },
|
|
{ NL80211_ATTR_MESH_SETUP,
|
|
"Mesh Setup" },
|
|
{ NL80211_ATTR_WIPHY_ANTENNA_AVAIL_TX,
|
|
"Wiphy Antenna Avail TX" },
|
|
{ NL80211_ATTR_WIPHY_ANTENNA_AVAIL_RX,
|
|
"Wiphy Antenna Avail RX" },
|
|
{ NL80211_ATTR_SUPPORT_MESH_AUTH,
|
|
"Support Mesh Auth", ATTR_FLAG },
|
|
{ NL80211_ATTR_STA_PLINK_STATE,
|
|
"Station P-Link State" },
|
|
{ NL80211_ATTR_WOWLAN_TRIGGERS,
|
|
"WoWLAN Triggers" },
|
|
{ NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED,
|
|
"WoWLAN Triggers Supported" },
|
|
{ NL80211_ATTR_SCHED_SCAN_INTERVAL,
|
|
"Scheduled Scan Interval", ATTR_U32 },
|
|
{ NL80211_ATTR_INTERFACE_COMBINATIONS,
|
|
"Interface Combinations" },
|
|
{ NL80211_ATTR_SOFTWARE_IFTYPES,
|
|
"Software Interface Types", ATTR_NESTED,
|
|
{ iftype_table } },
|
|
{ NL80211_ATTR_REKEY_DATA,
|
|
"Rekey Data", ATTR_NESTED, { rekey_table } },
|
|
{ NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS,
|
|
"Max Num Sched Scan SSIDs", ATTR_U8 },
|
|
{ NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN,
|
|
"Max Sched Scan IE Len", ATTR_U16 },
|
|
{ NL80211_ATTR_SCAN_SUPP_RATES,
|
|
"Scan Supported Rates" },
|
|
{ NL80211_ATTR_HIDDEN_SSID,
|
|
"Hidden SSID", ATTR_U32 },
|
|
{ NL80211_ATTR_IE_PROBE_RESP,
|
|
"IE Probe Response", ATTR_CUSTOM,
|
|
{ .function = print_management_ies } },
|
|
{ NL80211_ATTR_IE_ASSOC_RESP,
|
|
"IE Assoc Response", ATTR_CUSTOM,
|
|
{ .function = print_management_ies } },
|
|
{ NL80211_ATTR_STA_WME,
|
|
"Station WME" },
|
|
{ NL80211_ATTR_SUPPORT_AP_UAPSD,
|
|
"Support AP UAPSD", ATTR_FLAG },
|
|
{ NL80211_ATTR_ROAM_SUPPORT,
|
|
"Roaming Support", ATTR_FLAG },
|
|
{ NL80211_ATTR_SCHED_SCAN_MATCH,
|
|
"Scheduled Scan Match" },
|
|
{ NL80211_ATTR_MAX_MATCH_SETS,
|
|
"Max Match Sets", ATTR_U8 },
|
|
{ NL80211_ATTR_PMKSA_CANDIDATE,
|
|
"PMKSA Candidate" },
|
|
{ NL80211_ATTR_TX_NO_CCK_RATE,
|
|
"TX No CCK Rate", ATTR_FLAG },
|
|
{ NL80211_ATTR_TDLS_ACTION,
|
|
"TDLS Action", ATTR_U8 },
|
|
{ NL80211_ATTR_TDLS_DIALOG_TOKEN,
|
|
"TDLS Dialog Token", ATTR_U8 },
|
|
{ NL80211_ATTR_TDLS_OPERATION,
|
|
"TDLS Operation", ATTR_U8 },
|
|
{ NL80211_ATTR_TDLS_SUPPORT,
|
|
"TDLS Support", ATTR_FLAG },
|
|
{ NL80211_ATTR_TDLS_EXTERNAL_SETUP,
|
|
"TDLS External Setup", ATTR_FLAG },
|
|
{ NL80211_ATTR_DEVICE_AP_SME,
|
|
"Device AP SME" },
|
|
{ NL80211_ATTR_DONT_WAIT_FOR_ACK,
|
|
"Don't Wait for Ack", ATTR_FLAG },
|
|
{ NL80211_ATTR_FEATURE_FLAGS,
|
|
"Feature Flags" },
|
|
{ NL80211_ATTR_PROBE_RESP_OFFLOAD,
|
|
"Probe Response Offload" },
|
|
{ NL80211_ATTR_PROBE_RESP,
|
|
"Probe Response" },
|
|
{ NL80211_ATTR_DFS_REGION,
|
|
"DFS Region", ATTR_U8 },
|
|
{ NL80211_ATTR_DISABLE_HT,
|
|
"Diable HT", ATTR_FLAG },
|
|
{ NL80211_ATTR_HT_CAPABILITY_MASK,
|
|
"HT Capability Mask" },
|
|
{ NL80211_ATTR_NOACK_MAP,
|
|
"No-Ack Map", ATTR_U16 },
|
|
{ NL80211_ATTR_INACTIVITY_TIMEOUT,
|
|
"Inactivity Timeout", ATTR_U16 },
|
|
{ NL80211_ATTR_RX_SIGNAL_DBM,
|
|
"RX Signal dBm", ATTR_U32 },
|
|
{ NL80211_ATTR_BG_SCAN_PERIOD,
|
|
"Background Scan Period", ATTR_U16 },
|
|
{ NL80211_ATTR_WDEV,
|
|
"Wireless Device", ATTR_U64 },
|
|
{ NL80211_ATTR_USER_REG_HINT_TYPE,
|
|
"User Regulatroy Hint Type", ATTR_U32 },
|
|
{ NL80211_ATTR_CONN_FAILED_REASON,
|
|
"Connection Failed Reason" },
|
|
{ NL80211_ATTR_SAE_DATA,
|
|
"SAE Data" },
|
|
{ NL80211_ATTR_VHT_CAPABILITY,
|
|
"VHT Capability" },
|
|
{ NL80211_ATTR_SCAN_FLAGS,
|
|
"Scan Flags", ATTR_U32 },
|
|
{ NL80211_ATTR_CHANNEL_WIDTH,
|
|
"Channel Width", ATTR_U32 },
|
|
{ NL80211_ATTR_CENTER_FREQ1,
|
|
"Center Frequency 1", ATTR_U32 },
|
|
{ NL80211_ATTR_CENTER_FREQ2,
|
|
"Center Frequency 2", ATTR_U32 },
|
|
{ NL80211_ATTR_P2P_CTWINDOW,
|
|
"P2P CT Window", ATTR_U8 },
|
|
{ NL80211_ATTR_P2P_OPPPS,
|
|
"P2P OP PPS", ATTR_U8 },
|
|
{ NL80211_ATTR_LOCAL_MESH_POWER_MODE,
|
|
"Local Mesh Power Mode" },
|
|
{ NL80211_ATTR_ACL_POLICY,
|
|
"ACL Policy", ATTR_U32 },
|
|
{ NL80211_ATTR_MAC_ADDRS,
|
|
"MAC Addresses" },
|
|
{ NL80211_ATTR_MAC_ACL_MAX,
|
|
"MAC ACL Max" },
|
|
{ NL80211_ATTR_RADAR_EVENT,
|
|
"Radar Event" },
|
|
{ NL80211_ATTR_EXT_CAPA,
|
|
"Extended Capabilities", ATTR_CUSTOM,
|
|
{ .function = print_ie_extended_capabilities } },
|
|
{ NL80211_ATTR_EXT_CAPA_MASK,
|
|
"Extended Capabilities Mask" },
|
|
{ NL80211_ATTR_STA_CAPABILITY,
|
|
"Station Capability", ATTR_U16 },
|
|
{ NL80211_ATTR_STA_EXT_CAPABILITY,
|
|
"Station Extended Capability" },
|
|
{ NL80211_ATTR_PROTOCOL_FEATURES,
|
|
"Protocol Features", ATTR_U32 },
|
|
{ NL80211_ATTR_SPLIT_WIPHY_DUMP,
|
|
"Split Wiphy Dump", ATTR_FLAG},
|
|
{ NL80211_ATTR_DISABLE_VHT,
|
|
"Disable VHT" },
|
|
{ NL80211_ATTR_VHT_CAPABILITY_MASK,
|
|
"VHT Capability Mask" },
|
|
{ NL80211_ATTR_MDID,
|
|
"MDID", ATTR_U16 },
|
|
{ NL80211_ATTR_IE_RIC,
|
|
"IE RIC", ATTR_BINARY },
|
|
{ NL80211_ATTR_CRIT_PROT_ID,
|
|
"Critical Protocol ID" },
|
|
{ NL80211_ATTR_MAX_CRIT_PROT_DURATION,
|
|
"Max Criticial Protocol Duration" },
|
|
{ NL80211_ATTR_PEER_AID,
|
|
"Peer AID", ATTR_U16 },
|
|
{ NL80211_ATTR_COALESCE_RULE,
|
|
"Coalesce Rule" },
|
|
{ NL80211_ATTR_CH_SWITCH_COUNT,
|
|
"Channel Switch Count", ATTR_U32 },
|
|
{ NL80211_ATTR_CH_SWITCH_BLOCK_TX,
|
|
"Channel Switch Block TX", ATTR_FLAG },
|
|
{ NL80211_ATTR_CSA_IES,
|
|
"CSA IEs" },
|
|
{ NL80211_ATTR_CSA_C_OFF_BEACON,
|
|
"CSA C Off Beacon" },
|
|
{ NL80211_ATTR_CSA_C_OFF_PRESP,
|
|
"CSA C Off Response" },
|
|
{ NL80211_ATTR_RXMGMT_FLAGS,
|
|
"RX Management Flags", ATTR_U32 },
|
|
{ NL80211_ATTR_STA_SUPPORTED_CHANNELS,
|
|
"Station Supported Channels" },
|
|
{ NL80211_ATTR_STA_SUPPORTED_OPER_CLASSES,
|
|
"Station Supported Operation Classes" },
|
|
{ NL80211_ATTR_HANDLE_DFS,
|
|
"Handle DFS", ATTR_FLAG },
|
|
{ NL80211_ATTR_SUPPORT_5_MHZ,
|
|
"Support 5 MHz" },
|
|
{ NL80211_ATTR_SUPPORT_10_MHZ,
|
|
"Support 10 MHz" },
|
|
{ NL80211_ATTR_OPMODE_NOTIF,
|
|
"Operation Mode Notification", ATTR_U8 },
|
|
{ NL80211_ATTR_VENDOR_ID,
|
|
"Vendor ID", ATTR_U32 },
|
|
{ NL80211_ATTR_VENDOR_SUBCMD,
|
|
"Vendor Subcommand", ATTR_U32 },
|
|
{ NL80211_ATTR_VENDOR_DATA,
|
|
"Vendor Data", ATTR_BINARY },
|
|
{ NL80211_ATTR_VENDOR_EVENTS,
|
|
"Vendor Events" },
|
|
{ NL80211_ATTR_QOS_MAP,
|
|
"QoS Map", ATTR_BINARY },
|
|
{ NL80211_ATTR_MAC_HINT,
|
|
"MAC Hint", ATTR_ADDRESS },
|
|
{ NL80211_ATTR_WIPHY_FREQ_HINT,
|
|
"Wiphy Frequency Hint", ATTR_U32 },
|
|
{ NL80211_ATTR_MAX_AP_ASSOC_STA,
|
|
"Max AP Assoc Station" },
|
|
{ NL80211_ATTR_TDLS_PEER_CAPABILITY,
|
|
"TDLS Peer Capability", ATTR_U32 },
|
|
{ NL80211_ATTR_IFACE_SOCKET_OWNER,
|
|
"Interface Socket Owner", ATTR_FLAG },
|
|
{ NL80211_ATTR_CSA_C_OFFSETS_TX,
|
|
"CSA C Offsets TX" },
|
|
{ NL80211_ATTR_MAX_CSA_COUNTERS,
|
|
"Max CSA Counters" },
|
|
{ }
|
|
};
|
|
|
|
static void print_value(int indent, const char *label, enum attr_type type,
|
|
const void *buf, uint32_t len)
|
|
{
|
|
uint16_t val_u16;
|
|
uint32_t val_u32;
|
|
|
|
switch (type) {
|
|
case ATTR_U16:
|
|
val_u16 = *((uint16_t *) buf);
|
|
print_attr(indent, "%s: %u (0x%04x)", label, val_u16, val_u16);
|
|
if (len != 2)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U32:
|
|
val_u32 = *((uint32_t *) buf);
|
|
print_attr(indent, "%s: %u (0x%08x)", label, val_u32, val_u32);
|
|
if (len != 4)
|
|
printf("malformed packet\n");
|
|
break;
|
|
default:
|
|
print_attr(indent, "%s: len %u", label, len);
|
|
print_hexdump(indent + 1, buf, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void print_array(int indent, enum attr_type type,
|
|
const void *buf, uint32_t len)
|
|
{
|
|
const struct nlattr *nla;
|
|
|
|
for (nla = buf ; NLA_OK(nla, len); nla = NLA_NEXT(nla, len)) {
|
|
uint16_t nla_type = nla->nla_type & NLA_TYPE_MASK;
|
|
char str[8];
|
|
|
|
snprintf(str, sizeof(str), "%u", nla_type);
|
|
print_value(indent, str, type,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
}
|
|
}
|
|
|
|
static void print_attributes(int indent, const struct attr_entry *table,
|
|
const void *buf, uint32_t len)
|
|
{
|
|
const struct nlattr *nla;
|
|
const char *str;
|
|
int i;
|
|
|
|
for (nla = buf ; NLA_OK(nla, len); nla = NLA_NEXT(nla, len)) {
|
|
uint16_t nla_type = nla->nla_type & NLA_TYPE_MASK;
|
|
enum attr_type type;
|
|
enum attr_type array_type;
|
|
const struct attr_entry *nested;
|
|
attr_func_t function;
|
|
uint64_t val64;
|
|
uint32_t val32;
|
|
uint16_t val16;
|
|
uint8_t val8;
|
|
int8_t val_s8;
|
|
int32_t val_s32;
|
|
int64_t val_s64;
|
|
uint8_t *ptr;
|
|
|
|
str = "Reserved";
|
|
type = ATTR_UNSPEC;
|
|
array_type = ATTR_UNSPEC;
|
|
nested = NULL;
|
|
|
|
if (table) {
|
|
for (i = 0; table[i].str; i++) {
|
|
if (nla_type == table[i].attr) {
|
|
str = table[i].str;
|
|
type = table[i].type;
|
|
nested = table[i].nested;
|
|
array_type = table[i].array_type;
|
|
function = table[i].function;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case ATTR_UNSPEC:
|
|
print_attr(indent, "%s: len %u", str,
|
|
NLA_PAYLOAD(nla));
|
|
print_hexdump(indent + 1,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
break;
|
|
case ATTR_FLAG:
|
|
print_attr(indent, "%s: true", str);
|
|
if (NLA_PAYLOAD(nla) != 0)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U8:
|
|
val8 = *((uint8_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRIu8" (0x%02"PRIx8")",
|
|
str, val8, val8);
|
|
if (NLA_PAYLOAD(nla) != 1)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U16:
|
|
val16 = *((uint16_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRIu16" (0x%04"PRIx16")",
|
|
str, val16, val16);
|
|
if (NLA_PAYLOAD(nla) != 2)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U32:
|
|
val32 = *((uint32_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRIu32" (0x%08"PRIx32")",
|
|
str, val32, val32);
|
|
if (NLA_PAYLOAD(nla) != 4)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U64:
|
|
val64 = *((uint64_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRIu64" (0x%016"PRIx64")",
|
|
str, val64, val64);
|
|
if (NLA_PAYLOAD(nla) != 8)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_S8:
|
|
val_s8 = *((int8_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRId8, str, val_s8);
|
|
if (NLA_PAYLOAD(nla) != 1)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_S32:
|
|
val_s32 = *((int32_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRId32, str, val_s32);
|
|
if (NLA_PAYLOAD(nla) != 4)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_S64:
|
|
val_s64 = *((int64_t *) NLA_DATA(nla));
|
|
print_attr(indent, "%s: %"PRId64, str, val_s64);
|
|
if (NLA_PAYLOAD(nla) != 4)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_STRING:
|
|
print_attr(indent, "%s: %s", str,
|
|
(char *) NLA_DATA(nla));
|
|
break;
|
|
case ATTR_ADDRESS:
|
|
ptr = NLA_DATA(nla);
|
|
print_address(indent, str, ptr);
|
|
if (NLA_PAYLOAD(nla) != 6)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_BINARY:
|
|
print_attr(indent, "%s: len %u", str,
|
|
NLA_PAYLOAD(nla));
|
|
print_hexdump(indent + 1,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
break;
|
|
case ATTR_NESTED:
|
|
print_attr(indent, "%s: len %u", str,
|
|
NLA_PAYLOAD(nla));
|
|
if (!nested)
|
|
printf("missing table\n");
|
|
print_attributes(indent + 1, nested,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
break;
|
|
case ATTR_ARRAY:
|
|
print_attr(indent, "%s: len %u", str,
|
|
NLA_PAYLOAD(nla));
|
|
if (array_type == ATTR_UNSPEC)
|
|
printf("missing type\n");
|
|
print_array(indent + 1, array_type,
|
|
NLA_DATA(nla), NLA_PAYLOAD(nla));
|
|
break;
|
|
case ATTR_FLAG_OR_U16:
|
|
if (NLA_PAYLOAD(nla) == 0)
|
|
print_attr(indent, "%s: true", str);
|
|
else if (NLA_PAYLOAD(nla) == 2) {
|
|
val16 = *((uint16_t *) NLA_DATA(nla));
|
|
print_attr(indent,
|
|
"%s: %"PRIu16" (0x%04"PRIx16")",
|
|
str, val16, val16);
|
|
} else
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_CUSTOM:
|
|
if (function)
|
|
function(indent, str, NLA_DATA(nla),
|
|
NLA_PAYLOAD(nla));
|
|
else
|
|
printf("missing function\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_eapol_key(const void *data, uint32_t size)
|
|
{
|
|
const struct eapol_key *ek = eapol_key_validate(data, size);
|
|
|
|
if (!ek)
|
|
return;
|
|
|
|
print_attr(1, "Descriptor Type: %u", ek->descriptor_type);
|
|
print_attr(1, "Key MIC: %s", ek->key_mic ? "true" : "false");
|
|
print_attr(1, "Secure: %s", ek->secure ? "true" : "false");
|
|
print_attr(1, "Error: %s", ek->error ? "true" : "false");
|
|
print_attr(1, "Request: %s", ek->request ? "true" : "false");
|
|
print_attr(1, "Encrypted Key Data: %s",
|
|
ek->encrypted_key_data ? "true" : "false");
|
|
print_attr(1, "SMK Message: %s", ek->smk_message ? "true" : "false");
|
|
print_attr(1, "Key Descriptor Version: %d (%02x)",
|
|
ek->key_descriptor_version,
|
|
ek->key_descriptor_version);
|
|
print_attr(1, "Key Type: %s", ek->key_type ? "true" : "false");
|
|
|
|
if (ek->descriptor_type == EAPOL_DESCRIPTOR_TYPE_WPA)
|
|
print_attr(1, "Key Id: %u", ek->wpa_key_id);
|
|
|
|
print_attr(1, "Install: %s", ek->install ? "true" : "false");
|
|
print_attr(1, "Key ACK: %s", ek->key_ack ? "true" : "false");
|
|
print_attr(1, "Key Length: %d", L_BE16_TO_CPU(ek->key_length));
|
|
print_attr(1, "Key Replay Counter: %ld",
|
|
L_BE64_TO_CPU(ek->key_replay_counter));
|
|
print_attr(1, "Key NONCE");
|
|
print_hexdump(2, ek->key_nonce, 32);
|
|
print_attr(1, "Key IV");
|
|
print_hexdump(2, ek->eapol_key_iv, 16);
|
|
print_attr(1, "Key RSC ");
|
|
print_hexdump(2, ek->key_rsc, 8);
|
|
print_attr(1, "Key MIC Data");
|
|
print_hexdump(2, ek->key_mic_data, 16);
|
|
|
|
if (ek->encrypted_key_data) {
|
|
print_attr(1, "Key Data: len %d",
|
|
L_BE16_TO_CPU(ek->key_data_len));
|
|
print_hexdump(2, ek->key_data, L_BE16_TO_CPU(ek->key_data_len));
|
|
return;
|
|
}
|
|
|
|
print_ie(1, "Key Data", ek->key_data, L_BE16_TO_CPU(ek->key_data_len));
|
|
}
|
|
|
|
static void netlink_str(char *str, size_t size,
|
|
uint16_t type, uint16_t flags, uint32_t len)
|
|
{
|
|
int pos;
|
|
bool get_req = false, new_req = false;
|
|
|
|
pos = sprintf(str, "(0x%02x) len %u", type, len);
|
|
|
|
switch (type) {
|
|
case RTM_GETLINK:
|
|
case RTM_GETADDR:
|
|
case RTM_GETROUTE:
|
|
get_req = true;
|
|
break;
|
|
|
|
case RTM_NEWLINK:
|
|
case RTM_NEWADDR:
|
|
case RTM_NEWROUTE:
|
|
new_req = true;
|
|
break;
|
|
}
|
|
|
|
if (flags) {
|
|
pos += sprintf(str + pos, " [");
|
|
|
|
if (flags & NLM_F_REQUEST) {
|
|
flags &= ~NLM_F_REQUEST;
|
|
pos += sprintf(str + pos, "request%c",
|
|
flags ? ',' : ']');
|
|
}
|
|
|
|
if (flags & NLM_F_MULTI) {
|
|
flags &= ~NLM_F_MULTI;
|
|
pos += sprintf(str + pos, "multi%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (flags & NLM_F_ACK) {
|
|
flags &= ~NLM_F_ACK;
|
|
pos += sprintf(str + pos, "ack%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (flags & NLM_F_ECHO) {
|
|
flags &= ~NLM_F_ECHO;
|
|
pos += sprintf(str + pos, "echo%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (get_req && (flags & NLM_F_DUMP) == NLM_F_DUMP) {
|
|
flags &= ~NLM_F_DUMP;
|
|
pos += sprintf(str + pos, "dump%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (get_req && flags & NLM_F_ROOT) {
|
|
flags &= ~NLM_F_ROOT;
|
|
pos += sprintf(str + pos, "root%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (get_req && flags & NLM_F_MATCH) {
|
|
flags &= ~NLM_F_MATCH;
|
|
pos += sprintf(str + pos, "match%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (get_req && flags & NLM_F_ATOMIC) {
|
|
flags &= ~NLM_F_ATOMIC;
|
|
pos += sprintf(str + pos, "atomic%c",
|
|
flags ? ',' : ']');
|
|
}
|
|
|
|
if (new_req && flags & NLM_F_REPLACE) {
|
|
flags &= ~NLM_F_REPLACE;
|
|
pos += sprintf(str + pos, "replace%c",
|
|
flags ? ',' : ']');
|
|
}
|
|
|
|
if (new_req && flags & NLM_F_EXCL) {
|
|
flags &= ~NLM_F_EXCL;
|
|
pos += sprintf(str + pos, "excl%c", flags ? ',' : ']');
|
|
}
|
|
|
|
if (new_req && flags & NLM_F_CREATE) {
|
|
flags &= ~NLM_F_CREATE;
|
|
pos += sprintf(str + pos, "create%c",
|
|
flags ? ',' : ']');
|
|
}
|
|
|
|
if (new_req && flags & NLM_F_APPEND) {
|
|
flags &= ~NLM_F_APPEND;
|
|
pos += sprintf(str + pos, "append%c",
|
|
flags ? ',' : ']');
|
|
}
|
|
|
|
if (flags)
|
|
pos += sprintf(str + pos, "0x%x]", flags);
|
|
}
|
|
}
|
|
|
|
static void print_message(const struct timeval *tv, enum msg_type type,
|
|
uint16_t flags, int status,
|
|
uint8_t cmd, uint8_t version,
|
|
const void *data, uint32_t len)
|
|
{
|
|
char extra_str[64];
|
|
const char *label = "";
|
|
const char *color = COLOR_OFF;
|
|
const char *cmd_str;
|
|
bool out = false;
|
|
int i;
|
|
|
|
switch (type) {
|
|
case MSG_REQUEST:
|
|
label = "Request";
|
|
color = COLOR_REQUEST;
|
|
out = true;
|
|
break;
|
|
case MSG_RESPONSE:
|
|
label = "Response";
|
|
color = COLOR_RESPONSE;
|
|
break;
|
|
case MSG_COMPLETE:
|
|
label = "Complete";
|
|
color = COLOR_COMPLETE;
|
|
break;
|
|
case MSG_RESULT:
|
|
label = "Result";
|
|
color = COLOR_RESULT;
|
|
break;
|
|
case MSG_EVENT:
|
|
label = "Event";
|
|
color = COLOR_EVENT;
|
|
break;
|
|
}
|
|
|
|
cmd_str = "Reserved";
|
|
|
|
for (i = 0; cmd_table[i].str; i++) {
|
|
if (cmd_table[i].cmd == cmd) {
|
|
cmd_str = cmd_table[i].str;
|
|
break;
|
|
}
|
|
}
|
|
|
|
netlink_str(extra_str, sizeof(extra_str), cmd, flags, len);
|
|
|
|
print_packet(tv, out ? '<' : '>', color, label, cmd_str, extra_str);
|
|
|
|
switch (type) {
|
|
case MSG_REQUEST:
|
|
case MSG_RESULT:
|
|
case MSG_EVENT:
|
|
print_attributes(0, attr_table, data, len);
|
|
break;
|
|
case MSG_RESPONSE:
|
|
print_field("Status: %s (%d)", strerror(status), status);
|
|
break;
|
|
case MSG_COMPLETE:
|
|
if (status < 0)
|
|
print_field("Status: %s (%d)",
|
|
strerror(-status), -status);
|
|
else
|
|
print_field("Status: %d", status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct nlmon_req_match {
|
|
uint32_t seq;
|
|
uint32_t pid;
|
|
};
|
|
|
|
static bool nlmon_req_match(const void *a, const void *b)
|
|
{
|
|
const struct nlmon_req *req = a;
|
|
const struct nlmon_req_match *match = b;
|
|
|
|
return (req->seq == match->seq && req->pid == match->pid);
|
|
}
|
|
|
|
static void store_packet(struct nlmon *nlmon, const struct timeval *tv,
|
|
uint16_t pkt_type,
|
|
uint16_t arphrd_type,
|
|
uint16_t proto_type,
|
|
const void *data, uint32_t size)
|
|
{
|
|
uint8_t sll_hdr[16], *buf = sll_hdr;
|
|
|
|
if (!nlmon->pcap)
|
|
return;
|
|
|
|
memset(sll_hdr, 0, sizeof(sll_hdr));
|
|
|
|
pkt_type = L_CPU_TO_BE16(pkt_type);
|
|
L_PUT_UNALIGNED(pkt_type, (uint16_t *) buf);
|
|
|
|
arphrd_type = L_CPU_TO_BE16(arphrd_type);
|
|
L_PUT_UNALIGNED(arphrd_type, (uint16_t *) (buf + 2));
|
|
|
|
proto_type = L_CPU_TO_BE16(proto_type);
|
|
L_PUT_UNALIGNED(proto_type, (uint16_t *) (buf + 14));
|
|
|
|
pcap_write(nlmon->pcap, tv, &sll_hdr, sizeof(sll_hdr), data, size);
|
|
}
|
|
|
|
static void store_netlink(struct nlmon *nlmon, const struct timeval *tv,
|
|
uint16_t proto_type,
|
|
const struct nlmsghdr *nlmsg)
|
|
{
|
|
store_packet(nlmon, tv, PACKET_HOST, ARPHRD_NETLINK, proto_type,
|
|
nlmsg, nlmsg->nlmsg_len);
|
|
}
|
|
|
|
static void store_message(struct nlmon *nlmon, const struct timeval *tv,
|
|
const struct nlmsghdr *nlmsg)
|
|
{
|
|
store_netlink(nlmon, tv, NETLINK_GENERIC, nlmsg);
|
|
}
|
|
|
|
static void nlmon_message(struct nlmon *nlmon, const struct timeval *tv,
|
|
const struct tpacket_auxdata *tp,
|
|
const struct nlmsghdr *nlmsg)
|
|
{
|
|
struct nlmon_req *req;
|
|
|
|
if (nlmsg->nlmsg_type < NLMSG_MIN_TYPE) {
|
|
struct nlmon_req_match match = {
|
|
.seq = nlmsg->nlmsg_seq,
|
|
.pid = nlmsg->nlmsg_pid
|
|
};
|
|
|
|
req = l_queue_remove_if(nlmon->req_list,
|
|
nlmon_req_match, &match);
|
|
if (req) {
|
|
enum msg_type type;
|
|
struct nlmsgerr *err;
|
|
int status;
|
|
|
|
switch (nlmsg->nlmsg_type) {
|
|
case NLMSG_ERROR:
|
|
type = MSG_RESPONSE;
|
|
err = NLMSG_DATA(nlmsg);
|
|
status = -err->error;
|
|
break;
|
|
case NLMSG_DONE:
|
|
type = MSG_COMPLETE;
|
|
status = *((int *) NLMSG_DATA(nlmsg));
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
store_message(nlmon, tv, nlmsg);
|
|
print_message(tv, type, nlmsg->nlmsg_flags, status,
|
|
req->cmd, req->version,
|
|
NULL, sizeof(status));
|
|
nlmon_req_free(req);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (nlmsg->nlmsg_type != nlmon->id) {
|
|
if (nlmsg->nlmsg_type == GENL_ID_CTRL)
|
|
store_message(nlmon, tv, nlmsg);
|
|
return;
|
|
}
|
|
|
|
if (nlmsg->nlmsg_flags & NLM_F_REQUEST) {
|
|
const struct genlmsghdr *genlmsg = NLMSG_DATA(nlmsg);
|
|
uint32_t flags = nlmsg->nlmsg_flags & ~NLM_F_REQUEST;
|
|
|
|
req = l_new(struct nlmon_req, 1);
|
|
|
|
req->seq = nlmsg->nlmsg_seq;
|
|
req->pid = nlmsg->nlmsg_pid;
|
|
req->flags = nlmsg->nlmsg_flags;
|
|
req->cmd = genlmsg->cmd;
|
|
req->version = genlmsg->version;
|
|
|
|
l_queue_push_tail(nlmon->req_list, req);
|
|
|
|
store_message(nlmon, tv, nlmsg);
|
|
print_message(tv, MSG_REQUEST, flags, 0,
|
|
req->cmd, req->version,
|
|
NLMSG_DATA(nlmsg) + GENL_HDRLEN,
|
|
NLMSG_PAYLOAD(nlmsg, GENL_HDRLEN));
|
|
} else {
|
|
const struct genlmsghdr *genlmsg = NLMSG_DATA(nlmsg);
|
|
enum msg_type type = MSG_EVENT;
|
|
|
|
struct nlmon_req_match match = {
|
|
.seq = nlmsg->nlmsg_seq,
|
|
.pid = nlmsg->nlmsg_pid
|
|
};
|
|
|
|
req = l_queue_find(nlmon->req_list, nlmon_req_match, &match);
|
|
if (req) {
|
|
if (!(req->flags & NLM_F_ACK)) {
|
|
l_queue_remove(nlmon->req_list, req);
|
|
nlmon_req_free(req);
|
|
}
|
|
type = MSG_RESULT;
|
|
}
|
|
|
|
store_message(nlmon, tv, nlmsg);
|
|
print_message(tv, type, nlmsg->nlmsg_flags, 0,
|
|
genlmsg->cmd, genlmsg->version,
|
|
NLMSG_DATA(nlmsg) + GENL_HDRLEN,
|
|
NLMSG_PAYLOAD(nlmsg, GENL_HDRLEN));
|
|
}
|
|
}
|
|
|
|
struct nlmon *nlmon_create(uint16_t id)
|
|
{
|
|
struct nlmon *nlmon;
|
|
|
|
nlmon = l_new(struct nlmon, 1);
|
|
|
|
nlmon->id = id;
|
|
nlmon->req_list = l_queue_new();
|
|
|
|
return nlmon;
|
|
}
|
|
|
|
void nlmon_destroy(struct nlmon *nlmon)
|
|
{
|
|
if (!nlmon)
|
|
return;
|
|
|
|
l_queue_destroy(nlmon->req_list, nlmon_req_free);
|
|
|
|
l_free(nlmon);
|
|
}
|
|
|
|
static void genl_ctrl(struct nlmon *nlmon, const void *data, uint32_t len)
|
|
{
|
|
const struct genlmsghdr *genlmsg = data;
|
|
const struct nlattr *nla;
|
|
char name[GENL_NAMSIZ];
|
|
uint16_t id = GENL_ID_GENERATE;
|
|
|
|
if (genlmsg->cmd != CTRL_CMD_NEWFAMILY)
|
|
return;
|
|
|
|
for (nla = data + GENL_HDRLEN; NLA_OK(nla, len);
|
|
nla = NLA_NEXT(nla, len)) {
|
|
switch (nla->nla_type & NLA_TYPE_MASK) {
|
|
case CTRL_ATTR_FAMILY_ID:
|
|
id = *((uint16_t *) NLA_DATA(nla));
|
|
break;
|
|
case CTRL_ATTR_FAMILY_NAME:
|
|
strncpy(name, NLA_DATA(nla), GENL_NAMSIZ);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (id == GENL_ID_GENERATE)
|
|
return;
|
|
|
|
if (!strcmp(name, NL80211_GENL_NAME))
|
|
nlmon->id = id;
|
|
}
|
|
|
|
static void print_ifi_addr(unsigned int indent, const char *str,
|
|
const void *buf, uint16_t size)
|
|
{
|
|
struct ether_addr eth;
|
|
|
|
if (size != ETH_ALEN) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
memcpy(ð, buf, ETH_ALEN);
|
|
print_attr(indent, "%s: %s", str, ether_ntoa(ð));
|
|
}
|
|
|
|
static const char *oper_state_to_ascii(const uint8_t state)
|
|
{
|
|
switch(state) {
|
|
case IF_OPER_UNKNOWN:
|
|
return "unknown";
|
|
case IF_OPER_NOTPRESENT:
|
|
return "not present";
|
|
case IF_OPER_DOWN:
|
|
return "down";
|
|
case IF_OPER_LOWERLAYERDOWN:
|
|
return "lower layer down";
|
|
case IF_OPER_TESTING:
|
|
return "testing";
|
|
case IF_OPER_DORMANT:
|
|
return "dormant";
|
|
case IF_OPER_UP:
|
|
return "up";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void print_oper_state(unsigned int indent, const char *str,
|
|
const void *buf, uint16_t size)
|
|
{
|
|
uint8_t oper_state;
|
|
|
|
if (size != 1) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
oper_state = ((uint8_t *)buf)[0];
|
|
|
|
print_attr(indent, "%s: %s (%d)", str,
|
|
oper_state_to_ascii(oper_state), oper_state);
|
|
}
|
|
|
|
static const char *link_mode_to_ascii(const uint8_t mode)
|
|
{
|
|
switch(mode) {
|
|
case 0:
|
|
return "kernel controlled";
|
|
case 1:
|
|
return "userspace controlled";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void print_link_mode(unsigned int indent, const char *str,
|
|
const void *buf, uint16_t size)
|
|
{
|
|
uint8_t link_mode;
|
|
|
|
if (size != 1) {
|
|
printf("malformed packet\n");
|
|
return;
|
|
}
|
|
|
|
link_mode = ((uint8_t *)buf)[0];
|
|
|
|
print_attr(indent, "%s: %s (%d)", str,
|
|
link_mode_to_ascii(link_mode), link_mode);
|
|
}
|
|
|
|
static struct attr_entry info_entry[] = {
|
|
{ IFLA_ADDRESS, "Interface Address", ATTR_CUSTOM,
|
|
{ .function = print_ifi_addr } },
|
|
{ IFLA_BROADCAST, "Broadcast Address", ATTR_CUSTOM,
|
|
{ .function = print_ifi_addr } },
|
|
{ IFLA_IFNAME, "IfName", ATTR_STRING },
|
|
{ IFLA_MASTER, "Master", ATTR_U32 },
|
|
{ IFLA_MTU, "MTU", ATTR_U32 },
|
|
{ IFLA_TXQLEN, "Txqlen", ATTR_U32 },
|
|
{ IFLA_OPERSTATE, "OperState", ATTR_CUSTOM,
|
|
{ .function = print_oper_state } },
|
|
{ IFLA_LINKMODE, "LinkMode", ATTR_CUSTOM,
|
|
{ .function = print_link_mode } },
|
|
{ IFLA_LINK, "Link", ATTR_S32 },
|
|
{ IFLA_QDISC, "Qdisc", ATTR_STRING },
|
|
{ IFLA_STATS, "Stats", ATTR_BINARY },
|
|
{ IFLA_MAP, "Map", ATTR_BINARY },
|
|
{ IFLA_WIRELESS, "Wireless", ATTR_BINARY },
|
|
{ IFLA_COST, "Cost", ATTR_BINARY },
|
|
{ IFLA_PRIORITY, "Priority", ATTR_BINARY },
|
|
{ IFLA_PROTINFO, "ProtInfo", ATTR_BINARY },
|
|
{ IFLA_WEIGHT, "Weight", ATTR_BINARY },
|
|
{ IFLA_NET_NS_PID, "NetNSPid", ATTR_BINARY },
|
|
{ IFLA_IFALIAS, "IFAlias", ATTR_BINARY },
|
|
{ },
|
|
};
|
|
|
|
static void print_inet_addr(unsigned int indent, const char *str,
|
|
const void *buf, uint16_t size)
|
|
{
|
|
struct in_addr addr;
|
|
|
|
if (size != sizeof(struct in_addr))
|
|
return;
|
|
|
|
addr = *((struct in_addr *) buf);
|
|
print_attr(indent, "%s: %s", str, inet_ntoa(addr));
|
|
}
|
|
|
|
static struct attr_entry addr_entry[] = {
|
|
{ IFA_ADDRESS, "Interface Address", ATTR_CUSTOM,
|
|
{ .function = print_inet_addr } },
|
|
{ IFA_LOCAL, "Local Address", ATTR_CUSTOM,
|
|
{ .function = print_inet_addr } },
|
|
{ IFA_BROADCAST, "Broadcast Address", ATTR_CUSTOM,
|
|
{ .function = print_inet_addr } },
|
|
{ IFA_ANYCAST, "Anycast Address", ATTR_CUSTOM,
|
|
{ .function = print_inet_addr } },
|
|
{ IFA_LABEL, "Label", ATTR_STRING },
|
|
{ IFA_CACHEINFO, "CacheInfo", ATTR_BINARY },
|
|
{ },
|
|
};
|
|
|
|
static void print_rtnl_attributes(int indent, const struct attr_entry *table,
|
|
struct rtattr *rt_attr, int len)
|
|
{
|
|
struct rtattr *attr;
|
|
const char *str;
|
|
int i;
|
|
|
|
if (!table || !rt_attr)
|
|
return;
|
|
|
|
for (attr = rt_attr; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
|
|
uint16_t rta_type = attr->rta_type;
|
|
enum attr_type type = ATTR_UNSPEC;
|
|
attr_func_t function;
|
|
uint64_t val64;
|
|
uint32_t val32;
|
|
uint16_t val16;
|
|
uint8_t val8;
|
|
int8_t val_s8;
|
|
int32_t val_s32;
|
|
int64_t val_s64;
|
|
|
|
str = "Reserved";
|
|
|
|
for (i = 0; table[i].str; i++) {
|
|
if (rta_type == table[i].attr) {
|
|
str = table[i].str;
|
|
type = table[i].type;
|
|
function = table[i].function;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case ATTR_CUSTOM:
|
|
if (function)
|
|
function(indent, str, RTA_DATA(attr),
|
|
RTA_PAYLOAD(attr));
|
|
else
|
|
printf("missing function\n");
|
|
break;
|
|
case ATTR_STRING:
|
|
print_attr(indent, "%s: %s", str,
|
|
(char *) RTA_DATA(attr));
|
|
break;
|
|
case ATTR_U8:
|
|
val8 = *((uint8_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRIu8" (0x%02"PRIx8")", str,
|
|
val8, val8);
|
|
if (RTA_PAYLOAD(attr) != 1)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U16:
|
|
val16 = *((uint16_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRIu16" (0x%04"PRIx16")", str,
|
|
val16, val16);
|
|
if (RTA_PAYLOAD(attr) != 2)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U32:
|
|
val32 = *((uint32_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRIu32" (0x%08"PRIx32")", str,
|
|
val32, val32);
|
|
if (RTA_PAYLOAD(attr) != 4)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_U64:
|
|
val64 = *((uint64_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRIu64" (0x%016"PRIx64")",
|
|
str, val64, val64);
|
|
if (RTA_PAYLOAD(attr) != 8)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_S8:
|
|
val_s8 = *((int8_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRId8, str, val_s8);
|
|
if (RTA_PAYLOAD(attr) != 1)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_S32:
|
|
val_s32 = *((int32_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRId32, str, val_s32);
|
|
if (RTA_PAYLOAD(attr) != 4)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_S64:
|
|
val_s64 = *((int64_t *) RTA_DATA(attr));
|
|
print_attr(indent, "%s: %"PRId64, str, val_s64);
|
|
if (RTA_PAYLOAD(attr) != 8)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_FLAG:
|
|
print_attr(indent, "%s: true", str);
|
|
if (RTA_PAYLOAD(attr) != 0)
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_FLAG_OR_U16:
|
|
if (RTA_PAYLOAD(attr) == 0)
|
|
print_attr(indent, "%s: true", str);
|
|
else if (RTA_PAYLOAD(attr) == 2) {
|
|
val16 = *((uint16_t *) RTA_DATA(attr));
|
|
print_attr(indent,
|
|
"%s: %"PRIu16" (0x%04"PRIx16")",
|
|
str, val16, val16);
|
|
} else
|
|
printf("malformed packet\n");
|
|
break;
|
|
case ATTR_BINARY:
|
|
print_attr(indent, "%s: len %lu", str,
|
|
RTA_PAYLOAD(attr));
|
|
print_hexdump(indent + 1,
|
|
RTA_DATA(attr), RTA_PAYLOAD(attr));
|
|
break;
|
|
case ATTR_ADDRESS:
|
|
case ATTR_NESTED:
|
|
case ATTR_ARRAY:
|
|
case ATTR_UNSPEC:
|
|
print_attr(indent, "%s: len %lu", str,
|
|
RTA_PAYLOAD(attr));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct flag_names rtnl_flags[] = {
|
|
{ IFF_UP, "up" },
|
|
{ IFF_BROADCAST, "broadcast" },
|
|
{ IFF_DEBUG, "debug" },
|
|
{ IFF_LOOPBACK, "loopback" },
|
|
{ IFF_POINTOPOINT, "pointopoint"},
|
|
{ IFF_NOTRAILERS, "notrailers" },
|
|
{ IFF_RUNNING, "running" },
|
|
{ IFF_NOARP, "noarp" },
|
|
{ IFF_PROMISC, "promisc" },
|
|
{ IFF_ALLMULTI, "allmulti" },
|
|
{ IFF_MASTER, "master" },
|
|
{ IFF_SLAVE, "slave" },
|
|
{ IFF_MULTICAST, "multicast" },
|
|
{ IFF_PORTSEL, "portsel" },
|
|
{ IFF_AUTOMEDIA, "automedia" },
|
|
{ IFF_DYNAMIC, "dynamic" },
|
|
{ },
|
|
};
|
|
|
|
static void ififlags_str(char *str, size_t size, uint16_t flags)
|
|
{
|
|
int pos, i;
|
|
|
|
pos = sprintf(str, "(0x%02x)", flags);
|
|
if (!flags)
|
|
return;
|
|
|
|
pos += sprintf(str + pos, " [");
|
|
|
|
for (i = 0; rtnl_flags[i].name; i++) {
|
|
if (flags & rtnl_flags[i].flag) {
|
|
flags &= ~rtnl_flags[i].flag;
|
|
pos += sprintf(str + pos, "%s%s", rtnl_flags[i].name,
|
|
flags ? "," : "");
|
|
}
|
|
}
|
|
|
|
pos += sprintf(str + pos, "]");
|
|
}
|
|
|
|
static void print_ifinfomsg(const struct ifinfomsg *info)
|
|
{
|
|
char str[256];
|
|
|
|
if (!info)
|
|
return;
|
|
|
|
print_field("IFLA Family: %u", info->ifi_family);
|
|
print_field("IFLA Type: %u", info->ifi_type);
|
|
print_field("IFLA Index: %d", info->ifi_index);
|
|
print_field("IFLA ChangeMask: %u", info->ifi_change);
|
|
ififlags_str(str, sizeof(str), info->ifi_flags);
|
|
print_field("IFLA Flags: %s", str);
|
|
}
|
|
|
|
static void print_ifaddrmsg(const struct ifaddrmsg *addr)
|
|
{
|
|
if (!addr)
|
|
return;
|
|
|
|
print_field("IFA Family: %u", addr->ifa_family);
|
|
print_field("IFA Prefixlen: %u", addr->ifa_prefixlen);
|
|
print_field("IFA Index: %d", addr->ifa_index);
|
|
print_field("IFA Scope: %u", addr->ifa_scope);
|
|
print_field("IFA Flags: %u", addr->ifa_flags);
|
|
}
|
|
|
|
static void read_uevent(const char *ifname, int index)
|
|
{
|
|
char filename[64], line[128];
|
|
FILE *f;
|
|
|
|
snprintf(filename, sizeof(filename), "/sys/class/net/%s/uevent",
|
|
ifname);
|
|
f = fopen(filename, "re");
|
|
if (!f) {
|
|
printf("%s do not exist\n", filename);
|
|
return;
|
|
}
|
|
|
|
while (fgets(line, sizeof(line), f)) {
|
|
char *pos;
|
|
|
|
pos = strchr(line, '\n');
|
|
if (!pos)
|
|
continue;
|
|
pos[0] = '\0';
|
|
|
|
if (strncmp(line, "DEVTYPE=", 8) != 0)
|
|
continue;
|
|
|
|
if (strcmp(line + 8, "wlan") == 0) {
|
|
struct wlan_iface *iface;
|
|
|
|
iface = l_new(struct wlan_iface, 1);
|
|
iface->index = index;
|
|
|
|
if (!l_hashmap_insert(wlan_iface_list,
|
|
L_INT_TO_PTR(index), iface))
|
|
l_free(iface);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
static char *rtnl_get_ifname(const struct ifinfomsg *ifi, int len)
|
|
{
|
|
struct rtattr *attr;
|
|
char *ifname = NULL;
|
|
|
|
if (!ifi)
|
|
return NULL;
|
|
|
|
for (attr = IFLA_RTA(ifi); RTA_OK(attr, len);
|
|
attr = RTA_NEXT(attr, len))
|
|
if (attr->rta_type == IFLA_IFNAME)
|
|
ifname = (char *) RTA_DATA(attr);
|
|
|
|
return ifname;
|
|
}
|
|
|
|
static void print_rtm_link(uint16_t type, const struct ifinfomsg *info, int len)
|
|
{
|
|
struct wlan_iface *iface;
|
|
char *ifname;
|
|
|
|
if (!info || len <= 0)
|
|
return;
|
|
|
|
if (type == RTM_NEWLINK) {
|
|
ifname = rtnl_get_ifname(info, len);
|
|
if (!ifname)
|
|
return;
|
|
|
|
read_uevent(ifname, info->ifi_index);
|
|
}
|
|
|
|
iface = l_hashmap_lookup(wlan_iface_list,
|
|
L_INT_TO_PTR(info->ifi_index));
|
|
if (!iface)
|
|
return;
|
|
|
|
print_ifinfomsg(info);
|
|
print_rtnl_attributes(1, info_entry, IFLA_RTA(info), len);
|
|
|
|
if (type == RTM_DELLINK) {
|
|
iface = l_hashmap_remove(wlan_iface_list,
|
|
L_INT_TO_PTR(info->ifi_index));
|
|
if (!iface)
|
|
return;
|
|
|
|
l_free(iface);
|
|
}
|
|
}
|
|
|
|
static const char *nlmsg_type_to_str(uint32_t msg_type)
|
|
{
|
|
const char *str = NULL;
|
|
|
|
switch (msg_type) {
|
|
case NLMSG_NOOP:
|
|
str = "Noop";
|
|
break;
|
|
case NLMSG_ERROR:
|
|
str = "Error";
|
|
break;
|
|
case NLMSG_DONE:
|
|
str = "Done";
|
|
break;
|
|
case NLMSG_OVERRUN:
|
|
str = "Overrun";
|
|
break;
|
|
case RTM_NEWLINK:
|
|
str = "New Link";
|
|
break;
|
|
case RTM_DELLINK:
|
|
str = "Delete Link";
|
|
break;
|
|
case RTM_GETLINK:
|
|
str = "Get Link";
|
|
break;
|
|
case RTM_SETLINK:
|
|
str = "Set Link";
|
|
break;
|
|
case RTM_NEWADDR:
|
|
str = "New Address";
|
|
break;
|
|
case RTM_DELADDR:
|
|
str = "Delete Address";
|
|
break;
|
|
case RTM_GETADDR:
|
|
str = "Get Address";
|
|
break;
|
|
case RTM_NEWROUTE:
|
|
str = "New Route";
|
|
break;
|
|
case RTM_DELROUTE:
|
|
str = "Delete Route";
|
|
break;
|
|
case RTM_GETROUTE:
|
|
str = "Get Route";
|
|
break;
|
|
default:
|
|
str = "Reserved";
|
|
break;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static void print_nlmsghdr(const struct timeval *tv,
|
|
const struct nlmsghdr *nlmsg)
|
|
{
|
|
char extra_str[256];
|
|
const char *str;
|
|
bool out;
|
|
|
|
str = nlmsg_type_to_str(nlmsg->nlmsg_type);
|
|
out = !!(nlmsg->nlmsg_flags & NLM_F_REQUEST);
|
|
|
|
netlink_str(extra_str, sizeof(extra_str), nlmsg->nlmsg_type,
|
|
nlmsg->nlmsg_flags, NLMSG_PAYLOAD(nlmsg, 0));
|
|
|
|
print_packet(tv, out ? '<' : '>', COLOR_YELLOW, "RTNL", str, extra_str);
|
|
|
|
print_field("Flags: %d (0x%03x)", nlmsg->nlmsg_flags,
|
|
nlmsg->nlmsg_flags);
|
|
print_field("Sequence number: %d (0x%08x)",
|
|
nlmsg->nlmsg_seq, nlmsg->nlmsg_seq);
|
|
print_field("Port ID: %d", nlmsg->nlmsg_pid);
|
|
}
|
|
|
|
static void print_nlmsg(const struct timeval *tv, const struct nlmsghdr *nlmsg)
|
|
{
|
|
struct nlmsgerr *err;
|
|
int status;
|
|
|
|
print_nlmsghdr(tv, nlmsg);
|
|
|
|
switch (nlmsg->nlmsg_type) {
|
|
case NLMSG_ERROR:
|
|
err = NLMSG_DATA(nlmsg);
|
|
status = -err->error;
|
|
if (status < 0)
|
|
print_field("Error: %d (%s)", status, strerror(status));
|
|
else
|
|
print_field("ACK: %d", status);
|
|
break;
|
|
|
|
case NLMSG_DONE:
|
|
status = *((int *) NLMSG_DATA(nlmsg));
|
|
print_field("Status: %d", status);
|
|
break;
|
|
|
|
case NLMSG_NOOP:
|
|
case NLMSG_OVERRUN:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void print_rtnl_msg(const struct timeval *tv,
|
|
const struct nlmsghdr *nlmsg)
|
|
{
|
|
struct ifinfomsg *info;
|
|
struct ifaddrmsg *addr;
|
|
struct wlan_iface *iface;
|
|
int len;
|
|
|
|
switch (nlmsg->nlmsg_type) {
|
|
case RTM_NEWLINK:
|
|
case RTM_DELLINK:
|
|
case RTM_SETLINK:
|
|
case RTM_GETLINK:
|
|
info = (struct ifinfomsg *) NLMSG_DATA(nlmsg);
|
|
len = IFLA_PAYLOAD(nlmsg);
|
|
print_nlmsghdr(tv, nlmsg);
|
|
print_rtm_link(nlmsg->nlmsg_type, info, len);
|
|
break;
|
|
|
|
case RTM_NEWADDR:
|
|
case RTM_DELADDR:
|
|
case RTM_GETADDR:
|
|
addr = (struct ifaddrmsg *) NLMSG_DATA(nlmsg);
|
|
len = IFA_PAYLOAD(nlmsg);
|
|
if (!addr || len <= 0)
|
|
return;
|
|
|
|
iface = l_hashmap_lookup(wlan_iface_list,
|
|
L_INT_TO_PTR(addr->ifa_index));
|
|
if (!iface)
|
|
return;
|
|
|
|
print_nlmsghdr(tv, nlmsg);
|
|
print_ifaddrmsg(addr);
|
|
print_rtnl_attributes(1, addr_entry, IFA_RTA(addr), len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void nlmon_print_rtnl(struct nlmon *nlmon, const struct timeval *tv,
|
|
const void *data, uint32_t size)
|
|
{
|
|
uint32_t aligned_size = NLMSG_ALIGN(size);
|
|
const struct nlmsghdr *nlmsg;
|
|
|
|
update_time_offset(tv);
|
|
|
|
for (nlmsg = data; NLMSG_OK(nlmsg, aligned_size);
|
|
nlmsg = NLMSG_NEXT(nlmsg, aligned_size)) {
|
|
switch (nlmsg->nlmsg_type) {
|
|
case NLMSG_NOOP:
|
|
case NLMSG_OVERRUN:
|
|
case NLMSG_ERROR:
|
|
case NLMSG_DONE:
|
|
print_nlmsg(tv, nlmsg);
|
|
break;
|
|
|
|
case RTM_NEWLINK:
|
|
case RTM_DELLINK:
|
|
case RTM_SETLINK:
|
|
case RTM_GETLINK:
|
|
case RTM_NEWADDR:
|
|
case RTM_DELADDR:
|
|
case RTM_GETADDR:
|
|
print_rtnl_msg(tv, nlmsg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void nlmon_print_genl(struct nlmon *nlmon, const struct timeval *tv,
|
|
const void *data, uint32_t size)
|
|
{
|
|
const struct nlmsghdr *nlmsg;
|
|
|
|
update_time_offset(tv);
|
|
|
|
for (nlmsg = data; NLMSG_OK(nlmsg, size);
|
|
nlmsg = NLMSG_NEXT(nlmsg, size)) {
|
|
if (nlmsg->nlmsg_type == GENL_ID_CTRL)
|
|
genl_ctrl(nlmon, NLMSG_DATA(nlmsg),
|
|
NLMSG_PAYLOAD(nlmsg, 0));
|
|
else
|
|
nlmon_message(nlmon, tv, NULL, nlmsg);
|
|
}
|
|
}
|
|
|
|
static bool nlmon_receive(struct l_io *io, void *user_data)
|
|
{
|
|
struct nlmon *nlmon = user_data;
|
|
struct nlmsghdr *nlmsg;
|
|
struct msghdr msg;
|
|
struct sockaddr_ll sll;
|
|
struct iovec iov;
|
|
struct cmsghdr *cmsg;
|
|
struct timeval copy_tv;
|
|
struct tpacket_auxdata copy_tp;
|
|
const struct timeval *tv = NULL;
|
|
const struct tpacket_auxdata *tp = NULL;
|
|
uint16_t proto_type;
|
|
unsigned char buf[8192];
|
|
unsigned char control[32];
|
|
ssize_t bytes_read;
|
|
int fd;
|
|
|
|
fd = l_io_get_fd(io);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
memset(&sll, 0, sizeof(sll));
|
|
|
|
memset(&iov, 0, sizeof(iov));
|
|
iov.iov_base = buf;
|
|
iov.iov_len = sizeof(buf);
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_name = &sll;
|
|
msg.msg_namelen = sizeof(sll);
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = control;
|
|
msg.msg_controllen = sizeof(control);
|
|
|
|
bytes_read = recvmsg(fd, &msg, 0);
|
|
if (bytes_read < 0) {
|
|
if (errno != EAGAIN && errno != EINTR)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (sll.sll_hatype != ARPHRD_NETLINK)
|
|
return true;
|
|
|
|
proto_type = ntohs(sll.sll_protocol);
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
if (cmsg->cmsg_level == SOL_SOCKET &&
|
|
cmsg->cmsg_type == SCM_TIMESTAMP) {
|
|
memcpy(©_tv, CMSG_DATA(cmsg), sizeof(copy_tv));
|
|
tv = ©_tv;
|
|
}
|
|
|
|
if (cmsg->cmsg_level == SOL_PACKET &&
|
|
cmsg->cmsg_type != PACKET_AUXDATA) {
|
|
memcpy(©_tp, CMSG_DATA(cmsg), sizeof(copy_tp));
|
|
tp = ©_tp;
|
|
}
|
|
}
|
|
|
|
for (nlmsg = iov.iov_base; NLMSG_OK(nlmsg, bytes_read);
|
|
nlmsg = NLMSG_NEXT(nlmsg, bytes_read)) {
|
|
switch (proto_type) {
|
|
case NETLINK_ROUTE:
|
|
store_netlink(nlmon, tv, proto_type, nlmsg);
|
|
|
|
if (!nlmon->nortnl)
|
|
nlmon_print_rtnl(nlmon, tv, nlmsg,
|
|
nlmsg->nlmsg_len);
|
|
break;
|
|
case NETLINK_GENERIC:
|
|
nlmon_message(nlmon, tv, tp, nlmsg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* BPF filter to match skb->dev->type == 824 (ARPHRD_NETLINK) and
|
|
* either match skb->protocol == 0x0000 (NETLINK_ROUTE) or match
|
|
* skb->protocol == 0x0010 (NETLINK_GENERIC).
|
|
*/
|
|
static struct sock_filter mon_filter[] = {
|
|
{ 0x28, 0, 0, 0xfffff01c }, /* ldh #hatype */
|
|
{ 0x15, 0, 3, 0x00000338 }, /* jne #824, drop */
|
|
{ 0x28, 0, 0, 0xfffff000 }, /* ldh #proto */
|
|
{ 0x15, 2, 0, 0000000000 }, /* jeq #0x0000, pass */
|
|
{ 0x15, 1, 0, 0x00000010 }, /* jeq #0x0010, pass */
|
|
{ 0x06, 0, 0, 0000000000 }, /* drop: ret #0 */
|
|
{ 0x06, 0, 0, 0xffffffff }, /* pass: ret #-1 */
|
|
};
|
|
|
|
static const struct sock_fprog mon_fprog = { .len = 7, .filter = mon_filter };
|
|
|
|
static struct l_io *open_packet(const char *name)
|
|
{
|
|
struct l_io *io;
|
|
struct sockaddr_ll sll;
|
|
struct packet_mreq mr;
|
|
struct ifreq ifr;
|
|
int fd, opt = 1;
|
|
|
|
fd = socket(PF_PACKET, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
|
if (fd < 0) {
|
|
perror("Failed to create packet socket");
|
|
return NULL;
|
|
}
|
|
|
|
strncpy(ifr.ifr_name, name, IFNAMSIZ);
|
|
|
|
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
|
|
perror("Failed to get monitor index");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
memset(&sll, 0, sizeof(sll));
|
|
sll.sll_family = AF_PACKET;
|
|
sll.sll_protocol = htons(ETH_P_ALL);
|
|
sll.sll_ifindex = ifr.ifr_ifindex;
|
|
|
|
if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) < 0) {
|
|
perror("Failed to bind packet socket");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
memset(&mr, 0, sizeof(mr));
|
|
mr.mr_ifindex = ifr.ifr_ifindex;
|
|
mr.mr_type = PACKET_MR_ALLMULTI;
|
|
|
|
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
|
|
&mr, sizeof(mr)) < 0) {
|
|
perror("Failed to enable all multicast");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
|
|
&mon_fprog, sizeof(mon_fprog)) < 0) {
|
|
perror("Failed to enable monitor filter");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) < 0) {
|
|
perror("Failed to enable monitor timestamps");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
io = l_io_new(fd);
|
|
|
|
l_io_set_close_on_destroy(io, true);
|
|
|
|
return io;
|
|
}
|
|
|
|
void nlmon_print_pae(struct nlmon *nlmon, const struct timeval *tv,
|
|
uint8_t type, int index,
|
|
const void *data, uint32_t size)
|
|
{
|
|
char extra_str[16];
|
|
const char *str;
|
|
const struct eapol_header *eh;
|
|
|
|
update_time_offset(tv);
|
|
|
|
sprintf(extra_str, "len %u", size);
|
|
|
|
print_packet(tv, (type == PACKET_HOST) ? '>' : '<',
|
|
COLOR_YELLOW, "PAE", extra_str, "");
|
|
if (index >= 0)
|
|
print_attr(0, "Interface Index: %u", index);
|
|
|
|
if (size < 4)
|
|
return;
|
|
|
|
print_attr(0, "EAPoL: len %u", size);
|
|
print_hexdump(0, data, size);
|
|
|
|
eh = data;
|
|
|
|
switch (eh->protocol_version) {
|
|
case 0x01:
|
|
str = "802.1X-2001";
|
|
break;
|
|
case 0x02:
|
|
str = "802.1X-2004";
|
|
break;
|
|
case 0x03:
|
|
str = "802.1X-2010";
|
|
break;
|
|
default:
|
|
str = "Reserved";
|
|
break;
|
|
}
|
|
|
|
print_attr(1, "Protocol Version: %u (%s)", eh->protocol_version, str);
|
|
|
|
switch (eh->packet_type) {
|
|
case 0x00:
|
|
str = "EAP";
|
|
break;
|
|
case 0x01:
|
|
str = "Start";
|
|
break;
|
|
case 0x02:
|
|
str = "Logoff";
|
|
break;
|
|
case 0x03:
|
|
str = "Key";
|
|
break;
|
|
case 0x04:
|
|
str = "Encapsulated-ASF-Alert";
|
|
break;
|
|
case 0x05:
|
|
str = "MKA";
|
|
break;
|
|
case 0x06:
|
|
str = "Announcement (Generic)";
|
|
break;
|
|
case 0x07:
|
|
str = "Announcement (Specific)";
|
|
break;
|
|
case 0x08:
|
|
str = "Announcement-Req";
|
|
break;
|
|
default:
|
|
str = "Reserved";
|
|
break;
|
|
}
|
|
|
|
print_attr(1, "Type: %u (%s)", eh->packet_type, str);
|
|
print_attr(1, "Length: %d", L_BE16_TO_CPU(eh->packet_len));
|
|
|
|
switch (eh->packet_type) {
|
|
case 0x03:
|
|
print_eapol_key(data, size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool pae_receive(struct l_io *io, void *user_data)
|
|
{
|
|
struct nlmon *nlmon = user_data;
|
|
struct msghdr msg;
|
|
struct sockaddr_ll sll;
|
|
struct iovec iov;
|
|
struct cmsghdr *cmsg;
|
|
struct timeval copy_tv;
|
|
const struct timeval *tv = NULL;
|
|
unsigned char buf[8192];
|
|
unsigned char control[32];
|
|
ssize_t bytes_read;
|
|
int fd;
|
|
|
|
fd = l_io_get_fd(io);
|
|
if (fd < 0)
|
|
return false;
|
|
|
|
memset(&sll, 0, sizeof(sll));
|
|
|
|
memset(&iov, 0, sizeof(iov));
|
|
iov.iov_base = buf;
|
|
iov.iov_len = sizeof(buf);
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_name = &sll;
|
|
msg.msg_namelen = sizeof(sll);
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = control;
|
|
msg.msg_controllen = sizeof(control);
|
|
|
|
bytes_read = recvmsg(fd, &msg, 0);
|
|
if (bytes_read < 0) {
|
|
if (errno != EAGAIN && errno != EINTR)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (sll.sll_protocol != htons(ETH_P_PAE))
|
|
return true;
|
|
|
|
if (sll.sll_hatype != ARPHRD_ETHER)
|
|
return true;
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
if (cmsg->cmsg_level == SOL_SOCKET &&
|
|
cmsg->cmsg_type == SCM_TIMESTAMP) {
|
|
memcpy(©_tv, CMSG_DATA(cmsg), sizeof(copy_tv));
|
|
tv = ©_tv;
|
|
}
|
|
}
|
|
|
|
store_packet(nlmon, tv, sll.sll_pkttype, ARPHRD_ETHER, ETH_P_PAE,
|
|
buf, bytes_read);
|
|
|
|
nlmon_print_pae(nlmon, tv, sll.sll_pkttype, sll.sll_ifindex,
|
|
buf, bytes_read);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* BPF filter to match skb->dev->type == 1 (ARPHRD_ETHER) and
|
|
* match skb->protocol == 0x888e (PAE).
|
|
*/
|
|
static struct sock_filter pae_filter[] = {
|
|
{ 0x28, 0, 0, 0xfffff01c }, /* ldh #hatype */
|
|
{ 0x15, 0, 3, 0x00000001 }, /* jne #1, drop */
|
|
{ 0x28, 0, 0, 0xfffff000 }, /* ldh #proto */
|
|
{ 0x15, 0, 1, 0x0000888e }, /* jne #0x888e, drop */
|
|
{ 0x06, 0, 0, 0xffffffff }, /* ret #-1 */
|
|
{ 0x06, 0, 0, 0000000000 }, /* drop: ret #0 */
|
|
};
|
|
|
|
static const struct sock_fprog pae_fprog = { .len = 6, .filter = pae_filter };
|
|
|
|
static struct l_io *open_pae(void)
|
|
{
|
|
struct l_io *io;
|
|
int fd, opt = 1;
|
|
|
|
fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
|
|
htons(ETH_P_ALL));
|
|
if (fd < 0) {
|
|
perror("Failed to create authentication socket");
|
|
return NULL;
|
|
}
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
|
|
&pae_fprog, sizeof(pae_fprog)) < 0) {
|
|
perror("Failed to enable authentication filter");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) < 0) {
|
|
perror("Failed to enable authentication timestamps");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
io = l_io_new(fd);
|
|
|
|
l_io_set_close_on_destroy(io, true);
|
|
|
|
return io;
|
|
}
|
|
|
|
struct nlmon *nlmon_open(const char *ifname, uint16_t id, const char *pathname,
|
|
bool nortnl)
|
|
{
|
|
struct nlmon *nlmon;
|
|
struct l_io *io, *pae_io;
|
|
struct pcap *pcap;
|
|
|
|
io = open_packet(ifname);
|
|
if (!io)
|
|
return NULL;
|
|
|
|
pae_io = open_pae();
|
|
if (!pae_io) {
|
|
l_io_destroy(io);
|
|
return NULL;
|
|
}
|
|
|
|
if (pathname) {
|
|
pcap = pcap_create(pathname);
|
|
if (!pcap) {
|
|
l_io_destroy(pae_io);
|
|
l_io_destroy(io);
|
|
return NULL;
|
|
}
|
|
} else
|
|
pcap = NULL;
|
|
|
|
nlmon = l_new(struct nlmon, 1);
|
|
|
|
nlmon->id = id;
|
|
nlmon->io = io;
|
|
nlmon->pae_io = pae_io;
|
|
nlmon->req_list = l_queue_new();
|
|
nlmon->pcap = pcap;
|
|
nlmon->nortnl = nortnl;
|
|
|
|
l_io_set_read_handler(nlmon->io, nlmon_receive, nlmon, NULL);
|
|
l_io_set_read_handler(nlmon->pae_io, pae_receive, nlmon, NULL);
|
|
|
|
wlan_iface_list = l_hashmap_new();
|
|
|
|
return nlmon;
|
|
}
|
|
|
|
void nlmon_close(struct nlmon *nlmon)
|
|
{
|
|
if (!nlmon)
|
|
return;
|
|
|
|
l_io_destroy(nlmon->io);
|
|
l_io_destroy(nlmon->pae_io);
|
|
l_queue_destroy(nlmon->req_list, nlmon_req_free);
|
|
|
|
l_hashmap_destroy(wlan_iface_list, wlan_iface_list_free);
|
|
wlan_iface_list = NULL;
|
|
|
|
if (nlmon->pcap)
|
|
pcap_close(nlmon->pcap);
|
|
|
|
l_free(nlmon);
|
|
}
|