wifi_files: retain data longer and add tests.
wifi_files relies on nl80211 to supply data about clients, but
nl80211 forgets about clients after 5 minutes idle. We want to
report data about the client for longer than that. Have wifi_files
retain the last known data about each client.
Also:
+ add unit tests. The actual netlink operations are hard to
test on the build system, but many utility methods can be tested.
+ switch from the SVr4 hash functions to the much-more-sane glib.
+ track MCS and channel width, and the maximum MCS and width we've
seen from the client.
+ log information about clients to stdout periodically.
Fixes b/21446814
Fixes b/22152957
Change-Id: I79d72dfddee4f74476ac62aafb8ff2a8b8da8030
diff --git a/cmds/Makefile b/cmds/Makefile
index 11b2c8b..b29485a 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -48,10 +48,11 @@
HOST_TARGETS=$(addprefix host-,$(TARGETS))
LIB_TARGETS=\
stdoutline.so
-TEST_TARGETS=\
+HOST_TEST_TARGETS=\
host-asus_hosts_test \
host-netusage_test \
- host-utils_test
+ host-utils_test \
+ host-wifi_files_test
SCRIPT_TARGETS=\
is-secure-boot
ARCH_TARGETS=\
@@ -208,9 +209,14 @@
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
+wifi_files: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
+host-wifi_files_test: host-wifi_files_test.o
+host-wifi_files_test: LIBS += -lnl-3 -lnl-genl-3 -lglib-2.0
-TESTS=$(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py) $(TEST_TARGETS)
+TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py) $(TEST_TARGETS)
+ifeq ($(RUN_HOST_TESTS),y)
+TESTS += $(HOST_TEST_TARGETS)
+endif
runtests: all $(TESTS)
set -e; \
for d in $(TESTS); do \
@@ -226,6 +232,7 @@
$(HOST_TARGETS) \
$(LIB_TARGETS) \
$(TEST_TARGETS) \
+ $(HOST_TEST_TARGETS) \
$(ARCH_TARGETS) \
*~ .*~ */*.pyc test_file *.pb.*
rm -rf test_dir
diff --git a/cmds/wifi_files.c b/cmds/wifi_files.c
index 1d95683..31f9d4a 100644
--- a/cmds/wifi_files.c
+++ b/cmds/wifi_files.c
@@ -21,7 +21,10 @@
*/
#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>
@@ -34,7 +37,6 @@
#include <netlink/msg.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>
-#include <search.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
@@ -46,24 +48,135 @@
#include <unistd.h>
+#ifndef UNIT_TESTS
#define STATIONS_DIR "/tmp/stations"
-#define WIFISHOW_DIR "/tmp/wifi/wifiinfo"
+#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 {
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;
+
+ int sample_index;
+#define MAX_SAMPLE_INDEX 150
+ 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 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 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_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;
+
+ #define MAC_STR_LEN 18
+ char macstr[MAC_STR_LEN];
+ #define IFNAME_STR_LEN 16
+ char ifname[IFNAME_STR_LEN];
} 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];
-const char *interfaces[NINTERFACES];
+int ifindexes[NINTERFACES] = {0};
+const char *interfaces[NINTERFACES] = {0};
int ninterfaces = 0;
-static FILE *wifi_show_handle = NULL;
+/* FILE handle to /tmp/wifi/wifiinfo, while open. */
+static FILE *wifi_info_handle = NULL;
-int GetIfIndex(const char *ifname)
+
+static int GetIfIndex(const char *ifname)
{
int fd;
struct ifreq ifr;
@@ -104,6 +217,9 @@
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++;
@@ -113,9 +229,9 @@
}
-void HandleNLCommand(struct nl_sock *nlsk, int nl80211_id, int n,
- int cb(struct nl_msg *, void *),
- int cmd, int flag)
+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;
@@ -157,226 +273,7 @@
} /* RequestInterfaceList */
-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 int StationDumpCallback(struct nl_msg *msg, void *arg)
-{
- const char *ifname = (const char *)arg;
- char tmpfile[PATH_MAX];
- char filename[PATH_MAX];
- FILE *f;
- 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;
- char macstr[18];
- 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_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_BYTES64] = { .type = NLA_U64 },
- [NL80211_STA_INFO_RX_DROP_MISC] = { .type = NLA_U64 },
- [NL80211_STA_INFO_TX_BYTES64] = { .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]);
- snprintf(macstr, sizeof(macstr), "%02x:%02x:%02x:%02x:%02x:%02x",
- mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
-
- snprintf(tmpfile, sizeof(tmpfile), "%s/%s.new", STATIONS_DIR, macstr);
- snprintf(filename, sizeof(filename), "%s/%s", STATIONS_DIR, macstr);
-
- if ((f = fopen(tmpfile, "w+")) == NULL) {
- perror("fopen");
- return NL_SKIP;
- }
-
- fprintf(f, "{\n");
-
- if (si[NL80211_STA_INFO_INACTIVE_TIME]) {
- ENTRY e, *ep;
- uint32_t inactive = nla_get_u32(si[NL80211_STA_INFO_INACTIVE_TIME]);
- double inactive_since = time(NULL) - ((double)inactive / 1000.0);
-
- memset(&e, 0, sizeof(e));
- e.key = macstr;
- if ((ep = hsearch(e, FIND)) != NULL) {
- client_state_t *prev = (client_state_t *)ep->data;
- if ((fabs(inactive_since - prev->inactive_since)) > 2.0) {
- prev->inactive_since = inactive_since;
- } else {
- inactive_since = prev->inactive_since;
- }
- } else {
- client_state_t *state = (client_state_t *)malloc(sizeof(client_state_t));
-
- state->inactive_since = inactive_since;
- memset(&e, 0, sizeof(e));
- e.key = strdup(macstr);
- e.data = (void *)state;
- if (hsearch(e, ENTER) == NULL) {
- fprintf(stderr, "hsearch(ENTER) failed\n");
- exit(1); // rely on babysitter to restart us.
- }
- }
-
- fprintf(f, " \"inactive since\": %.3f,\n", inactive_since);
- fprintf(f, " \"inactive msec\": %u,\n", inactive);
- }
-
- if (si[NL80211_STA_INFO_RX_BITRATE]) {
- uint32_t rate = GetBitrate(si[NL80211_STA_INFO_RX_BITRATE]);
- if (rate) {
- fprintf(f, " \"rx bitrate\": %u.%u,\n", rate / 10, rate % 10);
- }
- }
- if (si[NL80211_STA_INFO_RX_BYTES])
- fprintf(f, " \"rx bytes\": %u,\n",
- nla_get_u32(si[NL80211_STA_INFO_RX_BYTES]));
- if (si[NL80211_STA_INFO_RX_PACKETS])
- fprintf(f, " \"rx packets\": %u,\n",
- nla_get_u32(si[NL80211_STA_INFO_RX_PACKETS]));
- if (si[NL80211_STA_INFO_TX_BITRATE]) {
- uint32_t rate = GetBitrate(si[NL80211_STA_INFO_TX_BITRATE]);
- if (rate) {
- fprintf(f, " \"tx bitrate\": %u.%u,\n", rate / 10, rate % 10);
- }
- }
- if (si[NL80211_STA_INFO_TX_BYTES])
- fprintf(f, " \"tx bytes\": %u,\n",
- nla_get_u32(si[NL80211_STA_INFO_TX_BYTES]));
- if (si[NL80211_STA_INFO_TX_PACKETS])
- fprintf(f, " \"tx packets\": %u,\n",
- nla_get_u32(si[NL80211_STA_INFO_TX_PACKETS]));
- if (si[NL80211_STA_INFO_TX_RETRIES])
- fprintf(f, " \"tx retries\": %u,\n",
- nla_get_u32(si[NL80211_STA_INFO_TX_RETRIES]));
- if (si[NL80211_STA_INFO_TX_FAILED])
- fprintf(f, " \"tx failed\": %u,\n",
- nla_get_u32(si[NL80211_STA_INFO_TX_FAILED]));
-
- if (si[NL80211_STA_INFO_SIGNAL]) {
- fprintf(f, " \"signal\": %hhd,\n",
- (int8_t)nla_get_u8(si[NL80211_STA_INFO_SIGNAL]));
- }
-
- if (si[NL80211_STA_INFO_SIGNAL_AVG]) {
- fprintf(f, " \"signal avg\": %hhd,\n",
- (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) (1ULL<<(x))
- #define PRINT_BOOL(name, bit) if (sta_flags->mask & BIT(bit)) \
- fprintf(f, " \"%s\": \"%s\",\n", name, \
- (sta_flags->set & BIT(bit) ? "yes" : "no"));
-
- PRINT_BOOL("authorized", NL80211_STA_FLAG_AUTHORIZED);
- PRINT_BOOL("authenticated", NL80211_STA_FLAG_AUTHENTICATED);
- PRINT_BOOL("preamble", NL80211_STA_FLAG_SHORT_PREAMBLE);
- PRINT_BOOL("WMM/WME", NL80211_STA_FLAG_WME);
- PRINT_BOOL("MFP", NL80211_STA_FLAG_MFP);
- PRINT_BOOL("TDLS peer", NL80211_STA_FLAG_TDLS_PEER);
-
- if (sta_flags->mask & BIT(NL80211_STA_FLAG_SHORT_PREAMBLE)) {
- uint32_t bit = BIT(NL80211_STA_FLAG_SHORT_PREAMBLE);
- const char *p = (sta_flags->set & bit ? "short" : "long");
- fprintf(f, " \"preamble\": \"%s\",\n", p);
- }
- }
-
-#ifdef NL80211_RECENT_FIELDS
- if (si[NL80211_STA_INFO_RX_BYTES64])
- fprintf(f, " \"rx bytes64\": %" PRIu64 ",\n",
- nla_get_u64(si[NL80211_STA_INFO_RX_BYTES64]));
- if (si[NL80211_STA_INFO_RX_DROP_MISC])
- fprintf(f, " \"rx drop64\": %" PRIu64 ",\n",
- nla_get_u64(si[NL80211_STA_INFO_RX_DROP_MISC]));
- if (si[NL80211_STA_INFO_TX_BYTES64])
- fprintf(f, " \"tx bytes64\": %" PRIu64 ",\n",
- nla_get_u64(si[NL80211_STA_INFO_TX_BYTES64]));
- if (si[NL80211_STA_INFO_EXPECTED_THROUGHPUT]) {
- uint32_t thr = nla_get_u32(si[NL80211_STA_INFO_EXPECTED_THROUGHPUT]);
- fprintf(f, " \"expected throughput\": \"%u.%uMbps\",\n",
- thr / 1000, thr % 1000);
- }
-#endif
-
- fprintf(f, " \"ifname\": \"%s\"\n", ifname);
- fprintf(f, "}\n");
-
- fclose(f);
- if (rename(tmpfile, filename)) {
- perror("rename");
- }
- 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 int NlFinish(struct nl_msg *msg, void *arg)
+int NlFinish(struct nl_msg *msg, void *arg)
{
int *ret = arg;
*ret = 1;
@@ -406,7 +303,7 @@
} /* InitNetlinkSocket */
-void ProcessNetlinkMessages(struct nl_sock *nlsk, int *done)
+static void ProcessNetlinkMessages(struct nl_sock *nlsk, int *done)
{
for (;;) {
int s = nl_socket_get_fd(nlsk);
@@ -431,7 +328,456 @@
}
-void TouchUpdateFile()
+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 GetRxMCS(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 RxHtMcsToNss(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);
+ 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, short_gi=0;
+ int ht_nss;
+ int n = state->sample_index + 1;
+
+ if (n >= MAX_SAMPLE_INDEX) n = 0;
+
+ state->rx_bitrate = GetBitrate(si[NL80211_STA_INFO_RX_BITRATE]);
+ GetRxMCS(si[NL80211_STA_INFO_RX_BITRATE], &rx_ht_mcs, &rx_vht_mcs,
+ &rx_width, &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 = RxHtMcsToNss(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->short_gi_samples[n] = short_gi;
+ if (short_gi) state->ever_short_gi = 1;
+
+ state->rx_width_samples[n] = rx_width;
+ if (rx_width > state->rx_max_width) state->rx_max_width = rx_width;
+
+ state->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]) {
+ state->tx_bitrate = GetBitrate(si[NL80211_STA_INFO_TX_BITRATE]);
+ }
+ 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, 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->short_gi_samples[i] > short_gi) {
+ short_gi = state->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->short_gi = 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->short_gi));
+ fprintf(f, " \"rx SHORT_GI seen\": %s,\n", BOOL(state->ever_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 TouchUpdateFile()
{
char filename[PATH_MAX];
int fd;
@@ -451,16 +797,74 @@
} /* TouchUpdateFile */
-void usage(const char *progname)
+static void ClientStateToLog(gpointer key, gpointer value, gpointer user_data)
{
- printf("usage: %s\n", progname);
- printf("\tWill write files to /tmp/stations for Wifi clients.\n");
- exit(1);
-} /* usage */
+ 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"
+ "\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->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_short_gi);
+}
-/* From iw package, try untouched except indentation */
-int ieee80211_frequency_to_channel(int freq)
+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);
+ TouchUpdateFile();
+}
+
+
+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)
@@ -478,7 +882,7 @@
}
-void print_ssid_escaped(FILE* f, const uint8_t len, const uint8_t *data)
+static void print_ssid_escaped(FILE *f, int len, const uint8_t *data)
{
int i;
@@ -486,7 +890,7 @@
if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\')
fprintf(f, "%c", data[i]);
else if (data[i] == ' ' && (i != 0 && i != len -1))
- fprintf(f," ");
+ fprintf(f, " ");
else
fprintf(f, "\\x%.2x", data[i]);
}
@@ -503,31 +907,28 @@
if (tb_msg[NL80211_ATTR_MAC]) {
unsigned char *mac_addr = nla_data(tb_msg[NL80211_ATTR_MAC]);
- fprintf(wifi_show_handle,
+ 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_show_handle, " \"SSID\": \"");
- print_ssid_escaped(wifi_show_handle, nla_len(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_show_handle, "\",\n");
+ 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_show_handle, " \"Channel\": %d",
+ fprintf(wifi_info_handle, " \"Channel\": %d,\n",
ieee80211_frequency_to_channel(freq));
-
- fprintf(wifi_show_handle, ",\n");
}
return NL_SKIP;
}
-void UpdateWifiShowContent(struct nl_sock *nlsk, int nl80211_id, int n)
+void RequestWifiInfo(struct nl_sock *nlsk, int nl80211_id, int n)
{
HandleNLCommand(nlsk, nl80211_id, n, WlanInfoCallback,
NL80211_CMD_GET_INTERFACE, 0);
@@ -538,7 +939,7 @@
{
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
- char *alpha2;
+ char *reg;
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
@@ -551,14 +952,14 @@
return NL_SKIP;
}
- alpha2 = nla_data(tb_msg[NL80211_ATTR_REG_ALPHA2]);
- fprintf(wifi_show_handle, " \"RegDomain\": \"%c%c\",\n", alpha2[0], alpha2[1]);
+ 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 UpdateWifiRegdomain(struct nl_sock *nlsk, int nl80211_id)
+void RequestRegdomain(struct nl_sock *nlsk, int nl80211_id)
{
HandleNLCommand(nlsk, nl80211_id, -1, RegdomainCallback,
NL80211_CMD_GET_REG, 0);
@@ -579,48 +980,53 @@
return;
}
- snprintf(tmpfile, sizeof(tmpfile), "%s/%s.new", WIFISHOW_DIR, ifname);
- snprintf(filename, sizeof(filename), "%s/%s", WIFISHOW_DIR, ifname);
+ snprintf(tmpfile, sizeof(tmpfile), "%s/%s.new", WIFIINFO_DIR, ifname);
+ snprintf(filename, sizeof(filename), "%s/%s", WIFIINFO_DIR, ifname);
- if ((wifi_show_handle = fopen(tmpfile, "w+")) == NULL) {
+ if ((wifi_info_handle = fopen(tmpfile, "w+")) == NULL) {
perror("fopen");
return;
}
- fprintf(wifi_show_handle, "{\n");
+ fprintf(wifi_info_handle, "{\n");
done = 0;
- UpdateWifiShowContent(nlsk, nl80211_id, n);
+ RequestWifiInfo(nlsk, nl80211_id, n);
ProcessNetlinkMessages(nlsk, &done);
done = 0;
- UpdateWifiRegdomain(nlsk, nl80211_id);
+ RequestRegdomain(nlsk, nl80211_id);
ProcessNetlinkMessages(nlsk, &done);
snprintf(autofile, sizeof(autofile), "/tmp/autochan.%s", ifname);
if (stat(autofile, &buffer) == 0) {
- fprintf(wifi_show_handle, " \"AutoChannel\": true,\n");
+ fprintf(wifi_info_handle, " \"AutoChannel\": true,\n");
} else {
- fprintf(wifi_show_handle, " \"AutoChannel\": false,\n");
+ fprintf(wifi_info_handle, " \"AutoChannel\": false,\n");
}
snprintf(autofile, sizeof(autofile), "/tmp/autotype.%s", ifname);
if ((fptr = fopen(autofile, "r")) == NULL) {
- fprintf(wifi_show_handle, " \"AutoType\": \"LOW\"\n");
+ fprintf(wifi_info_handle, " \"AutoType\": \"LOW\"\n");
} else {
char buf[24];
- if (fgets(buf, sizeof(buf), fptr) != NULL)
- fprintf(wifi_show_handle, " \"AutoType\": \"%s\"\n", buf);
+ if (fgets(buf, sizeof(buf), fptr) != NULL) {
+ fprintf(wifi_info_handle, " \"AutoType\": \"%s\"\n", buf);
+ }
fclose(fptr);
fptr = NULL;
}
- fprintf(wifi_show_handle, "}\n");
+ fprintf(wifi_info_handle, "}\n");
- fclose(wifi_show_handle);
- wifi_show_handle = NULL;
+ fclose(wifi_info_handle);
+ wifi_info_handle = NULL;
if (rename(tmpfile, filename)) {
- perror("rename");
+ char errbuf[256];
+ snprintf(errbuf, sizeof(errbuf), "%s: rename %s to %s : errno=%d",
+ __FUNCTION__, tmpfile, filename, errno);
+ perror(errbuf);
}
}
+#ifndef UNIT_TESTS
int main(int argc, char **argv)
{
int done = 0;
@@ -628,19 +1034,21 @@
struct nl_sock *nlsk = NULL;
struct rlimit rlim;
- hcreate(512);
-
memset(&rlim, 0, sizeof(rlim));
if (getrlimit(RLIMIT_AS, &rlim)) {
perror("getrlimit RLIMIT_AS failed");
exit(1);
}
- rlim.rlim_cur = 5 * 1024 * 1024;
+ 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");
@@ -650,21 +1058,34 @@
fprintf(stderr, "genl_ctrl_resolve failed\n");
exit(1);
}
- RequestInterfaceList(nlsk, nl80211_id);
- ProcessNetlinkMessages(nlsk, &done);
while (1) {
- int i;
- for (i = 0; i < ninterfaces; i++) {
- done = 0;
- RequestAssociatedDevices(nlsk, nl80211_id, i);
- ProcessNetlinkMessages(nlsk, &done);
+ 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);
}
- TouchUpdateFile();
- sleep(2);
+
+ /* 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_test.c b/cmds/wifi_files_test.c
new file mode 100644
index 0000000..bb0043c
--- /dev/null
+++ b/cmds/wifi_files_test.c
@@ -0,0 +1,183 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+
+time_t now = 1000;
+static time_t monotime(void)
+{
+ return now;
+}
+
+
+#define UNIT_TESTS
+const char *stations_dir;
+#define STATIONS_DIR stations_dir
+#define WIFIINFO_DIR STATIONS_DIR
+#include "wifi_files.c"
+
+
+int exit_code = 0;
+
+
+#define TEST_ASSERT(x) \
+ if (x) printf("! %s:%d \"x\"\tok\n", __FUNCTION__, __LINE__); \
+ else { \
+ printf("! %s:%d \"x\"\tFAILED\n", __FUNCTION__, __LINE__); \
+ exit_code = 1; \
+ }
+
+
+void testPrintSsidEscaped()
+{
+ FILE *f = tmpfile();
+ char buf[32];
+ const uint8_t ssid[] = {'a', 'b', 0x86, ' ', 'c'}; /* not NUL terminated. */
+ const uint8_t expected[] = {'a', 'b', '\\', 'x', '8', '6', ' ', 'c'};
+
+ printf("Testing \"%s\" in %s:\n", __FUNCTION__, __FILE__);
+ memset(buf, 0, sizeof(buf));
+ TEST_ASSERT(f != NULL);
+ print_ssid_escaped(f, sizeof(ssid), ssid);
+ fflush(f);
+ rewind(f);
+ TEST_ASSERT(fread(buf, 1, sizeof(buf), f) > 0);
+ TEST_ASSERT(memcmp(buf, expected, sizeof(expected)) == 0);
+ fclose(f);
+ printf("! %s:%d\t%s\tok\n", __FILE__, __LINE__, __FUNCTION__);
+}
+
+
+void testFrequencyToChannel()
+{
+ printf("Testing \"%s\" in %s:\n", __FUNCTION__, __FILE__);
+ TEST_ASSERT(ieee80211_frequency_to_channel(2484) == 14);
+ TEST_ASSERT(ieee80211_frequency_to_channel(5745) == 149);
+ printf("! %s:%d\t%s\tok\n", __FILE__, __LINE__, __FUNCTION__);
+}
+
+
+static char *expected_json = "{\n"
+" \"addr\": \"00:11:22:33:44:55\",\n"
+" \"inactive since\": 0.000,\n"
+" \"inactive msec\": 0,\n"
+" \"active\": false,\n"
+" \"rx bitrate\": 4.7,\n"
+" \"rx bytes\": 0,\n"
+" \"rx packets\": 0,\n"
+" \"tx bitrate\": 0.0,\n"
+" \"tx bytes\": 0,\n"
+" \"tx packets\": 0,\n"
+" \"tx retries\": 0,\n"
+" \"tx failed\": 0,\n"
+" \"rx mcs\": 0,\n"
+" \"rx max mcs\": 0,\n"
+" \"rx vht mcs\": 0,\n"
+" \"rx max vht mcs\": 0,\n"
+" \"rx width\": 0,\n"
+" \"rx max width\": 0,\n"
+" \"rx ht_nss\": 0,\n"
+" \"rx max ht_nss\": 0,\n"
+" \"rx vht_nss\": 0,\n"
+" \"rx max vht_nss\": 0,\n"
+" \"rx SHORT_GI\": false,\n"
+" \"rx SHORT_GI seen\": false,\n"
+" \"signal\": 0,\n"
+" \"signal_avg\": 0,\n"
+" \"authorized\": \"no\",\n"
+" \"authenticated\": \"yes\",\n"
+" \"preamble\": \"no\",\n"
+" \"wmm_wme\": \"no\",\n"
+" \"mfp\": \"no\",\n"
+" \"tdls_peer\": \"no\",\n"
+" \"preamble length\": \"long\",\n"
+" \"rx bytes64\": 1,\n"
+" \"rx drop64\": 2,\n"
+" \"tx bytes64\": 0,\n"
+" \"tx retries64\": 0,\n"
+" \"expected Mbps\": 7.009,\n"
+" \"ifname\": \"\"\n"
+"}";
+
+
+void testClientStateToJson()
+{
+ client_state_t state;
+ char *buf;
+ char filename[PATH_MAX];
+ int fd;
+
+ printf("Testing \"%s\" in %s:\n", __FUNCTION__, __FILE__);
+ 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);
+
+ #define SIZ 65536
+ TEST_ASSERT((buf = 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);
+ TEST_ASSERT(read(fd, buf, SIZ) > 0);
+ close(fd);
+ TEST_ASSERT(unlink(filename) == 0);
+ TEST_ASSERT(strcmp(buf, expected_json));
+ free(buf);
+ printf("! %s:%d\t%s\tok\n", __FILE__, __LINE__, __FUNCTION__);
+}
+
+
+void testAgeOutClients()
+{
+ uint8_t mac[] = {0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
+ client_state_t *state;
+
+ printf("Testing \"%s\" in %s:\n", __FUNCTION__, __FILE__);
+ mac[5] = 0x01;
+ state = FindClientState(mac);
+ state->last_seen = 1000;
+
+ mac[5] = 0x02;
+ state = FindClientState(mac);
+ state->last_seen = 10000;
+ TEST_ASSERT(g_hash_table_size(clients) == 2);
+
+ now = 1000 + MAX_CLIENT_AGE_SECS + 1;
+ ConsolidateAssociatedDevices();
+ TEST_ASSERT(g_hash_table_size(clients) == 1);
+
+ now = 10000 + MAX_CLIENT_AGE_SECS + 1;
+ ConsolidateAssociatedDevices();
+ TEST_ASSERT(g_hash_table_size(clients) == 0);
+ printf("! %s:%d\t%s\tok\n", __FILE__, __LINE__, __FUNCTION__);
+}
+
+
+int main(int argc, char** argv)
+{
+ char filename[PATH_MAX];
+
+ 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();
+ testFrequencyToChannel();
+ testClientStateToJson();
+ testAgeOutClients();
+
+ snprintf(filename, sizeof(filename), "%s/updated.new", STATIONS_DIR);
+ unlink(filename);
+ TEST_ASSERT(rmdir(stations_dir) == 0);
+
+ exit(exit_code);
+}