diff --git a/CHANGELOG.md b/CHANGELOG.md index 501716a..0f587e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ * foreign-toplevel: Wayland module that provides information about currently opened windows. * alsa: support for capture devices. +* network: `ssid`, `signal`, `rx-bitrate` and `rx-bitrate` tags. +* network: `poll-interval` option (for the new `signal` and + `*-bitrate` tags). ### Changed diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd index c8a66d8..8ac46a1 100644 --- a/doc/yambar-modules-network.5.scd +++ b/doc/yambar-modules-network.5.scd @@ -39,6 +39,19 @@ address. | ipv6 : string : IPv6 address assigned to the interface, or *""* if none +| ssid +: string +: SSID the adapter is connected to (Wi-Fi only) +| signal +: int +: Signal strength, in dBm (Wi-Fi only) +| rx-bitrate +: int +: RX bitrate, in Mbit/s +| tx-bitrate +: int +: TX bitrate in Mbit/s + # CONFIGURATION @@ -50,6 +63,11 @@ address. : string : yes : Name of network interface to monitor +| poll-interval +: int +: no +: Periodically (in seconds) update the signal and rx+tx bitrate tags. + # EXAMPLES diff --git a/examples/configurations/laptop.conf b/examples/configurations/laptop.conf index f079f2a..eab6c46 100644 --- a/examples/configurations/laptop.conf +++ b/examples/configurations/laptop.conf @@ -219,9 +219,13 @@ bar: up: map: tag: ipv4 - default: {string: {text: , font: *awesome}} - values: - "": {string: {text: , font: *awesome, foreground: ffffff66}} + default: + - string: {text: , font: *awesome} + - string: {text: "{ssid}"} + values: + "": + - string: {text: , font: *awesome, foreground: ffffff66} + - string: {text: "{ssid}", foreground: ffffff66} - alsa: card: hw:PCH mixer: Master diff --git a/modules/network.c b/modules/network.c index b9277d7..bf32c57 100644 --- a/modules/network.c +++ b/modules/network.c @@ -2,16 +2,21 @@ #include #include #include +#include #include #include +#include #include #include +#include #include #include #include +#include #include +#include #include @@ -24,6 +29,8 @@ #include "../module.h" #include "../plugin.h" +#define UNUSED __attribute__((unused)) + struct af_addr { int family; union { @@ -35,8 +42,16 @@ struct af_addr { struct private { char *iface; struct particle *label; + int poll_interval; - int nl_sock; + int genl_sock; + int rt_sock; + + struct { + uint16_t family_id; + uint32_t get_interface_seq_nr; + uint32_t get_station_seq_nr; + } nl80211; bool get_addresses; @@ -47,6 +62,12 @@ struct private { /* 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; }; static void @@ -54,12 +75,12 @@ destroy(struct module *mod) { struct private *m = mod->private; - assert(m->nl_sock == -1); + assert(m->rt_sock == -1); m->label->destroy(m->label); tll_free(m->addrs); - + free(m->ssid); free(m->iface); free(m); @@ -120,8 +141,12 @@ content(struct module *mod) 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", m->ssid), + tag_new_int(mod, "signal", m->signal_strength_dbm), + tag_new_int(mod, "rx-bitrate", m->rx_bitrate / 1000 / 1000), + tag_new_int(mod, "tx-bitrate", m->tx_bitrate / 1000 / 1000), }, - .count = 7, + .count = 11, }; mtx_unlock(&mod->lock); @@ -140,7 +165,7 @@ nl_pid_value(void) /* Connect and bind to netlink socket. Returns socket fd, or -1 on error */ static int -netlink_connect(void) +netlink_connect_rt(void) { int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (sock == -1) { @@ -154,7 +179,31 @@ netlink_connect(void) .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR, }; - if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) == -1) { + 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, 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; @@ -164,11 +213,22 @@ netlink_connect(void) } static bool -send_rt_request(int nl_sock, int request) +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; + struct rtgenmsg rt __attribute__((aligned(NLMSG_ALIGNTO))); } req = { .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.rt)), @@ -183,18 +243,157 @@ send_rt_request(int nl_sock, int request) }, }; - int r = sendto( - nl_sock, &req, req.hdr.nlmsg_len, 0, - (struct sockaddr *)&(struct sockaddr_nl){.nl_family = AF_NETLINK}, - sizeof(struct sockaddr_nl)); - - if (r == -1) { - LOG_ERRNO("failed to send netlink request"); + if (!send_nlmsg(m->rt_sock, &req, req.hdr.nlmsg_len)) { + LOG_ERRNO("%s: failed to send netlink RT request (%d)", + m->iface, request); return false; } 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("%s: failed to send netlink ctrl-get-family request", + m->iface); + return false; + } + + return true; +} + +static bool +send_nl80211_request(struct private *m, uint8_t cmd, uint16_t flags, uint32_t seq) +{ + if (m->ifindex < 0) + 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 = flags, + .nlmsg_seq = seq, + .nlmsg_pid = nl_pid_value(), + }, + + .msg = { + .genl = { + .cmd = cmd, + .version = 1, + }, + + .ifindex = { + .attr = { + .nla_type = NL80211_ATTR_IFINDEX, + .nla_len = sizeof(req.msg.ifindex), + }, + + .index = m->ifindex, + }, + }, + }; + + if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) { + LOG_ERRNO("%s: failed to send netlink nl80211 get-inteface request", + m->iface); + return false; + } + + return true; +} + +static bool +send_nl80211_get_interface(struct private *m) +{ + if (m->nl80211.get_interface_seq_nr > 0) { + LOG_DBG( + "%s: nl80211 get-interface request already in progress", m->iface); + return true; + } + + LOG_DBG("%s: sending nl80211 get-interface request", m->iface); + + uint32_t seq = time(NULL); + if (send_nl80211_request(m, NL80211_CMD_GET_INTERFACE, NLM_F_REQUEST, seq)) { + m->nl80211.get_interface_seq_nr = seq; + return true; + } else + return false; +} + +static bool +send_nl80211_get_station(struct private *m) +{ + if (m->nl80211.get_station_seq_nr > 0) { + LOG_DBG( + "%s: nl80211 get-station request already in progress", m->iface); + return true; + } + + LOG_DBG("%s: sending nl80211 get-station request", m->iface); + + uint32_t seq = time(NULL); + if (send_nl80211_request( + m, NL80211_CMD_GET_STATION, NLM_F_REQUEST | NLM_F_DUMP, seq)) + { + m->nl80211.get_station_seq_nr = seq; + return true; + } else + return false; +} + static bool find_my_ifindex(struct module *mod, const struct ifinfomsg *msg, size_t len) { @@ -212,6 +411,9 @@ find_my_ifindex(struct module *mod, const struct ifinfomsg *msg, size_t len) mtx_lock(&mod->lock); m->ifindex = msg->ifi_index; mtx_unlock(&mod->lock); + + send_nl80211_get_interface(m); + send_nl80211_get_station(m); return true; } @@ -372,6 +574,319 @@ handle_address(struct module *mod, uint16_t type, mod->bar->refresh(mod->bar); } +static bool +foreach_nlattr(struct module *mod, const struct genlmsghdr *genl, size_t len, + bool (*cb)(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len)) +{ + 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, type, nested, payload, attr->nla_len - NLA_HDRLEN)) + return false; + } + + return true; +} + +static bool +foreach_nlattr_nested(struct module *mod, const void *parent_payload, size_t len, + bool (*cb)(struct module *mod, 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, type, nested, payload, attr->nla_len - NLA_HDRLEN, ctx)) + return false; + } + + return true; +} + +struct mcast_group { + uint32_t id; + const char *name; +}; + +static bool +parse_mcast_group(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len, void *_ctx) +{ + struct private *m = mod->private; + 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("%s: unrecognized GENL MCAST GRP attribute: " + "type=%hu, nested=%d, len=%zu", m->iface, type, nested, len); + break; + } + + return true; +} + +static bool +parse_mcast_groups(struct module *mod, 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, 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, uint16_t type, bool nested, + const void *payload, size_t len) +{ + 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, payload, len, &parse_mcast_groups, NULL); + break; + + default: + LOG_DBG("%s: unrecognized GENL CTRL attribute: " + "type=%hu, nested=%d, len=%zu", m->iface, type, nested, len); + break; + } + + return true; +} + +static bool +check_for_nl80211_ifindex(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len) +{ + struct private *m = mod->private; + + switch (type) { + case NL80211_ATTR_IFINDEX: + return *(uint32_t *)payload == m->ifindex; + } + + return true; +} + +static bool +nl80211_is_for_us(struct module *mod, const struct genlmsghdr *genl, + size_t msg_size) +{ + return foreach_nlattr(mod, genl, msg_size, &check_for_nl80211_ifindex); +} + +static bool +handle_nl80211_new_interface(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len) +{ + struct private *m = mod->private; + + switch (type) { + case NL80211_ATTR_IFINDEX: + assert(*(uint32_t *)payload == m->ifindex); + break; + + case NL80211_ATTR_SSID: { + const char *ssid = payload; + + mtx_lock(&mod->lock); + free(m->ssid); + m->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", m->iface, type, nested, len); + break; + } + + return true; +} + +struct rate_info_ctx { + unsigned bitrate; +}; + +static bool +handle_nl80211_rate_info(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len, void *_ctx) +{ + struct private *m UNUSED = mod->private; + 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", m->iface, type, nested, len); + break; + } + + return true; +} + +struct station_info_ctx { + bool update_bar; +}; + +static bool +handle_nl80211_station_info(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len, void *_ctx) +{ + struct private *m = mod->private; + 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); + m->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, payload, len, &handle_nl80211_rate_info, &rctx); + + LOG_DBG("TX bitrate: %.1f Mbit/s", rctx.bitrate / 1000. / 1000.); + mtx_lock(&mod->lock); + m->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, payload, len, &handle_nl80211_rate_info, &rctx); + + LOG_DBG("RX bitrate: %.1f Mbit/s", rctx.bitrate / 1000. / 1000.); + mtx_lock(&mod->lock); + m->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", m->iface, type, nested, len); + break; + } + + return true; +} + +static bool +handle_nl80211_new_station(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len) +{ + struct private *m UNUSED = mod->private; + + switch (type) { + case NL80211_ATTR_STA_INFO: { + struct station_info_ctx ctx = {0}; + foreach_nlattr_nested( + mod, 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", m->iface, type, nested, len); + break; + } + + return true; +} + /* * Reads at least one (possibly more) message. * @@ -411,7 +926,7 @@ netlink_receive_messages(int sock, void **reply, size_t *len) } static bool -parse_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) +parse_rt_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) { struct private *m = mod->private; @@ -427,7 +942,7 @@ parse_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) /* Request initial list of IPv4/6 addresses */ if (m->get_addresses && m->ifindex != -1) { m->get_addresses = false; - send_rt_request(m->nl_sock, RTM_GETADDR); + send_rt_request(m, RTM_GETADDR); } break; @@ -451,14 +966,127 @@ parse_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) case NLMSG_ERROR:{ const struct nlmsgerr *err = NLMSG_DATA(hdr); - LOG_ERRNO_P(err->error, "netlink"); + LOG_ERRNO_P(-err->error, "%s: netlink RT reply", m->iface); return false; } default: LOG_WARN( - "unrecognized netlink message type: 0x%x", hdr->nlmsg_type); - break; + "%s: unrecognized netlink message type: 0x%x", + m->iface, 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; + + for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { + if (hdr->nlmsg_seq == m->nl80211.get_interface_seq_nr) { + /* Current request is now considered complete */ + m->nl80211.get_interface_seq_nr = 0; + } + + if (hdr->nlmsg_type == NLMSG_DONE) { + if (hdr->nlmsg_seq == m->nl80211.get_station_seq_nr) { + /* Current request is now considered complete */ + m->nl80211.get_station_seq_nr = 0; + } + } + + else if (hdr->nlmsg_type == GENL_ID_CTRL) { + const struct genlmsghdr *genl = NLMSG_DATA(hdr); + const size_t msg_size = NLMSG_PAYLOAD(hdr, 0); + foreach_nlattr(mod, genl, msg_size, &handle_genl_ctrl); + } + + 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 (nl80211_is_for_us(mod, genl, msg_size)) { + LOG_DBG("%s: got interface information", m->iface); + foreach_nlattr( + mod, genl, msg_size, &handle_nl80211_new_interface); + + LOG_INFO("%s: SSID: %s", m->iface, m->ssid); + } + 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. + */ + if (nl80211_is_for_us(mod, genl, msg_size)) { + LOG_DBG("%s: connected, requesting interface information", + m->iface); + send_nl80211_get_interface(m); + send_nl80211_get_station(m); + } + break; + + case NL80211_CMD_DISCONNECT: + if (nl80211_is_for_us(mod, genl, msg_size)) { + LOG_DBG("%s: disconnected, resetting SSID etc", m->iface); + + mtx_lock(&mod->lock); + free(m->ssid); + m->ssid = NULL; + m->signal_strength_dbm = 0; + m->rx_bitrate = m->tx_bitrate = 0; + mtx_unlock(&mod->lock); + } + break; + + case NL80211_CMD_NEW_STATION: + if (nl80211_is_for_us(mod, genl, msg_size)) { + LOG_DBG("%s: got station information", m->iface); + foreach_nlattr(mod, genl, msg_size, &handle_nl80211_new_station); + } + + LOG_INFO("%s: signal: %d dBm, RX=%u Mbit/s, TX=%u Mbit/s", + m->iface, m->signal_strength_dbm, + m->rx_bitrate / 1000 / 1000, + m->tx_bitrate / 1000 / 1000); + 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, "%s: nl80211 reply", m->iface); + } + + else { + LOG_WARN( + "%s: unrecognized netlink message type: 0x%x", + m->iface, hdr->nlmsg_type); + return false; } } @@ -468,65 +1096,133 @@ parse_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) static int run(struct module *mod) { + int ret = 1; struct private *m = mod->private; - m->nl_sock = netlink_connect(); - if (m->nl_sock == -1) - return 1; + int timer_fd = -1; + if (m->poll_interval > 0) { + timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (timer_fd < 0) { + LOG_ERRNO("%s: failed to create poll timer FD", m->iface); + goto out; + } - if (!send_rt_request(m->nl_sock, RTM_GETLINK)) { - close(m->nl_sock); - m->nl_sock = -1; - return 1; + struct itimerspec poll_time = { + .it_value = {.tv_sec = m->poll_interval}, + .it_interval = {.tv_sec = m->poll_interval}, + }; + + if (timerfd_settime(timer_fd, 0, &poll_time, NULL) < 0) { + LOG_ERRNO("%s: failed to arm poll timer", m->iface); + 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->nl_sock, .events = POLLIN} + {.fd = m->rt_sock, .events = POLLIN}, + {.fd = m->genl_sock, .events = POLLIN}, + {.fd = timer_fd, .events = POLLIN}, }; - poll(fds, 2, -1); + poll(fds, 3 + (timer_fd >= 0 ? 1 : 0), -1); - if (fds[0].revents & POLLIN) + if (fds[0].revents & (POLLIN | POLLHUP)) break; - if (fds[1].revents & POLLHUP) { - LOG_ERR("disconnected from netlink socket"); + if ((fds[1].revents & POLLHUP) || + (fds[2].revents & POLLHUP)) + { + LOG_ERR("%s: disconnected from netlink socket", m->iface); break; } - assert(fds[1].revents & POLLIN); - - /* Read one (or more) messages */ - void *reply; - size_t len; - if (!netlink_receive_messages(m->nl_sock, &reply, &len)) + if (fds[3].revents & POLLHUP) { + LOG_ERR("%s: disconnected from timer FD", m->iface); 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; + } - /* Parse (and act upon) the received message(s) */ - if (!parse_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; + } + + send_nl80211_get_station(m); + } } - close(m->nl_sock); - m->nl_sock = -1; - return 0; + 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(const char *iface, struct particle *label) +network_new(const char *iface, struct particle *label, int poll_interval) { struct private *priv = calloc(1, sizeof(*priv)); priv->iface = strdup(iface); priv->label = label; + priv->poll_interval = poll_interval; - priv->nl_sock = -1; + priv->genl_sock = -1; + priv->rt_sock = -1; + priv->nl80211.family_id = -1; priv->get_addresses = true; priv->ifindex = -1; priv->state = IF_OPER_DOWN; @@ -545,9 +1241,11 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) { const struct yml_node *name = yml_get_value(node, "name"); const struct yml_node *content = yml_get_value(node, "content"); + const struct yml_node *poll = yml_get_value(node, "poll-interval"); return network_new( - yml_value_as_string(name), conf_to_particle(content, inherited)); + yml_value_as_string(name), conf_to_particle(content, inherited), + poll != NULL ? yml_value_as_int(poll) : 0); } static bool @@ -555,6 +1253,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node) { static const struct attr_info attrs[] = { {"name", true, &conf_verify_string}, + {"poll-interval", false, &conf_verify_int}, MODULE_COMMON_ATTRS, };