860 lines
30 KiB
C++

#include <stdio.h>
#include <unistd.h>
#include <net/if.h>
#include <syslog.h>
#include <netlink/netlink.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <linux/nl80211.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "../mavlink/c_library/common/mavlink.h"
#include "util.h"
/*
* Retrieve wifi information and:
* - log it
* - send mavlinke rssi message
*
* Netlink documentation: http://www.infradead.org/~tgr/libnl/
* Netlink example: iw
*/
/*
* Implementation notes
*
* Each time this runs it creates a unix socket in /tmp. Since there is no
* "clean" exit from this program, that socket is not deleted until the next
* reboot, and then only because /tmp is not persistent. There is a very
* unlikely failure mode where when this program dies and restarts, it comes
* back with the same PID; if that happens, it will probably fail to bind to
* the unix socket (since it /tmp/rssi_send.<pid> will already exist) and
* exit. Whoever started this program (inittab) will notice and start it
* again. [That case has never been observed to happen.]
*
* The netlink message creation, sending, and parsing is rather mysterious;
* there is little in the way of documentation other than the source code.
* Some basic unknowns are things like when we allocate a message and give it
* to the send function, who should free it? It would be possible to drill
* down further into the source and figure it out, but instead, testing is
* used; a run at 1000 msgs/sec for tens of minutes shows that the program's
* size is not growning, so there are probably no leaks.
*
* Late conversion from C to C++ to add logging.
*
* The netlink stuff returns information in attribute tables. When we get
* one of those, we copy the attributes that are present into a simple
* structure defined here (station_info and rate_info). Each of those
* structures has a name that corresponds with one of the attribute enums
* in linux/nl80211.h, and the struct has fields corresponding to the
* enum values.
*/
/* system and component IDs to use in mavlink header */
static uint8_t system_id = 10; /* change with -s option */
static uint8_t component_id = 0; /* change with -c option */
/* time between rssi messages (default 1 second) */
static unsigned interval_ms = 1000; /* change with -i option */
/* run verbose (debug) */
static int verbose = 0;
#define DBG(X)
/*
* Rate info, contained in station info
*
* Fields commented out are ones where linux/nl80211.h does not say what the
* type is, and we don't need right now, so they are just left out.
*/
typedef struct rate_info {
uint16_t bitrate; /* total bitrate (u16, 100kbit/s) */
uint8_t mcs; /* mcs index for 802.11n (u8) */
/* n40_mhz_width: 40 MHz dualchannel bitrate */
/* short_gi: 400ns guard interval */
uint32_t bitrate32; /* total bitrate (u32, 100kbit/s) */
/* max: highest rate_info number currently defined */
uint8_t vht_mcs; /* MCS index for VHT (u8) */
uint8_t vht_nss; /* number of streams in VHT (u8) */
/* n80_mhz_width: 80 MHz VHT rate */
/* n80p80_mhz_width: 80+80 MHz VHT rate */
/* n160_mhz_width: 160 MHz VHT rate */
} rate_info_t;
/* print structure to a string, debug */
static char *rate_info_str(char *s, const rate_info_t *p)
{
/* 43 chars + 5 u32 = 43 + 5 * 10 = 93 chars max */
sprintf(s, "bitrate=%u mcs=%u bitrate32=%u vht_mcs=%u vht_nss=%u", p->bitrate, p->mcs,
p->bitrate32, p->vht_mcs, p->vht_nss);
return s;
}
/*
* Information retrieved via station info request message
*
* Fields commented out are ones where linux/nl80211.h does not say what the
* type is, and we don't need right now, so they are just left out.
*/
typedef struct station_info {
uint32_t inactive_time; /* time since last activity (u32, msecs) */
uint32_t rx_bytes; /* total received bytes (u32, from this station) */
uint32_t tx_bytes; /* total transmitted bytes (u32, to this station) */
uint64_t rx_bytes64; /* total received bytes (u64, from this station) */
uint64_t tx_bytes64; /* total transmitted bytes (u64, to this station) */
int8_t signal; /* signal strength of last received PPDU (u8, dBm) */
rate_info_t tx_bitrate; /* current unicast tx rate, nested attribute containing
info as possible, see &enum nl80211_rate_info */
uint32_t rx_packets; /* total received packet (u32, from this station) */
uint32_t tx_packets; /* total transmitted packets (u32, to this station) */
uint32_t tx_retries; /* total retries (u32, to this station) */
uint32_t tx_failed; /* total failed packets (u32, to this station) */
int8_t signal_avg; /* signal strength average (u8, dBm) */
/* llid: the station's mesh LLID */
/* plid: the station's mesh PLID */
/* plink_state: peer link state for the station (see %enum nl80211_plink_state) */
rate_info_t rx_bitrate; /* last unicast data frame rx rate, nested attribute,
like NL80211_STA_INFO_TX_BITRATE. */
/* bss_param: current station's view of BSS, nested attribute containing info as
possible, see &enum nl80211_sta_bss_param */
/* connected_time: time since the station is last connected */
/* sta_flags: Contains a struct nl80211_sta_flag_update. */
uint32_t beacon_loss; /* count of times beacon loss was detected (u32) */
int64_t t_offset; /* timing offset with respect to this STA (s64) */
/* local_pm: local mesh STA link-specific power mode */
/* peer_pm: peer mesh STA link-specific power mode */
/* nonpeer_pm: neighbor mesh STA power save mode towards non-peer STA */
} station_info_t;
/*
* Information retrieved via station info request message
*
* Fields commented out are ones where linux/nl80211.h does not say what the
* type is, and we don't need right now, so they are just left out.
*/
typedef struct survey_info {
/*
* NL80211_SURVEY_INFO_FREQUENCY: center frequency of channel
* NL80211_SURVEY_INFO_NOISE: noise level of channel (u8, dBm)
* NL80211_SURVEY_INFO_IN_USE: channel is currently being used
* NL80211_SURVEY_INFO_TIME: amount of time (in ms) that the radio
* was turned on (on channel or globally)
* NL80211_SURVEY_INFO_TIME_BUSY: amount of the time the primary
* channel was sensed busy (either due to activity or energy detect)
* NL80211_SURVEY_INFO_TIME_EXT_BUSY: amount of time the extension
* channel was sensed busy
* NL80211_SURVEY_INFO_TIME_RX: amount of time the radio spent
* receiving data (on channel or globally)
* NL80211_SURVEY_INFO_TIME_TX: amount of time the radio spent
* transmitting data (on channel or globally)
* NL80211_SURVEY_INFO_TIME_SCAN: time the radio spent for scan (on this channel or globally)
*/
bool in_use;
uint32_t freq;
int8_t noise;
uint64_t chan_active_time;
uint64_t chan_busy_time;
uint64_t chan_ext_busy_time;
uint64_t chan_receive_time;
uint64_t chan_transmit_time;
} survey_info_t;
// state related to our netlink context
typedef struct {
struct nl_sock *sock;
int nl80211_id;
unsigned int if_index;
struct nl_cb *cmd_cb;
struct nl_cb *sock_cb;
} nl_state_t;
// items that are referenced in netlink callback context
typedef struct {
volatile int
status; // ensure this is always dereferenced, even when only being written in callbacks
station_info_t station_info;
survey_info_t survey_info;
} cb_ctx_t;
/* print structure to console, debug */
static void station_info_dump(const station_info_t *s)
{
char buf[100];
printf("inactive_time=%u\n", s->inactive_time);
printf("rx_bytes=%u tx_bytes=%u rx_bytes64=%llu tx_bytes64=%llu\n", s->rx_bytes, s->tx_bytes,
s->rx_bytes64, s->tx_bytes64);
printf("signal=%d signal_avg=%d beacon_loss=%u t_offset=%lld\n", s->signal, s->signal_avg,
s->beacon_loss, s->t_offset);
printf("rx_packets=%u tx_packets=%u tx_retries=%u tx_failed=%u\n", s->rx_packets, s->tx_packets,
s->tx_retries, s->tx_failed);
printf("tx_bitrate: %s\n", rate_info_str(buf, &s->tx_bitrate));
printf("rx_bitrate: %s\n", rate_info_str(buf, &s->rx_bitrate));
}
static void send_rssi(int rssi, unsigned retries);
static void get_station_info(cb_ctx_t *cb_ctx, const nl_state_t *nl);
static void get_survey_info(cb_ctx_t *cb_ctx, const nl_state_t *nl);
static int valid_handler(struct nl_msg *msg, void *arg);
static int station_handler(struct nl_msg *msg, station_info_t *station_info);
static int survey_handler(struct nl_msg *msg, survey_info_t *survey_info);
static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg);
static int finish_handler(struct nl_msg *msg, void *arg);
static int ack_handler(struct nl_msg *msg, void *arg);
static bool nl_state_init(nl_state_t *nl);
static bool nl_state_init_callbacks(nl_state_t *nl, cb_ctx_t *cb);
static void usage(void)
{
printf("rssi_send [-i interval_msec] [-s system_id] [-c component_id] [-v]\n");
exit(1);
}
static uint64_t diff_clamp(uint64_t curr, uint64_t prev)
{
if (curr < prev) {
return 0;
}
return curr - prev;
}
static void log_station_info(const station_info_t *curr, const station_info_t *prev)
{
/* station_info.[tr]x_bitrate.bitrate32 is in units of 100,000 */
syslog(LOG_INFO, "station sig=%d txr=%d.%d rxr=%d.%d ret=%llu err=%d", curr->signal_avg,
curr->tx_bitrate.bitrate32 / 10, curr->tx_bitrate.bitrate32 % 10,
curr->rx_bitrate.bitrate32 / 10, curr->rx_bitrate.bitrate32 % 10,
diff_clamp(curr->tx_retries, prev->tx_retries), curr->tx_failed);
}
static void log_survey_info(const survey_info_t *curr, const survey_info_t *prev)
{
syslog(LOG_INFO, "survey freq=%d noise=%d active=%llu busy=%llu ext_busy=%llu rx=%llu tx=%llu",
curr->freq, curr->noise, diff_clamp(curr->chan_active_time, prev->chan_active_time),
diff_clamp(curr->chan_busy_time, prev->chan_busy_time),
diff_clamp(curr->chan_ext_busy_time, prev->chan_ext_busy_time),
diff_clamp(curr->chan_receive_time, prev->chan_receive_time),
diff_clamp(curr->chan_transmit_time, prev->chan_transmit_time));
}
/*
* main program -
* initialize, then loop: read wifi status, log it, send rssi message
*/
int main(int argc, char *argv[])
{
int opt;
uint64_t now_us;
uint64_t next_us;
unsigned interval_us;
cb_ctx_t cb_ctx;
nl_state_t nl_state;
station_info_t last_station_info;
survey_info_t last_survey_info;
/* all arguments are optional; normal use is with no arguments */
while ((opt = getopt(argc, argv, "i:s:c:v:")) != -1) {
switch (opt) {
case 'i':
interval_ms = atoi(optarg);
break;
case 's':
system_id = atoi(optarg);
break;
case 'c':
component_id = atoi(optarg);
break;
case 'v':
verbose = atoi(optarg);
break;
default:
usage();
}
}
memset(&cb_ctx, 0, sizeof cb_ctx);
memset(&last_station_info, 0, sizeof last_station_info);
memset(&last_survey_info, 0, sizeof last_survey_info);
openlog("rssi_send", LOG_NDELAY, LOG_LOCAL3);
syslog(LOG_INFO, "main: built " __DATE__ " " __TIME__);
if (!nl_state_init(&nl_state)) {
syslog(LOG_ERR, "failed to init netlink\n");
exit(1);
}
if (!nl_state_init_callbacks(&nl_state, &cb_ctx)) {
syslog(LOG_ERR, "failed to init callbacks\n");
exit(1);
}
interval_us = interval_ms * 1000;
next_us = clock_gettime_us(CLOCK_MONOTONIC) + interval_us;
while (1) {
// delay until the next message time
now_us = clock_gettime_us(CLOCK_MONOTONIC);
if (next_us > now_us)
usleep(next_us - now_us);
next_us += interval_us;
// both these are blocking
get_station_info(&cb_ctx, &nl_state);
get_survey_info(&cb_ctx, &nl_state);
if (verbose >= 1)
station_info_dump(&cb_ctx.station_info);
log_station_info(&cb_ctx.station_info, &last_station_info);
log_survey_info(&cb_ctx.survey_info, &last_survey_info);
unsigned retries = diff_clamp(cb_ctx.station_info.tx_retries, last_station_info.tx_retries);
memcpy(&last_station_info, &cb_ctx.station_info, sizeof last_station_info);
memcpy(&last_survey_info, &cb_ctx.survey_info, sizeof last_survey_info);
send_rssi(cb_ctx.station_info.signal_avg, retries);
}
return 0;
} /* main */
bool nl_state_init(nl_state_t *nl)
{
/*
* Initialize our netlink context.
*/
memset(nl, 0, sizeof *nl);
nl->sock = nl_socket_alloc();
if (nl->sock == NULL) {
fprintf(stderr, "ERROR allocating netlink socket\n");
goto cleanup;
}
DBG(printf("nl_socket_alloc: nl_sock=%p\n", nl_sock);)
if (genl_connect(nl->sock) != 0) {
fprintf(stderr, "ERROR connecting netlink socket\n");
goto cleanup;
}
DBG(printf("genl_connect: ok\n");)
nl->nl80211_id = genl_ctrl_resolve(nl->sock, "nl80211");
if (nl->nl80211_id < 0) {
fprintf(stderr, "ERROR resolving netlink socket\n");
goto cleanup;
}
DBG(printf("genl_ctrl_resolve: nl80211_id=%d\n", nl80211_id);)
nl->if_index = if_nametoindex("wlan0");
if (nl->if_index == 0) {
fprintf(stderr, "ERROR getting interface index\n");
goto cleanup;
}
DBG(printf("if_nametoindex: if_index=%u\n", if_index);)
return true;
cleanup:
if (nl->sock != NULL)
nl_socket_free(nl->sock);
return false;
}
bool nl_state_init_callbacks(nl_state_t *nl, cb_ctx_t *cb)
{
/*
* Register our callbacks to handle netlink responses/events.
*/
nl->cmd_cb = nl_cb_alloc(NL_CB_DEFAULT);
if (nl->cmd_cb == NULL) {
fprintf(stderr, "ERROR allocating netlink command callback\n");
goto cleanup;
}
DBG(printf("nl_cb_alloc: cmd_cb=%p\n", cmd_cb);)
nl->sock_cb = nl_cb_alloc(NL_CB_DEFAULT);
if (nl->sock_cb == NULL) {
fprintf(stderr, "ERROR allocating netlink socket callback\n");
goto cleanup;
}
DBG(printf("nl_cb_alloc: sock_cb=%p\n", nl->sock_cb);)
// handle all NL_CB_VALID callbacks via valid_handler()
if (nl_cb_set(nl->cmd_cb, NL_CB_VALID, NL_CB_CUSTOM, valid_handler, cb) != 0) {
fprintf(stderr, "ERROR setting command callback\n");
goto cleanup;
}
DBG(printf("nl_cb_set: ok\n");)
// not clear that this is necessary, but following example convention for now...
nl_socket_set_cb(nl->sock, nl->sock_cb);
nl_cb_err(nl->cmd_cb, NL_CB_CUSTOM, error_handler, cb);
nl_cb_set(nl->cmd_cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, cb);
nl_cb_set(nl->cmd_cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, cb);
return true;
cleanup:
if (nl->sock_cb != NULL) {
nl_cb_put(nl->sock_cb);
}
if (nl->cmd_cb != NULL) {
nl_cb_put(nl->cmd_cb);
}
return false;
}
/* send mavlink radio_status (rssi) message */
static void send_rssi(int rssi, unsigned retries)
{
/* socket used to send to telem downlink */
static int fd = -1;
/* destination address */
static struct sockaddr_un sa_dst;
char sock_name[32];
struct sockaddr_un sa_src;
static unsigned const msg_max = 256;
unsigned msg_len;
uint8_t msg_buf[msg_max];
mavlink_message_t msg;
int num_bytes;
uint8_t u8_rssi;
uint8_t u8_retries;
/* one-time init */
if (fd == -1) {
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd == -1) {
perror("socket");
return;
}
memset(sock_name, 0, sizeof(sock_name));
snprintf(sock_name, sizeof(sock_name) - 1, "/tmp/rssi_send.%u", getpid());
memset(&sa_src, 0, sizeof(sa_src));
sa_src.sun_family = AF_UNIX;
strncpy(sa_src.sun_path, sock_name, sizeof(sa_src.sun_path) - 1);
if (bind(fd, (struct sockaddr *)&sa_src, sizeof(sa_src)) != 0) {
perror("bind");
close(fd);
fd = -1;
return;
}
/* set destination address that will be used for sendto */
memset(sock_name, 0, sizeof(sock_name));
strcpy(sock_name, "/run/telem_downlink");
memset(&sa_dst, 0, sizeof(sa_dst));
sa_dst.sun_family = AF_UNIX;
strncpy(sa_dst.sun_path, sock_name, sizeof(sa_dst.sun_path) - 1);
} /* if (fd...) */
u8_rssi = (uint8_t)rssi;
u8_retries = (uint8_t)retries;
mavlink_msg_radio_status_pack(system_id, component_id, &msg, 0, /* rssi */
u8_rssi, /* remrssi */
0, /* txbuf */
0, /* noise */
0, /* remnoise */
u8_retries, /* rxerrors */
0); /* fixed */
msg_len = mavlink_msg_to_send_buffer(msg_buf, &msg);
if (verbose >= 2)
printf("rssi_send: %02x %02x %02x %02x %02x %02x\n", msg_buf[0], msg_buf[1], msg_buf[2],
msg_buf[3], msg_buf[4], msg_buf[5]);
num_bytes = sendto(fd, msg_buf, msg_len, 0, (struct sockaddr *)&sa_dst, sizeof(sa_dst));
if (num_bytes < 0)
perror("sendto");
} /* send_rssi */
static bool send_nlcmd(const nl_state_t *nl, nl80211_commands c, int nl_msg_flags)
{
/*
* Assemble and send the netlink command 'c'.
*/
void *hdr = NULL;
int num_bytes = -1;
uint32_t if_index_uint32;
struct nl_msg *msg;
bool success = true;
msg = nlmsg_alloc();
if (msg == NULL) {
fprintf(stderr, "ERROR allocating netlink message\n");
success = false;
goto cleanup;
}
DBG(printf("nlmsg_alloc: msg=%p\n", msg);)
hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl->nl80211_id, 0, nl_msg_flags, c, 0);
if (hdr == NULL) {
fprintf(stderr, "ERROR creating netlink message\n");
success = false;
goto cleanup;
}
DBG(printf("genlmsg_put: hdr=%p\n", hdr);)
if_index_uint32 = nl->if_index;
if (nla_put(msg, NL80211_ATTR_IFINDEX, sizeof(uint32_t), &if_index_uint32) != 0) {
fprintf(stderr, "ERROR setting message attribute\n");
success = false;
goto cleanup;
}
DBG(printf("nla_put: ok\n");)
num_bytes = nl_send_auto(nl->sock, msg);
if (num_bytes < 0) {
fprintf(stderr, "ERROR sending netlink message\n");
success = false;
goto cleanup;
}
DBG(printf("nl_send_auto_complete: num_bytes=%d\n", num_bytes);)
cleanup:
if (msg != NULL) {
nlmsg_free(msg);
}
return success;
}
/*
* Get station info from interface via netlink
*
* This sends the request to the kernel to send us the station info,
* then waits for the response to come back. The response comes back
* as a callback (station_handler):
*
* Normal operation:
* this function sends request
* this function calls nl_recvmsgs; inside that, station_handler is called
* station_handler fills in station_info
* this function calls nl_recvmsgs; inside that, finish_handler is called
* finish_handler clears 'status'
* this function sees status is cleared and returns
*
* Almost everything returned is parsed out and available. Only a few items
* are logged, and only the signal strength is used in the mavlink message,
* but pulling them all out of the netlink message should make it easier to
* log more (or something different) if we want.
*/
static void get_station_info(cb_ctx_t *cb_ctx, const nl_state_t *nl)
{
memset(&cb_ctx->station_info, 0, sizeof(cb_ctx->station_info));
cb_ctx->status = 1;
if (!send_nlcmd(nl, NL80211_CMD_GET_STATION, NLM_F_DUMP)) {
return;
}
/* wait for callback to set station_info and set status=0 */
while (cb_ctx->status == 1) {
int rv = nl_recvmsgs(nl->sock, nl->cmd_cb);
if (rv != 0) {
fprintf(stderr, "nl_recvmsgs: %d\n", rv);
}
}
}
void get_survey_info(cb_ctx_t *cb_ctx, const nl_state_t *nl)
{
memset(&cb_ctx->survey_info, 0, sizeof(cb_ctx->survey_info));
cb_ctx->status = 1;
if (!send_nlcmd(nl, NL80211_CMD_GET_SURVEY, NLM_F_DUMP)) {
return;
}
/* wait for callback to set set status=0 */
while (cb_ctx->status == 1) {
int rv = nl_recvmsgs(nl->sock, nl->cmd_cb);
if (rv != 0) {
fprintf(stderr, "nl_recvmsgs: %d\n", rv);
}
}
}
int valid_handler(struct nl_msg *msg, void *arg)
{
/*
* Top level handler for all NL_CB_VALID/NL_CB_CUSTOM responses.
*
* Check the reported cmd and dispatch accordingly.
*/
struct nlmsghdr *nl_hdr = nlmsg_hdr(msg);
struct genlmsghdr *genl_hdr = (struct genlmsghdr *)nlmsg_data(nl_hdr);
cb_ctx_t *ctx = (cb_ctx_t *)arg;
switch (genl_hdr->cmd) {
case NL80211_CMD_NEW_STATION:
return station_handler(msg, &ctx->station_info);
case NL80211_CMD_NEW_SURVEY_RESULTS:
return survey_handler(msg, &ctx->survey_info);
default:
// unexpected msg
return NL_SKIP;
}
}
/*
* Called for each station result message that comes back from the kernel.
* We expect only one before finish_handler is called.
*/
static int station_handler(struct nl_msg *msg, station_info_t *station_info)
{
struct nlattr *tb1[NL80211_ATTR_MAX + 1];
struct nlmsghdr *nl_hdr;
struct genlmsghdr *genl_hdr;
struct nlattr *genl_attr_data;
int genl_attr_len;
struct nlattr *tb2[NL80211_STA_INFO_MAX + 1];
static struct nla_policy policy2[NL80211_STA_INFO_MAX + 1] = {{0}};
struct nlattr *tb3[NL80211_RATE_INFO_MAX + 1];
static struct nla_policy policy3[NL80211_RATE_INFO_MAX + 1] = {{0}};
nl_hdr = nlmsg_hdr(msg);
genl_hdr = (struct genlmsghdr *)nlmsg_data(nl_hdr);
genl_attr_data = genlmsg_attrdata(genl_hdr, 0);
genl_attr_len = genlmsg_attrlen(genl_hdr, 0);
if (nla_parse(tb1, NL80211_ATTR_MAX, genl_attr_data, genl_attr_len, NULL) != 0) {
fprintf(stderr, "ERROR parsing netlink message attributes\n");
return NL_SKIP;
}
if (tb1[NL80211_ATTR_STA_INFO] == NULL) {
printf("no data\n");
return NL_SKIP;
}
if (nla_parse_nested(tb2, NL80211_STA_INFO_MAX, tb1[NL80211_ATTR_STA_INFO], policy2) != 0) {
printf("ERROR parsing netlink message nested attributes\n");
return NL_SKIP;
}
/* Description of what attributes there are is in linux/nl80211.h */
/* For each possible attribute, see if it is present in the info we
* got, and if so, copy it to our station_info_t structure. */
if (tb2[NL80211_STA_INFO_INACTIVE_TIME] != NULL)
station_info->inactive_time = nla_get_u32(tb2[NL80211_STA_INFO_INACTIVE_TIME]);
if (tb2[NL80211_STA_INFO_RX_BYTES] != NULL)
station_info->rx_bytes = nla_get_u32(tb2[NL80211_STA_INFO_RX_BYTES]);
if (tb2[NL80211_STA_INFO_TX_BYTES] != NULL)
station_info->tx_bytes = nla_get_u32(tb2[NL80211_STA_INFO_TX_BYTES]);
if (tb2[NL80211_STA_INFO_RX_BYTES64] != NULL)
station_info->rx_bytes64 = nla_get_u64(tb2[NL80211_STA_INFO_RX_BYTES64]);
if (tb2[NL80211_STA_INFO_TX_BYTES64] != NULL)
station_info->tx_bytes64 = nla_get_u64(tb2[NL80211_STA_INFO_TX_BYTES64]);
/* this appears to be signed dBm, not a u8 */
if (tb2[NL80211_STA_INFO_SIGNAL] != NULL)
station_info->signal = (int8_t)nla_get_u8(tb2[NL80211_STA_INFO_SIGNAL]);
/* tx_bitrate is a nested structure; the station_info points to a nested
* attribute thing that needs to be decoded */
if (tb2[NL80211_STA_INFO_TX_BITRATE] != NULL &&
nla_parse_nested(tb3, NL80211_RATE_INFO_MAX, tb2[NL80211_STA_INFO_TX_BITRATE], policy3) ==
0) {
rate_info_t *rate_info = &station_info->tx_bitrate;
if (tb3[NL80211_RATE_INFO_BITRATE] != NULL)
rate_info->bitrate = nla_get_u16(tb3[NL80211_RATE_INFO_BITRATE]);
if (tb3[NL80211_RATE_INFO_MCS] != NULL)
rate_info->mcs = nla_get_u8(tb3[NL80211_RATE_INFO_MCS]);
if (tb3[NL80211_RATE_INFO_BITRATE32] != NULL)
rate_info->bitrate32 = nla_get_u32(tb3[NL80211_RATE_INFO_BITRATE32]);
if (tb3[NL80211_RATE_INFO_VHT_MCS] != NULL)
rate_info->vht_mcs = nla_get_u8(tb3[NL80211_RATE_INFO_VHT_MCS]);
if (tb3[NL80211_RATE_INFO_VHT_NSS] != NULL)
rate_info->vht_nss = nla_get_u8(tb3[NL80211_RATE_INFO_VHT_NSS]);
}
if (tb2[NL80211_STA_INFO_RX_PACKETS] != NULL)
station_info->rx_packets = nla_get_u32(tb2[NL80211_STA_INFO_RX_PACKETS]);
if (tb2[NL80211_STA_INFO_TX_PACKETS] != NULL)
station_info->tx_packets = nla_get_u32(tb2[NL80211_STA_INFO_TX_PACKETS]);
if (tb2[NL80211_STA_INFO_TX_RETRIES] != NULL)
station_info->tx_retries = nla_get_u32(tb2[NL80211_STA_INFO_TX_RETRIES]);
if (tb2[NL80211_STA_INFO_TX_FAILED] != NULL)
station_info->tx_failed = nla_get_u32(tb2[NL80211_STA_INFO_TX_FAILED]);
/* this appears to be signed dBm, not a u8 */
if (tb2[NL80211_STA_INFO_SIGNAL_AVG] != NULL)
station_info->signal_avg = (int8_t)nla_get_u8(tb2[NL80211_STA_INFO_SIGNAL_AVG]);
/* rx_bitrate is nested, like tx_bitrate */
if (tb2[NL80211_STA_INFO_RX_BITRATE] != NULL &&
nla_parse_nested(tb3, NL80211_RATE_INFO_MAX, tb2[NL80211_STA_INFO_RX_BITRATE], policy3) ==
0) {
rate_info_t *rate_info = &station_info->rx_bitrate;
if (tb3[NL80211_RATE_INFO_BITRATE] != NULL)
rate_info->bitrate = nla_get_u16(tb3[NL80211_RATE_INFO_BITRATE]);
if (tb3[NL80211_RATE_INFO_MCS] != NULL)
rate_info->mcs = nla_get_u8(tb3[NL80211_RATE_INFO_MCS]);
if (tb3[NL80211_RATE_INFO_BITRATE32] != NULL)
rate_info->bitrate32 = nla_get_u32(tb3[NL80211_RATE_INFO_BITRATE32]);
if (tb3[NL80211_RATE_INFO_VHT_MCS] != NULL)
rate_info->vht_mcs = nla_get_u8(tb3[NL80211_RATE_INFO_VHT_MCS]);
if (tb3[NL80211_RATE_INFO_VHT_NSS] != NULL)
rate_info->vht_nss = nla_get_u8(tb3[NL80211_RATE_INFO_VHT_NSS]);
}
if (tb2[NL80211_STA_INFO_BEACON_LOSS] != NULL)
station_info->beacon_loss = nla_get_u32(tb2[NL80211_STA_INFO_BEACON_LOSS]);
if (tb2[NL80211_STA_INFO_T_OFFSET] != NULL)
station_info->t_offset = (int64_t)nla_get_u64(tb2[NL80211_STA_INFO_T_OFFSET]);
return NL_SKIP;
} /* station_handler */
static int survey_handler(struct nl_msg *msg, survey_info_t *survey_info)
{
/*
* https://wireless.wiki.kernel.org/en/users/Documentation/acs says
*
* - active time is the amount of time the radio has spent on the channel
* - busy time is the amount of time the channel has spent on the channel but noticed the
* channel was busy
* and could not initiate communication if it wanted to.
* - tx time is the amount of time the channel has spent on the channel transmitting data.
* - interference factor is defined as the ratio of the observed busy time over the time we
* spent on the channel,
* this value is then amplified by the noise floor observed on the channel in comparison to
* the lowest noise floor
* observed on the entire band.
*/
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
struct nlattr *sinfo[NL80211_SURVEY_INFO_MAX + 1];
char dev[20];
static struct nla_policy survey_policy[NL80211_SURVEY_INFO_MAX + 1];
survey_policy[NL80211_SURVEY_INFO_FREQUENCY].type = NLA_U32;
survey_policy[NL80211_SURVEY_INFO_NOISE].type = NLA_U8;
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), dev);
if (!tb[NL80211_ATTR_SURVEY_INFO]) {
fprintf(stderr, "survey data missing!\n");
return NL_SKIP;
}
if (nla_parse_nested(sinfo, NL80211_SURVEY_INFO_MAX, tb[NL80211_ATTR_SURVEY_INFO],
survey_policy)) {
fprintf(stderr, "failed to parse nested attributes!\n");
return NL_SKIP;
}
if (sinfo[NL80211_SURVEY_INFO_FREQUENCY]) {
survey_info->in_use = sinfo[NL80211_SURVEY_INFO_IN_USE] ? true : false;
if (!survey_info->in_use) {
return NL_SKIP;
}
survey_info->freq = nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]);
}
if (sinfo[NL80211_SURVEY_INFO_NOISE]) {
survey_info->noise = nla_get_u8(sinfo[NL80211_SURVEY_INFO_NOISE]);
}
if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]) {
survey_info->chan_active_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME]);
}
if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]) {
survey_info->chan_busy_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_BUSY]);
}
if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY]) {
survey_info->chan_ext_busy_time =
nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_EXT_BUSY]);
}
if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]) {
survey_info->chan_receive_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_RX]);
}
if (sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]) {
survey_info->chan_transmit_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_CHANNEL_TIME_TX]);
}
#if 0
if (sinfo[NL80211_SURVEY_INFO_TIME_SCAN]) {
survey_info->chan_scan_time = nla_get_u64(sinfo[NL80211_SURVEY_INFO_TIME_SCAN]);
}
#endif
return NL_SKIP;
}
/* Never seen this one called, but the examples have it. */
static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
{
cb_ctx_t *cb_ctx = (cb_ctx_t *)arg;
cb_ctx->status = err->error;
return NL_STOP;
}
/*
* Called after station info message is sent. The calling of this is how we
* know we're done.
*
* Other commands sent to netlink might get several responses back; this is
* more useful in those cases.
*/
static int finish_handler(struct nl_msg *msg, void *arg)
{
cb_ctx_t *cb_ctx = (cb_ctx_t *)arg;
cb_ctx->status = 0;
return NL_SKIP;
}
/* Never seen this one called, but the examples have it. */
static int ack_handler(struct nl_msg *msg, void *arg)
{
cb_ctx_t *cb_ctx = (cb_ctx_t *)arg;
cb_ctx->status = 0;
return NL_STOP;
}