blob: 1700cdad8bdaefa78a797470d4a3e02b2e8d8829 [file] [log] [blame]
/*
* Portions of this code are derived from iw-3.17.
*
* Copyright (c) 2007, 2008 Johannes Berg
* Copyright (c) 2007 Andy Lutomirski
* Copyright (c) 2007 Mike Kershaw
* Copyright (c) 2008-2009 Luis R. Rodriguez
* Copyright (c) 2015 Google, Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <glib.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/if_ether.h>
#include <linux/nl80211.h>
#include <math.h>
#include <net/if.h>
#include <netlink/attr.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#ifndef UNIT_TESTS
#define STATIONS_DIR "/tmp/stations"
#define WIFIINFO_DIR "/tmp/wifi/wifiinfo"
#endif
// Hash table of known Wifi clients.
GHashTable *clients = NULL;
#define MAX_CLIENT_AGE_SECS (4 * 60 * 60)
#ifndef UNIT_TESTS
static time_t monotime(void) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {
return time(NULL);
} else {
return ts.tv_sec;
}
}
#endif
/*
* Saved state for each associated Wifi device. Wifi clients drop out
* after 5 minutes inactive, we want to export information about the
* client for a while longer than that.
*/
typedef struct client_state {
#define MAC_STR_LEN 18
char macstr[MAC_STR_LEN];
#define IFNAME_STR_LEN 16
char ifname[IFNAME_STR_LEN];
double inactive_since;
uint64_t rx_drop64;
// Accumulated values from the 32 bit counters.
uint64_t rx_bytes64;
uint64_t tx_bytes64;
uint64_t rx_packets64;
uint64_t tx_packets64;
uint64_t tx_retries64;
uint64_t tx_failed64;
time_t first_seen; // CLOCK_MONOTONIC
time_t last_seen; // CLOCK_MONOTONIC
uint32_t inactive_msec;
uint32_t connected_secs;
uint32_t rx_bitrate;
uint32_t rx_bytes;
uint32_t rx_packets;
uint32_t tx_bitrate;
uint32_t tx_bytes;
uint32_t tx_packets;
uint32_t tx_retries;
uint32_t tx_failed;
uint32_t expected_mbps;
#define MAX_SAMPLE_INDEX 150
int rx_sample_index;
uint8_t rx_ht_mcs_samples[MAX_SAMPLE_INDEX];
uint8_t rx_vht_mcs_samples[MAX_SAMPLE_INDEX];
uint8_t rx_width_samples[MAX_SAMPLE_INDEX];
uint8_t rx_ht_nss_samples[MAX_SAMPLE_INDEX];
uint8_t rx_vht_nss_samples[MAX_SAMPLE_INDEX];
uint8_t rx_short_gi_samples[MAX_SAMPLE_INDEX];
int tx_sample_index;
uint8_t tx_ht_mcs_samples[MAX_SAMPLE_INDEX];
uint8_t tx_vht_mcs_samples[MAX_SAMPLE_INDEX];
uint8_t tx_width_samples[MAX_SAMPLE_INDEX];
uint8_t tx_ht_nss_samples[MAX_SAMPLE_INDEX];
uint8_t tx_vht_nss_samples[MAX_SAMPLE_INDEX];
uint8_t tx_short_gi_samples[MAX_SAMPLE_INDEX];
/*
* Clients spend a lot of time mostly idle, where they
* are only sending management frames and ACKs. These
* tend to be sent at much lower MCS rates than bulk data;
* if we report that MCS rate it gives a misleading
* picture of what the client is capable of getting.
*
* Instead, we choose the largest sample over the reporting
* interval. This is more likely to report a meaningful
* MCS rate.
*/
uint8_t rx_ht_mcs;
uint8_t rx_vht_mcs;
uint8_t rx_width;
uint8_t rx_ht_nss;
uint8_t rx_vht_nss;
uint8_t rx_short_gi;
uint8_t tx_ht_mcs;
uint8_t tx_vht_mcs;
uint8_t tx_width;
uint8_t tx_ht_nss;
uint8_t tx_vht_nss;
uint8_t tx_short_gi;
/* Track the largest value we've ever seen from this client. This
* shows client capabilities, even if current interference
* conditions don't allow it to use its full capability. */
uint8_t rx_max_ht_mcs;
uint8_t rx_max_vht_mcs;
uint8_t rx_max_width;
uint8_t rx_max_ht_nss;
uint8_t rx_max_vht_nss;
uint8_t ever_rx_short_gi;
uint8_t tx_max_ht_mcs;
uint8_t tx_max_vht_mcs;
uint8_t tx_max_width;
uint8_t tx_max_ht_nss;
uint8_t tx_max_vht_nss;
uint8_t ever_tx_short_gi;
int8_t signal;
int8_t signal_avg;
uint8_t authorized:1;
uint8_t authenticated:1;
uint8_t preamble:1;
uint8_t wmm_wme:1;
uint8_t mfp:1;
uint8_t tdls_peer:1;
uint8_t preamble_length:1;
} client_state_t;
typedef struct callback_data {
time_t mono_now;
} callback_data_t;
/* List of wifi interfaces in the system. */
#define NINTERFACES 16
int ifindexes[NINTERFACES] = {0};
const char *interfaces[NINTERFACES] = {0};
int ninterfaces = 0;
/* FILE handle to /tmp/wifi/wifiinfo, while open. */
static FILE *wifi_info_handle = NULL;
static void ClearClientStateCounters(client_state_t *state)
{
char macstr[MAC_STR_LEN];
memcpy(macstr, state->macstr, sizeof(macstr));
memset(state, 0, sizeof(*state));
memcpy(state->macstr, macstr, sizeof(state->macstr));
}
static int GetIfIndex(const char *ifname)
{
int fd;
struct ifreq ifr;
if (strlen(ifname) >= sizeof(ifr.ifr_name)) {
fprintf(stderr, "interface name %s is too long\n", ifname);
exit(1);
}
if ((fd = socket(AF_PACKET, SOCK_DGRAM, 0)) < 0) {
perror("socket");
exit(1);
}
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
char errbuf[128];
snprintf(errbuf, sizeof(errbuf), "SIOCGIFINDEX %s", ifname);
perror(errbuf);
close(fd);
exit(1);
}
close(fd);
return ifr.ifr_ifindex;
} /* GetIfIndex */
static int InterfaceListCallback(struct nl_msg *msg, void *arg)
{
struct nlattr *il[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gh = nlmsg_data(nlmsg_hdr(msg));
nla_parse(il, NL80211_ATTR_MAX, genlmsg_attrdata(gh, 0),
genlmsg_attrlen(gh, 0), NULL);
if (il[NL80211_ATTR_IFNAME]) {
const char *name = nla_get_string(il[NL80211_ATTR_IFNAME]);
if (interfaces[ninterfaces] != NULL) {
free((void *)interfaces[ninterfaces]);
}
interfaces[ninterfaces] = strdup(name);
ifindexes[ninterfaces] = GetIfIndex(name);
ninterfaces++;
}
return NL_OK;
}
static void HandleNLCommand(struct nl_sock *nlsk, int nl80211_id, int n,
int cb(struct nl_msg *, void *),
int cmd, int flag)
{
struct nl_msg *msg;
int ifindex = n >= 0 ? ifindexes[n] : -1;
const char *ifname = n>=0 ? interfaces[n] : NULL;
if (nl_socket_modify_cb(nlsk, NL_CB_VALID, NL_CB_CUSTOM,
cb, (void *)ifname)) {
fprintf(stderr, "nl_socket_modify_cb failed\n");
exit(1);
}
if ((msg = nlmsg_alloc()) == NULL) {
fprintf(stderr, "nlmsg_alloc failed\n");
exit(1);
}
if (genlmsg_put(msg, 0, 0, nl80211_id, 0, flag,
cmd, 0) == NULL) {
fprintf(stderr, "genlmsg_put failed\n");
exit(1);
}
if (ifindex >= 0 && nla_put_u32(msg, NL80211_ATTR_IFINDEX, ifindex)) {
fprintf(stderr, "NL80211_CMD_GET_STATION put IFINDEX failed\n");
exit(1);
}
if (nl_send_auto(nlsk, msg) < 0) {
fprintf(stderr, "nl_send_auto failed\n");
exit(1);
}
nlmsg_free(msg);
}
void RequestInterfaceList(struct nl_sock *nlsk, int nl80211_id)
{
HandleNLCommand(nlsk, nl80211_id, -1, InterfaceListCallback,
NL80211_CMD_GET_INTERFACE, NLM_F_DUMP);
} /* RequestInterfaceList */
int NlFinish(struct nl_msg *msg, void *arg)
{
int *ret = arg;
*ret = 1;
return NL_OK;
}
struct nl_sock *InitNetlinkSocket()
{
struct nl_sock *nlsk;
if ((nlsk = nl_socket_alloc()) == NULL) {
fprintf(stderr, "socket allocation failed\n");
exit(1);
}
if (genl_connect(nlsk) != 0) {
fprintf(stderr, "genl_connect failed\n");
exit(1);
}
if (nl_socket_set_nonblocking(nlsk)) {
fprintf(stderr, "nl_socket_set_nonblocking failed\n");
exit(1);
}
return nlsk;
} /* InitNetlinkSocket */
static void ProcessNetlinkMessages(struct nl_sock *nlsk, int *done)
{
for (;;) {
int s = nl_socket_get_fd(nlsk);
fd_set rfds;
struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };
FD_ZERO(&rfds);
FD_SET(s, &rfds);
if (select(s + 1, &rfds, NULL, NULL, &timeout) <= 0) {
break;
}
if (FD_ISSET(s, &rfds)) {
nl_recvmsgs_default(nlsk);
}
if (*done) {
break;
}
}
}
static uint32_t GetBitrate(struct nlattr *attr)
{
int rate = 0;
struct nlattr *ri[NL80211_RATE_INFO_MAX + 1];
static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
[NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 },
};
if (nla_parse_nested(ri, NL80211_RATE_INFO_MAX, attr, rate_policy)) {
fprintf(stderr, "nla_parse_nested NL80211_RATE_INFO_MAX failed");
return 0;
}
if (ri[NL80211_RATE_INFO_BITRATE]) {
rate = nla_get_u16(ri[NL80211_RATE_INFO_BITRATE]);
}
return rate;
}
static void GetMCS(struct nlattr *attr,
int *mcs, int *vht_mcs, int *width, int *short_gi, int *vht_nss)
{
int w160 = 0, w80_80 = 0, w80 = 0, w40 = 0;
struct nlattr *ri[NL80211_RATE_INFO_MAX + 1];
static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
[NL80211_RATE_INFO_MCS] = { .type = NLA_U8 },
[NL80211_RATE_INFO_VHT_MCS] = { .type = NLA_U8 },
[NL80211_RATE_INFO_VHT_NSS] = { .type = NLA_U8 },
[NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG },
[NL80211_RATE_INFO_80_MHZ_WIDTH] = { .type = NLA_FLAG },
[NL80211_RATE_INFO_80P80_MHZ_WIDTH] = { .type = NLA_FLAG },
[NL80211_RATE_INFO_160_MHZ_WIDTH] = { .type = NLA_FLAG },
[NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG },
};
if (nla_parse_nested(ri, NL80211_RATE_INFO_MAX, attr, rate_policy)) {
fprintf(stderr, "nla_parse_nested NL80211_RATE_INFO_MAX failed");
return;
}
if (ri[NL80211_RATE_INFO_MCS]) {
*mcs = nla_get_u8(ri[NL80211_RATE_INFO_MCS]);
}
if (ri[NL80211_RATE_INFO_VHT_MCS]) {
*vht_mcs = nla_get_u8(ri[NL80211_RATE_INFO_VHT_MCS]);
}
if (ri[NL80211_RATE_INFO_VHT_NSS]) {
*vht_nss = nla_get_u8(ri[NL80211_RATE_INFO_VHT_NSS]);
}
if (ri[NL80211_RATE_INFO_160_MHZ_WIDTH]) w160 = 1;
if (ri[NL80211_RATE_INFO_80P80_MHZ_WIDTH]) w80_80 = 1;
if (ri[NL80211_RATE_INFO_80_MHZ_WIDTH]) w80 = 1;
if (ri[NL80211_RATE_INFO_40_MHZ_WIDTH]) w40 = 1;
if (ri[NL80211_RATE_INFO_SHORT_GI]) *short_gi = 1;
if (w160 || w80_80) {
*width = 160;
} else if (w80) {
*width = 80;
} else if (w40) {
*width = 40;
} else {
*width = 20;
}
}
static client_state_t *FindClientState(const uint8_t mac[6])
{
client_state_t *s;
char macstr[MAC_STR_LEN];
snprintf(macstr, sizeof(macstr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
/* Find any existing state for this STA, or allocate new. */
if ((s = (client_state_t *)g_hash_table_lookup(clients, macstr)) == NULL) {
s = (client_state_t *)malloc(sizeof(*s));
memset(s, 0, sizeof(*s));
memcpy(s->macstr, macstr, sizeof(s->macstr));
s->first_seen = monotime();
g_hash_table_insert(clients, strdup(macstr), s);
}
return s;
}
static int HtMcsToNss(int rxmcs)
{
/* https://en.wikipedia.org/wiki/IEEE_802.11n-2009 */
switch(rxmcs) {
case 0 ... 7: return 1;
case 8 ... 15: return 2;
case 16 ... 23: return 3;
case 24 ... 31: return 4;
case 32: return 1;
case 33 ... 38: return 2;
case 39 ... 52: return 3;
case 53 ... 76: return 4;
default: return 0;
}
}
static int StationDumpCallback(struct nl_msg *msg, void *arg)
{
const char *ifname = (const char *)arg;
struct genlmsghdr *gh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1] = {0};
struct nlattr *si[NL80211_STA_INFO_MAX + 1] = {0};
uint8_t *mac;
client_state_t *state;
static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
[NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 },
[NL80211_STA_INFO_RX_BITRATE] = { .type = NLA_NESTED },
[NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 },
[NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
[NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED },
[NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 },
[NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
[NL80211_STA_INFO_TX_RETRIES] = { .type = NLA_U32 },
[NL80211_STA_INFO_TX_FAILED] = { .type = NLA_U32 },
[NL80211_STA_INFO_CONNECTED_TIME] = { .type = NLA_U32 },
[NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 },
[NL80211_STA_INFO_SIGNAL_AVG] = { .type = NLA_U8 },
[NL80211_STA_INFO_STA_FLAGS] = {
.minlen = sizeof(struct nl80211_sta_flag_update) },
#ifdef NL80211_RECENT_FIELDS
[NL80211_STA_INFO_RX_DROP_MISC] = { .type = NLA_U64 },
[NL80211_STA_INFO_EXPECTED_THROUGHPUT] = { .type = NLA_U32 },
#endif
};
if (nla_parse(tb, NL80211_ATTR_MAX,
genlmsg_attrdata(gh, 0), genlmsg_attrlen(gh, 0), NULL)) {
fprintf(stderr, "nla_parse failed.\n");
return NL_SKIP;
}
if (!tb[NL80211_ATTR_STA_INFO]) {
return NL_SKIP;
}
if (nla_parse_nested(si, NL80211_STA_INFO_MAX,
tb[NL80211_ATTR_STA_INFO],
stats_policy)) {
fprintf(stderr, "nla_parse_nested failed\n");
return NL_SKIP;
}
if (!tb[NL80211_ATTR_MAC]) {
fprintf(stderr, "No NL80211_ATTR_MAC\n");
return NL_SKIP;
}
mac = (uint8_t *)nla_data(tb[NL80211_ATTR_MAC]);
state = FindClientState(mac);
if (strcasecmp(state->ifname, ifname) != 0) {
/* Client moved from one interface to another */
ClearClientStateCounters(state);
}
state->last_seen = monotime();
snprintf(state->ifname, sizeof(state->ifname), "%s", ifname);
if (si[NL80211_STA_INFO_INACTIVE_TIME]) {
uint32_t inactive_msec = nla_get_u32(si[NL80211_STA_INFO_INACTIVE_TIME]);
double inactive_since = time(NULL) - ((double)inactive_msec / 1000.0);
state->inactive_msec = inactive_msec;
if ((fabs(inactive_since - state->inactive_since)) > 2.0) {
state->inactive_since = inactive_since;
}
}
if (si[NL80211_STA_INFO_RX_BITRATE]) {
int rx_ht_mcs=0, rx_vht_mcs=0, rx_vht_nss=0, rx_width=0, rx_short_gi=0;
int ht_nss;
int n = state->rx_sample_index + 1;
if (n >= MAX_SAMPLE_INDEX) n = 0;
state->rx_bitrate = GetBitrate(si[NL80211_STA_INFO_RX_BITRATE]);
GetMCS(si[NL80211_STA_INFO_RX_BITRATE], &rx_ht_mcs, &rx_vht_mcs,
&rx_width, &rx_short_gi, &rx_vht_nss);
state->rx_ht_mcs_samples[n] = rx_ht_mcs;
if (rx_ht_mcs > state->rx_max_ht_mcs) state->rx_max_ht_mcs = rx_ht_mcs;
ht_nss = HtMcsToNss(rx_ht_mcs);
state->rx_ht_nss_samples[n] = ht_nss;
if (ht_nss > state->rx_max_ht_nss) state->rx_max_ht_nss = ht_nss;
state->rx_vht_mcs_samples[n] = rx_vht_mcs;
if (rx_vht_mcs > state->rx_max_vht_mcs) state->rx_max_vht_mcs = rx_vht_mcs;
state->rx_vht_nss_samples[n] = rx_vht_nss;
if (rx_vht_nss > state->rx_max_vht_nss) state->rx_max_vht_nss = rx_vht_nss;
state->rx_short_gi_samples[n] = rx_short_gi;
if (rx_short_gi) state->ever_rx_short_gi = 1;
state->rx_width_samples[n] = rx_width;
if (rx_width > state->rx_max_width) state->rx_max_width = rx_width;
state->rx_sample_index = n;
}
if (si[NL80211_STA_INFO_RX_BYTES]) {
uint32_t last_rx_bytes = state->rx_bytes;
state->rx_bytes = nla_get_u32(si[NL80211_STA_INFO_RX_BYTES]);
state->rx_bytes64 += (state->rx_bytes - last_rx_bytes);
}
if (si[NL80211_STA_INFO_RX_PACKETS]) {
uint32_t last_rx_packets = state->rx_packets;
state->rx_packets = nla_get_u32(si[NL80211_STA_INFO_RX_PACKETS]);
state->rx_packets64 += (state->rx_packets - last_rx_packets);
}
if (si[NL80211_STA_INFO_TX_BITRATE]) {
int tx_ht_mcs=0, tx_vht_mcs=0, tx_vht_nss=0, tx_width=0, tx_short_gi=0;
int ht_nss;
int n = state->tx_sample_index + 1;
if (n >= MAX_SAMPLE_INDEX) n = 0;
state->tx_bitrate = GetBitrate(si[NL80211_STA_INFO_TX_BITRATE]);
GetMCS(si[NL80211_STA_INFO_TX_BITRATE], &tx_ht_mcs, &tx_vht_mcs,
&tx_width, &tx_short_gi, &tx_vht_nss);
state->tx_ht_mcs_samples[n] = tx_ht_mcs;
if (tx_ht_mcs > state->tx_max_ht_mcs) state->tx_max_ht_mcs = tx_ht_mcs;
ht_nss = HtMcsToNss(tx_ht_mcs);
state->tx_ht_nss_samples[n] = ht_nss;
if (ht_nss > state->tx_max_ht_nss) state->tx_max_ht_nss = ht_nss;
state->tx_vht_mcs_samples[n] = tx_vht_mcs;
if (tx_vht_mcs > state->tx_max_vht_mcs) state->tx_max_vht_mcs = tx_vht_mcs;
state->tx_vht_nss_samples[n] = tx_vht_nss;
if (tx_vht_nss > state->tx_max_vht_nss) state->tx_max_vht_nss = tx_vht_nss;
state->tx_short_gi_samples[n] = tx_short_gi;
if (tx_short_gi) state->ever_tx_short_gi = 1;
state->tx_width_samples[n] = tx_width;
if (tx_width > state->tx_max_width) state->tx_max_width = tx_width;
state->tx_sample_index = n;
}
if (si[NL80211_STA_INFO_TX_BYTES]) {
uint32_t last_tx_bytes = state->tx_bytes;
state->tx_bytes = nla_get_u32(si[NL80211_STA_INFO_TX_BYTES]);
state->tx_bytes64 += (state->tx_bytes - last_tx_bytes);
}
if (si[NL80211_STA_INFO_TX_PACKETS]) {
uint32_t last_tx_packets = state->tx_packets;
state->tx_packets = nla_get_u32(si[NL80211_STA_INFO_TX_PACKETS]);
state->tx_packets64 += (state->tx_packets - last_tx_packets);
}
if (si[NL80211_STA_INFO_TX_RETRIES]) {
uint32_t last_tx_retries = state->tx_retries;
state->tx_retries = nla_get_u32(si[NL80211_STA_INFO_TX_RETRIES]);
state->tx_retries64 += (state->tx_retries - last_tx_retries);
}
if (si[NL80211_STA_INFO_TX_FAILED]) {
uint32_t last_tx_failed = state->tx_failed;
state->tx_failed = nla_get_u32(si[NL80211_STA_INFO_TX_FAILED]);
state->tx_failed64 += (state->tx_failed - last_tx_failed);
}
if (si[NL80211_STA_INFO_CONNECTED_TIME]) {
state->connected_secs = nla_get_u32(si[NL80211_STA_INFO_CONNECTED_TIME]);
}
if (si[NL80211_STA_INFO_SIGNAL]) {
state->signal = (int8_t)nla_get_u8(si[NL80211_STA_INFO_SIGNAL]);
}
if (si[NL80211_STA_INFO_SIGNAL_AVG]) {
state->signal_avg = (int8_t)nla_get_u8(si[NL80211_STA_INFO_SIGNAL_AVG]);
}
if (si[NL80211_STA_INFO_STA_FLAGS]) {
struct nl80211_sta_flag_update *sta_flags;
sta_flags = (struct nl80211_sta_flag_update *)nla_data(
si[NL80211_STA_INFO_STA_FLAGS]);
#define BIT(x) ((sta_flags->mask & (1ULL<<(x))) ? 1 : 0)
state->authorized = BIT(NL80211_STA_FLAG_AUTHORIZED);
state->authenticated = BIT(NL80211_STA_FLAG_AUTHENTICATED);
state->preamble = BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
state->wmm_wme = BIT(NL80211_STA_FLAG_WME);
state->mfp = BIT(NL80211_STA_FLAG_MFP);
state->tdls_peer = BIT(NL80211_STA_FLAG_TDLS_PEER);
state->preamble_length = BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
#undef BIT
}
#ifdef NL80211_RECENT_FIELDS
if (si[NL80211_STA_INFO_RX_DROP_MISC]) {
state->rx_drop64 = nla_get_u64(si[NL80211_STA_INFO_RX_DROP_MISC]);
}
if (si[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) {
state->expected_mbps =
nla_get_u32(si[NL80211_STA_INFO_EXPECTED_THROUGHPUT]);
}
#endif
return NL_OK;
} /* StationDumpCallback */
void RequestAssociatedDevices(struct nl_sock *nlsk,
int nl80211_id, int n)
{
HandleNLCommand(nlsk, nl80211_id, n, StationDumpCallback,
NL80211_CMD_GET_STATION, NLM_F_DUMP);
} /* RequestAssociatedDevices */
static void ClearClientCounters(client_state_t *state)
{
/* Kernel cleared its counters when client re-joined the WLAN,
* clear out previous state as well. */
state->rx_bytes = 0;
state->rx_packets = 0;
state->tx_bytes = 0;
state->tx_packets = 0;
state->tx_retries = 0;
state->tx_failed = 0;
}
static gboolean AgeOutClient(gpointer key, gpointer value, gpointer user_data)
{
client_state_t *state = (client_state_t *)value;
time_t mono_now = monotime();
if ((mono_now - state->last_seen) > MAX_CLIENT_AGE_SECS) {
char filename[PATH_MAX];
snprintf(filename, sizeof(filename), "%s/%s", STATIONS_DIR, state->macstr);
unlink(filename);
return TRUE;
}
if (state->connected_secs < 60) {
/* If the client recently dropped off and came back, clear any counters
* we've been maintaining. */
ClearClientCounters(state);
}
return FALSE;
}
static void ConsolidateClientSamples(gpointer key, gpointer value,
gpointer user_data)
{
client_state_t *state = (client_state_t *)value;
int i;
uint8_t rx_ht_mcs=0, rx_vht_mcs=0, rx_width=0, rx_ht_nss=0;
uint8_t rx_vht_nss=0, rx_short_gi=0;
uint8_t tx_ht_mcs=0, tx_vht_mcs=0, tx_width=0, tx_ht_nss=0;
uint8_t tx_vht_nss=0, tx_short_gi=0;
for (i = 0; i < MAX_SAMPLE_INDEX; ++i) {
if (state->rx_ht_mcs_samples[i] > rx_ht_mcs) {
rx_ht_mcs = state->rx_ht_mcs_samples[i];
}
if (state->rx_vht_mcs_samples[i] > rx_vht_mcs) {
rx_vht_mcs = state->rx_vht_mcs_samples[i];
}
if (state->rx_width_samples[i] > rx_width) {
rx_width = state->rx_width_samples[i];
}
if (state->rx_ht_nss_samples[i] > rx_ht_nss) {
rx_ht_nss = state->rx_ht_nss_samples[i];
}
if (state->rx_vht_nss_samples[i] > rx_vht_nss) {
rx_vht_nss = state->rx_vht_nss_samples[i];
}
if (state->rx_short_gi_samples[i] > rx_short_gi) {
rx_short_gi = state->rx_short_gi_samples[i];
}
if (state->tx_ht_mcs_samples[i] > tx_ht_mcs) {
tx_ht_mcs = state->tx_ht_mcs_samples[i];
}
if (state->tx_vht_mcs_samples[i] > tx_vht_mcs) {
tx_vht_mcs = state->tx_vht_mcs_samples[i];
}
if (state->tx_width_samples[i] > tx_width) {
tx_width = state->tx_width_samples[i];
}
if (state->tx_ht_nss_samples[i] > tx_ht_nss) {
tx_ht_nss = state->tx_ht_nss_samples[i];
}
if (state->tx_vht_nss_samples[i] > tx_vht_nss) {
tx_vht_nss = state->tx_vht_nss_samples[i];
}
if (state->tx_short_gi_samples[i] > tx_short_gi) {
tx_short_gi = state->tx_short_gi_samples[i];
}
}
state->rx_ht_mcs = rx_ht_mcs;
state->rx_vht_mcs = rx_vht_mcs;
state->rx_width = rx_width;
state->rx_ht_nss = rx_ht_nss;
state->rx_vht_nss = rx_vht_nss;
state->rx_short_gi = rx_short_gi;
state->tx_ht_mcs = tx_ht_mcs;
state->tx_vht_mcs = tx_vht_mcs;
state->tx_width = tx_width;
state->tx_ht_nss = tx_ht_nss;
state->tx_vht_nss = tx_vht_nss;
state->tx_short_gi = tx_short_gi;
}
static void ClientStateToJson(gpointer key, gpointer value, gpointer user_data)
{
const client_state_t *state = (const client_state_t *)value;
char tmpfile[PATH_MAX];
char filename[PATH_MAX];
time_t mono_now = monotime();
FILE *f;
snprintf(tmpfile, sizeof(tmpfile), "%s/%s.new", STATIONS_DIR, state->macstr);
snprintf(filename, sizeof(filename), "%s/%s", STATIONS_DIR, state->macstr);
if ((f = fopen(tmpfile, "w+")) == NULL) {
char errbuf[80];
snprintf(errbuf, sizeof(errbuf), "fopen %s", tmpfile);
perror(errbuf);
return;
}
fprintf(f, "{\n");
fprintf(f, " \"addr\": \"%s\",\n", state->macstr);
fprintf(f, " \"inactive since\": %.3f,\n", state->inactive_since);
fprintf(f, " \"inactive msec\": %u,\n", state->inactive_msec);
fprintf(f, " \"active\": %s,\n",
((mono_now - state->last_seen) < 600) ? "true" : "false");
fprintf(f, " \"rx bitrate\": %u.%u,\n",
(state->rx_bitrate / 10), (state->rx_bitrate % 10));
fprintf(f, " \"rx bytes\": %u,\n", state->rx_bytes);
fprintf(f, " \"rx packets\": %u,\n", state->rx_packets);
fprintf(f, " \"tx bitrate\": %u.%u,\n",
(state->tx_bitrate / 10), (state->tx_bitrate % 10));
fprintf(f, " \"tx bytes\": %u,\n", state->tx_bytes);
fprintf(f, " \"tx packets\": %u,\n", state->tx_packets);
fprintf(f, " \"tx retries\": %u,\n", state->tx_retries);
fprintf(f, " \"tx failed\": %u,\n", state->tx_failed);
fprintf(f, " \"rx mcs\": %u,\n", state->rx_ht_mcs);
fprintf(f, " \"rx max mcs\": %u,\n", state->rx_max_ht_mcs);
fprintf(f, " \"rx vht mcs\": %u,\n", state->rx_vht_mcs);
fprintf(f, " \"rx max vht mcs\": %u,\n", state->rx_max_vht_mcs);
fprintf(f, " \"rx width\": %u,\n", state->rx_width);
fprintf(f, " \"rx max width\": %u,\n", state->rx_max_width);
fprintf(f, " \"rx ht_nss\": %u,\n", state->rx_ht_nss);
fprintf(f, " \"rx max ht_nss\": %u,\n", state->rx_max_ht_nss);
fprintf(f, " \"rx vht_nss\": %u,\n", state->rx_vht_nss);
fprintf(f, " \"rx max vht_nss\": %u,\n", state->rx_max_vht_nss);
#define BOOL(x) (x ? "true" : "false")
fprintf(f, " \"rx SHORT_GI\": %s,\n", BOOL(state->rx_short_gi));
fprintf(f, " \"rx SHORT_GI seen\": %s,\n", BOOL(state->ever_rx_short_gi));
#undef BOOL
fprintf(f, " \"signal\": %hhd,\n", state->signal);
fprintf(f, " \"signal_avg\": %hhd,\n", state->signal_avg);
#define BOOL(x) (x ? "yes" : "no")
fprintf(f, " \"authorized\": \"%s\",\n", BOOL(state->authorized));
fprintf(f, " \"authenticated\": \"%s\",\n", BOOL(state->authenticated));
fprintf(f, " \"preamble\": \"%s\",\n", BOOL(state->preamble));
fprintf(f, " \"wmm_wme\": \"%s\",\n", BOOL(state->wmm_wme));
fprintf(f, " \"mfp\": \"%s\",\n", BOOL(state->mfp));
fprintf(f, " \"tdls_peer\": \"%s\",\n", BOOL(state->tdls_peer));
#undef BOOL
fprintf(f, " \"preamble length\": \"%s\",\n",
(state->preamble_length ? "short" : "long"));
fprintf(f, " \"rx bytes64\": %" PRIu64 ",\n", state->rx_bytes64);
fprintf(f, " \"rx drop64\": %" PRIu64 ",\n", state->rx_drop64);
fprintf(f, " \"tx bytes64\": %" PRIu64 ",\n", state->tx_bytes64);
fprintf(f, " \"tx retries64\": %" PRIu64 ",\n", state->tx_retries64);
fprintf(f, " \"expected Mbps\": %u.%03u,\n",
(state->expected_mbps / 1000), (state->expected_mbps % 1000));
fprintf(f, " \"ifname\": \"%s\"\n", state->ifname);
fprintf(f, "}\n");
fclose(f);
if (rename(tmpfile, filename)) {
char errstr[160];
snprintf(errstr, sizeof(errstr), "%s: rename %s to %s",
__FUNCTION__, tmpfile, filename);
perror(errstr);
}
}
static void ClientStateToLog(gpointer key, gpointer value, gpointer user_data)
{
const client_state_t *state = (const client_state_t *)value;
const callback_data_t *cb_data = (const callback_data_t *)user_data;
time_t mono_now = cb_data->mono_now;
if (!state->authorized || !state->authenticated) {
/* Don't log about non-associated clients */
return;
}
if ((mono_now - state->first_seen) < 120) {
/* Allow data to accumulate before beginning to log it. */
return;
}
printf(
"%s %s %ld %" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64 ",%" PRIu64
" %c,%hhd,%hhd,%u,%u,%u,%u,%u,%d"
" %u,%u,%u,%u,%u,%d"
" %u,%u,%u,%u,%u,%d"
" %u,%u,%u,%u,%u,%d"
"\n",
state->macstr, state->ifname,
((mono_now - state->last_seen) + (state->inactive_msec / 1000)),
/* L2 traffic stats */
state->rx_bytes64, state->rx_drop64, state->tx_bytes64,
state->tx_retries64, state->tx_failed64,
/* L1 information */
(state->preamble_length ? 'S' : 'L'),
state->signal, state->signal_avg,
state->rx_ht_mcs, state->rx_ht_nss,
state->rx_vht_mcs, state->rx_vht_nss,
state->rx_width, state->rx_short_gi,
/* information about the maximum we've ever seen from this client. */
state->rx_max_ht_mcs, state->rx_max_ht_nss,
state->rx_max_vht_mcs, state->rx_max_vht_nss,
state->rx_max_width, state->ever_rx_short_gi,
state->tx_ht_mcs, state->tx_ht_nss,
state->tx_vht_mcs, state->tx_vht_nss,
state->tx_width, state->tx_short_gi,
/* information about the maximum we've ever seen from this client. */
state->tx_max_ht_mcs, state->tx_max_ht_nss,
state->tx_max_vht_mcs, state->tx_max_vht_nss,
state->tx_max_width, state->ever_tx_short_gi);
}
void ConsolidateAssociatedDevices()
{
g_hash_table_foreach_remove(clients, AgeOutClient, NULL);
g_hash_table_foreach(clients, ConsolidateClientSamples, NULL);
}
/* Walk through all Wifi clients, printing their info to JSON files. */
void UpdateAssociatedDevices()
{
g_hash_table_foreach(clients, ClientStateToJson, NULL);
}
void LogAssociatedDevices()
{
callback_data_t cb_data;
memset(&cb_data, 0, sizeof(cb_data));
cb_data.mono_now = monotime();
g_hash_table_foreach(clients, ClientStateToLog, &cb_data);
}
static int ieee80211_frequency_to_channel(int freq)
{
/* see 802.11-2007 17.3.8.3.2 and Annex J */
if (freq == 2484)
return 14;
else if (freq < 2484)
return (freq - 2407) / 5;
else if (freq >= 4910 && freq <= 4980)
return (freq - 4000) / 5;
else if (freq <= 45000) /* DMG band lower limit */
return (freq - 5000) / 5;
else if (freq >= 58320 && freq <= 64800)
return (freq - 56160) / 2160;
else
return 0;
}
static void print_ssid_escaped(FILE *f, int len, const uint8_t *data)
{
int i;
for (i = 0; i < len; i++) {
switch(data[i]) {
case '\\': fprintf(f, "\\\\"); break;
case '"': fprintf(f, "\\\""); break;
case '\b': fprintf(f, "\\b"); break;
case '\f': fprintf(f, "\\f"); break;
case '\n': fprintf(f, "\\n"); break;
case '\r': fprintf(f, "\\r"); break;
case '\t': fprintf(f, "\\t"); break;
default:
if ((data[i] <= 0x1f) || !isprint(data[i])) {
fprintf(f, "\\u00%02x", data[i]);
} else {
fprintf(f, "%c", data[i]);
}
break;
}
}
}
static int WlanInfoCallback(struct nl_msg *msg, void *arg)
{
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (tb_msg[NL80211_ATTR_MAC]) {
unsigned char *mac_addr = nla_data(tb_msg[NL80211_ATTR_MAC]);
fprintf(wifi_info_handle,
" \"BSSID\": \"%02x:%02x:%02x:%02x:%02x:%02x\",\n",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
}
if (tb_msg[NL80211_ATTR_SSID]) {
fprintf(wifi_info_handle, " \"SSID\": \"");
print_ssid_escaped(wifi_info_handle, nla_len(tb_msg[NL80211_ATTR_SSID]),
nla_data(tb_msg[NL80211_ATTR_SSID]));
fprintf(wifi_info_handle, "\",\n");
}
if (tb_msg[NL80211_ATTR_WIPHY_FREQ]) {
uint32_t freq = nla_get_u32(tb_msg[NL80211_ATTR_WIPHY_FREQ]);
fprintf(wifi_info_handle, " \"Channel\": %d,\n",
ieee80211_frequency_to_channel(freq));
}
return NL_SKIP;
}
void RequestWifiInfo(struct nl_sock *nlsk, int nl80211_id, int n)
{
HandleNLCommand(nlsk, nl80211_id, n, WlanInfoCallback,
NL80211_CMD_GET_INTERFACE, 0);
}
static int RegdomainCallback(struct nl_msg *msg, void *arg)
{
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
char *reg;
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb_msg[NL80211_ATTR_REG_ALPHA2]) {
return NL_SKIP;
}
if (!tb_msg[NL80211_ATTR_REG_RULES]) {
return NL_SKIP;
}
reg = nla_data(tb_msg[NL80211_ATTR_REG_ALPHA2]);
fprintf(wifi_info_handle, " \"RegDomain\": \"%c%c\",\n", reg[0], reg[1]);
return NL_SKIP;
}
void RequestRegdomain(struct nl_sock *nlsk, int nl80211_id)
{
HandleNLCommand(nlsk, nl80211_id, -1, RegdomainCallback,
NL80211_CMD_GET_REG, 0);
}
void UpdateWifiShow(struct nl_sock *nlsk, int nl80211_id, int n)
{
char tmpfile[PATH_MAX];
char filename[PATH_MAX];
char autofile[PATH_MAX];
const char *ifname = interfaces[n];
int done = 0;
struct stat buffer;
FILE *fptr;
if (!ifname || !ifname[0]) {
return;
}
snprintf(tmpfile, sizeof(tmpfile), "%s/%s.new", WIFIINFO_DIR, ifname);
snprintf(filename, sizeof(filename), "%s/%s", WIFIINFO_DIR, ifname);
if ((wifi_info_handle = fopen(tmpfile, "w+")) == NULL) {
perror("fopen");
return;
}
fprintf(wifi_info_handle, "{\n");
done = 0;
RequestWifiInfo(nlsk, nl80211_id, n);
ProcessNetlinkMessages(nlsk, &done);
done = 0;
RequestRegdomain(nlsk, nl80211_id);
ProcessNetlinkMessages(nlsk, &done);
snprintf(autofile, sizeof(autofile), "/tmp/autochan.%s", ifname);
if (stat(autofile, &buffer) == 0) {
fprintf(wifi_info_handle, " \"AutoChannel\": true,\n");
} else {
fprintf(wifi_info_handle, " \"AutoChannel\": false,\n");
}
snprintf(autofile, sizeof(autofile), "/tmp/autotype.%s", ifname);
if ((fptr = fopen(autofile, "r")) == NULL) {
fprintf(wifi_info_handle, " \"AutoType\": \"LOW\"\n");
} else {
char buf[24];
if (fgets(buf, sizeof(buf), fptr) != NULL) {
fprintf(wifi_info_handle, " \"AutoType\": \"%s\"\n", buf);
}
fclose(fptr);
fptr = NULL;
}
fprintf(wifi_info_handle, "}\n");
fclose(wifi_info_handle);
wifi_info_handle = NULL;
if (rename(tmpfile, filename)) {
char errbuf[256];
snprintf(errbuf, sizeof(errbuf), "%s: rename %s to %s : errno=%d",
__FUNCTION__, tmpfile, filename, errno);
perror(errbuf);
}
}
#ifndef UNIT_TESTS
static void TouchUpdateFile()
{
char filename[PATH_MAX];
int fd;
snprintf(filename, sizeof(filename), "%s/updated.new", STATIONS_DIR);
if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) < 0) {
perror("TouchUpdatedFile open");
exit(1);
}
if (write(fd, "updated", 7) < 7) {
perror("TouchUpdatedFile write");
exit(1);
}
close(fd);
} /* TouchUpdateFile */
int main(int argc, char **argv)
{
int done = 0;
int nl80211_id = -1;
struct nl_sock *nlsk = NULL;
struct rlimit rlim;
memset(&rlim, 0, sizeof(rlim));
if (getrlimit(RLIMIT_AS, &rlim)) {
perror("getrlimit RLIMIT_AS failed");
exit(1);
}
rlim.rlim_cur = 6 * 1024 * 1024;
if (setrlimit(RLIMIT_AS, &rlim)) {
perror("getrlimit RLIMIT_AS failed");
exit(1);
}
setlinebuf(stdout);
clients = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
nlsk = InitNetlinkSocket();
if (nl_socket_modify_cb(nlsk, NL_CB_FINISH, NL_CB_CUSTOM, NlFinish, &done)) {
fprintf(stderr, "nl_socket_modify_cb failed\n");
exit(1);
}
if ((nl80211_id = genl_ctrl_resolve(nlsk, "nl80211")) < 0) {
fprintf(stderr, "genl_ctrl_resolve failed\n");
exit(1);
}
while (1) {
int i, j;
/* Check if new interfaces have appeared */
ninterfaces = 0;
RequestInterfaceList(nlsk, nl80211_id);
ProcessNetlinkMessages(nlsk, &done);
for (i = 0; i < ninterfaces; ++i) {
UpdateWifiShow(nlsk, nl80211_id, i);
}
/* Accumulate MAX_SAMPLE_INDEX samples between calls to
* LogAssociatedDevices() */
for (i = 0; i < MAX_SAMPLE_INDEX; ++i) {
sleep(2);
for (j = 0; j < ninterfaces; ++j) {
done = 0;
RequestAssociatedDevices(nlsk, nl80211_id, j);
ProcessNetlinkMessages(nlsk, &done);
ConsolidateAssociatedDevices();
UpdateAssociatedDevices();
}
TouchUpdateFile();
}
LogAssociatedDevices();
}
exit(0);
}
#endif /* UNIT_TESTS */