mirror of
				https://git.kernel.org/pub/scm/network/wireless/iwd.git
				synced 2025-11-04 08:57:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			6912 lines
		
	
	
		
			175 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			6912 lines
		
	
	
		
			175 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
 | 
						|
 | 
						|
#define _GNU_SOURCE
 | 
						|
#include <stdio.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <sys/ioctl.h>
 | 
						|
#include <sys/socket.h>
 | 
						|
#include <arpa/inet.h>
 | 
						|
#include <net/ethernet.h>
 | 
						|
#include <netinet/ether.h>
 | 
						|
#include <linux/if.h>
 | 
						|
#include <linux/if_packet.h>
 | 
						|
#include <linux/if_ether.h>
 | 
						|
#include <linux/netlink.h>
 | 
						|
#include <linux/genetlink.h>
 | 
						|
#include <linux/rtnetlink.h>
 | 
						|
#include <linux/filter.h>
 | 
						|
#include <ell/ell.h>
 | 
						|
 | 
						|
#ifndef ARPHRD_NETLINK
 | 
						|
#define ARPHRD_NETLINK	824
 | 
						|
#endif
 | 
						|
 | 
						|
#include "linux/nl80211.h"
 | 
						|
#include "src/ie.h"
 | 
						|
#include "src/mpdu.h"
 | 
						|
#include "src/eapol.h"
 | 
						|
#include "src/util.h"
 | 
						|
#include "src/p2putil.h"
 | 
						|
#include "src/nl80211cmd.h"
 | 
						|
#include "monitor/pcap.h"
 | 
						|
#include "monitor/display.h"
 | 
						|
#include "monitor/nlmon.h"
 | 
						|
#include "src/anqputil.h"
 | 
						|
 | 
						|
#define COLOR_TIMESTAMP		COLOR_YELLOW
 | 
						|
 | 
						|
#define COLOR_REQUEST		COLOR_BLUE
 | 
						|
#define COLOR_RESPONSE		COLOR_MAGENTA
 | 
						|
#define COLOR_COMPLETE		COLOR_MAGENTA
 | 
						|
#define COLOR_RESULT		COLOR_MAGENTA
 | 
						|
#define COLOR_EVENT		COLOR_CYAN
 | 
						|
 | 
						|
/* BSS Capabilities */
 | 
						|
#define BSS_CAPABILITY_ESS		(1<<0)
 | 
						|
#define BSS_CAPABILITY_IBSS		(1<<1)
 | 
						|
#define BSS_CAPABILITY_CF_POLLABLE	(1<<2)
 | 
						|
#define BSS_CAPABILITY_CF_POLL_REQUEST	(1<<3)
 | 
						|
#define BSS_CAPABILITY_PRIVACY		(1<<4)
 | 
						|
#define BSS_CAPABILITY_SHORT_PREAMBLE	(1<<5)
 | 
						|
#define BSS_CAPABILITY_PBCC		(1<<6)
 | 
						|
#define BSS_CAPABILITY_CHANNEL_AGILITY	(1<<7)
 | 
						|
#define BSS_CAPABILITY_SPECTRUM_MGMT	(1<<8)
 | 
						|
#define BSS_CAPABILITY_QOS		(1<<9)
 | 
						|
#define BSS_CAPABILITY_SHORT_SLOT_TIME	(1<<10)
 | 
						|
#define BSS_CAPABILITY_APSD		(1<<11)
 | 
						|
#define BSS_CAPABILITY_DSSS_OFDM	(1<<13)
 | 
						|
 | 
						|
enum msg_type {
 | 
						|
	MSG_REQUEST,
 | 
						|
	MSG_RESPONSE,
 | 
						|
	MSG_COMPLETE,
 | 
						|
	MSG_RESULT,
 | 
						|
	MSG_EVENT,
 | 
						|
};
 | 
						|
 | 
						|
struct nlmon {
 | 
						|
	uint16_t id;
 | 
						|
	struct l_io *io;
 | 
						|
	struct l_io *pae_io;
 | 
						|
	struct l_queue *req_list;
 | 
						|
	struct pcap *pcap;
 | 
						|
	bool nortnl;
 | 
						|
	bool nowiphy;
 | 
						|
	bool noscan;
 | 
						|
};
 | 
						|
 | 
						|
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, 0x04, 0x0e }, "AVM"			},
 | 
						|
	{ { 0x00, 0x0f, 0xac }, "IEEE 802.11"		},
 | 
						|
	{ { 0x00, 0x10, 0x18 }, "Broadcom"		},
 | 
						|
	{ { 0x00, 0x17, 0xf2 }, "Apple"			},
 | 
						|
	{ { 0x00, 0x40, 0x96 }, "Cisco Systems"		},
 | 
						|
	{ { 0x00, 0x50, 0xf2 }, "Microsoft"		},
 | 
						|
	{ { 0x00, 0x90, 0x4c }, "Epigram"		},
 | 
						|
	{ { 0x50, 0x6f, 0x9a }, "Wi-Fi Alliance"	},
 | 
						|
	{ }
 | 
						|
};
 | 
						|
 | 
						|
