forked from external/yambar
1659 lines
46 KiB
C
1659 lines
46 KiB
C
#include <assert.h>
|
||
#include <errno.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <time.h>
|
||
#include <unistd.h>
|
||
|
||
#include <poll.h>
|
||
#include <sys/timerfd.h>
|
||
#include <threads.h>
|
||
|
||
#include <fcntl.h>
|
||
#include <sys/stat.h>
|
||
|
||
#include <arpa/inet.h>
|
||
#include <linux/genetlink.h>
|
||
#include <linux/if.h>
|
||
#include <linux/if_arp.h>
|
||
#include <linux/netlink.h>
|
||
#include <linux/nl80211.h>
|
||
#include <linux/rtnetlink.h>
|
||
|
||
#include <tllist.h>
|
||
|
||
#define LOG_MODULE "network"
|
||
#define LOG_ENABLE_DBG 1
|
||
#include "../bar/bar.h"
|
||
#include "../config-verify.h"
|
||
#include "../config.h"
|
||
#include "../log.h"
|
||
#include "../module.h"
|
||
#include "../particles/dynlist.h"
|
||
#include "../plugin.h"
|
||
|
||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||
|
||
static const long min_poll_interval = 250;
|
||
|
||
struct rt_stats_msg {
|
||
struct rtmsg rth;
|
||
struct rtnl_link_stats64 stats;
|
||
};
|
||
|
||
struct af_addr {
|
||
int family;
|
||
union {
|
||
struct in_addr ipv4;
|
||
struct in6_addr ipv6;
|
||
} addr;
|
||
};
|
||
|
||
struct iface {
|
||
char *name;
|
||
char *type; /* ARPHRD_NNN */
|
||
char *kind; /* IFLA_LINKINFO::IFLA_INFO_KIND */
|
||
|
||
uint32_t get_stats_seq_nr;
|
||
|
||
int index;
|
||
uint8_t mac[6];
|
||
bool carrier;
|
||
uint8_t state; /* IFLA_OPERSTATE */
|
||
|
||
/* IPv4 and IPv6 addresses */
|
||
tll(struct af_addr) addrs;
|
||
|
||
/* WiFi extensions */
|
||
char *ssid;
|
||
int signal_strength_dbm;
|
||
uint32_t rx_bitrate;
|
||
uint32_t tx_bitrate;
|
||
|
||
double ul_speed;
|
||
uint64_t ul_bits;
|
||
|
||
double dl_speed;
|
||
uint64_t dl_bits;
|
||
};
|
||
|
||
struct private
|
||
{
|
||
struct particle *label;
|
||
int poll_interval;
|
||
|
||
int left_spacing;
|
||
int right_spacing;
|
||
|
||
bool get_addresses;
|
||
|
||
int genl_sock;
|
||
int rt_sock;
|
||
int urandom_fd;
|
||
|
||
struct {
|
||
uint16_t family_id;
|
||
uint32_t get_interface_seq_nr;
|
||
uint32_t get_scan_seq_nr;
|
||
} nl80211;
|
||
|
||
tll(struct iface) ifaces;
|
||
};
|
||
|
||
static void
|
||
free_iface(struct iface iface)
|
||
{
|
||
tll_free(iface.addrs);
|
||
free(iface.ssid);
|
||
free(iface.kind);
|
||
free(iface.type);
|
||
free(iface.name);
|
||
}
|
||
|
||
static void
|
||
destroy(struct module *mod)
|
||
{
|
||
struct private *m = mod->private;
|
||
|
||
assert(m->rt_sock == -1);
|
||
|
||
m->label->destroy(m->label);
|
||
|
||
if (m->urandom_fd >= 0)
|
||
close(m->urandom_fd);
|
||
|
||
tll_foreach(m->ifaces, it) {
|
||
free_iface(it->item);
|
||
tll_remove(m->ifaces, it);
|
||
}
|
||
|
||
free(m);
|
||
module_default_destroy(mod);
|
||
}
|
||
|
||
static const char *
|
||
description(const struct module *mod)
|
||
{
|
||
return "network";
|
||
}
|
||
|
||
static struct exposable *
|
||
content(struct module *mod)
|
||
{
|
||
struct private *m = mod->private;
|
||
|
||
mtx_lock(&mod->lock);
|
||
|
||
struct exposable *exposables[max(tll_length(m->ifaces), 1)];
|
||
size_t idx = 0;
|
||
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
struct iface *iface = &it->item;
|
||
|
||
const char *state = NULL;
|
||
switch (iface->state) {
|
||
case IF_OPER_UNKNOWN:
|
||
state = "unknown";
|
||
break;
|
||
case IF_OPER_NOTPRESENT:
|
||
state = "not present";
|
||
break;
|
||
case IF_OPER_DOWN:
|
||
state = "down";
|
||
break;
|
||
case IF_OPER_LOWERLAYERDOWN:
|
||
state = "lower layers down";
|
||
break;
|
||
case IF_OPER_TESTING:
|
||
state = "testing";
|
||
break;
|
||
case IF_OPER_DORMANT:
|
||
state = "dormant";
|
||
break;
|
||
case IF_OPER_UP:
|
||
state = "up";
|
||
break;
|
||
default:
|
||
state = "unknown";
|
||
break;
|
||
}
|
||
|
||
char mac_str[6 * 2 + 5 + 1];
|
||
char ipv4_str[INET_ADDRSTRLEN] = {0};
|
||
char ipv6_str[INET6_ADDRSTRLEN] = {0};
|
||
|
||
snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", iface->mac[0], iface->mac[1], iface->mac[2],
|
||
iface->mac[3], iface->mac[4], iface->mac[5]);
|
||
|
||
/* TODO: this exposes the *last* added address of each kind. Can
|
||
* we expose all in some way? */
|
||
tll_foreach(iface->addrs, it)
|
||
{
|
||
if (it->item.family == AF_INET)
|
||
inet_ntop(AF_INET, &it->item.addr.ipv4, ipv4_str, sizeof(ipv4_str));
|
||
else if (it->item.family == AF_INET6)
|
||
if (!IN6_IS_ADDR_LINKLOCAL(&it->item.addr.ipv6))
|
||
inet_ntop(AF_INET6, &it->item.addr.ipv6, ipv6_str, sizeof(ipv6_str));
|
||
}
|
||
|
||
int quality = 0;
|
||
if (iface->signal_strength_dbm != 0) {
|
||
if (iface->signal_strength_dbm <= -100)
|
||
quality = 0;
|
||
else if (iface->signal_strength_dbm >= -50)
|
||
quality = 100;
|
||
else
|
||
quality = 2 * (iface->signal_strength_dbm + 100);
|
||
}
|
||
|
||
struct tag_set tags = {
|
||
.tags = (struct tag *[]){
|
||
tag_new_string(mod, "name", iface->name),
|
||
tag_new_string(mod, "type", iface->type),
|
||
tag_new_string(mod, "kind", iface->kind),
|
||
tag_new_int(mod, "index", iface->index),
|
||
tag_new_bool(mod, "carrier", iface->carrier),
|
||
tag_new_string(mod, "state", state),
|
||
tag_new_string(mod, "mac", mac_str),
|
||
tag_new_string(mod, "ipv4", ipv4_str),
|
||
tag_new_string(mod, "ipv6", ipv6_str),
|
||
tag_new_string(mod, "ssid", iface->ssid),
|
||
tag_new_int(mod, "signal", iface->signal_strength_dbm),
|
||
tag_new_int_range(mod, "quality", quality, 0, 100),
|
||
tag_new_int(mod, "rx-bitrate", iface->rx_bitrate),
|
||
tag_new_int(mod, "tx-bitrate", iface->tx_bitrate),
|
||
tag_new_float(mod, "dl-speed", iface->dl_speed),
|
||
tag_new_float(mod, "ul-speed", iface->ul_speed),
|
||
},
|
||
.count = 16,
|
||
};
|
||
exposables[idx++] = m->label->instantiate(m->label, &tags);
|
||
tag_set_destroy(&tags);
|
||
}
|
||
|
||
mtx_unlock(&mod->lock);
|
||
|
||
return dynlist_exposable_new(exposables, idx, m->left_spacing, m->right_spacing);
|
||
}
|
||
|
||
/* Returns a value suitable for nl_pid/nlmsg_pid */
|
||
static uint32_t
|
||
nl_pid_value(void)
|
||
{
|
||
return (pid_t)(uintptr_t)thrd_current() ^ getpid();
|
||
}
|
||
|
||
/* Connect and bind to netlink socket. Returns socket fd, or -1 on error */
|
||
static int
|
||
netlink_connect_rt(void)
|
||
{
|
||
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
|
||
if (sock == -1) {
|
||
LOG_ERRNO("failed to create netlink socket");
|
||
return -1;
|
||
}
|
||
|
||
const struct sockaddr_nl addr = {
|
||
.nl_family = AF_NETLINK,
|
||
.nl_pid = nl_pid_value(),
|
||
.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR,
|
||
};
|
||
|
||
if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||
LOG_ERRNO("failed to bind netlink RT socket");
|
||
close(sock);
|
||
return -1;
|
||
}
|
||
|
||
return sock;
|
||
}
|
||
|
||
static int
|
||
netlink_connect_genl(void)
|
||
{
|
||
int sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC);
|
||
if (sock == -1) {
|
||
LOG_ERRNO("failed to create netlink socket");
|
||
return -1;
|
||
}
|
||
|
||
const struct sockaddr_nl addr = {
|
||
.nl_family = AF_NETLINK, .nl_pid = nl_pid_value(),
|
||
/* no multicast notifications by default, will be added later */
|
||
};
|
||
|
||
if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||
LOG_ERRNO("failed to bind netlink socket");
|
||
close(sock);
|
||
return -1;
|
||
}
|
||
|
||
return sock;
|
||
}
|
||
|
||
static bool
|
||
send_nlmsg(int sock, const void *nlmsg, size_t len)
|
||
{
|
||
int r = sendto(sock, nlmsg, len, 0, (struct sockaddr *)&(struct sockaddr_nl){.nl_family = AF_NETLINK},
|
||
sizeof(struct sockaddr_nl));
|
||
|
||
return r == len;
|
||
}
|
||
|
||
static bool
|
||
send_rt_request(struct private *m, int request)
|
||
{
|
||
struct {
|
||
struct nlmsghdr hdr;
|
||
struct rtgenmsg rt __attribute__((aligned(NLMSG_ALIGNTO)));
|
||
} req = {
|
||
.hdr = {
|
||
.nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)),
|
||
.nlmsg_type = request,
|
||
.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
|
||
.nlmsg_seq = 1,
|
||
.nlmsg_pid = nl_pid_value(),
|
||
},
|
||
|
||
.rt = {
|
||
.rtgen_family = AF_UNSPEC,
|
||
},
|
||
};
|
||
|
||
if (!send_nlmsg(m->rt_sock, &req, req.hdr.nlmsg_len)) {
|
||
LOG_ERRNO("failed to send netlink RT request (%d)", request);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
send_rt_getstats_request(struct private *m, struct iface *iface)
|
||
{
|
||
if (iface->get_stats_seq_nr > 0) {
|
||
LOG_DBG("%s: RT get-stats request already in progress", iface->name);
|
||
return true;
|
||
}
|
||
|
||
LOG_DBG("%s: sending RT get-stats request", iface->name);
|
||
|
||
uint32_t seq;
|
||
if (read(m->urandom_fd, &seq, sizeof(seq)) != sizeof(seq)) {
|
||
LOG_ERRNO("failed to read from /dev/urandom");
|
||
return false;
|
||
}
|
||
|
||
struct {
|
||
struct nlmsghdr hdr;
|
||
struct if_stats_msg rt;
|
||
} req = {
|
||
.hdr = {
|
||
.nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)),
|
||
.nlmsg_type = RTM_GETSTATS,
|
||
.nlmsg_flags = NLM_F_REQUEST,
|
||
.nlmsg_seq = seq,
|
||
.nlmsg_pid = nl_pid_value(),
|
||
},
|
||
|
||
.rt = {
|
||
.ifindex = iface->index,
|
||
.filter_mask = IFLA_STATS_LINK_64,
|
||
.family = AF_UNSPEC,
|
||
},
|
||
};
|
||
|
||
if (!send_nlmsg(m->rt_sock, &req, req.hdr.nlmsg_len)) {
|
||
LOG_ERRNO("%s: failed to send netlink RT getstats request (%d)", iface->name, RTM_GETSTATS);
|
||
return false;
|
||
}
|
||
iface->get_stats_seq_nr = seq;
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
send_ctrl_get_family_request(struct private *m)
|
||
{
|
||
const struct {
|
||
struct nlmsghdr hdr;
|
||
struct {
|
||
struct genlmsghdr genl;
|
||
struct {
|
||
struct nlattr hdr;
|
||
char data[8] __attribute__((aligned(NLA_ALIGNTO)));
|
||
} family_name_attr __attribute__((aligned(NLA_ALIGNTO)));
|
||
} msg __attribute__((aligned(NLMSG_ALIGNTO)));
|
||
} req = {
|
||
.hdr = {
|
||
.nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)),
|
||
.nlmsg_type = GENL_ID_CTRL,
|
||
.nlmsg_flags = NLM_F_REQUEST,
|
||
.nlmsg_seq = 1,
|
||
.nlmsg_pid = nl_pid_value(),
|
||
},
|
||
|
||
.msg = {
|
||
.genl = {
|
||
.cmd = CTRL_CMD_GETFAMILY,
|
||
.version = 1,
|
||
},
|
||
|
||
.family_name_attr = {
|
||
.hdr = {
|
||
.nla_type = CTRL_ATTR_FAMILY_NAME,
|
||
.nla_len = sizeof(req.msg.family_name_attr),
|
||
},
|
||
|
||
.data = NL80211_GENL_NAME,
|
||
},
|
||
},
|
||
};
|
||
|
||
_Static_assert(sizeof(req.msg.family_name_attr) == NLA_HDRLEN + NLA_ALIGN(sizeof(req.msg.family_name_attr.data)),
|
||
"");
|
||
|
||
if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) {
|
||
LOG_ERRNO("failed to send netlink ctrl-get-family request");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
send_nl80211_request(struct private *m, uint8_t cmd, uint32_t seq)
|
||
{
|
||
if (m->nl80211.family_id == (uint16_t)-1)
|
||
return false;
|
||
|
||
const struct {
|
||
struct nlmsghdr hdr;
|
||
struct {
|
||
struct genlmsghdr genl;
|
||
} msg __attribute__((aligned(NLMSG_ALIGNTO)));
|
||
} req = {
|
||
.hdr = {
|
||
.nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)),
|
||
.nlmsg_type = m->nl80211.family_id,
|
||
.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
|
||
.nlmsg_seq = seq,
|
||
.nlmsg_pid = nl_pid_value(),
|
||
},
|
||
|
||
.msg = {
|
||
.genl = {
|
||
.cmd = cmd,
|
||
.version = 1,
|
||
},
|
||
},
|
||
};
|
||
|
||
if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) {
|
||
LOG_ERRNO("failed to send netlink nl80211 get-inteface request");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
send_nl80211_get_interface(struct private *m)
|
||
{
|
||
if (m->nl80211.get_interface_seq_nr > 0) {
|
||
LOG_DBG("nl80211 get-interface request already in progress");
|
||
return true;
|
||
}
|
||
|
||
uint32_t seq;
|
||
if (read(m->urandom_fd, &seq, sizeof(seq)) != sizeof(seq)) {
|
||
LOG_ERRNO("failed to read from /dev/urandom");
|
||
return false;
|
||
}
|
||
|
||
LOG_DBG("sending nl80211 get-interface request %d", seq);
|
||
|
||
if (!send_nl80211_request(m, NL80211_CMD_GET_INTERFACE, seq))
|
||
return false;
|
||
|
||
m->nl80211.get_interface_seq_nr = seq;
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
send_nl80211_get_station(struct private *m, struct iface *iface)
|
||
{
|
||
LOG_DBG("sending nl80211 get-station request");
|
||
|
||
if (m->nl80211.family_id == (uint16_t)-1)
|
||
return false;
|
||
|
||
const struct {
|
||
struct nlmsghdr hdr;
|
||
struct {
|
||
struct genlmsghdr genl;
|
||
struct {
|
||
struct nlattr attr;
|
||
int index __attribute__((aligned(NLA_ALIGNTO)));
|
||
} ifindex __attribute__((aligned(NLA_ALIGNTO)));
|
||
} msg __attribute__((aligned(NLMSG_ALIGNTO)));
|
||
} req = {
|
||
.hdr = {
|
||
.nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)),
|
||
.nlmsg_type = m->nl80211.family_id,
|
||
.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
|
||
.nlmsg_seq = 1,
|
||
.nlmsg_pid = nl_pid_value(),
|
||
},
|
||
|
||
.msg = {
|
||
.genl = {
|
||
.cmd = NL80211_CMD_GET_STATION,
|
||
.version = 1,
|
||
},
|
||
|
||
.ifindex = {
|
||
.attr = {
|
||
.nla_type = NL80211_ATTR_IFINDEX,
|
||
.nla_len = sizeof(req.msg.ifindex),
|
||
},
|
||
|
||
.index = iface->index,
|
||
},
|
||
},
|
||
};
|
||
|
||
if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) {
|
||
LOG_ERRNO("failed to send netlink nl80211 get-inteface request");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
send_nl80211_get_scan(struct private *m)
|
||
{
|
||
if (m->nl80211.get_scan_seq_nr > 0) {
|
||
LOG_ERR("nl80211 get-scan request already in progress");
|
||
return true;
|
||
}
|
||
|
||
uint32_t seq;
|
||
if (read(m->urandom_fd, &seq, sizeof(seq)) != sizeof(seq)) {
|
||
LOG_ERRNO("failed to read from /dev/urandom");
|
||
return false;
|
||
}
|
||
|
||
LOG_DBG("sending nl80211 get-scan request %d", seq);
|
||
|
||
if (!send_nl80211_request(m, NL80211_CMD_GET_SCAN, seq))
|
||
return false;
|
||
|
||
m->nl80211.get_scan_seq_nr = seq;
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
foreach_nlattr(struct module *mod, struct iface *iface, const struct genlmsghdr *genl, size_t len,
|
||
bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *ctx),
|
||
void *ctx)
|
||
{
|
||
const uint8_t *raw = (const uint8_t *)genl + GENL_HDRLEN;
|
||
const uint8_t *end = (const uint8_t *)genl + len;
|
||
|
||
for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end;
|
||
raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) {
|
||
uint16_t type = attr->nla_type & NLA_TYPE_MASK;
|
||
bool nested = (attr->nla_type & NLA_F_NESTED) != 0;
|
||
;
|
||
const void *payload = raw + NLA_HDRLEN;
|
||
|
||
if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx))
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
foreach_nlattr_nested(struct module *mod, struct iface *iface, const void *parent_payload, size_t len,
|
||
bool (*cb)(struct module *mod, struct iface *iface, uint16_t type, bool nested,
|
||
const void *payload, size_t len, void *ctx),
|
||
void *ctx)
|
||
{
|
||
const uint8_t *raw = parent_payload;
|
||
const uint8_t *end = parent_payload + len;
|
||
|
||
for (const struct nlattr *attr = (const struct nlattr *)raw; raw < end;
|
||
raw += NLA_ALIGN(attr->nla_len), attr = (const struct nlattr *)raw) {
|
||
uint16_t type = attr->nla_type & NLA_TYPE_MASK;
|
||
bool nested = (attr->nla_type & NLA_F_NESTED) != 0;
|
||
const void *payload = raw + NLA_HDRLEN;
|
||
|
||
if (!cb(mod, iface, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx))
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
parse_linkinfo(struct module *mod, struct iface *iface, uint16_t type,
|
||
bool nested, const void *payload, size_t len, void *_void)
|
||
{
|
||
switch (type) {
|
||
case IFLA_INFO_KIND: {
|
||
const char *kind = payload;
|
||
free(iface->kind);
|
||
iface->kind = strndup(kind, len);
|
||
|
||
LOG_DBG("%s: IFLA_INFO_KIND: %s", iface->name, iface->kind);
|
||
break;
|
||
}
|
||
|
||
case IFLA_INFO_DATA:
|
||
//LOG_DBG("%s: IFLA_INFO_DATA", iface->name);
|
||
break;
|
||
|
||
default:
|
||
LOG_WARN("unrecognized IFLA_LINKINFO attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static void
|
||
handle_link(struct module *mod, uint16_t type, const struct ifinfomsg *msg, size_t len)
|
||
{
|
||
assert(type == RTM_NEWLINK || type == RTM_DELLINK);
|
||
|
||
struct private *m = mod->private;
|
||
|
||
if (type == RTM_DELLINK) {
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
if (msg->ifi_index != it->item.index)
|
||
continue;
|
||
mtx_lock(&mod->lock);
|
||
tll_remove_and_free(m->ifaces, it, free_iface);
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
}
|
||
|
||
mod->bar->refresh(mod->bar);
|
||
return;
|
||
}
|
||
|
||
struct iface *iface = NULL;
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
if (msg->ifi_index != it->item.index)
|
||
continue;
|
||
iface = &it->item;
|
||
break;
|
||
}
|
||
|
||
if (iface == NULL) {
|
||
char *type = NULL;
|
||
|
||
switch (msg->ifi_type) {
|
||
case ARPHRD_ETHER:
|
||
type = strdup("ether");
|
||
break;
|
||
|
||
case ARPHRD_LOOPBACK:
|
||
type = strdup("loopback");
|
||
break;
|
||
|
||
case ARPHRD_IEEE80211:
|
||
type = strdup("wlan");
|
||
break;
|
||
|
||
default:
|
||
if (asprintf(&type, "ARPHRD_%hu", msg->ifi_type) < 0)
|
||
type = strdup("unknown");
|
||
break;
|
||
}
|
||
|
||
mtx_lock(&mod->lock);
|
||
tll_push_back(m->ifaces, ((struct iface){
|
||
.index = msg->ifi_index,
|
||
.type = type,
|
||
.state = IF_OPER_DOWN,
|
||
.addrs = tll_init(),
|
||
}));
|
||
mtx_unlock(&mod->lock);
|
||
iface = &tll_back(m->ifaces);
|
||
}
|
||
|
||
for (const struct rtattr *attr = IFLA_RTA(msg); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
|
||
switch (attr->rta_type) {
|
||
case IFLA_IFNAME:
|
||
mtx_lock(&mod->lock);
|
||
iface->name = strdup((const char *)RTA_DATA(attr));
|
||
LOG_DBG("%s: index=%d, type=%s", iface->name, iface->index, iface->type);
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
|
||
case IFLA_OPERSTATE: {
|
||
uint8_t operstate = *(const uint8_t *)RTA_DATA(attr);
|
||
if (iface->state == operstate)
|
||
break;
|
||
|
||
LOG_DBG("%s: IFLA_OPERSTATE: %hhu -> %hhu", iface->name, iface->state, operstate);
|
||
|
||
mtx_lock(&mod->lock);
|
||
iface->state = operstate;
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
}
|
||
|
||
case IFLA_CARRIER: {
|
||
uint8_t carrier = *(const uint8_t *)RTA_DATA(attr);
|
||
if (iface->carrier == carrier)
|
||
break;
|
||
|
||
LOG_DBG("%s: IFLA_CARRIER: %hhu -> %hhu", iface->name, iface->carrier, carrier);
|
||
|
||
mtx_lock(&mod->lock);
|
||
iface->carrier = carrier;
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
}
|
||
|
||
case IFLA_ADDRESS: {
|
||
if (RTA_PAYLOAD(attr) != 6)
|
||
break;
|
||
|
||
const uint8_t *mac = RTA_DATA(attr);
|
||
if (memcmp(iface->mac, mac, sizeof(iface->mac)) == 0)
|
||
break;
|
||
|
||
LOG_DBG("%s: IFLA_ADDRESS: %02x:%02x:%02x:%02x:%02x:%02x",
|
||
iface->name, mac[0], mac[1], mac[2], mac[3],
|
||
mac[4], mac[5]);
|
||
|
||
mtx_lock(&mod->lock);
|
||
memcpy(iface->mac, mac, sizeof(iface->mac));
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
}
|
||
|
||
case IFLA_LINKINFO: {
|
||
foreach_nlattr_nested(
|
||
mod, iface, RTA_DATA(attr), RTA_PAYLOAD(attr),
|
||
&parse_linkinfo, NULL);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
assert(iface->name != NULL);
|
||
|
||
/* Reset address initialization */
|
||
m->get_addresses = true;
|
||
|
||
send_nl80211_get_interface(m);
|
||
mod->bar->refresh(mod->bar);
|
||
}
|
||
|
||
static void
|
||
handle_address(struct module *mod, uint16_t type, const struct ifaddrmsg *msg, size_t len)
|
||
{
|
||
assert(type == RTM_NEWADDR || type == RTM_DELADDR);
|
||
|
||
struct private *m = mod->private;
|
||
|
||
bool update_bar = false;
|
||
|
||
struct iface *iface = NULL;
|
||
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
if (msg->ifa_index != it->item.index)
|
||
continue;
|
||
iface = &it->item;
|
||
break;
|
||
}
|
||
|
||
if (iface == NULL) {
|
||
LOG_ERR("failed to find network interface with index %d. Probably a yambar bug", msg->ifa_index);
|
||
return;
|
||
}
|
||
|
||
for (const struct rtattr *attr = IFA_RTA(msg); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
|
||
switch (attr->rta_type) {
|
||
case IFA_ADDRESS: {
|
||
const void *raw_addr = RTA_DATA(attr);
|
||
size_t addr_len = RTA_PAYLOAD(attr);
|
||
|
||
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
|
||
char s[INET6_ADDRSTRLEN];
|
||
inet_ntop(msg->ifa_family, raw_addr, s, sizeof(s));
|
||
#endif
|
||
LOG_DBG("%s: IFA_ADDRESS (%s): %s", iface->name, type == RTM_NEWADDR ? "add" : "del", s);
|
||
|
||
mtx_lock(&mod->lock);
|
||
|
||
if (type == RTM_DELADDR) {
|
||
/* Find address in our list and remove it */
|
||
tll_foreach(iface->addrs, it)
|
||
{
|
||
if (it->item.family != msg->ifa_family)
|
||
continue;
|
||
|
||
if (memcmp(&it->item.addr, raw_addr, addr_len) != 0)
|
||
continue;
|
||
|
||
tll_remove(iface->addrs, it);
|
||
update_bar = true;
|
||
break;
|
||
}
|
||
} else {
|
||
/* Append address to our list */
|
||
struct af_addr a = {.family = msg->ifa_family};
|
||
memcpy(&a.addr, raw_addr, addr_len);
|
||
tll_push_back(iface->addrs, a);
|
||
update_bar = true;
|
||
}
|
||
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (update_bar)
|
||
mod->bar->refresh(mod->bar);
|
||
}
|
||
|
||
struct mcast_group {
|
||
uint32_t id;
|
||
const char *name;
|
||
};
|
||
|
||
static bool
|
||
parse_mcast_group(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len,
|
||
void *_ctx)
|
||
{
|
||
struct mcast_group *ctx = _ctx;
|
||
|
||
switch (type) {
|
||
case CTRL_ATTR_MCAST_GRP_ID: {
|
||
ctx->id = *(uint32_t *)payload;
|
||
break;
|
||
}
|
||
|
||
case CTRL_ATTR_MCAST_GRP_NAME: {
|
||
ctx->name = (const char *)payload;
|
||
break;
|
||
}
|
||
|
||
default:
|
||
LOG_WARN("unrecognized GENL MCAST GRP attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
parse_mcast_groups(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len,
|
||
void *_ctx)
|
||
{
|
||
struct private *m = mod->private;
|
||
|
||
struct mcast_group group = {0};
|
||
foreach_nlattr_nested(mod, NULL, payload, len, &parse_mcast_group, &group);
|
||
|
||
LOG_DBG("MCAST: %s -> %u", group.name, group.id);
|
||
|
||
if (strcmp(group.name, NL80211_MULTICAST_GROUP_MLME) == 0) {
|
||
/*
|
||
* Join the nl80211 MLME multicast group - for
|
||
* CONNECT/DISCONNECT events.
|
||
*/
|
||
|
||
int r = setsockopt(m->genl_sock, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group.id, sizeof(int));
|
||
|
||
if (r < 0)
|
||
LOG_ERRNO("failed to joint the nl80211 MLME mcast group");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
handle_genl_ctrl(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len,
|
||
void *_ctx)
|
||
{
|
||
struct private *m = mod->private;
|
||
|
||
switch (type) {
|
||
case CTRL_ATTR_FAMILY_ID: {
|
||
m->nl80211.family_id = *(const uint16_t *)payload;
|
||
send_nl80211_get_interface(m);
|
||
break;
|
||
}
|
||
|
||
case CTRL_ATTR_FAMILY_NAME:
|
||
// LOG_INFO("NAME: %.*s (%zu bytes)", (int)len, (const char *)payload, len);
|
||
break;
|
||
|
||
case CTRL_ATTR_MCAST_GROUPS:
|
||
foreach_nlattr_nested(mod, NULL, payload, len, &parse_mcast_groups, NULL);
|
||
break;
|
||
|
||
default:
|
||
LOG_DBG("unrecognized GENL CTRL attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
find_nl80211_iface(struct module *mod, struct iface *_iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *ctx)
|
||
{
|
||
struct private *m = mod->private;
|
||
struct iface **iface = ctx;
|
||
|
||
switch (type) {
|
||
case NL80211_ATTR_IFINDEX:
|
||
if (*iface != NULL)
|
||
if (*(uint32_t *)payload == (*iface)->index)
|
||
return false;
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
if (*(uint32_t *)payload != it->item.index)
|
||
continue;
|
||
*iface = &it->item;
|
||
return false;
|
||
}
|
||
LOG_ERR("could not find interface with index %d", *(uint32_t *)payload);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
handle_nl80211_new_interface(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *_ctx)
|
||
{
|
||
switch (type) {
|
||
case NL80211_ATTR_IFINDEX:
|
||
assert(*(uint32_t *)payload == iface->index);
|
||
break;
|
||
|
||
case NL80211_ATTR_SSID: {
|
||
const char *ssid = payload;
|
||
|
||
if (iface->ssid == NULL || strncmp(iface->ssid, ssid, len) != 0)
|
||
LOG_INFO("%s: SSID: %.*s", iface->name, (int)len, ssid);
|
||
|
||
mtx_lock(&mod->lock);
|
||
free(iface->ssid);
|
||
iface->ssid = strndup(ssid, len);
|
||
mtx_unlock(&mod->lock);
|
||
|
||
mod->bar->refresh(mod->bar);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
LOG_DBG("%s: unrecognized nl80211 attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
iface->name, type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
struct rate_info_ctx {
|
||
unsigned bitrate;
|
||
};
|
||
|
||
static bool
|
||
handle_nl80211_rate_info(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *_ctx)
|
||
{
|
||
struct rate_info_ctx *ctx = _ctx;
|
||
|
||
switch (type) {
|
||
case NL80211_RATE_INFO_BITRATE32: {
|
||
uint32_t bitrate_100kbit = *(uint32_t *)payload;
|
||
ctx->bitrate = bitrate_100kbit * 100 * 1000;
|
||
break;
|
||
}
|
||
|
||
case NL80211_RATE_INFO_BITRATE:
|
||
if (ctx->bitrate == 0) {
|
||
uint16_t bitrate_100kbit = *(uint16_t *)payload;
|
||
ctx->bitrate = bitrate_100kbit * 100 * 1000;
|
||
} else {
|
||
/* Prefer the BITRATE32 attribute */
|
||
}
|
||
break;
|
||
|
||
default:
|
||
LOG_DBG("%s: unrecognized nl80211 rate info attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
iface->name, type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
struct station_info_ctx {
|
||
bool update_bar;
|
||
};
|
||
|
||
static bool
|
||
handle_nl80211_station_info(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *_ctx)
|
||
{
|
||
struct station_info_ctx *ctx = _ctx;
|
||
|
||
switch (type) {
|
||
case NL80211_STA_INFO_SIGNAL:
|
||
LOG_DBG("signal strength (last): %hhd dBm", *(uint8_t *)payload);
|
||
break;
|
||
|
||
case NL80211_STA_INFO_SIGNAL_AVG:
|
||
LOG_DBG("signal strength (average): %hhd dBm", *(uint8_t *)payload);
|
||
mtx_lock(&mod->lock);
|
||
iface->signal_strength_dbm = *(int8_t *)payload;
|
||
mtx_unlock(&mod->lock);
|
||
ctx->update_bar = true;
|
||
break;
|
||
|
||
case NL80211_STA_INFO_TX_BITRATE: {
|
||
struct rate_info_ctx rctx = {0};
|
||
foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_rate_info, &rctx);
|
||
|
||
LOG_DBG("TX bitrate: %.1f Mbit/s", rctx.bitrate / 1000. / 1000.);
|
||
mtx_lock(&mod->lock);
|
||
iface->tx_bitrate = rctx.bitrate;
|
||
mtx_unlock(&mod->lock);
|
||
ctx->update_bar = true;
|
||
break;
|
||
}
|
||
|
||
case NL80211_STA_INFO_RX_BITRATE: {
|
||
struct rate_info_ctx rctx = {0};
|
||
foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_rate_info, &rctx);
|
||
|
||
LOG_DBG("RX bitrate: %.1f Mbit/s", rctx.bitrate / 1000. / 1000.);
|
||
mtx_lock(&mod->lock);
|
||
iface->rx_bitrate = rctx.bitrate;
|
||
mtx_unlock(&mod->lock);
|
||
ctx->update_bar = true;
|
||
break;
|
||
}
|
||
|
||
default:
|
||
LOG_DBG("%s: unrecognized nl80211 station info attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
iface->name, type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
handle_nl80211_new_station(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *_ctx)
|
||
{
|
||
switch (type) {
|
||
case NL80211_ATTR_IFINDEX:
|
||
break;
|
||
|
||
case NL80211_ATTR_STA_INFO: {
|
||
struct station_info_ctx ctx = {0};
|
||
foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_station_info, &ctx);
|
||
|
||
if (ctx.update_bar)
|
||
mod->bar->refresh(mod->bar);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
LOG_DBG("%s: unrecognized nl80211 attribute: "
|
||
"type=%hu, nested=%d, len=%zu",
|
||
iface->name, type, nested, len);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
handle_ies(struct module *mod, struct iface *iface, const void *_ies, size_t len)
|
||
{
|
||
const uint8_t *ies = _ies;
|
||
|
||
while (len >= 2 && len - 2 >= ies[1]) {
|
||
switch (ies[0]) {
|
||
case 0: { /* SSID */
|
||
const char *ssid = (const char *)&ies[2];
|
||
const size_t ssid_len = ies[1];
|
||
|
||
if (iface->ssid == NULL || strncmp(iface->ssid, ssid, ssid_len) != 0)
|
||
LOG_INFO("%s: SSID: %.*s", iface->name, (int)ssid_len, ssid);
|
||
|
||
mtx_lock(&mod->lock);
|
||
free(iface->ssid);
|
||
iface->ssid = strndup(ssid, ssid_len);
|
||
mtx_unlock(&mod->lock);
|
||
|
||
mod->bar->refresh(mod->bar);
|
||
break;
|
||
}
|
||
}
|
||
len -= ies[1] + 2;
|
||
ies += ies[1] + 2;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
struct scan_results_context {
|
||
bool associated;
|
||
|
||
const void *ies;
|
||
size_t ies_size;
|
||
};
|
||
|
||
static bool
|
||
handle_nl80211_bss(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload, size_t len,
|
||
void *_ctx)
|
||
{
|
||
struct scan_results_context *ctx = _ctx;
|
||
|
||
switch (type) {
|
||
case NL80211_BSS_STATUS: {
|
||
const uint32_t status = *(uint32_t *)payload;
|
||
|
||
if (status == NL80211_BSS_STATUS_ASSOCIATED) {
|
||
ctx->associated = true;
|
||
|
||
if (ctx->ies != NULL) {
|
||
/* Deferred handling of BSS_INFORMATION_ELEMENTS */
|
||
return handle_ies(mod, iface, ctx->ies, ctx->ies_size);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
case NL80211_BSS_INFORMATION_ELEMENTS:
|
||
if (ctx->associated)
|
||
return handle_ies(mod, iface, payload, len);
|
||
else {
|
||
/*
|
||
* We’re either not associated, or, we haven’t seen the
|
||
* BSS_STATUS attribute yet.
|
||
*
|
||
* Save a pointer to the IES payload, so that we can
|
||
* process it later, if we see a
|
||
* BSS_STATUS == BSS_STATUS_ASSOCIATED.
|
||
*/
|
||
ctx->ies = payload;
|
||
ctx->ies_size = len;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
handle_nl80211_scan_results(struct module *mod, struct iface *iface, uint16_t type, bool nested, const void *payload,
|
||
size_t len, void *_ctx)
|
||
{
|
||
struct scan_results_context ctx = {0};
|
||
|
||
switch (type) {
|
||
case NL80211_ATTR_BSS:
|
||
foreach_nlattr_nested(mod, iface, payload, len, &handle_nl80211_bss, &ctx);
|
||
break;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* Reads at least one (possibly more) message.
|
||
*
|
||
* On success, 'reply' will point to a malloc:ed buffer, to be freed
|
||
* by the caller. 'len' is set to the size of the message (note that
|
||
* the allocated size may actually be larger).
|
||
*
|
||
* Returns true on success, otherwise false
|
||
*/
|
||
static bool
|
||
netlink_receive_messages(int sock, void **reply, size_t *len)
|
||
{
|
||
/* Use MSG_PEEK to find out how large buffer we need */
|
||
const size_t chunk_sz = 1024;
|
||
size_t sz = chunk_sz;
|
||
*reply = malloc(sz);
|
||
|
||
while (true) {
|
||
ssize_t bytes = recvfrom(sock, *reply, sz, MSG_PEEK, NULL, NULL);
|
||
if (bytes == -1) {
|
||
LOG_ERRNO("failed to receive from netlink socket");
|
||
free(*reply);
|
||
return false;
|
||
}
|
||
|
||
if (bytes < sz)
|
||
break;
|
||
|
||
sz += chunk_sz;
|
||
*reply = realloc(*reply, sz);
|
||
}
|
||
|
||
*len = recvfrom(sock, *reply, sz, 0, NULL, NULL);
|
||
assert(*len >= 0);
|
||
assert(*len < sz);
|
||
return true;
|
||
}
|
||
|
||
static void
|
||
handle_stats(struct module *mod, uint32_t seq, struct rt_stats_msg *msg)
|
||
{
|
||
struct private *m = mod->private;
|
||
|
||
struct iface *iface = NULL;
|
||
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
if (seq != it->item.get_stats_seq_nr)
|
||
continue;
|
||
iface = &it->item;
|
||
/* Current request is now considered complete */
|
||
iface->get_stats_seq_nr = 0;
|
||
break;
|
||
}
|
||
|
||
if (iface == NULL) {
|
||
LOG_ERR("Couldn't find iface");
|
||
return;
|
||
}
|
||
|
||
uint64_t ul_bits = msg->stats.tx_bytes * 8;
|
||
uint64_t dl_bits = msg->stats.rx_bytes * 8;
|
||
|
||
const double poll_interval_secs = (double)m->poll_interval / 1000.;
|
||
|
||
if (iface->ul_bits != 0)
|
||
iface->ul_speed = (double)(ul_bits - iface->ul_bits) / poll_interval_secs;
|
||
if (iface->dl_bits != 0)
|
||
iface->dl_speed = (double)(dl_bits - iface->dl_bits) / poll_interval_secs;
|
||
|
||
iface->ul_bits = ul_bits;
|
||
iface->dl_bits = dl_bits;
|
||
}
|
||
|
||
static bool
|
||
parse_rt_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len)
|
||
{
|
||
struct private *m = mod->private;
|
||
|
||
/* Process response */
|
||
for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) {
|
||
|
||
switch (hdr->nlmsg_type) {
|
||
case NLMSG_DONE:
|
||
|
||
/* Request initial list of IPv4/6 addresses */
|
||
if (m->get_addresses) {
|
||
m->get_addresses = false;
|
||
send_rt_request(m, RTM_GETADDR);
|
||
}
|
||
break;
|
||
|
||
case RTM_NEWLINK:
|
||
case RTM_DELLINK: {
|
||
const struct ifinfomsg *msg = NLMSG_DATA(hdr);
|
||
size_t msg_len = IFLA_PAYLOAD(hdr);
|
||
|
||
handle_link(mod, hdr->nlmsg_type, msg, msg_len);
|
||
break;
|
||
}
|
||
|
||
case RTM_NEWADDR:
|
||
case RTM_DELADDR: {
|
||
const struct ifaddrmsg *msg = NLMSG_DATA(hdr);
|
||
size_t msg_len = IFA_PAYLOAD(hdr);
|
||
|
||
handle_address(mod, hdr->nlmsg_type, msg, msg_len);
|
||
break;
|
||
}
|
||
case RTM_NEWSTATS: {
|
||
struct rt_stats_msg *msg = NLMSG_DATA(hdr);
|
||
handle_stats(mod, hdr->nlmsg_seq, msg);
|
||
break;
|
||
}
|
||
|
||
case NLMSG_ERROR: {
|
||
const struct nlmsgerr *err = NLMSG_DATA(hdr);
|
||
LOG_ERRNO_P(-err->error, "netlink RT reply");
|
||
return false;
|
||
}
|
||
|
||
default:
|
||
LOG_WARN("unrecognized netlink message type: 0x%x", hdr->nlmsg_type);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len)
|
||
{
|
||
struct private *m = mod->private;
|
||
struct iface *iface = NULL;
|
||
|
||
for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) {
|
||
if (hdr->nlmsg_type == GENL_ID_CTRL) {
|
||
assert(hdr->nlmsg_seq == 1);
|
||
const struct genlmsghdr *genl = NLMSG_DATA(hdr);
|
||
const size_t msg_size = NLMSG_PAYLOAD(hdr, 0);
|
||
foreach_nlattr(mod, NULL, genl, msg_size, &handle_genl_ctrl, NULL);
|
||
continue;
|
||
}
|
||
|
||
if (hdr->nlmsg_seq == m->nl80211.get_interface_seq_nr) {
|
||
/* Current request is now considered complete */
|
||
m->nl80211.get_interface_seq_nr = 0;
|
||
|
||
/* Can’t issue both get-station and get-scan at the
|
||
* same time. So, always run a get-scan when a
|
||
* get-station is complete */
|
||
send_nl80211_get_scan(m);
|
||
}
|
||
|
||
if (hdr->nlmsg_type == NLMSG_DONE) {
|
||
if (hdr->nlmsg_seq == m->nl80211.get_scan_seq_nr) {
|
||
/* Current request is now considered complete */
|
||
m->nl80211.get_scan_seq_nr = 0;
|
||
|
||
tll_foreach(m->ifaces, it) send_nl80211_get_station(m, &it->item);
|
||
}
|
||
}
|
||
|
||
else if (hdr->nlmsg_type == m->nl80211.family_id) {
|
||
const struct genlmsghdr *genl = NLMSG_DATA(hdr);
|
||
const size_t msg_size = NLMSG_PAYLOAD(hdr, 0);
|
||
|
||
switch (genl->cmd) {
|
||
case NL80211_CMD_NEW_INTERFACE:
|
||
if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface))
|
||
continue;
|
||
|
||
LOG_DBG("%s: got interface information", iface->name);
|
||
free(iface->type);
|
||
iface->type = strdup("wlan");
|
||
foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_new_interface, NULL);
|
||
break;
|
||
|
||
case NL80211_CMD_CONNECT:
|
||
/*
|
||
* Update SSID
|
||
*
|
||
* Unfortunately, the SSID doesn’t appear to be
|
||
* included in *any* of the notifications sent when
|
||
* associating, authenticating and connecting to a
|
||
* station.
|
||
*
|
||
* Thus, we need to explicitly request an update.
|
||
*/
|
||
LOG_DBG("connected, requesting interface information");
|
||
send_nl80211_get_interface(m);
|
||
break;
|
||
|
||
case NL80211_CMD_DISCONNECT:
|
||
if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface))
|
||
continue;
|
||
|
||
LOG_DBG("%s: disconnected, resetting SSID etc", iface->name);
|
||
|
||
mtx_lock(&mod->lock);
|
||
free(iface->ssid);
|
||
iface->ssid = NULL;
|
||
iface->signal_strength_dbm = 0;
|
||
iface->rx_bitrate = iface->tx_bitrate = 0;
|
||
mtx_unlock(&mod->lock);
|
||
break;
|
||
|
||
case NL80211_CMD_NEW_STATION:
|
||
if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface))
|
||
continue;
|
||
|
||
LOG_DBG("%s: got station information", iface->name);
|
||
foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_new_station, NULL);
|
||
|
||
LOG_DBG("%s: signal: %d dBm, RX=%u Mbit/s, TX=%u Mbit/s", iface->name, iface->signal_strength_dbm,
|
||
iface->rx_bitrate / 1000 / 1000, iface->tx_bitrate / 1000 / 1000);
|
||
break;
|
||
|
||
case NL80211_CMD_NEW_SCAN_RESULTS:
|
||
if (foreach_nlattr(mod, NULL, genl, msg_size, &find_nl80211_iface, &iface))
|
||
continue;
|
||
|
||
LOG_DBG("%s: got scan results", iface->name);
|
||
foreach_nlattr(mod, iface, genl, msg_size, &handle_nl80211_scan_results, NULL);
|
||
break;
|
||
|
||
default:
|
||
LOG_DBG("unrecognized nl80211 command: %hhu", genl->cmd);
|
||
break;
|
||
}
|
||
}
|
||
|
||
else if (hdr->nlmsg_type == NLMSG_ERROR) {
|
||
const struct nlmsgerr *err = NLMSG_DATA(hdr);
|
||
int nl_errno = -err->error;
|
||
|
||
if (nl_errno == ENODEV)
|
||
; /* iface is not an nl80211 device */
|
||
else if (nl_errno == ENOENT)
|
||
; /* iface down? */
|
||
else {
|
||
LOG_ERRNO_P(nl_errno, "nl80211 reply (seq-nr: %u)", hdr->nlmsg_seq);
|
||
}
|
||
}
|
||
|
||
else {
|
||
LOG_WARN("unrecognized netlink message type: 0x%x", hdr->nlmsg_type);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static int
|
||
run(struct module *mod)
|
||
{
|
||
int ret = 1;
|
||
struct private *m = mod->private;
|
||
|
||
int timer_fd = -1;
|
||
if (m->poll_interval > 0) {
|
||
timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
|
||
if (timer_fd < 0) {
|
||
LOG_ERRNO("failed to create poll timer FD");
|
||
goto out;
|
||
}
|
||
|
||
const long secs = m->poll_interval / 1000;
|
||
const long msecs = m->poll_interval % 1000;
|
||
|
||
struct itimerspec poll_time = {
|
||
.it_value = {.tv_sec = secs, .tv_nsec = msecs * 1000000},
|
||
.it_interval = {.tv_sec = secs, .tv_nsec = msecs * 1000000},
|
||
};
|
||
|
||
if (timerfd_settime(timer_fd, 0, &poll_time, NULL) < 0) {
|
||
LOG_ERRNO("failed to arm poll timer");
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
m->rt_sock = netlink_connect_rt();
|
||
m->genl_sock = netlink_connect_genl();
|
||
|
||
if (m->rt_sock < 0 || m->genl_sock < 0)
|
||
goto out;
|
||
|
||
if (!send_rt_request(m, RTM_GETLINK) || !send_ctrl_get_family_request(m)) {
|
||
goto out;
|
||
}
|
||
|
||
/* Main loop */
|
||
while (true) {
|
||
struct pollfd fds[] = {
|
||
{.fd = mod->abort_fd, .events = POLLIN},
|
||
{.fd = m->rt_sock, .events = POLLIN},
|
||
{.fd = m->genl_sock, .events = POLLIN},
|
||
{.fd = timer_fd, .events = POLLIN},
|
||
};
|
||
|
||
poll(fds, 3 + (timer_fd >= 0 ? 1 : 0), -1);
|
||
|
||
if (fds[0].revents & (POLLIN | POLLHUP))
|
||
break;
|
||
|
||
if ((fds[1].revents & POLLHUP) || (fds[2].revents & POLLHUP)) {
|
||
LOG_ERR("disconnected from netlink socket");
|
||
break;
|
||
}
|
||
|
||
if (fds[3].revents & POLLHUP) {
|
||
LOG_ERR("disconnected from timer FD");
|
||
break;
|
||
}
|
||
|
||
if (fds[1].revents & POLLIN) {
|
||
/* Read one (or more) messages */
|
||
void *reply;
|
||
size_t len;
|
||
if (!netlink_receive_messages(m->rt_sock, &reply, &len))
|
||
break;
|
||
|
||
/* Parse (and act upon) the received message(s) */
|
||
if (!parse_rt_reply(mod, (const struct nlmsghdr *)reply, len)) {
|
||
free(reply);
|
||
break;
|
||
}
|
||
|
||
free(reply);
|
||
}
|
||
|
||
if (fds[2].revents & POLLIN) {
|
||
/* Read one (or more) messages */
|
||
void *reply;
|
||
size_t len;
|
||
if (!netlink_receive_messages(m->genl_sock, &reply, &len))
|
||
break;
|
||
|
||
if (!parse_genl_reply(mod, (const struct nlmsghdr *)reply, len)) {
|
||
free(reply);
|
||
break;
|
||
}
|
||
|
||
free(reply);
|
||
}
|
||
|
||
if (fds[3].revents & POLLIN) {
|
||
uint64_t count;
|
||
ssize_t amount = read(timer_fd, &count, sizeof(count));
|
||
if (amount < 0) {
|
||
LOG_ERRNO("failed to read from timer FD");
|
||
break;
|
||
}
|
||
|
||
tll_foreach(m->ifaces, it)
|
||
{
|
||
send_nl80211_get_station(m, &it->item);
|
||
send_rt_getstats_request(m, &it->item);
|
||
};
|
||
}
|
||
}
|
||
|
||
ret = 0;
|
||
|
||
out:
|
||
if (m->rt_sock >= 0)
|
||
close(m->rt_sock);
|
||
if (m->genl_sock >= 0)
|
||
close(m->genl_sock);
|
||
if (timer_fd >= 0)
|
||
close(timer_fd);
|
||
m->rt_sock = m->genl_sock = -1;
|
||
return ret;
|
||
}
|
||
|
||
static struct module *
|
||
network_new(struct particle *label, int poll_interval, int left_spacing, int right_spacing)
|
||
{
|
||
int urandom_fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
|
||
if (urandom_fd < 0) {
|
||
LOG_ERRNO("failed to open /dev/urandom");
|
||
return NULL;
|
||
}
|
||
|
||
struct private *priv = calloc(1, sizeof(*priv));
|
||
priv->label = label;
|
||
priv->poll_interval = poll_interval;
|
||
priv->left_spacing = left_spacing;
|
||
priv->right_spacing = right_spacing;
|
||
|
||
priv->genl_sock = -1;
|
||
priv->rt_sock = -1;
|
||
priv->urandom_fd = urandom_fd;
|
||
priv->get_addresses = false;
|
||
priv->nl80211.family_id = -1;
|
||
|
||
struct module *mod = module_common_new();
|
||
mod->private = priv;
|
||
mod->run = &run;
|
||
mod->destroy = &destroy;
|
||
mod->content = &content;
|
||
mod->description = &description;
|
||
return mod;
|
||
}
|
||
|
||
static struct module *
|
||
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||
{
|
||
const struct yml_node *content = yml_get_value(node, "content");
|
||
const struct yml_node *poll = yml_get_value(node, "poll-interval");
|
||
const struct yml_node *spacing = yml_get_value(node, "spacing");
|
||
const struct yml_node *left_spacing = yml_get_value(node, "left-spacing");
|
||
const struct yml_node *right_spacing = yml_get_value(node, "right-spacing");
|
||
|
||
int left = spacing != NULL ? yml_value_as_int(spacing) : left_spacing != NULL ? yml_value_as_int(left_spacing) : 0;
|
||
int right = spacing != NULL ? yml_value_as_int(spacing)
|
||
: right_spacing != NULL ? yml_value_as_int(right_spacing)
|
||
: 0;
|
||
|
||
return network_new(conf_to_particle(content, inherited), poll != NULL ? yml_value_as_int(poll) : 0, left, right);
|
||
}
|
||
|
||
static bool
|
||
conf_verify_poll_interval(keychain_t *chain, const struct yml_node *node)
|
||
{
|
||
if (!conf_verify_unsigned(chain, node))
|
||
return false;
|
||
|
||
int interval = yml_value_as_int(node);
|
||
if (interval > 0 && interval < min_poll_interval) {
|
||
LOG_ERR("%s: interval value cannot be less than %ldms", conf_err_prefix(chain, node), min_poll_interval);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool
|
||
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||
{
|
||
static const struct attr_info attrs[] = {
|
||
{"spacing", false, &conf_verify_unsigned},
|
||
{"left-spacing", false, &conf_verify_unsigned},
|
||
{"right-spacing", false, &conf_verify_unsigned},
|
||
{"poll-interval", false, &conf_verify_poll_interval},
|
||
MODULE_COMMON_ATTRS,
|
||
};
|
||
|
||
return conf_verify_dict(chain, node, attrs);
|
||
}
|
||
|
||
const struct module_iface module_network_iface = {
|
||
.verify_conf = &verify_conf,
|
||
.from_conf = &from_conf,
|
||
};
|
||
|
||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||
extern const struct module_iface iface __attribute__((weak, alias("module_network_iface")));
|
||
#endif
|