wifi_files: client interface stats, convert to C++
1. client interface stats
Rearrange code to move stats to a separate struct.
For AP, output every associated client.
For STA, output stats of local client interface.
2. convert to C++, remove libglib dependency
libglib is one of the largest files in the GFMN100
filesystem, and appears to only be used by wifi_files
for g_hash support. Convert wifi_files to C++ and use
unordered_map instead. libstdc++ is even larger than
libglib but is used by a bunch of stuff in the system
already, so we're better off being dependant on it
than on glib.
Change-Id: Ib454830cf6298729c23cf1caddc0ff880f573a66
diff --git a/cmds/Makefile b/cmds/Makefile
index aabebe5..43362dc 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -252,9 +252,9 @@
netusage: CFLAGS += -Wno-sign-compare
host-netusage_test: host-netusage_test.o
wifi_files: wifi_files.o
-wifi_files: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
+wifi_files: LIBS += -lnl-3 -lnl-genl-3 -lstdc++ -lm
host-wifi_files_test: host-wifi_files_test.o
-host-wifi_files_test: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
+host-wifi_files_test: LIBS += -lnl-3 -lnl-genl-3 -lstdc++ -lm
dhcpvendortax: dhcpvendortax.o dhcpvendorlookup.tmp.o
dhcpvendorlookup.tmp.c: dhcpvendorlookup.gperf
$(GPERF) -G -C -t -L ANSI-C -N exact_match -K vendor_class \
diff --git a/cmds/wifi_files.c b/cmds/wifi_files.c
deleted file mode 100644
index 1700cda..0000000
--- a/cmds/wifi_files.c
+++ /dev/null
@@ -1,1206 +0,0 @@
-/*
- * 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 */
diff --git a/cmds/wifi_files.cc b/cmds/wifi_files.cc
new file mode 100644
index 0000000..95ca209
--- /dev/null
+++ b/cmds/wifi_files.cc
@@ -0,0 +1,1471 @@
+/*
+ * 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.
+ */
+
+/* for inttypes.h */
+#define __STDC_FORMAT_MACROS
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <linux/if_ether.h>
+#include <linux/nl80211.h>
+#include <math.h>
+#include <net/if.h>
+#include <net/if_arp.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>
+
+#include <cinttypes>
+#include <string>
+#include <tr1/unordered_map>
+
+#ifndef UNIT_TESTS
+#define STATIONS_DIR "/tmp/stations"
+#define WIFIINFO_DIR "/tmp/wifi/wifiinfo"
+#endif
+
+
+#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
+
+
+typedef struct wifi_stats {
+ 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;
+
+ 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];
+ int8_t signal_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;
+
+ double max_signal;
+ double min_signal;
+ double avg_signal;
+
+ 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;
+
+ double inactive_since;
+ uint32_t inactive_msec;
+ uint32_t connected_secs;
+} wifi_stats_t;
+
+
+#define MAC_STR_LEN 18
+#define IFNAME_STR_LEN 16
+
+
+/*
+ * 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 {
+ char macstr[MAC_STR_LEN];
+ char ifname[IFNAME_STR_LEN];
+
+ time_t first_seen; // CLOCK_MONOTONIC
+ time_t last_seen; // CLOCK_MONOTONIC
+
+ wifi_stats_t s;
+} client_state_t;
+
+
+// Hash table of known Wifi clients.
+typedef std::tr1::unordered_map<std::string, client_state_t *> ClientMapType;
+ClientMapType clients;
+
+
+/* Data about each wifi interface. */
+typedef struct wifi_interface {
+ int ifindex;
+ char ifname[IFNAME_STR_LEN];
+ uint8_t bssid[ETH_ALEN];
+
+ int is_client:1;
+ uint32_t freq;
+
+ wifi_stats_t s;
+} wifi_interface_t;
+
+
+/* List of wifi interfaces in the system. */
+#define NINTERFACES 16
+wifi_interface_t interfaces[NINTERFACES];
+int ninterfaces = 0;
+
+
+typedef struct callback_data {
+ time_t mono_now;
+} callback_data_t;
+
+
+/* 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 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];
+
+ memset(&rate_policy, 0, sizeof(rate_policy));
+ rate_policy[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];
+
+ memset(&rate_policy, 0, sizeof(rate_policy));
+ rate_policy[NL80211_RATE_INFO_MCS].type = NLA_U8;
+ rate_policy[NL80211_RATE_INFO_VHT_MCS].type = NLA_U8;
+ rate_policy[NL80211_RATE_INFO_VHT_NSS].type = NLA_U8;
+ rate_policy[NL80211_RATE_INFO_40_MHZ_WIDTH].type = NLA_FLAG;
+ rate_policy[NL80211_RATE_INFO_80_MHZ_WIDTH].type = NLA_FLAG;
+ rate_policy[NL80211_RATE_INFO_80P80_MHZ_WIDTH].type = NLA_FLAG;
+ rate_policy[NL80211_RATE_INFO_160_MHZ_WIDTH].type = NLA_FLAG;
+ rate_policy[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 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 ParseWifiStats(struct nlattr *sta_info, wifi_stats_t *stats)
+{
+ struct nlattr *si[NL80211_STA_INFO_MAX + 1] = {0};
+ static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1];
+
+ memset(&stats_policy, 0, sizeof(stats_policy));
+ stats_policy[NL80211_STA_INFO_INACTIVE_TIME].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_RX_BITRATE].type = NLA_NESTED;
+ stats_policy[NL80211_STA_INFO_RX_BYTES].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_RX_PACKETS].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_TX_BITRATE].type = NLA_NESTED;
+ stats_policy[NL80211_STA_INFO_TX_BYTES].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_TX_PACKETS].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_TX_RETRIES].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_TX_FAILED].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_CONNECTED_TIME].type = NLA_U32;
+ stats_policy[NL80211_STA_INFO_SIGNAL].type = NLA_U8;
+ stats_policy[NL80211_STA_INFO_SIGNAL_AVG].type = NLA_U8;
+ stats_policy[NL80211_STA_INFO_STA_FLAGS].minlen = sizeof(struct nl80211_sta_flag_update);
+
+#ifdef NL80211_RECENT_FIELDS
+ stats_policy[NL80211_STA_INFO_RX_DROP_MISC].type = NLA_U64;
+ stats_policy[NL80211_STA_INFO_EXPECTED_THROUGHPUT].type = NLA_U32;
+#endif
+
+ if (nla_parse_nested(si, NL80211_STA_INFO_MAX, sta_info, stats_policy)) {
+ fprintf(stderr, "nla_parse_nested failed\n");
+ return NL_SKIP;
+ }
+
+ 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);
+
+ stats->inactive_msec = inactive_msec;
+ if ((fabs(inactive_since - stats->inactive_since)) > 2.0) {
+ stats->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 = stats->rx_sample_index + 1;
+
+ if (n >= MAX_SAMPLE_INDEX) n = 0;
+
+ stats->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);
+
+ stats->rx_ht_mcs_samples[n] = rx_ht_mcs;
+ if (rx_ht_mcs > stats->rx_max_ht_mcs) stats->rx_max_ht_mcs = rx_ht_mcs;
+
+ ht_nss = HtMcsToNss(rx_ht_mcs);
+ stats->rx_ht_nss_samples[n] = ht_nss;
+ if (ht_nss > stats->rx_max_ht_nss) stats->rx_max_ht_nss = ht_nss;
+
+ stats->rx_vht_mcs_samples[n] = rx_vht_mcs;
+ if (rx_vht_mcs > stats->rx_max_vht_mcs) stats->rx_max_vht_mcs = rx_vht_mcs;
+
+ stats->rx_vht_nss_samples[n] = rx_vht_nss;
+ if (rx_vht_nss > stats->rx_max_vht_nss) stats->rx_max_vht_nss = rx_vht_nss;
+
+ stats->rx_short_gi_samples[n] = rx_short_gi;
+ if (rx_short_gi) stats->ever_rx_short_gi = 1;
+
+ stats->rx_width_samples[n] = rx_width;
+ if (rx_width > stats->rx_max_width) stats->rx_max_width = rx_width;
+
+ if (si[NL80211_STA_INFO_SIGNAL]) {
+ int8_t signal = (int8_t)nla_get_u8(si[NL80211_STA_INFO_SIGNAL]);
+ stats->signal_samples[n] = signal;
+ }
+
+ stats->rx_sample_index = n;
+ }
+ if (si[NL80211_STA_INFO_RX_BYTES]) {
+ uint32_t last_rx_bytes = stats->rx_bytes;
+ stats->rx_bytes = nla_get_u32(si[NL80211_STA_INFO_RX_BYTES]);
+ stats->rx_bytes64 += (stats->rx_bytes - last_rx_bytes);
+ }
+ if (si[NL80211_STA_INFO_RX_PACKETS]) {
+ uint32_t last_rx_packets = stats->rx_packets;
+ stats->rx_packets = nla_get_u32(si[NL80211_STA_INFO_RX_PACKETS]);
+ stats->rx_packets64 += (stats->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 = stats->tx_sample_index + 1;
+
+ if (n >= MAX_SAMPLE_INDEX) n = 0;
+
+ stats->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);
+
+ stats->tx_ht_mcs_samples[n] = tx_ht_mcs;
+ if (tx_ht_mcs > stats->tx_max_ht_mcs) stats->tx_max_ht_mcs = tx_ht_mcs;
+
+ ht_nss = HtMcsToNss(tx_ht_mcs);
+ stats->tx_ht_nss_samples[n] = ht_nss;
+ if (ht_nss > stats->tx_max_ht_nss) stats->tx_max_ht_nss = ht_nss;
+
+ stats->tx_vht_mcs_samples[n] = tx_vht_mcs;
+ if (tx_vht_mcs > stats->tx_max_vht_mcs) stats->tx_max_vht_mcs = tx_vht_mcs;
+
+ stats->tx_vht_nss_samples[n] = tx_vht_nss;
+ if (tx_vht_nss > stats->tx_max_vht_nss) stats->tx_max_vht_nss = tx_vht_nss;
+
+ stats->tx_short_gi_samples[n] = tx_short_gi;
+ if (tx_short_gi) stats->ever_tx_short_gi = 1;
+
+ stats->tx_width_samples[n] = tx_width;
+ if (tx_width > stats->tx_max_width) stats->tx_max_width = tx_width;
+
+ stats->tx_sample_index = n;
+ }
+ if (si[NL80211_STA_INFO_TX_BYTES]) {
+ uint32_t last_tx_bytes = stats->tx_bytes;
+ stats->tx_bytes = nla_get_u32(si[NL80211_STA_INFO_TX_BYTES]);
+ stats->tx_bytes64 += (stats->tx_bytes - last_tx_bytes);
+ }
+ if (si[NL80211_STA_INFO_TX_PACKETS]) {
+ uint32_t last_tx_packets = stats->tx_packets;
+ stats->tx_packets = nla_get_u32(si[NL80211_STA_INFO_TX_PACKETS]);
+ stats->tx_packets64 += (stats->tx_packets - last_tx_packets);
+ }
+ if (si[NL80211_STA_INFO_TX_RETRIES]) {
+ uint32_t last_tx_retries = stats->tx_retries;
+ stats->tx_retries = nla_get_u32(si[NL80211_STA_INFO_TX_RETRIES]);
+ stats->tx_retries64 += (stats->tx_retries - last_tx_retries);
+ }
+ if (si[NL80211_STA_INFO_TX_FAILED]) {
+ uint32_t last_tx_failed = stats->tx_failed;
+ stats->tx_failed = nla_get_u32(si[NL80211_STA_INFO_TX_FAILED]);
+ stats->tx_failed64 += (stats->tx_failed - last_tx_failed);
+ }
+ if (si[NL80211_STA_INFO_CONNECTED_TIME]) {
+ stats->connected_secs = nla_get_u32(si[NL80211_STA_INFO_CONNECTED_TIME]);
+ }
+ if (si[NL80211_STA_INFO_SIGNAL]) {
+ stats->signal = (int8_t)nla_get_u8(si[NL80211_STA_INFO_SIGNAL]);
+ }
+ if (si[NL80211_STA_INFO_SIGNAL_AVG]) {
+ stats->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)
+ stats->authorized = BIT(NL80211_STA_FLAG_AUTHORIZED);
+ stats->authenticated = BIT(NL80211_STA_FLAG_AUTHENTICATED);
+ stats->preamble = BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
+ stats->wmm_wme = BIT(NL80211_STA_FLAG_WME);
+ stats->mfp = BIT(NL80211_STA_FLAG_MFP);
+ stats->tdls_peer = BIT(NL80211_STA_FLAG_TDLS_PEER);
+ stats->preamble_length = BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
+ #undef BIT
+ }
+
+#ifdef NL80211_RECENT_FIELDS
+ if (si[NL80211_STA_INFO_RX_DROP_MISC]) {
+ stats->rx_drop64 = nla_get_u64(si[NL80211_STA_INFO_RX_DROP_MISC]);
+ }
+ if (si[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) {
+ stats->expected_mbps =
+ nla_get_u32(si[NL80211_STA_INFO_EXPECTED_THROUGHPUT]);
+ }
+#endif
+
+ return NL_OK;
+} /* ParseWifiStats */
+
+
+static int InterfaceListCallback(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *il[NL80211_ATTR_MAX + 1];
+ struct genlmsghdr *gh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+ wifi_interface_t *wif = &interfaces[ninterfaces];
+ const char *name;
+
+ nla_parse(il, NL80211_ATTR_MAX, genlmsg_attrdata(gh, 0),
+ genlmsg_attrlen(gh, 0), NULL);
+
+ if (!il[NL80211_ATTR_IFNAME]) {
+ return NL_SKIP;
+ }
+
+ name = nla_get_string(il[NL80211_ATTR_IFNAME]);
+ snprintf(wif->ifname, sizeof(wif->ifname), "%s", name);
+ wif->ifindex = GetIfIndex(name);
+ ninterfaces++;
+
+ if (il[NL80211_ATTR_STA_INFO]) {
+ ParseWifiStats(il[NL80211_ATTR_STA_INFO], &wif->s);
+ }
+
+ return NL_OK;
+}
+
+
+static int BssInfoCallback(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *nl[NL80211_ATTR_MAX + 1];
+ struct nlattr *bi[NL80211_BSS_MAX + 1];
+ struct genlmsghdr *gh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+ wifi_interface_t *wif = NULL;
+ uint32_t ifindex;
+ int i;
+ static struct nla_policy bss_policy[NL80211_BSS_MAX + 1];
+
+ memset(&bss_policy, 0, sizeof(bss_policy));
+ bss_policy[NL80211_BSS_TSF].type = NLA_U64;
+ bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;
+ bss_policy[NL80211_BSS_BSSID].type = NLA_UNSPEC;
+ bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16;
+ bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16;
+ bss_policy[NL80211_BSS_INFORMATION_ELEMENTS].type = NLA_UNSPEC;
+ bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32;
+ bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
+ bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
+
+ nla_parse(nl, NL80211_ATTR_MAX, genlmsg_attrdata(gh, 0),
+ genlmsg_attrlen(gh, 0), NULL);
+
+ if (!nl[NL80211_ATTR_IFINDEX]) {
+ return NL_SKIP;
+ }
+
+ ifindex = nla_get_u32(nl[NL80211_ATTR_IFINDEX]);
+ for (i = 0; i < ninterfaces; ++i) {
+ if (interfaces[i].ifindex == (int)ifindex) {
+ wif = &interfaces[i];
+ break;
+ }
+ }
+ if (wif == NULL) {
+ return NL_SKIP;
+ }
+
+ if (nla_parse_nested(bi, NL80211_BSS_MAX, nl[NL80211_ATTR_BSS],
+ bss_policy)) {
+ return NL_SKIP;
+ }
+
+ if (!bi[NL80211_BSS_BSSID] || !bi[NL80211_BSS_STATUS]) {
+ return NL_SKIP;
+ }
+
+ wif->is_client = 0;
+ if (bi[NL80211_BSS_STATUS]) {
+ uint32_t status = nla_get_u32(bi[NL80211_BSS_STATUS]);
+ wif->is_client = (status == NL80211_BSS_STATUS_ASSOCIATED) ? 1 : 0;
+ }
+ if (bi[NL80211_BSS_BSSID]) {
+ memcpy(wif->bssid, nla_data(bi[NL80211_BSS_BSSID]), sizeof(wif->bssid));
+ }
+ if (bi[NL80211_BSS_FREQUENCY]) {
+ wif->freq = nla_get_u32(bi[NL80211_BSS_FREQUENCY]);
+ }
+
+ return NL_OK;
+}
+
+
+static int InterfaceInfoCallback(struct nl_msg *msg, void *arg)
+{
+ struct nlattr *si[NL80211_ATTR_MAX + 1];
+ struct genlmsghdr *gh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+ wifi_interface_t *wif = NULL;
+ uint32_t ifindex;
+ int i;
+
+ nla_parse(si, NL80211_ATTR_MAX, genlmsg_attrdata(gh, 0),
+ genlmsg_attrlen(gh, 0), NULL);
+
+ if (!si[NL80211_ATTR_IFINDEX]) {
+ return NL_SKIP;
+ }
+
+ ifindex = nla_get_u32(si[NL80211_ATTR_IFINDEX]);
+ for (i = 0; i < ninterfaces; ++i) {
+ if (interfaces[i].ifindex == (int)ifindex) {
+ wif = &interfaces[i];
+ break;
+ }
+ }
+
+ if (wif == NULL) {
+ return NL_SKIP;
+ }
+
+ if (si[NL80211_ATTR_STA_INFO]) {
+ ParseWifiStats(si[NL80211_ATTR_STA_INFO], &wif->s);
+ }
+
+ return NL_OK;
+}
+
+
+static void HandleNLCommand(struct nl_sock *nlsk, int nl80211_id,
+ int n, const uint8_t *bssid,
+ int cb(struct nl_msg *, void *),
+ int cmd, int flag)
+{
+ struct nl_msg *msg;
+ int ifindex = n >= 0 ? interfaces[n].ifindex : -1;
+ const char *ifname = n >= 0 ? interfaces[n].ifname : 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 (bssid && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, bssid)) {
+ fprintf(stderr, "NL80211_CMD_GET_STATION put MAC_ADDR 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, NULL, InterfaceListCallback,
+ NL80211_CMD_GET_INTERFACE, NLM_F_DUMP);
+}
+
+
+void RequestInterfaceInfo(struct nl_sock *nlsk, int nl80211_id, int n)
+{
+ int done = 0;
+ wifi_interface_t *wif = &interfaces[n];
+
+ HandleNLCommand(nlsk, nl80211_id, n, NULL, BssInfoCallback,
+ NL80211_CMD_GET_SCAN, NLM_F_DUMP);
+ ProcessNetlinkMessages(nlsk, &done);
+
+ if (wif->is_client) {
+ done = 0;
+ HandleNLCommand(nlsk, nl80211_id, n, wif->bssid, InterfaceInfoCallback,
+ NL80211_CMD_GET_STATION, 0);
+ ProcessNetlinkMessages(nlsk, &done);
+ }
+}
+
+
+int NlFinish(struct nl_msg *msg, void *arg)
+{
+ int *ret = (int *)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 client_state_t *FindClientState(const uint8_t mac[6])
+{
+ ClientMapType::iterator it;
+ 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 ((it = clients.find(macstr)) == clients.end()) {
+ s = (client_state_t *)malloc(sizeof(*s));
+ memset(s, 0, sizeof(*s));
+ memcpy(s->macstr, macstr, sizeof(s->macstr));
+ s->first_seen = monotime();
+ clients[std::string(macstr)] = s;
+ } else {
+ s = it->second;
+ }
+
+ return s;
+}
+
+
+static int StationDumpCallback(struct nl_msg *msg, void *arg)
+{
+ const char *ifname = (const char *)arg;
+ struct genlmsghdr *gh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *tb[NL80211_ATTR_MAX + 1] = {0};
+ uint8_t *mac;
+ client_state_t *state;
+
+ 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 (!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 (ParseWifiStats(tb[NL80211_ATTR_STA_INFO], &state->s) != NL_OK) {
+ return NL_SKIP;
+ }
+
+ return NL_OK;
+} /* StationDumpCallback */
+
+
+void RequestAssociatedDevices(struct nl_sock *nlsk,
+ int nl80211_id, int n)
+{
+ HandleNLCommand(nlsk, nl80211_id, n, NULL, 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->s.rx_bytes = 0;
+ state->s.rx_packets = 0;
+ state->s.tx_bytes = 0;
+ state->s.tx_packets = 0;
+ state->s.tx_retries = 0;
+ state->s.tx_failed = 0;
+}
+
+
+static int AgeOutClient(client_state_t *state)
+{
+ 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 1;
+ }
+
+ if (state->s.connected_secs < 60) {
+ /* If the client recently dropped off and came back, clear any counters
+ * we've been maintaining. */
+ ClearClientCounters(state);
+ }
+
+ return 0;
+}
+
+
+static void ConsolidateSamples(wifi_stats_t *stats)
+{
+ 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;
+ double max_signal = -1000.0;
+ double min_signal = 0.0;
+ double sum_signal = 0.0;
+
+ for (i = 0; i < MAX_SAMPLE_INDEX; ++i) {
+ if (stats->rx_ht_mcs_samples[i] > rx_ht_mcs) {
+ rx_ht_mcs = stats->rx_ht_mcs_samples[i];
+ }
+ if (stats->rx_vht_mcs_samples[i] > rx_vht_mcs) {
+ rx_vht_mcs = stats->rx_vht_mcs_samples[i];
+ }
+ if (stats->rx_width_samples[i] > rx_width) {
+ rx_width = stats->rx_width_samples[i];
+ }
+ if (stats->rx_ht_nss_samples[i] > rx_ht_nss) {
+ rx_ht_nss = stats->rx_ht_nss_samples[i];
+ }
+ if (stats->rx_vht_nss_samples[i] > rx_vht_nss) {
+ rx_vht_nss = stats->rx_vht_nss_samples[i];
+ }
+ if (stats->rx_short_gi_samples[i] > rx_short_gi) {
+ rx_short_gi = stats->rx_short_gi_samples[i];
+ }
+
+ if (stats->signal_samples[i] > max_signal) {
+ max_signal = stats->signal_samples[i];
+ }
+ if (stats->signal_samples[i] < min_signal) {
+ min_signal = stats->signal_samples[i];
+ }
+ sum_signal += stats->signal_samples[i];
+
+ if (stats->tx_ht_mcs_samples[i] > tx_ht_mcs) {
+ tx_ht_mcs = stats->tx_ht_mcs_samples[i];
+ }
+ if (stats->tx_vht_mcs_samples[i] > tx_vht_mcs) {
+ tx_vht_mcs = stats->tx_vht_mcs_samples[i];
+ }
+ if (stats->tx_width_samples[i] > tx_width) {
+ tx_width = stats->tx_width_samples[i];
+ }
+ if (stats->tx_ht_nss_samples[i] > tx_ht_nss) {
+ tx_ht_nss = stats->tx_ht_nss_samples[i];
+ }
+ if (stats->tx_vht_nss_samples[i] > tx_vht_nss) {
+ tx_vht_nss = stats->tx_vht_nss_samples[i];
+ }
+ if (stats->tx_short_gi_samples[i] > tx_short_gi) {
+ tx_short_gi = stats->tx_short_gi_samples[i];
+ }
+ }
+
+ stats->rx_ht_mcs = rx_ht_mcs;
+ stats->rx_vht_mcs = rx_vht_mcs;
+ stats->rx_width = rx_width;
+ stats->rx_ht_nss = rx_ht_nss;
+ stats->rx_vht_nss = rx_vht_nss;
+ stats->rx_short_gi = rx_short_gi;
+
+ stats->max_signal = max_signal;
+ stats->min_signal = min_signal;
+ stats->avg_signal = sum_signal / (double)MAX_SAMPLE_INDEX;
+
+ stats->tx_ht_mcs = tx_ht_mcs;
+ stats->tx_vht_mcs = tx_vht_mcs;
+ stats->tx_width = tx_width;
+ stats->tx_ht_nss = tx_ht_nss;
+ stats->tx_vht_nss = tx_vht_nss;
+ stats->tx_short_gi = tx_short_gi;
+}
+
+
+static void ClientStateToJson(const client_state_t *state)
+{
+ 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->s.inactive_since);
+ fprintf(f, " \"inactive msec\": %u,\n", state->s.inactive_msec);
+
+ fprintf(f, " \"active\": %s,\n",
+ ((mono_now - state->last_seen) < 600) ? "true" : "false");
+
+ fprintf(f, " \"rx bitrate\": %u.%u,\n",
+ (state->s.rx_bitrate / 10), (state->s.rx_bitrate % 10));
+ fprintf(f, " \"rx bytes\": %u,\n", state->s.rx_bytes);
+ fprintf(f, " \"rx packets\": %u,\n", state->s.rx_packets);
+
+ fprintf(f, " \"tx bitrate\": %u.%u,\n",
+ (state->s.tx_bitrate / 10), (state->s.tx_bitrate % 10));
+ fprintf(f, " \"tx bytes\": %u,\n", state->s.tx_bytes);
+ fprintf(f, " \"tx packets\": %u,\n", state->s.tx_packets);
+ fprintf(f, " \"tx retries\": %u,\n", state->s.tx_retries);
+ fprintf(f, " \"tx failed\": %u,\n", state->s.tx_failed);
+
+ fprintf(f, " \"rx mcs\": %u,\n", state->s.rx_ht_mcs);
+ fprintf(f, " \"rx max mcs\": %u,\n", state->s.rx_max_ht_mcs);
+ fprintf(f, " \"rx vht mcs\": %u,\n", state->s.rx_vht_mcs);
+ fprintf(f, " \"rx max vht mcs\": %u,\n", state->s.rx_max_vht_mcs);
+ fprintf(f, " \"rx width\": %u,\n", state->s.rx_width);
+ fprintf(f, " \"rx max width\": %u,\n", state->s.rx_max_width);
+ fprintf(f, " \"rx ht_nss\": %u,\n", state->s.rx_ht_nss);
+ fprintf(f, " \"rx max ht_nss\": %u,\n", state->s.rx_max_ht_nss);
+ fprintf(f, " \"rx vht_nss\": %u,\n", state->s.rx_vht_nss);
+ fprintf(f, " \"rx max vht_nss\": %u,\n", state->s.rx_max_vht_nss);
+
+ #define BOOL(x) (x ? "true" : "false")
+ fprintf(f, " \"rx SHORT_GI\": %s,\n", BOOL(state->s.rx_short_gi));
+ fprintf(f, " \"rx SHORT_GI seen\": %s,\n", BOOL(state->s.ever_rx_short_gi));
+ #undef BOOL
+
+ fprintf(f, " \"signal\": %hhd,\n", state->s.signal);
+ fprintf(f, " \"signal_avg\": %hhd,\n", state->s.signal_avg);
+
+ #define BOOL(x) (x ? "yes" : "no")
+ fprintf(f, " \"authorized\": \"%s\",\n", BOOL(state->s.authorized));
+ fprintf(f, " \"authenticated\": \"%s\",\n", BOOL(state->s.authenticated));
+ fprintf(f, " \"preamble\": \"%s\",\n", BOOL(state->s.preamble));
+ fprintf(f, " \"wmm_wme\": \"%s\",\n", BOOL(state->s.wmm_wme));
+ fprintf(f, " \"mfp\": \"%s\",\n", BOOL(state->s.mfp));
+ fprintf(f, " \"tdls_peer\": \"%s\",\n", BOOL(state->s.tdls_peer));
+ #undef BOOL
+
+ fprintf(f, " \"preamble length\": \"%s\",\n",
+ (state->s.preamble_length ? "short" : "long"));
+
+ fprintf(f, " \"rx bytes64\": %" PRIu64 ",\n", state->s.rx_bytes64);
+ fprintf(f, " \"rx drop64\": %" PRIu64 ",\n", state->s.rx_drop64);
+ fprintf(f, " \"tx bytes64\": %" PRIu64 ",\n", state->s.tx_bytes64);
+ fprintf(f, " \"tx retries64\": %" PRIu64 ",\n", state->s.tx_retries64);
+ fprintf(f, " \"expected Mbps\": %u.%03u,\n",
+ (state->s.expected_mbps / 1000), (state->s.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(const client_state_t *state, time_t mono_now)
+{
+ if (!state->s.authorized || !state->s.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"
+ " rssi:%0.2f/%0.2f/%0.2f"
+ "\n",
+ state->macstr, state->ifname,
+ ((mono_now - state->last_seen) + (state->s.inactive_msec / 1000)),
+
+ /* L2 traffic stats */
+ state->s.rx_bytes64, state->s.rx_drop64, state->s.tx_bytes64,
+ state->s.tx_retries64, state->s.tx_failed64,
+
+ /* L1 information */
+ (state->s.preamble_length ? 'S' : 'L'),
+ state->s.signal, state->s.signal_avg,
+ state->s.rx_ht_mcs, state->s.rx_ht_nss,
+ state->s.rx_vht_mcs, state->s.rx_vht_nss,
+ state->s.rx_width, state->s.rx_short_gi,
+
+ /* information about the maximum we've ever seen from this client. */
+ state->s.rx_max_ht_mcs, state->s.rx_max_ht_nss,
+ state->s.rx_max_vht_mcs, state->s.rx_max_vht_nss,
+ state->s.rx_max_width, state->s.ever_rx_short_gi,
+
+ state->s.tx_ht_mcs, state->s.tx_ht_nss,
+ state->s.tx_vht_mcs, state->s.tx_vht_nss,
+ state->s.tx_width, state->s.tx_short_gi,
+
+ /* information about the maximum we've ever seen from this client. */
+ state->s.tx_max_ht_mcs, state->s.tx_max_ht_nss,
+ state->s.tx_max_vht_mcs, state->s.tx_max_vht_nss,
+ state->s.tx_max_width, state->s.ever_tx_short_gi,
+
+ state->s.min_signal, state->s.avg_signal, state->s.max_signal);
+}
+
+
+void ConsolidateAssociatedDevices()
+{
+ ClientMapType::iterator it = clients.begin();
+
+ while (it != clients.end()) {
+ client_state_t *state = it->second;
+ ConsolidateSamples(&state->s);
+ if (AgeOutClient(state)) {
+ clients.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+
+/* Walk through all Wifi clients, printing their info to JSON files. */
+void UpdateAssociatedDevices()
+{
+ ClientMapType::iterator it = clients.begin();
+
+ while (it != clients.end()) {
+ client_state_t *state = it->second;
+ ClientStateToJson(state);
+ ++it;
+ }
+}
+
+
+void LogAssociatedDevices()
+{
+ ClientMapType::iterator it = clients.begin();
+ time_t mono_now = monotime();
+
+ while (it != clients.end()) {
+ client_state_t *state = it->second;
+ ClientStateToLog(state, mono_now);
+ ++it;
+ }
+}
+
+
+void LogInterfaces()
+{
+ int i;
+ for (i = 0; i < ninterfaces; ++i) {
+ wifi_interface_t *wif = &interfaces[i];
+
+ if (!wif->is_client) {
+ continue;
+ }
+
+ ConsolidateSamples(&wif->s);
+
+ printf(
+ "C %s %d %" 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"
+ " %0.2f %0.2f %0.2f"
+ "\n",
+ wif->ifname, wif->freq,
+
+ /* L2 traffic stats */
+ wif->s.rx_bytes64, wif->s.rx_drop64, wif->s.tx_bytes64,
+ wif->s.tx_retries64, wif->s.tx_failed64,
+
+ /* L1 information */
+ (wif->s.preamble_length ? 'S' : 'L'),
+ wif->s.signal, wif->s.signal_avg,
+ wif->s.rx_ht_mcs, wif->s.rx_ht_nss,
+ wif->s.rx_vht_mcs, wif->s.rx_vht_nss,
+ wif->s.rx_width, wif->s.rx_short_gi,
+
+ /* information about the maximum we've ever received. */
+ wif->s.rx_max_ht_mcs, wif->s.rx_max_ht_nss,
+ wif->s.rx_max_vht_mcs, wif->s.rx_max_vht_nss,
+ wif->s.rx_max_width, wif->s.ever_rx_short_gi,
+
+ wif->s.tx_ht_mcs, wif->s.tx_ht_nss,
+ wif->s.tx_vht_mcs, wif->s.tx_vht_nss,
+ wif->s.tx_width, wif->s.tx_short_gi,
+
+ /* information about the maximum we've ever achieved. */
+ wif->s.tx_max_ht_mcs, wif->s.tx_max_ht_nss,
+ wif->s.tx_max_vht_mcs, wif->s.tx_max_vht_nss,
+ wif->s.tx_max_width, wif->s.ever_tx_short_gi,
+ wif->s.min_signal, wif->s.avg_signal, wif->s.max_signal);
+ }
+}
+
+
+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 = (struct genlmsghdr *)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 = (unsigned char *)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]),
+ (const uint8_t *)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, NULL, 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 = (struct genlmsghdr *)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 = (char *)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, NULL, 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].ifname;
+ 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);
+
+ 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;
+ memset(interfaces, 0, sizeof(interfaces));
+ 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();
+
+ done = 0;
+ RequestInterfaceInfo(nlsk, nl80211_id, j);
+ ProcessNetlinkMessages(nlsk, &done);
+ }
+ TouchUpdateFile();
+ }
+ LogAssociatedDevices();
+ LogInterfaces();
+ }
+
+ exit(0);
+}
+#endif /* UNIT_TESTS */
diff --git a/cmds/wifi_files_test.c b/cmds/wifi_files_test.cc
similarity index 89%
rename from cmds/wifi_files_test.c
rename to cmds/wifi_files_test.cc
index 902cd73..4880fad 100644
--- a/cmds/wifi_files_test.c
+++ b/cmds/wifi_files_test.cc
@@ -16,7 +16,7 @@
const char *stations_dir;
#define STATIONS_DIR stations_dir
#define WIFIINFO_DIR STATIONS_DIR
-#include "wifi_files.c"
+#include "wifi_files.cc"
int exit_code = 0;
@@ -80,7 +80,7 @@
}
-static char *expected_json = "{\n"
+static const char *expected_json = "{\n"
" \"addr\": \"00:11:22:33:44:55\",\n"
" \"inactive since\": 0.000,\n"
" \"inactive msec\": 0,\n"
@@ -134,17 +134,17 @@
TEST_ASSERT(ieee80211_frequency_to_channel(2484) == 14);
memset(&state, 0, sizeof(client_state_t));
snprintf(state.macstr, sizeof(state.macstr), "00:11:22:33:44:55");
- state.rx_bytes64 = 1ULL;
- state.rx_drop64 = 2ULL;
- state.rx_bitrate = 47;
- state.authorized = 0;
- state.authenticated = 1;
- state.preamble_length = 0;
- state.expected_mbps = 7009;
- ClientStateToJson((gpointer)(&state.macstr), (gpointer)&state, NULL);
+ state.s.rx_bytes64 = 1ULL;
+ state.s.rx_drop64 = 2ULL;
+ state.s.rx_bitrate = 47;
+ state.s.authorized = 0;
+ state.s.authenticated = 1;
+ state.s.preamble_length = 0;
+ state.s.expected_mbps = 7009;
+ ClientStateToJson(&state);
#define SIZ 65536
- TEST_ASSERT((buf = malloc(SIZ)) != NULL);
+ TEST_ASSERT((buf = (char *)malloc(SIZ)) != NULL);
memset(buf, 0, SIZ);
snprintf(filename, sizeof(filename), "%s/%s", STATIONS_DIR, state.macstr);
TEST_ASSERT((fd = open(filename, O_RDONLY)) >= 0);
@@ -170,15 +170,15 @@
mac[5] = 0x02;
state = FindClientState(mac);
state->last_seen = 10000;
- TEST_ASSERT(g_hash_table_size(clients) == 2);
+ TEST_ASSERT(clients.size() == 2);
now = 1000 + MAX_CLIENT_AGE_SECS + 1;
ConsolidateAssociatedDevices();
- TEST_ASSERT(g_hash_table_size(clients) == 1);
+ TEST_ASSERT(clients.size() == 1);
now = 10000 + MAX_CLIENT_AGE_SECS + 1;
ConsolidateAssociatedDevices();
- TEST_ASSERT(g_hash_table_size(clients) == 0);
+ TEST_ASSERT(clients.size() == 0);
printf("! %s:%d\t%s\tok\n", __FILE__, __LINE__, __FUNCTION__);
}
@@ -189,7 +189,6 @@
stations_dir = mkdtemp(strdup("/tmp/wifi_files_test_XXXXXX"));
printf("stations_dir = %s\n", stations_dir);
- clients = g_hash_table_new(g_str_hash, g_str_equal);
testPrintSsidEscaped();
testPrintSsidEscapedQuoteBackslash();