static void print_ie_error(unsigned int level, const char *label,
 | 
						|
				uint16_t len, int err)
 | 
						|
{
 | 
						|
	print_attr(level, "Error decoding %s IE len %d: %s (%d)", label, len,
 | 
						|
			strerror(-err), err);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_ssid(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	print_attr(level, "%s: %s", label, util_ssid_to_utf8(size, data));
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_rate(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t *rate = (uint8_t *)data;
 | 
						|
	int pos = 0, i = 0;
 | 
						|
	char str[128];
 | 
						|
 | 
						|
	if (!size) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	while (i < size) {
 | 
						|
		bool mandatory = (rate[i] & 0x80);
 | 
						|
 | 
						|
		if (rate[i] == 0xff) {
 | 
						|
			print_attr(level + 1, "BSS membership HT_PHY");
 | 
						|
			i++;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		pos += snprintf(&str[pos], sizeof(str) - pos, "%.1f%s ",
 | 
						|
				(rate[i] & 127) * 0.5, mandatory? "(B)": "");
 | 
						|
 | 
						|
		i++;
 | 
						|
 | 
						|
		if (i % 8 && i != size)
 | 
						|
			continue;
 | 
						|
 | 
						|
		if (pos) {
 | 
						|
			pos += snprintf(&str[pos], sizeof(str) - pos, "Mbit/s");
 | 
						|
			print_attr(level + 1, "%s", str);
 | 
						|
			pos = 0;
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_ds(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t *channel = (uint8_t *)data;
 | 
						|
 | 
						|
	if (!size) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: channel %d", label, *channel);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_tim(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const char *dtim = data;
 | 
						|
	int t, len = size - 3, pos = 0;
 | 
						|
	uint8_t bit;
 | 
						|
	char str[128];
 | 
						|
 | 
						|
	if (size < 4) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
	print_attr(level + 1, "DTIM count    %2d %s", dtim[0],
 | 
						|
			dtim[0] ? "beacon frame(s)" :
 | 
						|
				"this beacon frame is DTIM");
 | 
						|
	print_attr(level + 1, "DTIM period   %2d beacon frame(s)", dtim[1]);
 | 
						|
	print_attr(level + 1, "Group buffered %d offset %d",
 | 
						|
			!!(dtim[2] & 0x01), dtim[2] >> 1);
 | 
						|
 | 
						|
	len = size - 3;
 | 
						|
 | 
						|
	for (t = 0; t < len ; t++) {
 | 
						|
		if (((t + 1) % 4) == 1) {
 | 
						|
			pos = 0;
 | 
						|
			pos += snprintf(&str[pos], sizeof(str) - pos,
 | 
						|
					"AID  %4d - %4d ",
 | 
						|
					t * 8 + 1,
 | 
						|
					t + 4 > len ? len * 8 : (t + 4) * 8);
 | 
						|
		}
 | 
						|
 | 
						|
		for (bit = 0x01; bit; bit <<= 1)
 | 
						|
			pos += snprintf(&str[pos], sizeof(str) - pos,
 | 
						|
					"%d", !!(dtim[t + 3] & bit));
 | 
						|
 | 
						|
		pos += snprintf(&str[pos], sizeof(str) - pos, " ");
 | 
						|
 | 
						|
		if ((t + 1) % 4 == 0 || t + 1 == len)
 | 
						|
			print_attr(level + 1, "%s", str);
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_country(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t *code = (uint8_t *)data;
 | 
						|
	int i = 3;
 | 
						|
 | 
						|
	if (size < 6 || size % 2) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: %c%c%c", label, code[0], code[1], code[2]);
 | 
						|
 | 
						|
	while (i < size) {
 | 
						|
		if (code[i] > 200) {
 | 
						|
			print_attr(level + 1, "Regulatory ID %3d class %3d "
 | 
						|
				"coverage class %3d",
 | 
						|
				code[i], code[i + 1], code[i + 2]);
 | 
						|
 | 
						|
			if (code[i + 2] < 32)
 | 
						|
				print_attr(level + 1, "%27c (air propagation "
 | 
						|
					"time %2d µs)", ' ', 3 * code[i + 2]);
 | 
						|
		} else {
 | 
						|
			print_attr(level + 1, "First channel %3d number of "
 | 
						|
				"channels %2d max tx power %2d dBm",
 | 
						|
				code[i], code[i + 1], code[i + 2]);
 | 
						|
		}
 | 
						|
 | 
						|
		i += 3;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_bss_load(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t stations, capacity;
 | 
						|
	uint8_t utilization;
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 5) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	stations = bytes[0] | bytes[1] << 8;
 | 
						|
	utilization = bytes[2];
 | 
						|
	capacity = bytes[3] | bytes[4] << 8;
 | 
						|
 | 
						|
	print_attr(level, "%s: %2d station(s) utilization %d/255 available "
 | 
						|
			"capacity %d 32µs/s units",
 | 
						|
			label, stations, utilization, capacity);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_power_constraint(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t *dB = (uint8_t *)data;
 | 
						|
 | 
						|
	if (!size) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: %2d dB", label, *dB);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_tpc(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	signed char *dB = (signed char*)data;
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: transmit power %2d dB link margin %2d dB",
 | 
						|
			label, dB[0], dB[1]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_erp(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t *flags = (uint8_t *)data;
 | 
						|
 | 
						|
	if (!size) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
	print_attr(level + 1, "non-ERP present      %d", !!(*flags & 0x01));
 | 
						|
	print_attr(level + 1, "use protection       %d", !!(*flags & 0x02));
 | 
						|
	print_attr(level + 1, "Barker preamble mode %d", !!(*flags & 0x04));
 | 
						|
}
 | 
						|
 | 
						|
struct cipher_suites {
 | 
						|
	uint32_t cipher;
 | 
						|
	const char *str;
 | 
						|
};
 | 
						|
 | 
						|
static const struct cipher_suites rsn_cipher_selectors[] = {
 | 
						|
	{ 0x000fac00, "Use group cipher suite"		},
 | 
						|
	{ 0x000fac01, "WEP-40"				},
 | 
						|
	{ 0x000fac02, "TKIP"				},
 | 
						|
	{ 0x000fac04, "CCMP"				},
 | 
						|
	{ 0x000fac05, "WEP-104"				},
 | 
						|
	{ 0x000fac06, "BIP"				},
 | 
						|
	{ 0x000fac07, "Group traffic not allowed"	},
 | 
						|
	{ 0x00147201, "WPI-SMS4"			},
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
static const struct cipher_suites rsn_akm_selectors[] = {
 | 
						|
	{ 0x000fac01, "IEEE 802.1X/PMKSA; RSNA/PMKSA caching"                                     },
 | 
						|
	{ 0x000fac02, "PSK; RSNA PSK"                                                             },
 | 
						|
	{ 0x000fac03, "IEEE 802.1X FT; FT"                                                        },
 | 
						|
	{ 0x000fac04, "PSK FT; FT"                                                                },
 | 
						|
	{ 0x000fac05, "IEEE 802.1X/PMKSA caching SHA256; RSNA/RSNA caching SHA256"                },
 | 
						|
	{ 0x000fac06, "PSK SHA256; RSNA PSK SHA256"                                               },
 | 
						|
	{ 0x000fac07, "TDLS; TPK"                                                                 },
 | 
						|
	{ 0x000fac08, "SAE/PMKSA caching SHA256; RSNA PMKSA caching SHA256/mesh peering exchange" },
 | 
						|
	{ 0x000fac09, "FT SAE SHA256; FT"                                                         },
 | 
						|
	{ 0x000fac0e, "FILS SHA256"                                                               },
 | 
						|
	{ 0x000fac0f, "FILS SHA384"                                                               },
 | 
						|
	{ 0x000fac10, "FILS FT SHA256"                                                            },
 | 
						|
	{ 0x000fac11, "FILS FT SHA3854"                                                           },
 | 
						|
	{ 0x000fac12, "OWE"                                                                       },
 | 
						|
	{ 0x506f9a01, "WFA OSEN"                                                                  },
 | 
						|
	{ }
 | 
						|
};
 | 
						|
 | 
						|
static const struct cipher_suites wpa_cipher_selectors[] = {
 | 
						|
	{ 0x0050f200, "Use group cipher suite"		},
 | 
						|
	{ 0x0050f201, "WEP-40"				},
 | 
						|
	{ 0x0050f202, "TKIP"				},
 | 
						|
	{ 0x0050f204, "CCMP"				},
 | 
						|
	{ 0x0050f205, "WEP-104"				},
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
static const struct cipher_suites wpa_akm_selectors[] = {
 | 
						|
	{ 0x0050f201, "IEEE 802.1X/PMKSA; RSNA/PMKSA caching"	},
 | 
						|
	{ 0x0050f202, "PSK; RSNA PSK"				},
 | 
						|
	{ }
 | 
						|
};
 | 
						|
 | 
						|
static void print_ie_cipher_suite(unsigned int level, const char *label,
 | 
						|
				const uint32_t cipher,
 | 
						|
				const struct cipher_suites cipher_table[])
 | 
						|
{
 | 
						|
	const char *str = NULL;
 | 
						|
	unsigned int i;
 | 
						|
	unsigned char oui[] = {
 | 
						|
		(cipher & 0xff000000) >> 24,
 | 
						|
		(cipher & 0x00ff0000) >> 16,
 | 
						|
		(cipher & 0x0000ff00) >> 8,
 | 
						|
	};
 | 
						|
	char suite_value[32] = "";
 | 
						|
 | 
						|
	for (i = 0; cipher_table[i].str; i++) {
 | 
						|
		if (cipher_table[i].cipher == cipher) {
 | 
						|
			str = cipher_table[i].str;
 | 
						|
			snprintf(suite_value, sizeof(suite_value), " %02x",
 | 
						|
				cipher & 0x000000ff);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!str) {
 | 
						|
		for (i = 0; oui_table[i].str; i++) {
 | 
						|
			if (!memcmp(oui_table[i].oui, oui, 3)) {
 | 
						|
				str = oui_table[i].str;
 | 
						|
				snprintf(suite_value, sizeof(suite_value),
 | 
						|
					" %02x (vendor specific)",
 | 
						|
					cipher & 0x000000ff);
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!str) {
 | 
						|
		str = "unknown";
 | 
						|
		snprintf(suite_value, sizeof(suite_value), "%02x (unknown)",
 | 
						|
			cipher & 0x000000ff);
 | 
						|
	}
 | 
						|
 | 
						|
	if (label)
 | 
						|
		print_attr(level, "%s: %s (%02x:%02x:%02x) suite %s",
 | 
						|
			label, str, oui[0], oui[1], oui[2], suite_value);
 | 
						|
	else
 | 
						|
		print_attr(level, "%s (%02x:%02x:%02x) suite %s",
 | 
						|
			str, oui[0], oui[1], oui[2], suite_value);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_cipher_suites(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size,
 | 
						|
				const struct cipher_suites cipher_table[])
 | 
						|
{
 | 
						|
	uint32_t cipher;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	while (size >= 4) {
 | 
						|
		cipher = l_get_be32(data);
 | 
						|
 | 
						|
		print_ie_cipher_suite(level + 1, NULL, cipher, cipher_table);
 | 
						|
 | 
						|
		data += 4;
 | 
						|
		size -= 4;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static const char *rsn_capabilities_bitfield[] = {
 | 
						|
	"Preauthentication",
 | 
						|
	"No Pairwise",
 | 
						|
	"",
 | 
						|
	"",
 | 
						|
	"",
 | 
						|
	"",
 | 
						|
	"Management Frame Protection Required",
 | 
						|
	"Management Frame Protection Capable",
 | 
						|
	"Reserved",
 | 
						|
	"Peerkey Enabled",
 | 
						|
	"SPP A-MSDU Capable",
 | 
						|
	"SPP A-MSDU Required",
 | 
						|
	"PBAC",
 | 
						|
	"Extended Key ID for Individually Addressed Frames",
 | 
						|
	"Reserved",
 | 
						|
	"Reserved",
 | 
						|
	NULL
 | 
						|
};
 | 
						|
 | 
						|
static void print_ie_bitfield(unsigned int level, const char *label,
 | 
						|
			const uint8_t *bytes, const uint8_t *mask, size_t len,
 | 
						|
			const char *bitfield_table[])
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	for (i = 0; i < len * 8; i++) {
 | 
						|
		uint8_t byte = i / 8;
 | 
						|
		uint8_t bit = i % 8;
 | 
						|
 | 
						|
		if (!util_is_bit_set(bytes[byte] & mask[byte], bit))
 | 
						|
			continue;
 | 
						|
 | 
						|
		print_attr(level, "%s: bit %2d: %s", label, i,
 | 
						|
			bitfield_table[i]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static size_t print_ie_rsn_suites(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t count;
 | 
						|
	uint16_t orig_size = size;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "Group Data Cipher Suite", data, 4,
 | 
						|
				rsn_cipher_selectors);
 | 
						|
 | 
						|
	data += 4;
 | 
						|
	size -= 4;
 | 
						|
 | 
						|
	if (size < 2)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	count = l_get_le16(data) * 4;
 | 
						|
	data += 2;
 | 
						|
	size -= 2;
 | 
						|
 | 
						|
	if (size < count)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "Pairwise Cipher Suite", data,
 | 
						|
					count, rsn_cipher_selectors);
 | 
						|
	data += count;
 | 
						|
	size -= count;
 | 
						|
 | 
						|
	if (size < 2)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	count = l_get_le16(data) * 4;
 | 
						|
	data += 2;
 | 
						|
	size -= 2;
 | 
						|
 | 
						|
	if (size < count)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "AKM Suite", data, count,
 | 
						|
				rsn_akm_selectors);
 | 
						|
	data += count;
 | 
						|
	size -= count;
 | 
						|
 | 
						|
	if (size < 2)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	return orig_size - size;
 | 
						|
 | 
						|
end:
 | 
						|
	if (size)
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
 | 
						|
	return orig_size - size;
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_rsn(unsigned int level, const char *label,
 | 
						|
			const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const void *end = data + size;
 | 
						|
 | 
						|
	uint16_t version, count;
 | 
						|
	uint8_t bytemask[2];
 | 
						|
	int i;
 | 
						|
	const char *rsn_capabilities_replay_counter[] = {
 | 
						|
		"1 replay counter",
 | 
						|
		"2 replay counters",
 | 
						|
		"4 replay counters",
 | 
						|
		"16 replay counters"
 | 
						|
	};
 | 
						|
 | 
						|
	print_attr(level, "RSN:");
 | 
						|
 | 
						|
	if (end - data < 2) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	version = l_get_le16(data);
 | 
						|
	if (version != 1) {
 | 
						|
		print_attr(level, "Unknown RSN version %d", version);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	data += 2;
 | 
						|
 | 
						|
	if (end - data < 4)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	data += print_ie_rsn_suites(level, label, data, size);
 | 
						|
 | 
						|
	bytemask[0] = 0x03;
 | 
						|
	bytemask[1] = 0x00;
 | 
						|
	print_ie_bitfield(level + 1, "RSN capabilities", data, bytemask,
 | 
						|
			sizeof(bytemask), rsn_capabilities_bitfield);
 | 
						|
 | 
						|
	count = (*((uint8_t *)data) & 0x0c) >> 2;
 | 
						|
	print_attr(level + 1, "RSN capabilities: bits  2 - 3: %s per PTKSA",
 | 
						|
		rsn_capabilities_replay_counter[count]);
 | 
						|
 | 
						|
	count = (*((uint8_t *)data) & 0x30) >> 4;
 | 
						|
	print_attr(level + 1, "RSN capabilities: bits  4 - 5: %s per GTKSA",
 | 
						|
		rsn_capabilities_replay_counter[count]);
 | 
						|
 | 
						|
	bytemask[0] = 0xc0;
 | 
						|
	bytemask[1] = 0xff;
 | 
						|
	print_ie_bitfield(level + 1, "RSN capabilities", data, bytemask,
 | 
						|
			sizeof(bytemask), rsn_capabilities_bitfield);
 | 
						|
 | 
						|
	data += 2;
 | 
						|
 | 
						|
	if (end - data < 2)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	count = l_get_le16(data) * 16;
 | 
						|
	data += 2;
 | 
						|
 | 
						|
	if (end - data < count)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	for (i = 0; i < count; i += 16) {
 | 
						|
		const uint8_t *bytes = data;
 | 
						|
 | 
						|
		print_attr(level + 1, "PMKID: %02x:%02x:%02x:%02x:"
 | 
						|
			"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
 | 
						|
			"%02x:%02x:%02x:%02x",
 | 
						|
			bytes[i], bytes[i + 1],
 | 
						|
			bytes[i + 2], bytes[i + 3],
 | 
						|
			bytes[i + 4], bytes[i + 5],
 | 
						|
			bytes[i + 6], bytes[i + 7],
 | 
						|
			bytes[i + 8], bytes[i + 9],
 | 
						|
			bytes[i + 10], bytes[i + 11],
 | 
						|
			bytes[i + 12], bytes[i + 13],
 | 
						|
			bytes[i + 14], bytes[i + 15]);
 | 
						|
	}
 | 
						|
 | 
						|
	data += count;
 | 
						|
 | 
						|
	if (end - data < 4)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "Group Management Cipher Suite",
 | 
						|
			data, 4, rsn_cipher_selectors);
 | 
						|
 | 
						|
	data += 4;
 | 
						|
 | 
						|
end:
 | 
						|
	if (end - data)
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_wpa(unsigned int level, const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t offset;
 | 
						|
	uint16_t version, count;
 | 
						|
 | 
						|
	if (size < 2)
 | 
						|
		return;
 | 
						|
 | 
						|
	offset = 0;
 | 
						|
	version = l_get_le16(data + offset);
 | 
						|
	offset += 2;
 | 
						|
 | 
						|
	if (version != 1)
 | 
						|
		return;
 | 
						|
 | 
						|
	print_attr(level, "WPA:");
 | 
						|
	print_attr(level + 1, "Version: %d(%04x)", version, version);
 | 
						|
 | 
						|
	if (offset + 4 > size)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "Group Data Cipher Suite",
 | 
						|
					data + offset, 4, wpa_cipher_selectors);
 | 
						|
 | 
						|
	offset += 4;
 | 
						|
	if (offset + 2 > size)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	count = l_get_le16(data + offset) * 4;
 | 
						|
	offset += 2;
 | 
						|
 | 
						|
	if (offset + count > size)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "Pairwise Cipher Suite",
 | 
						|
				data + offset, count, wpa_cipher_selectors);
 | 
						|
	offset += count;
 | 
						|
	if (offset + 2 > size)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	count = l_get_le16(data + offset) * 4;
 | 
						|
	offset += 2;
 | 
						|
	if (offset + count > size)
 | 
						|
		goto end;
 | 
						|
 | 
						|
	print_ie_cipher_suites(level + 1, "AKM Suite", data + offset, count,
 | 
						|
							wpa_akm_selectors);
 | 
						|
	return;
 | 
						|
 | 
						|
end:
 | 
						|
	print_ie_error(level, label, size, -EINVAL);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_wfa_hs20(unsigned int level, const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *ptr = data;
 | 
						|
	bool pps_mo_id_present;
 | 
						|
	bool anpq_domain_id_present;
 | 
						|
 | 
						|
	if (size < 1)
 | 
						|
		return;
 | 
						|
 | 
						|
	pps_mo_id_present = util_is_bit_set(ptr[0], 1);
 | 
						|
	anpq_domain_id_present = util_is_bit_set(ptr[0], 2);
 | 
						|
 | 
						|
	print_attr(level + 1, "HS2.0 Indication Element:");
 | 
						|
	print_attr(level + 2, "DGAF Disabled: %u", util_is_bit_set(ptr[0], 0));
 | 
						|
	print_attr(level + 2, "PPS MO ID Present: %u", pps_mo_id_present);
 | 
						|
	print_attr(level + 2, "ANQP Domain ID Present: %u",
 | 
						|
				anpq_domain_id_present);
 | 
						|
 | 
						|
	switch (util_bit_field(ptr[0], 4, 7)) {
 | 
						|
	case 0:
 | 
						|
		print_attr(level + 2, "Version Number: 1.x");
 | 
						|
		break;
 | 
						|
	case 1:
 | 
						|
		print_attr(level + 2, "Version Number: 2.x");
 | 
						|
		break;
 | 
						|
	case 2:
 | 
						|
		print_attr(level + 2, "Version Number: 3.x");
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	ptr += 1;
 | 
						|
	size -= 1;
 | 
						|
 | 
						|
	if (pps_mo_id_present) {
 | 
						|
		if (size < 2)
 | 
						|
			return;
 | 
						|
 | 
						|
		print_attr(level + 2, "PPS MO ID: %02x %02x", ptr[0], ptr[1]);
 | 
						|
		ptr += 2;
 | 
						|
		size -= 2;
 | 
						|
	}
 | 
						|
 | 
						|
	if (anpq_domain_id_present) {
 | 
						|
		if (size < 2)
 | 
						|
			return;
 | 
						|
 | 
						|
		print_attr(level + 2, "ANQP Domain ID: %02x %02x",
 | 
						|
				ptr[0], ptr[1]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static bool print_oui(unsigned int level, const uint8_t *oui)
 | 
						|
{
 | 
						|
	const char *str = NULL;
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	for (i = 0; oui_table[i].str; i++) {
 | 
						|
		if (!memcmp(oui_table[i].oui, oui, 3)) {
 | 
						|
			str = oui_table[i].str;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!str) {
 | 
						|
		print_attr(level + 1, "OUI: %02x:%02x:%02x type:%02x",
 | 
						|
							oui[0], oui[1], oui[2],
 | 
						|
							oui[3]);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level + 1, "%s (%02x:%02x:%02x) type: %02x", str,
 | 
						|
							oui[0], oui[1], oui[2],
 | 
						|
							oui[3]);
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static void print_ipv4(unsigned int level, const char *label,
 | 
						|
				const uint8_t *addr)
 | 
						|
{
 | 
						|
	print_attr(level, "%s: %u.%u.%u.%u", label,
 | 
						|
			addr[0], addr[1], addr[2], addr[3]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_vendor(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *oui = data;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	if (size < 4)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (!print_oui(level, oui))
 | 
						|
		return;
 | 
						|
 | 
						|
	data += 4;
 | 
						|
	size -= 4;
 | 
						|
 | 
						|
	if (!memcmp(oui, microsoft_oui, 3)) {
 | 
						|
		switch (oui[3]) {
 | 
						|
		case 1:		/* MSoft WPA IE */
 | 
						|
			print_ie_wpa(level + 2, label, data, size);
 | 
						|
			return;
 | 
						|
		default:
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	} else if (!memcmp(oui, wifi_alliance_oui, 3)) {
 | 
						|
		switch (oui[3]) {
 | 
						|
		case 0x04:
 | 
						|
			print_attr(level + 1, "IP Address Request KDE");
 | 
						|
			return;
 | 
						|
		case 0x05:
 | 
						|
			print_attr(level + 1, "IP Address Allocation KDE");
 | 
						|
 | 
						|
			if (size < 12)
 | 
						|
				return;
 | 
						|
 | 
						|
			print_ipv4(level + 2, "Client IP Address", data + 0);
 | 
						|
			print_ipv4(level + 2, "Subnet Mask", data + 4);
 | 
						|
			print_ipv4(level + 2, "GO IP Address", data + 8);
 | 
						|
			return;
 | 
						|
		case 0x10:
 | 
						|
			print_ie_wfa_hs20(level + 1, label, data, size);
 | 
						|
			return;
 | 
						|
		case 0x12:
 | 
						|
			print_ie_rsn_suites(level + 1, label, data, size);
 | 
						|
			return;
 | 
						|
		default:
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	} else if (!memcmp(oui, ieee_oui, 3)) {
 | 
						|
		const char *kde;
 | 
						|
 | 
						|
		/* EAPoL-Key KDEs */
 | 
						|
		switch (oui[3]) {
 | 
						|
		case 1:
 | 
						|
			kde = "GTK";
 | 
						|
			break;
 | 
						|
		case 3:
 | 
						|
			kde = "MAC address";
 | 
						|
			break;
 | 
						|
		case 4:
 | 
						|
			kde = "PMKID";
 | 
						|
			break;
 | 
						|
		case 5:
 | 
						|
			kde = "SMK";
 | 
						|
			break;
 | 
						|
		case 6:
 | 
						|
			kde = "Nonce";
 | 
						|
			break;
 | 
						|
		case 7:
 | 
						|
			kde = "Lifetime";
 | 
						|
			break;
 | 
						|
		case 8:
 | 
						|
			kde = "Error";
 | 
						|
			break;
 | 
						|
		case 9:
 | 
						|
			kde = "IGTK";
 | 
						|
			break;
 | 
						|
		case 10:
 | 
						|
			kde = "Key ID";
 | 
						|
			break;
 | 
						|
		case 11:
 | 
						|
			kde = "Multi-band GTK";
 | 
						|
			break;
 | 
						|
		case 12:
 | 
						|
			kde = "Multi-band Key ID";
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		print_attr(level + 1, "%s KDE", kde);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_mcs(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
	int i;
 | 
						|
	uint8_t bytemask[16];
 | 
						|
	uint16_t data_rate;
 | 
						|
	const char *mcs_set[128] = {
 | 
						|
		[77] = "Reserved",
 | 
						|
		[78] = "Reserved",
 | 
						|
		[79] = "Reserved",
 | 
						|
 | 
						|
		[90] = "Reserved",
 | 
						|
		[91] = "Reserved",
 | 
						|
		[92] = "Reserved",
 | 
						|
		[93] = "Reserved",
 | 
						|
		[94] = "Reserved",
 | 
						|
		[95] = "Reserved",
 | 
						|
		[96] = "Tx MCS set defined",
 | 
						|
 | 
						|
		[97]  = "Tx Rx MCS set not equal",
 | 
						|
		[100] = "Tx unequal modulation supported",
 | 
						|
		[101] = "Reserved",
 | 
						|
		[102] = "Reserved",
 | 
						|
		[103] = "Reserved",
 | 
						|
 | 
						|
		[104] = "Reserved",
 | 
						|
		[105] = "Reserved",
 | 
						|
		[106] = "Reserved",
 | 
						|
		[107] = "Reserved",
 | 
						|
		[108] = "Reserved",
 | 
						|
		[109] = "Reserved",
 | 
						|
		[110] = "Reserved",
 | 
						|
		[111] = "Reserved",
 | 
						|
 | 
						|
		[112] = "Reserved",
 | 
						|
		[113] = "Reserved",
 | 
						|
		[114] = "Reserved",
 | 
						|
		[115] = "Reserved",
 | 
						|
		[116] = "Reserved",
 | 
						|
		[117] = "Reserved",
 | 
						|
		[118] = "Reserved",
 | 
						|
		[119] = "Reserved",
 | 
						|
 | 
						|
		[120] = "Reserved",
 | 
						|
		[121] = "Reserved",
 | 
						|
		[122] = "Reserved",
 | 
						|
		[123] = "Reserved",
 | 
						|
		[124] = "Reserved",
 | 
						|
		[125] = "Reserved",
 | 
						|
		[126] = "Reserved",
 | 
						|
		[127] = "Reserved",
 | 
						|
	};
 | 
						|
 | 
						|
	if (size != 16)
 | 
						|
		return print_ie_error(level, label, size, -EINVAL);
 | 
						|
 | 
						|
	for (i = 0; i < 77; i++) {
 | 
						|
		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, "Supported 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 void print_ie_interworking(unsigned int level,
 | 
						|
					const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *ptr = data;
 | 
						|
	const char *msg;
 | 
						|
	uint8_t type;
 | 
						|
	bool venue = false;
 | 
						|
	bool hessid = false;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	type = util_bit_field(ptr[0], 0, 3);
 | 
						|
 | 
						|
	switch (type) {
 | 
						|
	case 0:
 | 
						|
		msg = "Private network";
 | 
						|
		break;
 | 
						|
	case 1:
 | 
						|
		msg = "Private network w/ guest access";
 | 
						|
		break;
 | 
						|
	case 2:
 | 
						|
		msg = "Chargeable public network";
 | 
						|
		break;
 | 
						|
	case 3:
 | 
						|
		msg = "Free public network";
 | 
						|
		break;
 | 
						|
	case 4:
 | 
						|
		msg = "Personal device network";
 | 
						|
		break;
 | 
						|
	case 5:
 | 
						|
		msg = "Emergency services only network";
 | 
						|
		break;
 | 
						|
	case 14:
 | 
						|
		msg = "Test/Experimental";
 | 
						|
		break;
 | 
						|
	case 15:
 | 
						|
		msg = "Wildcard";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level + 1, "Network Type: %s", msg);
 | 
						|
	print_attr(level + 1, "Internet: %u", util_is_bit_set(ptr[0], 4));
 | 
						|
	print_attr(level + 1, "ASRA: %u", util_is_bit_set(ptr[0], 5));
 | 
						|
	print_attr(level + 1, "ESR: %u", util_is_bit_set(ptr[0], 6));
 | 
						|
	print_attr(level + 1, "UESA: %u", util_is_bit_set(ptr[0], 7));
 | 
						|
 | 
						|
	size--;
 | 
						|
	ptr++;
 | 
						|
 | 
						|
	if (!size)
 | 
						|
		return;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * There is venue/hessid info optionally, and no way of determining if
 | 
						|
	 * they exist except looking at the length.
 | 
						|
	 */
 | 
						|
	if (size == 2)
 | 
						|
		venue = true;
 | 
						|
	else if (size == 6)
 | 
						|
		hessid = true;
 | 
						|
	else if (size == 8) {
 | 
						|
		venue = true;
 | 
						|
		hessid = true;
 | 
						|
	}
 | 
						|
 | 
						|
	if (venue) {
 | 
						|
		switch (ptr[0]) {
 | 
						|
		case 0:
 | 
						|
			msg = "Unspecified";
 | 
						|
			break;
 | 
						|
		case 1:
 | 
						|
			msg = "Assembly";
 | 
						|
			break;
 | 
						|
		case 2:
 | 
						|
			msg = "Business";
 | 
						|
			break;
 | 
						|
		case 3:
 | 
						|
			msg = "Educational";
 | 
						|
			break;
 | 
						|
		case 5:
 | 
						|
			msg = "Factory and Industrial";
 | 
						|
			break;
 | 
						|
		case 6:
 | 
						|
			msg = "Institutional";
 | 
						|
			break;
 | 
						|
		case 7:
 | 
						|
			msg = "Mercantile";
 | 
						|
			break;
 | 
						|
		case 8:
 | 
						|
			msg = "Residential";
 | 
						|
			break;
 | 
						|
		case 9:
 | 
						|
			msg = "Utility and Miscellaneous";
 | 
						|
			break;
 | 
						|
		case 10:
 | 
						|
			msg = "Vehicular";
 | 
						|
			break;
 | 
						|
		case 11:
 | 
						|
			msg = "Outdoor";
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Each of the above groups have many group types, but if
 | 
						|
		 * anyone really cares they can cross reference the integer
 | 
						|
		 * type with IEEE 802.11-2016 Table 9-62
 | 
						|
		 */
 | 
						|
		print_attr(level + 1, "Venue: %s, type: %u", msg, ptr[1]);
 | 
						|
 | 
						|
		ptr += 2;
 | 
						|
		size -= 2;
 | 
						|
	}
 | 
						|
 | 
						|
	if (hessid)
 | 
						|
		print_attr(level + 1, "HESSID: "MAC, MAC_STR(ptr));
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_advertisement(unsigned int level,
 | 
						|
					const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *ptr = data;
 | 
						|
	const char *msg = NULL;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	while (size) {
 | 
						|
		uint8_t qr_len = util_bit_field(ptr[0], 0, 7);
 | 
						|
		uint8_t id = ptr[1];
 | 
						|
 | 
						|
		switch (id) {
 | 
						|
		case IE_ADVERTISEMENT_ANQP:
 | 
						|
			msg = "ANQP";
 | 
						|
			break;
 | 
						|
		case IE_ADVERTISEMENT_MIH_SERVICE:
 | 
						|
			msg = "MIH Information Service";
 | 
						|
			break;
 | 
						|
		case IE_ADVERTISEMENT_MIH_DISCOVERY:
 | 
						|
			msg = "MIH Command and Event Services";
 | 
						|
			break;
 | 
						|
		case IE_ADVERTISEMENT_EAS:
 | 
						|
			msg = "EAS";
 | 
						|
			break;
 | 
						|
		case IE_ADVERTISEMENT_RLQP:
 | 
						|
			msg = "RLQP";
 | 
						|
			break;
 | 
						|
		case IE_ADVERTISEMENT_VENDOR_SPECIFIC:
 | 
						|
			msg = "Vendor Specific";
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		if (id == IE_ADVERTISEMENT_VENDOR_SPECIFIC) {
 | 
						|
			size -= ptr[3];
 | 
						|
			ptr += ptr[3];
 | 
						|
		} else {
 | 
						|
			size -= 2;
 | 
						|
			ptr += 2;
 | 
						|
		}
 | 
						|
 | 
						|
		print_attr(level + 1, "Protocol: %s, Query Resp Limit: %u",
 | 
						|
				msg, qr_len);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_owe(unsigned int level,
 | 
						|
					const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t group;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	group = l_get_le16(data);
 | 
						|
 | 
						|
	print_attr(level + 1, "ECC Group: %u", group);
 | 
						|
	print_attr(level + 1, "Public Key:");
 | 
						|
	print_hexdump(level + 2, data + 2, size - 2);
 | 
						|
}
 | 
						|
 | 
						|
static void print_fils_indication(unsigned int level,
 | 
						|
					const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	print_attr(level, "FILS Indication: len %u", size);
 | 
						|
 | 
						|
	print_attr(level + 1, "Num Public Key Identifiers: %u",
 | 
						|
				util_bit_field(*bytes, 0, 3));
 | 
						|
	print_attr(level + 1, "Num Realm Identifiers: %u",
 | 
						|
				util_bit_field(*bytes, 3, 3));
 | 
						|
	print_attr(level + 1, "IP configuration: %u", util_is_bit_set(*bytes, 6));
 | 
						|
	print_attr(level + 1, "Cache Identifier Included: %u",
 | 
						|
				util_is_bit_set(*bytes, 7));
 | 
						|
 | 
						|
	bytes++;
 | 
						|
 | 
						|
	print_attr(level + 1, "HES-SID Included: %u", util_is_bit_set(*bytes, 0));
 | 
						|
	print_attr(level + 1, "SK Auth without PFS supported: %u",
 | 
						|
				util_is_bit_set(*bytes, 1));
 | 
						|
	print_attr(level + 1, "SK Auth with PFS supported: %u",
 | 
						|
				util_is_bit_set(*bytes, 2));
 | 
						|
	print_attr(level + 1, "PK Auth supported: %u", util_is_bit_set(*bytes, 3));
 | 
						|
 | 
						|
	bytes++;
 | 
						|
 | 
						|
	print_hexdump(level + 1, bytes, size - 2);
 | 
						|
}
 | 
						|
 | 
						|
static void print_fils_key_confirmation(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	print_attr(level, "FILS Key Confirmation (KeyAuth): len %u", size);
 | 
						|
}
 | 
						|
 | 
						|
static void print_fils_session(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	print_attr(level, "FILS Session: len %u", size);
 | 
						|
}
 | 
						|
 | 
						|
static void print_ie_supported_operating_classes(unsigned int level,
 | 
						|
							const char *label,
 | 
						|
							const void *data,
 | 
						|
							uint16_t size)
 | 
						|
{
 | 
						|
	const void *end = data + size;
 | 
						|
 | 
						|
	if (size < 1) {
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "Current Operating Class: %u", l_get_u8(data));
 | 
						|
 | 
						|
	data += 1;
 | 
						|
 | 
						|
	while (end - data) {
 | 
						|
		uint8_t cls = l_get_u8(data);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * TODO: Support Current Operating Class Extension Sequence
 | 
						|
		 *       and Operating Class Duple Sequence
 | 
						|
		*/
 | 
						|
		if (cls == 130 || cls == 0) {
 | 
						|
			data = end;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		print_attr(level, "Supported Operating Class: %u", cls);
 | 
						|
		data += 1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (end - data)
 | 
						|
		print_ie_error(level, label, size, -EINVAL);
 | 
						|
}
 | 
						|
 | 
						|
static struct attr_entry ie_entry[] = {
 | 
						|
	{ IE_TYPE_SSID,				"SSID",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_ssid } },
 | 
						|
	{ IE_TYPE_SUPPORTED_RATES,		"Supported rates",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_rate } },
 | 
						|
	{ IE_TYPE_DSSS_PARAMETER_SET,		"DSSS parameter set",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_ds } },
 | 
						|
	{ IE_TYPE_TIM,				"TIM",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_tim } },
 | 
						|
	{ IE_TYPE_COUNTRY,			"Country",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_country } },
 | 
						|
	{ IE_TYPE_BSS_LOAD,			"BSS load",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_bss_load } },
 | 
						|
	{ IE_TYPE_POWER_CONSTRAINT,		"Power constraint",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_power_constraint } },
 | 
						|
	{ IE_TYPE_TPC_REPORT,			"TPC report",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_tpc } },
 | 
						|
	{ IE_TYPE_ERP,				"ERP Information",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_erp } },
 | 
						|
	{ IE_TYPE_RSN,				"RSN",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_rsn } },
 | 
						|
	{ IE_TYPE_EXTENDED_SUPPORTED_RATES,	"Extended supported rates",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_rate } },
 | 
						|
	{ IE_TYPE_HT_OPERATION,			"HT Operation",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_ht_operation } },
 | 
						|
	{ IE_TYPE_VENDOR_SPECIFIC,		"Vendor specific",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_vendor } },
 | 
						|
	{ IE_TYPE_EXTENDED_CAPABILITIES,	"Extended Capabilities",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_extended_capabilities } },
 | 
						|
	{ IE_TYPE_HT_CAPABILITIES,		"HT Capabilities",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_ht_capabilities } },
 | 
						|
	{ IE_TYPE_RM_ENABLED_CAPABILITIES,	"RM Enabled Capabilities",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_rm_enabled_caps } },
 | 
						|
	{ IE_TYPE_INTERWORKING,			"Interworking",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_interworking } },
 | 
						|
	{ IE_TYPE_ADVERTISEMENT_PROTOCOL,	"Advertisement Protocol",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_advertisement } },
 | 
						|
	{ IE_TYPE_OWE_DH_PARAM,			"OWE Diffie-Hellman Parameter",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_ie_owe } },
 | 
						|
	{ IE_TYPE_FILS_INDICATION,		"FILS Indication",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_fils_indication } },
 | 
						|
	{ IE_TYPE_FILS_KEY_CONFIRMATION,	"FILS Key Confirmation",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_fils_key_confirmation } },
 | 
						|
	{ IE_TYPE_FILS_SESSION,			"FILS Session",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_fils_session } },
 | 
						|
	{ IE_TYPE_SUPPORTED_OPERATING_CLASSES,	"Supported Operating Classes",
 | 
						|
		ATTR_CUSTOM,
 | 
						|
		{ .function = print_ie_supported_operating_classes } },
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
static void print_ie(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	struct ie_tlv_iter iter;
 | 
						|
	int i;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	ie_tlv_iter_init(&iter, data, size);
 | 
						|
 | 
						|
	while (ie_tlv_iter_next(&iter)) {
 | 
						|
		uint16_t tag = ie_tlv_iter_get_tag(&iter);
 | 
						|
		struct attr_entry *entry = NULL;
 | 
						|
 | 
						|
		for (i = 0; ie_entry[i].str; i++) {
 | 
						|
			if (ie_entry[i].attr == tag) {
 | 
						|
				entry = &ie_entry[i];
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (entry && entry->function)
 | 
						|
			entry->function(level + 1, entry->str,
 | 
						|
					iter.data, iter.len);
 | 
						|
		else
 | 
						|
			print_attr(level + 1, "Tag %u: len %u", tag,
 | 
						|
					iter.len);
 | 
						|
 | 
						|
		print_hexdump(level + 2, iter.data, iter.len);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_byte(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: %u", label, bytes[0]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_bytes(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	print_attr(level, "%s: (len: %d)", label, size);
 | 
						|
	print_hexdump(level + 1, data, size);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_bool(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: %s", label, bytes[0] ? "True" : "False");
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_ascii_string(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size,
 | 
						|
					uint16_t max_len)
 | 
						|
{
 | 
						|
	const char *p = data;
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	if (size >= max_len) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < size; i++) {
 | 
						|
		if (!p[i])
 | 
						|
			break;
 | 
						|
 | 
						|
		if (!l_ascii_isprint(p[i]))
 | 
						|
			goto invalid_ascii;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: %.*s", label, i, p);
 | 
						|
	return;
 | 
						|
 | 
						|
invalid_ascii:
 | 
						|
	print_attr(level, "%s: (Non-Ascii, len: %d)", label, size);
 | 
						|
	print_hexdump(level + 1, data, size);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_utf8_string(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size,
 | 
						|
					uint16_t max_len)
 | 
						|
{
 | 
						|
	const char *p = data;
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	if (size >= max_len) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; i < size; i++) {
 | 
						|
		if (!p[i])
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!l_utf8_validate((const char *) p, i, NULL))
 | 
						|
		goto invalid_utf8;
 | 
						|
 | 
						|
	print_attr(level, "%s: %.*s", label, i, p);
 | 
						|
	return;
 | 
						|
 | 
						|
invalid_utf8:
 | 
						|
	print_attr(level, "%s: (Non-utf8, len: %d)", label, size);
 | 
						|
	print_hexdump(level + 1, data, size);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_uuid(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 16) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: %02x%02x%02x%02x-%02x%02x-%02x%02x-"
 | 
						|
				"%02x%02x-%02x%02x%02x%02x%02x%02x",
 | 
						|
				label,
 | 
						|
				bytes[0], bytes[1], bytes[2], bytes[3],
 | 
						|
				bytes[4], bytes[5], bytes[6], bytes[7],
 | 
						|
				bytes[8], bytes[9], bytes[10], bytes[11],
 | 
						|
				bytes[12], bytes[13], bytes[14], bytes[15]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_association_state(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t state;
 | 
						|
	static const char *state_table[] = {
 | 
						|
		"Not Associated",
 | 
						|
		"Connection Success",
 | 
						|
		"Configuration Failure",
 | 
						|
		"Association Failure",
 | 
						|
		"IP Failure",
 | 
						|
	};
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	state = l_get_be16(data);
 | 
						|
 | 
						|
	if (state > 4)
 | 
						|
		print_attr(level, "%s: Reserved", label);
 | 
						|
	else
 | 
						|
		print_attr(level, "%s: %s", label, state_table[state]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_auth_type_flags(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t v;
 | 
						|
 | 
						|
	if (size != 2)
 | 
						|
		return;
 | 
						|
 | 
						|
	v = l_get_be16(data);
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	if (v & WSC_AUTHENTICATION_TYPE_OPEN)
 | 
						|
		print_attr(level + 1, "Open");
 | 
						|
 | 
						|
	if (v & WSC_AUTHENTICATION_TYPE_WPA_PERSONAL)
 | 
						|
		print_attr(level + 1, "WPA-Personal");
 | 
						|
 | 
						|
	if (v & WSC_AUTHENTICATION_TYPE_SHARED)
 | 
						|
		print_attr(level + 1, "Shared");
 | 
						|
 | 
						|
	if (v & WSC_AUTHENTICATION_TYPE_WPA_ENTERPRISE)
 | 
						|
		print_attr(level + 1, "WPA-Enterprise");
 | 
						|
 | 
						|
	if (v & WSC_AUTHENTICATION_TYPE_WPA2_ENTERPRISE)
 | 
						|
		print_attr(level + 1, "WPA2-Enterprise");
 | 
						|
 | 
						|
	if (v & WSC_AUTHENTICATION_TYPE_WPA2_PERSONAL)
 | 
						|
		print_attr(level + 1, "WPA2-Personal");
 | 
						|
 | 
						|
	if (v & 0xffc0)
 | 
						|
		print_attr(level + 1, "Unknown: %04x", v & 0xffc0);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_authenticator(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *a = data;
 | 
						|
 | 
						|
	if (size != 8)
 | 
						|
		return;
 | 
						|
 | 
						|
	print_attr(level, "%s: %02x%02x%02x%02x%02x%02x%02x%02x",
 | 
						|
			label, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_configuration_error(unsigned int level, const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t error;
 | 
						|
	static const char *error_table[] = {
 | 
						|
		"No Error",
 | 
						|
		"OOB Interface Read Error",
 | 
						|
		"Decryption CRC Failure",
 | 
						|
		"2.4 channel not supported",
 | 
						|
		"5.0 channel not supported",
 | 
						|
		"Signal too weak",
 | 
						|
		"Network auth failure",
 | 
						|
		"Network association failure",
 | 
						|
		"No DHCP response",
 | 
						|
		"Failed DHCP config",
 | 
						|
		"IP Address conflict",
 | 
						|
		"Couldn't connect to Registrar",
 | 
						|
		"Multiple PBC sessions detected",
 | 
						|
		"Rogue activity suspected",
 | 
						|
		"Device busy",
 | 
						|
		"Setup locked",
 | 
						|
		"Message timeout",
 | 
						|
		"Registration session timeout",
 | 
						|
		"Device Password Auth Failure",
 | 
						|
		"60 Ghz channel not supported",
 | 
						|
		"Public Key Hash Mismatch",
 | 
						|
	};
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	error = l_get_be16(data);
 | 
						|
 | 
						|
	if (error > 20)
 | 
						|
		print_attr(level, "%s: Reserved", label);
 | 
						|
	else
 | 
						|
		print_attr(level, "%s: %s", label, error_table[error]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_config_methods(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t v;
 | 
						|
	uint16_t flags;
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	v = l_get_be16(data);
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	if ((v & WSC_CONFIGURATION_METHOD_PHYSICAL_DISPLAY_PIN) ==
 | 
						|
			WSC_CONFIGURATION_METHOD_PHYSICAL_DISPLAY_PIN)
 | 
						|
		print_attr(level + 1, "Physical Display PIN");
 | 
						|
 | 
						|
	if ((v & WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN) ==
 | 
						|
			WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN)
 | 
						|
		print_attr(level + 1, "Virtual Display PIN");
 | 
						|
 | 
						|
	flags = WSC_CONFIGURATION_METHOD_PHYSICAL_DISPLAY_PIN |
 | 
						|
			WSC_CONFIGURATION_METHOD_VIRTUAL_DISPLAY_PIN;
 | 
						|
	if (v & flags)
 | 
						|
		v &= ~flags;
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_P2P) {
 | 
						|
		print_attr(level + 1, "P2P");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_P2P;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((v & WSC_CONFIGURATION_METHOD_PHYSICAL_PUSH_BUTTON) ==
 | 
						|
			WSC_CONFIGURATION_METHOD_PHYSICAL_PUSH_BUTTON)
 | 
						|
		print_attr(level + 1, "Physical PushButton");
 | 
						|
 | 
						|
	if ((v & WSC_CONFIGURATION_METHOD_VIRTUAL_PUSH_BUTTON) ==
 | 
						|
			WSC_CONFIGURATION_METHOD_VIRTUAL_PUSH_BUTTON)
 | 
						|
		print_attr(level + 1, "Virtual PushButton");
 | 
						|
 | 
						|
	flags = WSC_CONFIGURATION_METHOD_PHYSICAL_PUSH_BUTTON |
 | 
						|
			WSC_CONFIGURATION_METHOD_VIRTUAL_PUSH_BUTTON;
 | 
						|
	if (v & flags)
 | 
						|
		v &= ~flags;
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_KEYPAD) {
 | 
						|
		print_attr(level + 1, "Keypad");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_KEYPAD;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_PUSH_BUTTON) {
 | 
						|
		print_attr(level + 1, "PushButton");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_NFC_INTERFACE) {
 | 
						|
		print_attr(level + 1, "NFC Interface");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_NFC_INTERFACE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_INTEGRATED_NFC_TOKEN) {
 | 
						|
		print_attr(level + 1, "Integrated NFC Token");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_INTEGRATED_NFC_TOKEN;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_EXTERNAL_NFC_TOKEN) {
 | 
						|
		print_attr(level + 1, "External NFC Token");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_EXTERNAL_NFC_TOKEN;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_DISPLAY) {
 | 
						|
		print_attr(level + 1, "Display");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_DISPLAY;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_LABEL) {
 | 
						|
		print_attr(level + 1, "Label");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_LABEL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_ETHERNET) {
 | 
						|
		print_attr(level + 1, "Ethernet");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_ETHERNET;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v & WSC_CONFIGURATION_METHOD_USBA) {
 | 
						|
		print_attr(level + 1, "USBA");
 | 
						|
		v &= ~WSC_CONFIGURATION_METHOD_USBA;
 | 
						|
	}
 | 
						|
 | 
						|
	if (v)
 | 
						|
		print_attr(level + 1, "Unknown: %04x", v);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_connection_type_flags(unsigned int level,
 | 
						|
						const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t v;
 | 
						|
 | 
						|
	if (size != 1)
 | 
						|
		return;
 | 
						|
 | 
						|
	v = *((uint8_t *) data);
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	if (v & WSC_CONNECTION_TYPE_ESS)
 | 
						|
		print_attr(level + 1, "ESS");
 | 
						|
 | 
						|
	if (v & WSC_CONNECTION_TYPE_IBSS)
 | 
						|
		print_attr(level + 1, "IBSS");
 | 
						|
 | 
						|
	if (v & 0xfffc)
 | 
						|
		print_attr(level + 1, "Unknown: %04x", v & 0xfffc);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_device_name(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	print_wsc_utf8_string(level, label, data, size, 32);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_device_password_id(unsigned int level, const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t v;
 | 
						|
	static const char *device_password_id_table[] = {
 | 
						|
		"Default (PIN)",
 | 
						|
		"User-specified",
 | 
						|
		"Machine-specified",
 | 
						|
		"Rekey",
 | 
						|
		"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_encryption_type_flags(unsigned int level,
 | 
						|
						const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint16_t v;
 | 
						|
 | 
						|
	if (size != 2)
 | 
						|
		return;
 | 
						|
 | 
						|
	v = l_get_be16(data);
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	if (v & WSC_ENCRYPTION_TYPE_NONE)
 | 
						|
		print_attr(level + 1, "None");
 | 
						|
 | 
						|
	if (v & WSC_ENCRYPTION_TYPE_WEP)
 | 
						|
		print_attr(level + 1, "WEP");
 | 
						|
 | 
						|
	if (v & WSC_ENCRYPTION_TYPE_TKIP)
 | 
						|
		print_attr(level + 1, "TKIP");
 | 
						|
 | 
						|
	if (v & WSC_ENCRYPTION_TYPE_AES)
 | 
						|
		print_attr(level + 1, "AES");
 | 
						|
 | 
						|
	if (v & 0xfff0)
 | 
						|
		print_attr(level + 1, "Unknown: %04x", v & 0xfff0);
 | 
						|
}
 | 
						|
 | 
						|
static void print_wsc_mac_address(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const char *str;
 | 
						|
 | 
						|
	if (size != 6)
 | 
						|
		return;
 | 
						|
 | 
						|
	str = util_address_to_string(data);
 | 
						|
	print_attr(level, "%s: %s", label, str);
 | 
						|
}
 | 
						|
 | 
						|
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_message_type(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	static const char *message_type_table[] = {
 | 
						|
		"Reserved",
 | 
						|
		"Beacon",
 | 
						|
		"Probe Request",
 | 
						|
		"Probe Response",
 | 
						|
		"M1",
 | 
						|
		"M2",
 | 
						|
		"M2D",
 | 
						|
		"M3",
 | 
						|
		"M4",
 | 
						|
		"M5",
 | 
						|
		"M6",
 | 
						|
		"M7",
 | 
						|
		"M8",
 | 
						|
		"WSC_ACK",
 | 
						|
		"WSC_NACK",
 | 
						|
		"WSC_DONE"
 | 
						|
	};
 | 
						|
	const char *s = "Reserved";
 | 
						|
	uint8_t t = ((uint8_t *) data)[0];
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed packet\n");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (t <= 0x0f)
 | 
						|
		s = message_type_table[t];
 | 
						|
 | 
						|
	print_attr(level, "%s: %s", label, s);
 | 
						|
}
 | 
						|
 | 
						|
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_os_version(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint32_t version;
 | 
						|
 | 
						|
	if (size != 4)
 | 
						|
		return;
 | 
						|
 | 
						|
	version = l_get_be32(data);
 | 
						|
	print_attr(level, "%s: %u", label, version & 0x7fffffff);
 | 
						|
}
 | 
						|
 | 
						|
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_AUTHENTICATION_TYPE_FLAGS,	"Authentication Type Flags",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_auth_type_flags } },
 | 
						|
	{ WSC_ATTR_AUTHENTICATOR,		"Authenticator",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_authenticator } },
 | 
						|
	{ 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_CONNECTION_TYPE_FLAGS,	"Connection Type Flags",
 | 
						|
		ATTR_CUSTOM,	{ .function =
 | 
						|
					print_wsc_connection_type_flags } },
 | 
						|
	{ 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_E_HASH1,			"E-Hash1",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ WSC_ATTR_E_HASH2,			"E-Hash2",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ WSC_ATTR_ENCRYPTED_SETTINGS,		"Encrypted Settings",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ WSC_ATTR_ENCRYPTION_TYPE_FLAGS,	"Encryption Type Flags",
 | 
						|
		ATTR_CUSTOM,	{ .function =
 | 
						|
					print_wsc_encryption_type_flags } },
 | 
						|
	{ WSC_ATTR_ENROLLEE_NONCE,		"Enrollee Nonce",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ WSC_ATTR_KEY_PROVIDED_AUTOMATICALLY,	"Key Provided Automatically",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bool } },
 | 
						|
	{ WSC_ATTR_MAC_ADDRESS,			"MAC Address",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_mac_address } },
 | 
						|
	{ WSC_ATTR_MANUFACTURER,		"Manufacturer",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_manufacturer } },
 | 
						|
	{ WSC_ATTR_MESSAGE_TYPE,		"Message Type",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_message_type } },
 | 
						|
	{ 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_OS_VERSION,			"OS Version",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_os_version } },
 | 
						|
	{ 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_PUBLIC_KEY,			"Public Key",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ 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_REGISTRAR_NONCE,		"Registrar Nonce",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ 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_R_HASH1,			"R_Hash1",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ WSC_ATTR_R_HASH2,			"R_Hash2",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ 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_p2p_status(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
	static const struct p2p_status_desc {
 | 
						|
		uint8_t code;
 | 
						|
		const char *desc;
 | 
						|
	} descs[] = {
 | 
						|
		{ P2P_STATUS_SUCCESS, "Success" },
 | 
						|
		{ P2P_STATUS_FAIL_INFO_NOT_AVAIL, "Fail; information is "
 | 
						|
			"currently unavailable" },
 | 
						|
		{ P2P_STATUS_FAIL_INCOMPATIBLE_PARAMS, "Fail; incompatible "
 | 
						|
			"parameters" },
 | 
						|
		{ P2P_STATUS_FAIL_LIMIT_REACHED, "Fail; limit reached" },
 | 
						|
		{ P2P_STATUS_FAIL_INVALID_PARAMS, "Fail; invalid parameters" },
 | 
						|
		{ P2P_STATUS_FAIL_UNABLE_TO_ACCOMMODATE_REQUEST, "Fail; unable "
 | 
						|
			"to accomodate request" },
 | 
						|
		{ P2P_STATUS_FAIL_PREV_PROTOCOL_ERROR, "Fail; previous protocol"
 | 
						|
			" error, or distruptive behavior" },
 | 
						|
		{ P2P_STATUS_FAIL_NO_COMMON_CHANNELS, "Fail; no common "
 | 
						|
			"channels" },
 | 
						|
		{ P2P_STATUS_FAIL_UNKNOWN_P2P_GROUP, "Fail; unknown P2P "
 | 
						|
			"Group" },
 | 
						|
		{ P2P_STATUS_FAIL_INTENT_15_IN_GO_NEGOTIATION, "Fail; both P2P "
 | 
						|
			"Devices indicated an Intent of 15 in Group Owner "
 | 
						|
			"Negotiation" },
 | 
						|
		{ P2P_STATUS_FAIL_INCOMPATIBLE_PROVISIONING, "Fail; "
 | 
						|
			"incompatible provisioning method" },
 | 
						|
		{ P2P_STATUS_FAIL_REJECTED_BY_USER, "Fail; rejected by user" },
 | 
						|
		{ P2P_STATUS_SUCCESS_ACCEPTED_BY_USER, "Success; accepted by "
 | 
						|
			"user" },
 | 
						|
		{}
 | 
						|
	};
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i = 0; descs[i].desc; i++)
 | 
						|
		if (descs[i].code == bytes[0])
 | 
						|
			break;
 | 
						|
 | 
						|
	if (descs[i].desc)
 | 
						|
		print_attr(level, "%s: %s", label, descs[i].desc);
 | 
						|
	else
 | 
						|
		print_attr(level, "%s: 0x%02x", label, bytes[0]);
 | 
						|
}
 | 
						|
 | 
						|
#define CHECK_CAPS_BIT(v, str)	\
 | 
						|
	if (caps & (v)) {	\
 | 
						|
		print_attr(level + 1, "%s", (str));	\
 | 
						|
		caps &= ~(v);	\
 | 
						|
	}
 | 
						|
static void print_p2p_device_capability(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t caps;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	caps = *(const uint8_t *) data;
 | 
						|
 | 
						|
	print_attr(level, "%s:%s", label, !caps ? " None" : "");
 | 
						|
 | 
						|
	CHECK_CAPS_BIT(P2P_DEVICE_CAP_SVC_DISCOVERY,
 | 
						|
			"Service Discovery");
 | 
						|
	CHECK_CAPS_BIT(P2P_DEVICE_CAP_CLIENT_DISCOVERABILITY,
 | 
						|
			"P2P Client Discoverability");
 | 
						|
	CHECK_CAPS_BIT(P2P_DEVICE_CAP_CONCURRENT_OP,
 | 
						|
			"Concurrent Operation");
 | 
						|
	CHECK_CAPS_BIT(P2P_DEVICE_CAP_INFRASTRUCTURE_MANAGED,
 | 
						|
			"P2P Infrastructure Managed");
 | 
						|
	CHECK_CAPS_BIT(P2P_DEVICE_CAP_DEVICE_LIMIT,
 | 
						|
			"P2P Device Limit");
 | 
						|
	CHECK_CAPS_BIT(P2P_DEVICE_CAP_INVITATION_PROCEDURE,
 | 
						|
			"P2P Invitation Procedure");
 | 
						|
 | 
						|
	if (caps)
 | 
						|
		print_attr(level + 1, "Reserved: 0x%02x", caps);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_capability(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t caps;
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_p2p_device_capability(level, "P2P Device Capability", data++, 1);
 | 
						|
 | 
						|
	caps = *(const uint8_t *) data++;
 | 
						|
 | 
						|
	print_attr(level, "P2P Group Capability:%s", !caps ? " None" : "");
 | 
						|
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_GO,
 | 
						|
			"P2P Group Owner");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_PERSISTENT_GROUP,
 | 
						|
			"Persistent P2P Group");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_GROUP_LIMIT,
 | 
						|
			"P2P Group Limit");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_INTRA_BSS_DISTRIBUTION,
 | 
						|
			"Intra-BSS Distribution");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_CROSS_CONNECT,
 | 
						|
			"Cross Connection");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_PERSISTENT_RECONNECT,
 | 
						|
			"Persistent Reconnect");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_GROUP_FORMATION,
 | 
						|
			"Group Formation");
 | 
						|
	CHECK_CAPS_BIT(P2P_GROUP_CAP_IP_ALLOCATION,
 | 
						|
			"IP Address Allocation");
 | 
						|
}
 | 
						|
#undef CHECK_CAPS_BIT
 | 
						|
 | 
						|
static void print_p2p_go_intent(unsigned int level, const char *label,
 | 
						|
				const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: Intent %u out of 15, tie breaker %u", label,
 | 
						|
			bytes[0] >> 1, bytes[0] & 1);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_config_timeout(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: GO Timeout %ums, Client Timeout %ums", label,
 | 
						|
			bytes[0] * 10, bytes[1] * 10);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_oper_channel(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 5) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
	print_attr(level + 1, "Country %c%c table %u", bytes[0], bytes[1],
 | 
						|
			bytes[2]);
 | 
						|
	print_attr(level + 1, "Operating Class %u", bytes[3]);
 | 
						|
	print_attr(level + 1, "Channel Number %u", bytes[4]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_extended_timing(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	if (size != 4) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: Availability period: %ums, interval: %ums", label,
 | 
						|
			l_get_le16(data + 0), l_get_le16(data + 2));
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_manageability(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t val;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
#define CHECK_BIT(v, str)	\
 | 
						|
	if (val & (v)) {	\
 | 
						|
		print_attr(level + 1, "%s", (str));	\
 | 
						|
		val &= ~(v);	\
 | 
						|
	}
 | 
						|
 | 
						|
	val = *(const uint8_t *) data;
 | 
						|
 | 
						|
	print_attr(level, "%s:%s", label, !val ? " None" : "");
 | 
						|
 | 
						|
	CHECK_BIT(P2P_MANAGEABILITY_DEVICE_MGMT, "P2P Device Management");
 | 
						|
	CHECK_BIT(P2P_MANAGEABILITY_CROSS_CONNECT,
 | 
						|
			"Cross Connection Permitted");
 | 
						|
	CHECK_BIT(P2P_MANAGEABILITY_COEXIST_OPTIONAL, "Coexistence Optional");
 | 
						|
 | 
						|
	if (val)
 | 
						|
		print_attr(level + 1, "Reserved: 0x%02x", val);
 | 
						|
#undef CHECK_BIT
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_channel_list(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size < 3) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
	print_attr(level + 1, "Country %c%c table %u", bytes[0], bytes[1],
 | 
						|
			bytes[2]);
 | 
						|
	bytes += 3;
 | 
						|
	size -= 3;
 | 
						|
 | 
						|
	while (size) {
 | 
						|
		uint8_t channels;
 | 
						|
		char str[128];
 | 
						|
		int pos = 0;
 | 
						|
 | 
						|
		if (size < 2 || size < 2 + bytes[1]) {
 | 
						|
			printf("malformed P2P %s\n", label);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		print_attr(level + 1, "Operating Class %u channels:", *bytes++);
 | 
						|
		channels = *bytes++;
 | 
						|
		size -= 2 + channels;
 | 
						|
 | 
						|
		while (channels--)
 | 
						|
			snprintf(str + pos, sizeof(str) - pos, "%s%u",
 | 
						|
					pos ? ", " : "", (int) *bytes++);
 | 
						|
 | 
						|
		print_attr(level + 2, "%s", str);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_notice_of_absence(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size < 2) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s %u:", label, bytes[0]);
 | 
						|
	print_attr(level + 1, "GO uses Opportunistic Power Save: %s",
 | 
						|
			(bytes[1] & 0x80) ? "true" : "false");
 | 
						|
	print_attr(level + 1, "Client Traffic window len: %u TUs",
 | 
						|
			bytes[1] & 0x7f);
 | 
						|
	bytes += 2;
 | 
						|
	size -= 2;
 | 
						|
 | 
						|
	while (size) {
 | 
						|
		if (size < 13) {
 | 
						|
			printf("malformed P2P Channel List\n");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		print_attr(level + 1, "Notice:");
 | 
						|
		print_attr(level + 2, "Count/Type: %u", bytes[0]);
 | 
						|
		print_attr(level + 2, "Duration: %uus", l_get_le32(bytes + 1));
 | 
						|
		print_attr(level + 2, "Interval: %uus", l_get_le32(bytes + 5));
 | 
						|
		print_attr(level + 2, "Start Time: %u", l_get_le32(bytes + 8));
 | 
						|
		bytes += 13;
 | 
						|
		size -= 13;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_device_info(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
	int secondary_types;
 | 
						|
 | 
						|
	if (size < 17 || size < 17 + bytes[16] * 8 + 4) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
	print_wsc_mac_address(level + 1, "P2P Device address", bytes, 6);
 | 
						|
	print_wsc_config_methods(level + 1, "Config Methods", bytes + 6, 2);
 | 
						|
	print_wsc_primary_device_type(level + 1, "Primary Device Type",
 | 
						|
					bytes + 8, 8);
 | 
						|
	secondary_types = bytes[16];
 | 
						|
	bytes += 17;
 | 
						|
	size -= 17;
 | 
						|
 | 
						|
	while (secondary_types--) {
 | 
						|
		print_wsc_primary_device_type(level + 1,
 | 
						|
						"Secondary Device Type",
 | 
						|
						bytes, 8);
 | 
						|
		bytes += 8;
 | 
						|
		size -= 8;
 | 
						|
	}
 | 
						|
 | 
						|
	if (l_get_be16(bytes) != WSC_ATTR_DEVICE_NAME ||
 | 
						|
			4 + l_get_be16(bytes + 2) > size) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_wsc_device_name(level + 1, "Device Name", bytes + 4,
 | 
						|
				l_get_be16(bytes + 2));
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_group_info(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	while (size) {
 | 
						|
		size_t desc_size = bytes[0];
 | 
						|
		int secondary_types;
 | 
						|
 | 
						|
		if (1 + desc_size > size || desc_size < 24 ||
 | 
						|
				desc_size < (size_t) 24 + bytes[24] * 8 + 4) {
 | 
						|
			printf("malformed P2P Client Info Descriptor\n");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		size -= 1 + desc_size;
 | 
						|
 | 
						|
		print_attr(level + 1, "P2P Client Info Descriptor:");
 | 
						|
		print_wsc_mac_address(level + 2, "P2P Device address",
 | 
						|
					bytes + 1, 6);
 | 
						|
		print_wsc_mac_address(level + 2, "P2P Interface address",
 | 
						|
					bytes + 7, 6);
 | 
						|
		print_p2p_device_capability(level + 2, "P2P Device Capability",
 | 
						|
						bytes + 13, 1);
 | 
						|
		print_wsc_config_methods(level + 2, "Config Methods",
 | 
						|
						bytes + 14, 2);
 | 
						|
		print_wsc_primary_device_type(level + 2, "Primary Device Type",
 | 
						|
						bytes + 16, 8);
 | 
						|
		secondary_types = bytes[24];
 | 
						|
		bytes += 25;
 | 
						|
		desc_size -= 24;
 | 
						|
 | 
						|
		while (secondary_types--) {
 | 
						|
			print_wsc_primary_device_type(level + 2,
 | 
						|
							"Secondary Device Type",
 | 
						|
							bytes, 8);
 | 
						|
			bytes += 8;
 | 
						|
			desc_size -= 8;
 | 
						|
		}
 | 
						|
 | 
						|
		if (l_get_be16(bytes) != WSC_ATTR_DEVICE_NAME ||
 | 
						|
				(size_t) 4 + l_get_be16(bytes + 2) >
 | 
						|
				desc_size) {
 | 
						|
			printf("malformed P2P Client Info Descriptor\n");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		print_wsc_device_name(level + 1, "Device Name", bytes + 4,
 | 
						|
					l_get_be16(bytes + 2));
 | 
						|
		bytes += 4 + l_get_be16(bytes + 2);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_group_id(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size < 6 || size > 38) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	print_wsc_mac_address(level + 1, "P2P Device address",
 | 
						|
				bytes, 6);
 | 
						|
	print_ie_ssid(level + 1, "SSID", bytes + 6, size - 6);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_interface(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
	int addr_count;
 | 
						|
 | 
						|
	if (size < 7 || size < 7 + bytes[6] * 6) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	print_wsc_mac_address(level + 1, "P2P Device Address",
 | 
						|
				bytes, 6);
 | 
						|
	addr_count = bytes[6];
 | 
						|
	print_attr(level + 1, "P2P Interface Address Count: %u", addr_count);
 | 
						|
	bytes += 7;
 | 
						|
	size -= 7;
 | 
						|
 | 
						|
	while (addr_count--) {
 | 
						|
		print_wsc_mac_address(level + 2, "Interface Address", bytes, 6);
 | 
						|
		bytes += 6;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_invite_flags(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t flags;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	flags = *(const uint8_t *) data;
 | 
						|
	print_attr(level, "Invitation Type: %s",
 | 
						|
			(flags & 1) ? "re-invoke a Persistent Group" :
 | 
						|
			"join active group");
 | 
						|
	flags &= ~1;
 | 
						|
 | 
						|
	if (flags)
 | 
						|
		print_attr(level, "Invitation Flags: reserved 0x%02x", flags);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_oob_neg_channel(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
	const char *role;
 | 
						|
 | 
						|
	if (size != 6) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_p2p_oper_channel(level, label, bytes + 0, 5);
 | 
						|
 | 
						|
	switch (bytes[5]) {
 | 
						|
	case 0x00:
 | 
						|
		role = "not in a group";
 | 
						|
		break;
 | 
						|
	case 0x01:
 | 
						|
		role = "client";
 | 
						|
		break;
 | 
						|
	case 0x02:
 | 
						|
		role = "Group Owner";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		role = "reserved";
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level + 1, "Current group role indication: %s", role);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_service_hash(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	if (size % 6 != 0) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	while (size) {
 | 
						|
		print_wsc_bytes(level, "Service Hash", data, 6);
 | 
						|
		data += 6;
 | 
						|
		size -= 6;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_connection_caps(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	uint8_t caps;
 | 
						|
	const char *first;
 | 
						|
 | 
						|
	if (size != 1) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	caps = *(const uint8_t *) data;
 | 
						|
 | 
						|
	if (caps & 1)
 | 
						|
		first = "New";
 | 
						|
	else if (caps & 2)
 | 
						|
		first = "Cli";
 | 
						|
	else if (caps & ~4)
 | 
						|
		first = "Unknown";
 | 
						|
	else
 | 
						|
		first = "GO";
 | 
						|
 | 
						|
	print_attr(level, "%s: %s%s", label, first,
 | 
						|
			(caps & ~4) && (caps & 4) ? ", GO" : "");
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_advertisement_id(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	if (size != 10) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: 0x%08x", label, l_get_le32(data + 0));
 | 
						|
	print_wsc_mac_address(level + 1, "Service MAC Address", data + 4, 6);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_advertised_svc_info(unsigned int level, const char *label,
 | 
						|
						const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	while (size) {
 | 
						|
		if (size < 7 || size < 7 + bytes[6]) {
 | 
						|
			printf("malformed P2P %s\n", label);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		print_attr(level, "%s:", label);
 | 
						|
 | 
						|
		print_attr(level, "Servce advertisement: ID 0x%08x",
 | 
						|
				l_get_le32(bytes + 0));
 | 
						|
		print_wsc_utf8_string(level + 1, "Service Name",
 | 
						|
					bytes + 6, bytes[6], 255);
 | 
						|
		print_wsc_config_methods(level + 1, "Service Config Methods",
 | 
						|
						bytes + 4, 2);
 | 
						|
		size -= 7 + bytes[6];
 | 
						|
		bytes += 7 + bytes[6];
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_session_id(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	if (size != 10) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s: 0x%08x", label, l_get_le32(data + 0));
 | 
						|
	print_wsc_mac_address(level + 1, "Session MAC Address", data + 4, 6);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_feature_caps(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const uint8_t *bytes = data;
 | 
						|
 | 
						|
	if (size != 2) {
 | 
						|
		printf("malformed P2P %s\n", label);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "%s:", label);
 | 
						|
 | 
						|
	if (bytes[0] == 0x01)
 | 
						|
		print_attr(level + 1, "Coordination Protocol Transport: UDP");
 | 
						|
	else
 | 
						|
		print_attr(level + 1, "Coordination Protocol Transport: "
 | 
						|
				"reserved 0x%02x", bytes[1]);
 | 
						|
}
 | 
						|
 | 
						|
static struct attr_entry p2p_attr_entry[] = {
 | 
						|
	{ P2P_ATTR_STATUS,			"Status",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_status } },
 | 
						|
	{ P2P_ATTR_MINOR_REASON_CODE,		"Minor Reason Code",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_byte } },
 | 
						|
	{ P2P_ATTR_P2P_CAPABILITY,		"P2P Capability",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_capability } },
 | 
						|
	{ P2P_ATTR_P2P_DEVICE_ID,		"P2P Device ID",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_mac_address } },
 | 
						|
	{ P2P_ATTR_GO_INTENT,			"Group Owner Intent",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_go_intent } },
 | 
						|
	{ P2P_ATTR_CONFIGURATION_TIMEOUT,	"Configuration Timeout",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_config_timeout } },
 | 
						|
	{ P2P_ATTR_LISTEN_CHANNEL,		"Listen Channel",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_oper_channel } },
 | 
						|
	{ P2P_ATTR_P2P_GROUP_BSSID,		"P2P Group BSSID",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_mac_address } },
 | 
						|
	{ P2P_ATTR_EXTENDED_LISTEN_TIMING,	"Extended Listen Timing",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_extended_timing } },
 | 
						|
	{ P2P_ATTR_INTENDED_P2P_INTERFACE_ADDR,	"Intended P2P Interface "
 | 
						|
		"Address",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_mac_address } },
 | 
						|
	{ P2P_ATTR_P2P_MANAGEABILITY,		"P2P Manageability",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_manageability } },
 | 
						|
	{ P2P_ATTR_CHANNEL_LIST,		"Channel List",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_channel_list } },
 | 
						|
	{ P2P_ATTR_NOTICE_OF_ABSENCE,		"Notice of Absence",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_notice_of_absence } },
 | 
						|
	{ P2P_ATTR_P2P_DEVICE_INFO,		"P2P Device Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_device_info } },
 | 
						|
	{ P2P_ATTR_P2P_GROUP_INFO,		"P2P Group Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_group_info } },
 | 
						|
	{ P2P_ATTR_P2P_GROUP_ID,		"P2P Group ID",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_group_id } },
 | 
						|
	{ P2P_ATTR_P2P_INTERFACE,		"P2P Interface",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_interface } },
 | 
						|
	{ P2P_ATTR_OPERATING_CHANNEL,		"Operating Channel",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_oper_channel } },
 | 
						|
	{ P2P_ATTR_INVITATION_FLAGS,		"Invitation Flags",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_invite_flags } },
 | 
						|
	{ P2P_ATTR_OOB_GO_NEGOTIATION_CHANNEL,	"Out-of-Band Group Owner "
 | 
						|
		"Negotiation Channel",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_oob_neg_channel } },
 | 
						|
	{ P2P_ATTR_SVC_HASH,			"Service Hash",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_service_hash } },
 | 
						|
	{ P2P_ATTR_SESSION_INFO_DATA_INFO,	"Session Information Data Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ P2P_ATTR_CONNECTION_CAPABILITY_INFO,	"Connection Capability Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_connection_caps } },
 | 
						|
	{ P2P_ATTR_ADVERTISEMENT_ID_INFO,	"Advertisement_ID Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_advertisement_id } },
 | 
						|
	{ P2P_ATTR_ADVERTISED_SVC_INFO,		"Advertised Service Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_advertised_svc_info } },
 | 
						|
	{ P2P_ATTR_SESSION_ID_INFO,		"Session ID Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_session_id } },
 | 
						|
	{ P2P_ATTR_FEATURE_CAPABILITY,		"Feature Capability",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_feature_caps } },
 | 
						|
	{ P2P_ATTR_PERSISTENT_GROUP_INFO,	"Persistent Group Info",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_p2p_group_id } },
 | 
						|
	{ P2P_ATTR_VENDOR_SPECIFIC_ATTR,	"Vendor specific attribute",
 | 
						|
		ATTR_CUSTOM,	{ .function = print_wsc_bytes } },
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
static void print_p2p_attributes(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	struct p2p_attr_iter iter;
 | 
						|
	int i;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	p2p_attr_iter_init(&iter, data, size);
 | 
						|
 | 
						|
	while (p2p_attr_iter_next(&iter)) {
 | 
						|
		uint16_t type = p2p_attr_iter_get_type(&iter);
 | 
						|
		uint16_t len = p2p_attr_iter_get_length(&iter);
 | 
						|
		const void *attr = p2p_attr_iter_get_data(&iter);
 | 
						|
		struct attr_entry *entry = NULL;
 | 
						|
 | 
						|
		for (i = 0; p2p_attr_entry[i].str; i++) {
 | 
						|
			if (p2p_attr_entry[i].attr == type) {
 | 
						|
				entry = &p2p_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, *p2p_data;
 | 
						|
	ssize_t wsc_len, p2p_len;
 | 
						|
 | 
						|
	print_ie(level, label, data, size);
 | 
						|
 | 
						|
	wsc_data = ie_tlv_extract_wsc_payload(data, size, &wsc_len);
 | 
						|
	if (wsc_data) {
 | 
						|
		print_wsc_attributes(level + 1, "WSC Payload",
 | 
						|
					wsc_data, wsc_len);
 | 
						|
		l_free(wsc_data);
 | 
						|
	}
 | 
						|
 | 
						|
	p2p_data = ie_tlv_extract_p2p_payload(data, size, &p2p_len);
 | 
						|
	if (p2p_data) {
 | 
						|
		print_p2p_attributes(level + 1, "P2P Attributes",
 | 
						|
					p2p_data, p2p_len);
 | 
						|
		l_free(p2p_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_mmpdu_header(unsigned int level,
 | 
						|
					const struct mmpdu_header *mmpdu)
 | 
						|
{
 | 
						|
	print_attr(level, "Duration: %u", L_LE16_TO_CPU(mmpdu->duration));
 | 
						|
 | 
						|
	print_address(level, "Address 1 (RA):", mmpdu->address_1);
 | 
						|
	print_address(level, "Address 2 (TA):", mmpdu->address_2);
 | 
						|
	print_address(level, "Address 3:", mmpdu->address_3);
 | 
						|
 | 
						|
	print_attr(level, "Fragment Number: %u", mmpdu->fragment_number);
 | 
						|
	print_attr(level, "Sequence Number: %u",
 | 
						|
					MPDU_SEQUENCE_NUMBER(*mmpdu));
 | 
						|
}
 | 
						|
 | 
						|
static void print_authentication_mgmt_frame(unsigned int level,
 | 
						|
					const struct mmpdu_header *mmpdu,
 | 
						|
					size_t size)
 | 
						|
{
 | 
						|
	const char *str;
 | 
						|
	const struct mmpdu_authentication *body;
 | 
						|
	struct ie_tlv_iter iter;
 | 
						|
 | 
						|
	if (!mmpdu)
 | 
						|
		return;
 | 
						|
 | 
						|
	body = mmpdu_body(mmpdu);
 | 
						|
 | 
						|
	print_attr(level, "Authentication:");
 | 
						|
 | 
						|
	print_mpdu_frame_control(level + 1, &mmpdu->fc);
 | 
						|
	print_mmpdu_header(level + 1, mmpdu);
 | 
						|
 | 
						|
	switch (L_LE16_TO_CPU(body->algorithm)) {
 | 
						|
	case MMPDU_AUTH_ALGO_OPEN_SYSTEM:
 | 
						|
		str = "Open";
 | 
						|
		break;
 | 
						|
	case MMPDU_AUTH_ALGO_SHARED_KEY:
 | 
						|
		str = "Shared key";
 | 
						|
		break;
 | 
						|
	case MMPDU_AUTH_ALGO_FT:
 | 
						|
		str = "FT";
 | 
						|
		break;
 | 
						|
	case MMPDU_AUTH_ALGO_SAE:
 | 
						|
		str = "SAE";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		str = "Reserved";
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level + 1, "Algorithm: %s (seq: %u, status: %u)", str,
 | 
						|
				L_LE16_TO_CPU(body->transaction_sequence),
 | 
						|
				L_LE16_TO_CPU(body->status));
 | 
						|
 | 
						|
	if (L_LE16_TO_CPU(body->algorithm) != MMPDU_AUTH_ALGO_SHARED_KEY)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (L_LE16_TO_CPU(body->transaction_sequence) < 2 ||
 | 
						|
			L_LE16_TO_CPU(body->transaction_sequence) > 3)
 | 
						|
		return;
 | 
						|
 | 
						|
	ie_tlv_iter_init(&iter, body->ies, (const uint8_t *) mmpdu + size -
 | 
						|
				body->ies);
 | 
						|
	ie_tlv_iter_next(&iter);
 | 
						|
 | 
						|
	print_attr(level + 1, "Challenge text: \"%s\" (%u)",
 | 
						|
				ie_tlv_iter_get_data(&iter),
 | 
						|
				ie_tlv_iter_get_length(&iter));
 | 
						|
}
 | 
						|
 | 
						|
static void print_deauthentication_mgmt_frame(unsigned int level,
 | 
						|
					const struct mmpdu_header *mmpdu)
 | 
						|
{
 | 
						|
	const struct mmpdu_deauthentication *body;
 | 
						|
 | 
						|
	if (!mmpdu)
 | 
						|
		return;
 | 
						|
 | 
						|
	body = mmpdu_body(mmpdu);
 | 
						|
 | 
						|
	print_attr(level, "Deauthentication:");
 | 
						|
 | 
						|
	print_mpdu_frame_control(level + 1, &mmpdu->fc);
 | 
						|
	print_mmpdu_header(level + 1, mmpdu);
 | 
						|
 | 
						|
	print_attr(level + 1, "Reason code: %u",
 | 
						|
				L_LE16_TO_CPU(body->reason_code));
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_public_action_frame(unsigned int level,
 | 
						|
						const uint8_t *body,
 | 
						|
						size_t body_len)
 | 
						|
{
 | 
						|
	const char *subtype;
 | 
						|
 | 
						|
	/* P2P v1.7 Table 60 */
 | 
						|
	static const char *p2p_public_table[] = {
 | 
						|
		[0] = "GO Negotiation Request",
 | 
						|
		[1] = "GO Negotiation Response",
 | 
						|
		[2] = "GO Negotiation Confirmation",
 | 
						|
		[3] = "P2P Invitation Request",
 | 
						|
		[4] = "P2P Invitation Response",
 | 
						|
		[5] = "Device Discoverability Request",
 | 
						|
		[6] = "Device Discoverability Response",
 | 
						|
		[7] = "Provision Discovery Request",
 | 
						|
		[8] = "Provision Discovery Response",
 | 
						|
	};
 | 
						|
 | 
						|
	if (body_len < 2)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (body[1] < L_ARRAY_SIZE(p2p_public_table) &&
 | 
						|
			p2p_public_table[body[0]])
 | 
						|
		subtype = p2p_public_table[body[0]];
 | 
						|
	else
 | 
						|
		subtype = "Unknown";
 | 
						|
 | 
						|
	print_attr(level, "OUI Type: P2P public action frame");
 | 
						|
	print_attr(level, "OUI Subtype: %s (%u)", subtype, body[0]);
 | 
						|
	print_attr(level + 1, "Dialog Token: %u", body[1]);
 | 
						|
 | 
						|
	print_management_ies(level, "IEs", body + 2, body_len - 2);
 | 
						|
}
 | 
						|
 | 
						|
static void print_p2p_action_frame(unsigned int level, const uint8_t *body,
 | 
						|
					size_t body_len)
 | 
						|
{
 | 
						|
	const char *subtype;
 | 
						|
 | 
						|
	/* P2P v1.7 Table 74 */
 | 
						|
	static const char *p2p_action_table[] = {
 | 
						|
		[0] = "Notice of Absence",
 | 
						|
		[1] = "P2P Presence Request",
 | 
						|
		[2] = "P2P Presence Response",
 | 
						|
		[3] = "GO Discoverability Request",
 | 
						|
	};
 | 
						|
 | 
						|
	if (body_len < 2)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (body[1] < L_ARRAY_SIZE(p2p_action_table) &&
 | 
						|
			p2p_action_table[body[0]])
 | 
						|
		subtype = p2p_action_table[body[0]];
 | 
						|
	else
 | 
						|
		subtype = "Unknown";
 | 
						|
 | 
						|
	print_attr(level, "OUI Type: P2P action frame");
 | 
						|
	print_attr(level, "OUI Subtype: %s (%u)", subtype, body[0]);
 | 
						|
	print_attr(level + 1, "Dialog Token: %u", body[1]);
 | 
						|
 | 
						|
	print_management_ies(level, "IEs", body + 2, body_len - 2);
 | 
						|
}
 | 
						|
 | 
						|
static void print_anqp_frame(unsigned int level, const uint8_t *anqp,
 | 
						|
				size_t anqp_len)
 | 
						|
{
 | 
						|
	struct anqp_iter iter;
 | 
						|
	uint16_t id, len;
 | 
						|
	const void *data;
 | 
						|
 | 
						|
	static const char *anqp_elements[] = {
 | 
						|
		[ANQP_QUERY_LIST] = "Query List",
 | 
						|
		[ANQP_CAPABILITY_LIST] = "Capability List",
 | 
						|
		[ANQP_VENUE_NAME] = "Venue Name",
 | 
						|
		[ANQP_EMERGENCY_CALL_NUMBER] = "Emergency Call Number",
 | 
						|
		[ANQP_NETWORK_AUTH_TYPE] = "Network Authentication Type",
 | 
						|
		[ANQP_ROAMING_CONSORTIUM] = "Roaming Consortium",
 | 
						|
		[ANQP_IP_ADDRESS_TYPE_AVAILABILITY] = "IP Address type avail",
 | 
						|
		[ANQP_NAI_REALM] = "NAI Realm",
 | 
						|
		[ANQP_3GPP_CELLULAR_NETWORK] = "3GPP Cellular Network",
 | 
						|
		[ANQP_AP_GEOSPATIAL_LOCATION] = "AP Geospatial location",
 | 
						|
		[ANQP_AP_CIVIC_LOCATION] = "AP Civic Location",
 | 
						|
		[ANQP_AP_LOCATION_PUBLIC_ID] = "AP Location Public ID",
 | 
						|
		[ANQP_DOMAIN_NAME] = "Domain Name",
 | 
						|
		[ANQP_EMERGENCY_ALERT_ID_URI] = "Emergency Alery ID URI",
 | 
						|
		[ANQP_TDLS_CAPABILITY] = "TDLS Capability",
 | 
						|
		[ANQP_EMERGENCY_NAI] = "Emergency NAI",
 | 
						|
		[ANQP_NEIGHBOR_REPORT] = "Neighbor Report",
 | 
						|
		[ANQP_VENUE_URI] = "Venue URI",
 | 
						|
		[ANQP_ADVICE_OF_CHARGE] = "Advice of Charge",
 | 
						|
		[ANQP_LOCAL_CONTENT] = "Local Content",
 | 
						|
		[ANQP_NETWORK_AUTH_TYPE_WITH_TIMESTAMP] =
 | 
						|
					"Network Auth Type with Timestamp",
 | 
						|
		[ANQP_VENDOR_SPECIFIC] = "Vendor Specific"
 | 
						|
	};
 | 
						|
 | 
						|
	anqp_iter_init(&iter, anqp, anqp_len);
 | 
						|
 | 
						|
	while (anqp_iter_next(&iter, &id, &len, &data)) {
 | 
						|
		const char *str;
 | 
						|
		char **nai_realms;
 | 
						|
		int i;
 | 
						|
 | 
						|
		if (id >= L_ARRAY_SIZE(anqp_elements) || id < ANQP_QUERY_LIST)
 | 
						|
			str = "Unknown";
 | 
						|
		else
 | 
						|
			str = anqp_elements[id];
 | 
						|
 | 
						|
		print_attr(level, "ANQP ID: %s, Len: %u", str, len);
 | 
						|
 | 
						|
		switch (id) {
 | 
						|
		case ANQP_NAI_REALM:
 | 
						|
			nai_realms = anqp_parse_nai_realms(data, len);
 | 
						|
			if (!nai_realms) {
 | 
						|
				print_attr(level + 1, "bad NAI Realm data");
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			for (i = 0; nai_realms[i]; i++)
 | 
						|
				print_attr(level + 2, "Realm[%u] %s", i,
 | 
						|
						nai_realms[i]);
 | 
						|
 | 
						|
			l_strv_free(nai_realms);
 | 
						|
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			print_hexdump(level + 1, anqp + 4, len);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_public_action_frame(unsigned int level, const uint8_t *body,
 | 
						|
					size_t body_len)
 | 
						|
{
 | 
						|
	const char *category;
 | 
						|
	const uint8_t *oui = body + 1;
 | 
						|
 | 
						|
	/* 802.11-2016, Table 9-307 */
 | 
						|
	static const char *public_action_table[] = {
 | 
						|
		[0] = "20/40 BSS Coexistence Management",
 | 
						|
		[1] = "DSE enablement",
 | 
						|
		[2] = "DSE deenablement",
 | 
						|
		[3] = "DSE Registered Location Announcement",
 | 
						|
		[4] = "Extended Channel Switch Announcement",
 | 
						|
		[5] = "DSE measurement request",
 | 
						|
		[6] = "DSE measurement report",
 | 
						|
		[7] = "Measurement Pilot",
 | 
						|
		[8] = "DSE power constraint",
 | 
						|
		[9] = "Vendor-specific",
 | 
						|
		[10] = "GAS Initial Request",
 | 
						|
		[11] = "GAS Initial Response",
 | 
						|
		[12] = "GAS Comeback Request",
 | 
						|
		[13] = "GAS Comeback Response",
 | 
						|
		[14] = "TDLS Discovery Response",
 | 
						|
		[15] = "Location Track Notification",
 | 
						|
		[16] = "QAB Request frame",
 | 
						|
		[17] = "QAB Response frame",
 | 
						|
		[18] = "QMF Policy",
 | 
						|
		[19] = "QMF Policy Change",
 | 
						|
		[20] = "QLoad Request",
 | 
						|
		[21] = "QLoad Report",
 | 
						|
		[22] = "HCCA TXOP Advertisement",
 | 
						|
		[23] = "HCCA TXOP Response",
 | 
						|
		[24] = "Public Key",
 | 
						|
		[25] = "Channel Availabilty Query",
 | 
						|
		[26] = "Channel Schedule Management",
 | 
						|
		[27] = "Contact Verification Signal",
 | 
						|
		[28] = "GDD Enablement Request",
 | 
						|
		[29] = "GDD Enablement Response",
 | 
						|
		[30] = "Network Channel Control",
 | 
						|
		[31] = "White Space Map Announcement",
 | 
						|
		[32] = "Fine Timing Measurement Request",
 | 
						|
		[33] = "Fine Timing Measurement",
 | 
						|
	};
 | 
						|
 | 
						|
	if (body_len < 1)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (body[0] < L_ARRAY_SIZE(public_action_table) &&
 | 
						|
			public_action_table[body[0]])
 | 
						|
		category = public_action_table[body[0]];
 | 
						|
	else
 | 
						|
		category = "Unknown";
 | 
						|
 | 
						|
	print_attr(level, "Public Action: %s (%u)", category, body[0]);
 | 
						|
 | 
						|
	if (body_len < 5)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (!memcmp(oui, wsc_wfa_oui, 3) && oui[3] == 0x09) {
 | 
						|
		if (body[0] != 9)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (!print_oui(level, oui))
 | 
						|
			return;
 | 
						|
 | 
						|
		print_p2p_public_action_frame(level + 1, body + 5,
 | 
						|
						body_len - 5);
 | 
						|
	} else if (body[0] == 0x0a) {
 | 
						|
		if (body_len < 9)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (body[2] != IE_TYPE_ADVERTISEMENT_PROTOCOL)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (body[5] != IE_ADVERTISEMENT_ANQP)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (body_len < l_get_le16(body + 6) + 8u)
 | 
						|
			return;
 | 
						|
 | 
						|
		print_anqp_frame(level + 1, body + 8, l_get_le16(body + 6));
 | 
						|
	} else if (body[0] == 0x0b) {
 | 
						|
		if (body_len < 10)
 | 
						|
			return;
 | 
						|
 | 
						|
		print_attr(level + 1, "Dialog Token: %u", body[1]);
 | 
						|
		print_attr(level + 1, "Status: %u", l_get_le16(body + 2));
 | 
						|
		print_attr(level + 1, "Delay: %u", l_get_le16(body + 4));
 | 
						|
 | 
						|
		if (body[6] != IE_TYPE_ADVERTISEMENT_PROTOCOL)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (body_len < body[7] + 7u)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (body_len < l_get_le16(body + 8 + body[7]) + 10u)
 | 
						|
			return;
 | 
						|
 | 
						|
		print_anqp_frame(level + 1, body + 10 + body[7],
 | 
						|
					l_get_le16(body + 8 + body[7]));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_rm_action_frame(unsigned int level, const uint8_t *body,
 | 
						|
					size_t body_len)
 | 
						|
{
 | 
						|
	const char *category;
 | 
						|
 | 
						|
	/* 802.11-2016, Table 9-306 */
 | 
						|
	static const char *rm_action_table[] = {
 | 
						|
		[0] = "Radio Measurement Request",
 | 
						|
		[1] = "Radio Measurement Report",
 | 
						|
		[2] = "Link Measurement Request",
 | 
						|
		[3] = "Link Measurement Report",
 | 
						|
		[4] = "Neighbor Report Request",
 | 
						|
		[5] = "Neighbor Report Response",
 | 
						|
	};
 | 
						|
 | 
						|
	if (body_len < 1)
 | 
						|
		return;
 | 
						|
 | 
						|
	if (body[0] < L_ARRAY_SIZE(rm_action_table) && rm_action_table[body[0]])
 | 
						|
		category = rm_action_table[body[0]];
 | 
						|
	else
 | 
						|
		category = "Unknown";
 | 
						|
 | 
						|
	print_attr(level, "Radio Measurement Action: %s (%u)", category, body[0]);
 | 
						|
}
 | 
						|
 | 
						|
static void print_action_mgmt_frame(unsigned int level,
 | 
						|
					const struct mmpdu_header *mmpdu,
 | 
						|
					size_t total_len, bool no_ack)
 | 
						|
{
 | 
						|
	const uint8_t *body;
 | 
						|
	size_t body_len;
 | 
						|
	const char *category;
 | 
						|
 | 
						|
	/* 802.11-2016, Table 9-47 */
 | 
						|
	static const char *category_table[] = {
 | 
						|
		[0] = "Sepctrum Management",
 | 
						|
		[1] = "QoS",
 | 
						|
		[2] = "DLS",
 | 
						|
		[3] = "Block Ack",
 | 
						|
		[4] = "Public",
 | 
						|
		[5] = "Radio Measurement",
 | 
						|
		[6] = "Fast BSS Transition",
 | 
						|
		[7] = "HT",
 | 
						|
		[8] = "SA Query",
 | 
						|
		[9] = "Protected Dual of Public Action",
 | 
						|
		[10] = "WNM",
 | 
						|
		[11] = "Unprotected WNM",
 | 
						|
		[12] = "TDLS",
 | 
						|
		[13] = "Mesh",
 | 
						|
		[14] = "Multihop",
 | 
						|
		[15] = "Self-protected",
 | 
						|
		[16] = "DMG",
 | 
						|
		[18] = "Fast Session Transfer",
 | 
						|
		[19] = "Robust AV Streaming",
 | 
						|
		[20] = "Unprotected DMG",
 | 
						|
		[21] = "VHT",
 | 
						|
		[126] = "Vendor-specific Protected",
 | 
						|
		[127] = "Vendor-specific",
 | 
						|
		[128 ... 255] = "Error",
 | 
						|
	};
 | 
						|
 | 
						|
	body = mmpdu_body(mmpdu);
 | 
						|
	body_len = total_len - (body - (uint8_t *) mmpdu);
 | 
						|
 | 
						|
	if (category_table[body[0]])
 | 
						|
		category = category_table[body[0]];
 | 
						|
	else
 | 
						|
		category = "Unknown";
 | 
						|
 | 
						|
	print_attr(level, "Subtype: Action%s", no_ack ? " No Ack" : "");
 | 
						|
	print_attr(level, "Action Category: %s (%u)", category, body[0]);
 | 
						|
 | 
						|
	if (body[0] == 4) {
 | 
						|
		print_public_action_frame(level, body + 1, body_len - 1);
 | 
						|
		return;
 | 
						|
	} else if (body[0] == 5) {
 | 
						|
		print_rm_action_frame(level, body + 1, body_len - 1);
 | 
						|
		return;
 | 
						|
	} else if ((body[0] == 126 || body[0] == 127) && body_len >= 5) {
 | 
						|
		const uint8_t *oui = body + 1;
 | 
						|
 | 
						|
		if (!print_oui(level, oui))
 | 
						|
			return;
 | 
						|
 | 
						|
		if (!memcmp(oui, wsc_wfa_oui, 3) && oui[3] == 0x09)
 | 
						|
			print_p2p_action_frame(level + 1, body + 5,
 | 
						|
						body_len - 5);
 | 
						|
	}
 | 
						|
 | 
						|
	print_mpdu_frame_control(level + 1, &mmpdu->fc);
 | 
						|
	print_mmpdu_header(level + 1, mmpdu);
 | 
						|
}
 | 
						|
 | 
						|
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 mmpdu_header *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);
 | 
						|
 | 
						|
	str = NULL;
 | 
						|
 | 
						|
	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:
 | 
						|
		if (mpdu)
 | 
						|
			print_authentication_mgmt_frame(level + 1, mpdu, size);
 | 
						|
		else
 | 
						|
			str = "Authentication";
 | 
						|
		break;
 | 
						|
	case 0x0c:
 | 
						|
		if (mpdu)
 | 
						|
			print_deauthentication_mgmt_frame(level + 1, mpdu);
 | 
						|
		else
 | 
						|
			str = "Deauthentication";
 | 
						|
		break;
 | 
						|
	case 0x0d:
 | 
						|
	case 0x0e:
 | 
						|
		if (mpdu)
 | 
						|
			print_action_mgmt_frame(level + 1, mpdu, size,
 | 
						|
						subtype == 0x0e);
 | 
						|
		else if (subtype == 0x0d)
 | 
						|
			str = "Action";
 | 
						|
		else
 | 
						|
			str = "Action No Ack";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		str = "Reserved";
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	if (str)
 | 
						|
		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 + 1, 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 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)) {
 | 
						|
		uint32_t cmd = *((uint32_t *) NLA_DATA(nla));
 | 
						|
 | 
						|
		print_attr(level + 1, "%s [%d]",
 | 
						|
					nl80211cmd_to_string(cmd), 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 void print_eapol_key(unsigned int level, const void *data, uint32_t size)
 | 
						|
{
 | 
						|
	const struct eapol_key *ek = (struct eapol_key *)data;
 | 
						|
	size_t mic_lengths[] = { 16, 24, 32 };
 | 
						|
	size_t mic_len = 0;
 | 
						|
	int i;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * The MIC length is not encoded anywhere in the frame, and should be
 | 
						|
	 * determined by AKM. To even further complicate things, some non
 | 
						|
	 * 802.11 AKMs define their own MIC lengths. But since the only valid
 | 
						|
	 * lengths are 16, 24 and 32 its trivial to try each until we find a
 | 
						|
	 * matching length.
 | 
						|
	 */
 | 
						|
	for (i = 0; i < 3; i++) {
 | 
						|
		size_t mlen = mic_lengths[i];
 | 
						|
 | 
						|
		if (size < EAPOL_FRAME_LEN(mlen))
 | 
						|
			break;
 | 
						|
 | 
						|
		if (size == EAPOL_FRAME_LEN(mlen) +
 | 
						|
				EAPOL_KEY_DATA_LEN(ek, mlen)) {
 | 
						|
			mic_len = mlen;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* could not determine MIC length, malformed packet? */
 | 
						|
	if (!mic_len)
 | 
						|
		return;
 | 
						|
 | 
						|
	ek = eapol_key_validate(data, size, mic_len);
 | 
						|
	if (!ek)
 | 
						|
		return;
 | 
						|
 | 
						|
	print_attr(level, "Descriptor Type: %u", ek->descriptor_type);
 | 
						|
	print_attr(level, "Key MIC: %s", ek->key_mic ? "true" : "false");
 | 
						|
	print_attr(level, "Secure: %s", ek->secure ? "true" : "false");
 | 
						|
	print_attr(level, "Error: %s", ek->error ? "true" : "false");
 | 
						|
	print_attr(level, "Request: %s", ek->request ? "true" : "false");
 | 
						|
	print_attr(level, "Encrypted Key Data: %s",
 | 
						|
				ek->encrypted_key_data ? "true" : "false");
 | 
						|
	print_attr(level, "SMK Message: %s",
 | 
						|
				ek->smk_message ? "true" : "false");
 | 
						|
	print_attr(level, "Key Descriptor Version: %d (%02x)",
 | 
						|
						ek->key_descriptor_version,
 | 
						|
						ek->key_descriptor_version);
 | 
						|
	print_attr(level, "Key Type: %s", ek->key_type ? "true" : "false");
 | 
						|
 | 
						|
	if (ek->descriptor_type == EAPOL_DESCRIPTOR_TYPE_WPA)
 | 
						|
		print_attr(level, "Key Id: %u", ek->wpa_key_id);
 | 
						|
 | 
						|
	print_attr(level, "Install: %s", ek->install ? "true" : "false");
 | 
						|
	print_attr(level, "Key ACK: %s", ek->key_ack ? "true" : "false");
 | 
						|
	print_attr(level, "Key Length: %d", L_BE16_TO_CPU(ek->key_length));
 | 
						|
	print_attr(level, "Key Replay Counter: %" PRIu64,
 | 
						|
					L_BE64_TO_CPU(ek->key_replay_counter));
 | 
						|
	print_attr(level, "Key NONCE");
 | 
						|
	print_hexdump(level + 1, ek->key_nonce, 32);
 | 
						|
	print_attr(level, "Key IV");
 | 
						|
	print_hexdump(level + 1, ek->eapol_key_iv, 16);
 | 
						|
	print_attr(level, "Key RSC ");
 | 
						|
	print_hexdump(level + 1, ek->key_rsc, 8);
 | 
						|
 | 
						|
	print_attr(level, "Key MIC Data");
 | 
						|
 | 
						|
	print_hexdump(level + 1, EAPOL_KEY_MIC(ek), mic_len);
 | 
						|
 | 
						|
	if (ek->encrypted_key_data) {
 | 
						|
		print_attr(level, "Key Data: len %d",
 | 
						|
					EAPOL_KEY_DATA_LEN(ek, mic_len));
 | 
						|
		print_hexdump(level + 1, EAPOL_KEY_DATA(ek, mic_len),
 | 
						|
				EAPOL_KEY_DATA_LEN(ek, mic_len));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	print_ie(level, "Key Data", EAPOL_KEY_DATA(ek, mic_len),
 | 
						|
			EAPOL_KEY_DATA_LEN(ek, mic_len));
 | 
						|
}
 | 
						|
 | 
						|
static void print_eap_wsc(unsigned int level,
 | 
						|
				const uint8_t *eap_wsc, uint32_t size)
 | 
						|
{
 | 
						|
	const char *str;
 | 
						|
 | 
						|
	if (size < 2)
 | 
						|
		return;
 | 
						|
 | 
						|
	switch (eap_wsc[0]) {
 | 
						|
	case 0x01:
 | 
						|
		str = "WSC-Start";
 | 
						|
		break;
 | 
						|
	case 0x02:
 | 
						|
		str = "WSC-Ack";
 | 
						|
		break;
 | 
						|
	case 0x03:
 | 
						|
		str = "WSC-Nack";
 | 
						|
		break;
 | 
						|
	case 0x04:
 | 
						|
		str = "WSC-Msg";
 | 
						|
		break;
 | 
						|
	case 0x05:
 | 
						|
		str = "WSC-Done";
 | 
						|
		break;
 | 
						|
	case 0x06:
 | 
						|
		str = "WSC-Frag-Ack";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		str = "Reserved";
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "Op-Code: %u (%s)", eap_wsc[0], str);
 | 
						|
	print_attr(level, "Flags: %02x", eap_wsc[1]);
 | 
						|
 | 
						|
	if (eap_wsc[1] == 0)
 | 
						|
		print_wsc_attributes(level + 1, "EAP-WSC Payload",
 | 
						|
						eap_wsc + 2, size - 2);
 | 
						|
}
 | 
						|
 | 
						|
static void print_eap(unsigned int level, const void *data, uint32_t size)
 | 
						|
{
 | 
						|
	static const uint8_t wfa_smi[3] = { 0x00, 0x37, 0x2a };
 | 
						|
	const uint8_t *eap = data;
 | 
						|
	const char *str;
 | 
						|
 | 
						|
	if (size < 5)
 | 
						|
		return;
 | 
						|
 | 
						|
	switch (eap[0]) {
 | 
						|
	case 0x01:
 | 
						|
		str = "Request";
 | 
						|
		break;
 | 
						|
	case 0x02:
 | 
						|
		str = "Response";
 | 
						|
		break;
 | 
						|
	case 0x03:
 | 
						|
		str = "Success";
 | 
						|
		break;
 | 
						|
	case 0x04:
 | 
						|
		str = "Failure";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		str = "Reserved";
 | 
						|
		break;
 | 
						|
	};
 | 
						|
 | 
						|
	print_attr(level, "Code: %u (%s)", eap[0], str);
 | 
						|
	print_attr(level, "Identifier: %u", eap[1]);
 | 
						|
	print_attr(level, "Length: %u", l_get_be16(eap + 2));
 | 
						|
 | 
						|
	switch (eap[4]) {
 | 
						|
	case 1:
 | 
						|
		str = "Identity";
 | 
						|
		break;
 | 
						|
	case 2:
 | 
						|
		str = "Notification";
 | 
						|
		break;
 | 
						|
	case 3:
 | 
						|
		str = "Nak";
 | 
						|
		break;
 | 
						|
	case 4:
 | 
						|
		str = "MD5 Challenge";
 | 
						|
		break;
 | 
						|
	case 13:
 | 
						|
		str = "TLS EAP";
 | 
						|
		break;
 | 
						|
	case 21:
 | 
						|
		str = "TTLS";
 | 
						|
		break;
 | 
						|
	case 254:
 | 
						|
		str = "Expanded";
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		str = "Reserved";
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	print_attr(level, "Type: %u (%s)", eap[4], str);
 | 
						|
 | 
						|
	if (eap[4] == 254) {
 | 
						|
		if (size < 12)
 | 
						|
			return;
 | 
						|
 | 
						|
		if (memcmp(eap + 5, wfa_smi, 3))
 | 
						|
			return;
 | 
						|
 | 
						|
		if (l_get_be32(eap + 8) == 1)
 | 
						|
			print_eap_wsc(level, eap + 12, size - 12);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void print_eapol(unsigned int level, const char *label,
 | 
						|
					const void *data, uint16_t size)
 | 
						|
{
 | 
						|
	const struct eapol_header *eh;
 | 
						|
	const char *str;
 | 
						|
 | 
						|
	print_attr(level, "%s: len %u", label, size);
 | 
						|
 | 
						|
	if (size < 4)
 | 
						|
		return;
 | 
						|
 | 
						|
	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(level + 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(level + 1, "Type: %u (%s)", eh->packet_type, str);
 | 
						|
	print_attr(level + 1, "Length: %d", L_BE16_TO_CPU(eh->packet_len));
 | 
						|
 | 
						|
	switch (eh->packet_type) {
 | 
						|
	case 0x03:
 | 
						|
		print_eapol_key(level + 1, data, size);
 | 
						|
		break;
 | 
						|
	case 0x00:
 | 
						|
		print_eap(level + 1, data + 4, size - 4);
 | 
						|
	}
 | 
						|
 | 
						|
	print_hexdump(level + 1, data, size);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Control Port sends a EAPoL frame inside ATTR_FRAME and not a management
 | 
						|
 * frame.  So a separate table with all the possible attributes is provided
 | 
						|
 */
 | 
						|
static const struct attr_entry control_port_attr_table[] = {
 | 
						|
	{ 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_MAC,
 | 
						|
			"MAC Address", ATTR_ADDRESS },
 | 
						|
	{ NL80211_ATTR_FRAME,
 | 
						|
			"Frame", ATTR_CUSTOM, { .function = print_eapol } },
 | 
						|
	{ NL80211_ATTR_WDEV,
 | 
						|
			"Wireless Device", ATTR_U64 },
 | 
						|
	{ NL80211_ATTR_IFINDEX,
 | 
						|
			"Interface Index", ATTR_U32 },
 | 
						|
	{ NL80211_ATTR_WIPHY,
 | 
						|
			"Wiphy", ATTR_U32 },
 | 
						|
};
 | 
						|
 | 
						|
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_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,
 | 
						|
			"Disable 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_S32 },
 | 
						|
	{ 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_AUTH_DATA,
 | 
						|
			"Auth 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", ATTR_ARRAY,
 | 
						|
					{ .array_type = ATTR_ADDRESS } },
 | 
						|
	{ 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 Critical 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" },
 | 
						|
	{ NL80211_ATTR_TDLS_INITIATOR,
 | 
						|
			"TDLS Initiator" },
 | 
						|
	{ NL80211_ATTR_USE_RRM,
 | 
						|
			"Use RRM", ATTR_FLAG },
 | 
						|
	{ NL80211_ATTR_EXT_FEATURES,
 | 
						|
			"Extended Features" },
 | 
						|
	{ NL80211_ATTR_FILS_KEK,
 | 
						|
			"FILS KEK" },
 | 
						|
	{ NL80211_ATTR_FILS_NONCES,
 | 
						|
			"FILS Nonces" },
 | 
						|
	{ NL80211_ATTR_MULTICAST_TO_UNICAST_ENABLED,
 | 
						|
			"Multicast to Unicast Enabled", ATTR_FLAG },
 | 
						|
	{ NL80211_ATTR_BSSID,
 | 
						|
			"BSSID", ATTR_ADDRESS },
 | 
						|
	{ NL80211_ATTR_SCHED_SCAN_RELATIVE_RSSI,
 | 
						|
			"Scheduled Scan Relative RSSI" },
 | 
						|
	{ NL80211_ATTR_SCHED_SCAN_RSSI_ADJUST,
 | 
						|
			"Scheduled Scan RSSI Adjust" },
 | 
						|
	{ NL80211_ATTR_TIMEOUT_REASON,
 | 
						|
			"Timeout Reason" },
 | 
						|
	{ NL80211_ATTR_FILS_ERP_USERNAME,
 | 
						|
			"FILS ERP Username" },
 | 
						|
	{ NL80211_ATTR_FILS_ERP_REALM,
 | 
						|
			"FILS ERP Realm" },
 | 
						|
	{ NL80211_ATTR_FILS_ERP_NEXT_SEQ_NUM,
 | 
						|
			"FILS ERP Next Sequence Number" },
 | 
						|
	{ NL80211_ATTR_FILS_ERP_RRK,
 | 
						|
			"FILS ERP RRK" },
 | 
						|
	{ NL80211_ATTR_FILS_CACHE_ID,
 | 
						|
			"FILS Cache ID" },
 | 
						|
	{ NL80211_ATTR_PMK,
 | 
						|
			"PMK" },
 | 
						|
	{ NL80211_ATTR_SCHED_SCAN_MULTI,
 | 
						|
			"Scheduled Scan Multi" },
 | 
						|
	{ NL80211_ATTR_SCHED_SCAN_MAX_REQS,
 | 
						|
			"Scheduled Scan Maximum Requests" },
 | 
						|
	{ NL80211_ATTR_WANT_1X_4WAY_HS,
 | 
						|
			"Want 1X 4Way Handshake" },
 | 
						|
	{ NL80211_ATTR_PMKR0_NAME,
 | 
						|
			"PMKR0 Name" },
 | 
						|
	{ NL80211_ATTR_PORT_AUTHORIZED,
 | 
						|
			"Port Authorized" },
 | 
						|
	{ NL80211_ATTR_EXTERNAL_AUTH_ACTION,
 | 
						|
			"External Auth Action" },
 | 
						|
	{ NL80211_ATTR_EXTERNAL_AUTH_SUPPORT,
 | 
						|
			"External Auth Support" },
 | 
						|
	{ NL80211_ATTR_NSS,
 | 
						|
			"NSS" },
 | 
						|
	{ NL80211_ATTR_ACK_SIGNAL,
 | 
						|
			"Ack Signal" },
 | 
						|
	{ NL80211_ATTR_CONTROL_PORT_OVER_NL80211,
 | 
						|
			"Control Port over NL80211", ATTR_FLAG },
 | 
						|
	{ NL80211_ATTR_TXQ_STATS,
 | 
						|
			"TXQ Stats" },
 | 
						|
	{ NL80211_ATTR_TXQ_LIMIT,
 | 
						|
			"TXQ Limit" },
 | 
						|
	{ NL80211_ATTR_TXQ_MEMORY_LIMIT,
 | 
						|
			"TXQ Memory Limit" },
 | 
						|
	{ NL80211_ATTR_TXQ_QUANTUM,
 | 
						|
			"TXQ Quantum" },
 | 
						|
	{ NL80211_ATTR_HE_CAPABILITY,
 | 
						|
			"HE Capability" },
 | 
						|
	{ NL80211_ATTR_FTM_RESPONDER,
 | 
						|
			"FTM Responder" },
 | 
						|
	{ NL80211_ATTR_FTM_RESPONDER_STATS,
 | 
						|
			"FTM Responder Stats" },
 | 
						|
	{ }
 | 
						|
};
 | 
						|
 | 
						|
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;
 | 
						|
	case ATTR_ADDRESS:
 | 
						|
		print_address(indent, label, buf);
 | 
						|
		break;
 | 
						|
	case ATTR_UNSPEC:
 | 
						|
	case ATTR_FLAG:
 | 
						|
	case ATTR_U8:
 | 
						|
	case ATTR_U64:
 | 
						|
	case ATTR_S8:
 | 
						|
	case ATTR_S32:
 | 
						|
	case ATTR_S64:
 | 
						|
	case ATTR_STRING:
 | 
						|
	case ATTR_BINARY:
 | 
						|
	case ATTR_NESTED:
 | 
						|
	case ATTR_ARRAY:
 | 
						|
	case ATTR_FLAG_OR_U16:
 | 
						|
	case ATTR_CUSTOM:
 | 
						|
		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 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(struct nlmon *nlmon, 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;
 | 
						|
 | 
						|
	if (nlmon->nowiphy && (cmd == NL80211_CMD_NEW_WIPHY))
 | 
						|
		return;
 | 
						|
 | 
						|
	if (nlmon->noscan && ((cmd == NL80211_CMD_NEW_SCAN_RESULTS) ||
 | 
						|
			(cmd == NL80211_CMD_TRIGGER_SCAN)))
 | 
						|
		return;
 | 
						|
 | 
						|
	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 = nl80211cmd_to_string(cmd);
 | 
						|
 | 
						|
	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:
 | 
						|
		switch (cmd) {
 | 
						|
		case NL80211_CMD_CONTROL_PORT_FRAME:
 | 
						|
			print_attributes(0, control_port_attr_table, data, len);
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			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));
 | 
						|
 | 
						|
	l_put_be16(pkt_type, buf);
 | 
						|
	l_put_be16(arphrd_type, buf + 2);
 | 
						|
	l_put_be16(proto_type, 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(nlmon, 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(nlmon, 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(nlmon, 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 = 0;
 | 
						|
 | 
						|
	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 - 1);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (id == 0)
 | 
						|
		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 link_info_entry[] = {
 | 
						|
	{ IFLA_INFO_KIND,	"Kind",		ATTR_STRING },
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
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 },
 | 
						|
	{ IFLA_LINKINFO,	"LinkInfo",
 | 
						|
					ATTR_NESTED, { link_info_entry } },
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
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 struct attr_entry route_entry[] = {
 | 
						|
	{ RTA_DST,		"Destination Address", ATTR_CUSTOM,
 | 
						|
					{ .function = print_inet_addr } },
 | 
						|
	{ RTA_SRC,		"Source Address", ATTR_CUSTOM,
 | 
						|
					{ .function = print_inet_addr } },
 | 
						|
	{ RTA_IIF,		"Input Interface Index", ATTR_S32 },
 | 
						|
	{ RTA_OIF,		"Output Interface Index", ATTR_S32 },
 | 
						|
	{ RTA_GATEWAY,		"Gateway", ATTR_CUSTOM,
 | 
						|
					{ .function = print_inet_addr } },
 | 
						|
	{ RTA_PRIORITY,		"Priority of the route", ATTR_S32 },
 | 
						|
	{ RTA_METRICS,		"Metric of the route", ATTR_S32 },
 | 
						|
	{ RTA_TABLE,		"Routing Table", ATTR_U32 },
 | 
						|
	{ RTA_PREFSRC,		"Preferred Source", ATTR_CUSTOM,
 | 
						|
					{ .function = print_inet_addr } },
 | 
						|
	{ },
 | 
						|
};
 | 
						|
 | 
						|
static void print_rtnl_attributes(int indent, const struct attr_entry *table,
 | 
						|
						struct rtattr *rt_attr, int len)
 | 
						|
{
 | 
						|
	struct rtattr *attr;
 | 
						|
 | 
						|
	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;
 | 
						|
		const struct attr_entry *nested;
 | 
						|
		uint64_t val64;
 | 
						|
		uint32_t val32;
 | 
						|
		uint16_t val16;
 | 
						|
		uint8_t val8;
 | 
						|
		int8_t val_s8;
 | 
						|
		int32_t val_s32;
 | 
						|
		int64_t val_s64;
 | 
						|
		const char *str;
 | 
						|
		int i, payload;
 | 
						|
 | 
						|
		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;
 | 
						|
				nested = table[i].nested;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		payload = RTA_PAYLOAD(attr);
 | 
						|
 | 
						|
		switch (type) {
 | 
						|
		case ATTR_CUSTOM:
 | 
						|
			if (function)
 | 
						|
				function(indent, str, RTA_DATA(attr), payload);
 | 
						|
			else
 | 
						|
				printf("missing function\n");
 | 
						|
			break;
 | 
						|
		case ATTR_STRING:
 | 
						|
			print_attr(indent, "%s (len:%d): %s", str, payload,
 | 
						|
						(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 (payload != 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 (payload != 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 (payload != 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 (payload != 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 (payload != 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 (payload != 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 (payload != 8)
 | 
						|
				printf("malformed packet\n");
 | 
						|
			break;
 | 
						|
		case ATTR_FLAG:
 | 
						|
			print_attr(indent, "%s: true", str);
 | 
						|
			if (payload != 0)
 | 
						|
				printf("malformed packet\n");
 | 
						|
			break;
 | 
						|
		case ATTR_FLAG_OR_U16:
 | 
						|
			if (payload == 0)
 | 
						|
				print_attr(indent, "%s: true", str);
 | 
						|
			else if (payload == 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_NESTED:
 | 
						|
			print_attr(indent, "%s: len %u", str, payload);
 | 
						|
			if (!nested)
 | 
						|
				printf("missing table\n");
 | 
						|
			print_rtnl_attributes(indent + 1, nested,
 | 
						|
						RTA_DATA(attr), payload);
 | 
						|
			break;
 | 
						|
		case ATTR_BINARY:
 | 
						|
			print_attr(indent, "%s: len %d", str, payload);
 | 
						|
			print_hexdump(indent + 1, RTA_DATA(attr), payload);
 | 
						|
			break;
 | 
						|
		case ATTR_ADDRESS:
 | 
						|
		case ATTR_ARRAY:
 | 
						|
		case ATTR_UNSPEC:
 | 
						|
			print_attr(indent, "%s: len %d", str, payload);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void flags_str(const struct flag_names *table,
 | 
						|
				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; table[i].name; i++) {
 | 
						|
		if (flags & table[i].flag) {
 | 
						|
			flags &= ~table[i].flag;
 | 
						|
			pos += sprintf(str + pos, "%s%s", table[i].name,
 | 
						|
							flags ? "," : "");
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pos += sprintf(str + pos, "]");
 | 
						|
}
 | 
						|
 | 
						|
static void print_ifinfomsg(const struct ifinfomsg *info)
 | 
						|
{
 | 
						|
	static struct flag_names iff_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" },
 | 
						|
		{ },
 | 
						|
	};
 | 
						|
 | 
						|
	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);
 | 
						|
	flags_str(iff_flags, 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 print_rtmsg(const struct rtmsg *msg)
 | 
						|
{
 | 
						|
	static struct flag_names rtm_flags[] = {
 | 
						|
		{ RTM_F_NOTIFY, "notify" },
 | 
						|
		{ RTM_F_CLONED, "cloned" },
 | 
						|
		{ RTM_F_EQUALIZE, "multipath-equalizer" },
 | 
						|
		{ },
 | 
						|
	};
 | 
						|
	char str[256];
 | 
						|
 | 
						|
	print_field("RTM Family: %hhu", msg->rtm_family);
 | 
						|
	print_field("RTM Destination Len: %hhu", msg->rtm_dst_len);
 | 
						|
	print_field("RTM Source Len: %hhu", msg->rtm_src_len);
 | 
						|
	print_field("RTM TOS Field: %hhu", msg->rtm_tos);
 | 
						|
	print_field("RTM Table: %hhu", msg->rtm_table);
 | 
						|
	print_field("RTM Protocol: %hhu", msg->rtm_protocol);
 | 
						|
	print_field("RTM Scope: %hhu", msg->rtm_scope);
 | 
						|
	print_field("RTM Type: %hhu", msg->rtm_type);
 | 
						|
	flags_str(rtm_flags, str, sizeof(str), msg->rtm_flags);
 | 
						|
	print_field("RTM Flags: %s", str);
 | 
						|
}
 | 
						|
 | 
						|
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 void print_rtm_route(uint16_t type, const struct rtmsg *msg, size_t len)
 | 
						|
{
 | 
						|
	if (!msg || len < sizeof(struct rtmsg))
 | 
						|
		return;
 | 
						|
 | 
						|
	print_rtmsg(msg);
 | 
						|
	print_rtnl_attributes(1, route_entry, RTM_RTA(msg), len);
 | 
						|
}
 | 
						|
 | 
						|
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: %hu (0x%03x)", nlmsg->nlmsg_flags,
 | 
						|
							nlmsg->nlmsg_flags);
 | 
						|
	print_field("Sequence number: %u (0x%08x)",
 | 
						|
					nlmsg->nlmsg_seq, nlmsg->nlmsg_seq);
 | 
						|
	print_field("Port ID: %u", 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 rtmsg *rtm;
 | 
						|
	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_NEWROUTE:
 | 
						|
	case RTM_GETROUTE:
 | 
						|
	case RTM_DELROUTE:
 | 
						|
		rtm = (struct rtmsg *) NLMSG_DATA(nlmsg);
 | 
						|
		len = RTM_PAYLOAD(nlmsg);
 | 
						|
 | 
						|
		if (!rtm || len <= (int) sizeof(struct rtmsg))
 | 
						|
			return;
 | 
						|
 | 
						|
		/* Skip 'kernel' and 'default' tables */
 | 
						|
		if (rtm->rtm_table == RT_TABLE_LOCAL ||
 | 
						|
				rtm->rtm_table == RT_TABLE_DEFAULT)
 | 
						|
			return;
 | 
						|
 | 
						|
		print_nlmsghdr(tv, nlmsg);
 | 
						|
		print_rtm_route(nlmsg->nlmsg_type, rtm, 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:
 | 
						|
		case RTM_NEWROUTE:
 | 
						|
		case RTM_GETROUTE:
 | 
						|
		case RTM_DELROUTE:
 | 
						|
			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;
 | 
						|
	size_t nlmsg_len;
 | 
						|
	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;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	nlmsg_len = bytes_read;
 | 
						|
 | 
						|
	for (nlmsg = iov.iov_base; NLMSG_OK(nlmsg, nlmsg_len);
 | 
						|
				nlmsg = NLMSG_NEXT(nlmsg, nlmsg_len)) {
 | 
						|
		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 - 1);
 | 
						|
 | 
						|
	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];
 | 
						|
 | 
						|
	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);
 | 
						|
 | 
						|
	print_eapol(0, "EAPoL", data, size);
 | 
						|
}
 | 
						|
 | 
						|
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_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,
 | 
						|
				ntohs(sll.sll_protocol), 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) or 0x88c7 (preauthentication).
 | 
						|
 */
 | 
						|
static struct sock_filter pae_filter[] = {
 | 
						|
	{ 0x28,  0,  0, 0xfffff01c },	/* ldh #hatype		*/
 | 
						|
	{ 0x15,  0,  4, 0x00000001 },	/* jne #1, drop		*/
 | 
						|
	{ 0x28,  0,  0, 0xfffff000 },	/* ldh #proto		*/
 | 
						|
	{ 0x15,  1,  0, 0x0000888e },	/* je  #0x888e, keep	*/
 | 
						|
	{ 0x15,  0,  1, 0x000088c7 },	/* jne #0x88c7, drop	*/
 | 
						|
	{ 0x06,  0,  0, 0xffffffff },	/* keep: ret #-1	*/
 | 
						|
	{ 0x06,  0,  0, 0000000000 },	/* drop: ret #0		*/
 | 
						|
};
 | 
						|
 | 
						|
static const struct sock_fprog pae_fprog = { .len = 7, .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,
 | 
						|
				const struct nlmon_config *config)
 | 
						|
{
 | 
						|
	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 = config->nortnl;
 | 
						|
	nlmon->nowiphy = config->nowiphy;
 | 
						|
	nlmon->noscan = config->noscan;
 | 
						|
 | 
						|
	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);
 | 
						|
}
 |