From d39e6b8b9469d215c4565824fa8525e1076619a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 26 Aug 2021 18:52:30 +0200 Subject: [PATCH 01/12] module/network: initial support for Wifi extensions Currently capable of getting the SSID. --- modules/network.c | 404 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 348 insertions(+), 56 deletions(-) diff --git a/modules/network.c b/modules/network.c index b9277d7..784546b 100644 --- a/modules/network.c +++ b/modules/network.c @@ -2,8 +2,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -11,7 +13,9 @@ #include #include #include +#include #include +#include #include @@ -24,6 +28,8 @@ #include "../module.h" #include "../plugin.h" +#define UNUSED __attribute__((unused)) + struct af_addr { int family; union { @@ -36,7 +42,13 @@ struct private { char *iface; struct particle *label; - int nl_sock; + int genl_sock; + int rt_sock; + + struct { + uint16_t family_id; + uint32_t seq_nr; + } nl80211; bool get_addresses; @@ -47,6 +59,9 @@ struct private { /* IPv4 and IPv6 addresses */ tll(struct af_addr) addrs; + + /* WiFi extensions */ + char *ssid; }; static void @@ -54,12 +69,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 +135,9 @@ 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), }, - .count = 7, + .count = 8, }; mtx_unlock(&mod->lock); @@ -140,35 +156,48 @@ nl_pid_value(void) /* Connect and bind to netlink socket. Returns socket fd, or -1 on error */ static int -netlink_connect(void) +netlink_connect(int protocol, bool bind_socket) { - int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + int sock = socket(AF_NETLINK, SOCK_RAW, protocol); 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_socket) { + 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)) == -1) { - LOG_ERRNO("failed to bind netlink socket"); - close(sock); - return -1; + if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOG_ERRNO("failed to bind netlink socket"); + close(sock); + return -1; + } } return sock; } 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 +212,126 @@ 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_get_interface_request(struct private *m) +{ + assert(m->ifindex >= 0); + + if (m->nl80211.seq_nr > 0) { + LOG_DBG( + "%s: nl80211 get-interface request already in progress", m->iface); + return true; + } + + m->nl80211.seq_nr = time(NULL); + LOG_DBG("%s: sending nl80211 get-interface request", m->iface); + + 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, + .nlmsg_seq = m->nl80211.seq_nr, + .nlmsg_pid = nl_pid_value(), + }, + + .msg = { + .genl = { + .cmd = NL80211_CMD_GET_INTERFACE, + .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); + m->nl80211.seq_nr = 0; + return false; + } + + return true; +} + static bool find_my_ifindex(struct module *mod, const struct ifinfomsg *msg, size_t len) { @@ -212,6 +349,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); + + if (m->nl80211.family_id >= 0) + send_nl80211_get_interface_request(m); return true; } @@ -261,9 +401,18 @@ handle_link(struct module *mod, uint16_t type, LOG_DBG("%s: IFLA_OPERSTATE: %hhu -> %hhu", m->iface, m->state, operstate); mtx_lock(&mod->lock); - m->state = operstate; + { + m->state = operstate; + if (operstate != IF_OPER_UP) { + free(m->ssid); + m->ssid = NULL; + } + } mtx_unlock(&mod->lock); update_bar = true; + + if (operstate == IF_OPER_UP) + send_nl80211_get_interface_request(m); break; } @@ -411,7 +560,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 +576,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 +600,128 @@ 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; + + /* Process response */ + for (; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { + if (hdr->nlmsg_seq == m->nl80211.seq_nr) { + /* Current request is now considered complete */ + m->nl80211.seq_nr = 0; + } + + if (hdr->nlmsg_type == GENL_ID_CTRL) { + const struct genlmsghdr *genl = NLMSG_DATA(hdr); + const size_t attr_size = NLMSG_PAYLOAD(hdr, 0); + + const uint8_t *p = (const uint8_t *)(genl + 1); + const uint8_t *end = (const uint8_t *)genl + attr_size; + const struct nlattr *attr = (const struct nlattr *)p; + + for (; p < end; + p += NLA_ALIGN(attr->nla_len), + attr = (const struct nlattr *)p) + { + bool nested UNUSED = attr->nla_type & NLA_F_NESTED; + uint16_t type = attr->nla_type & NLA_TYPE_MASK; + + switch (type) { + case CTRL_ATTR_FAMILY_ID: { + m->nl80211.family_id = *(const uint16_t *)(attr + 1); + if (m->ifindex >= 0) + send_nl80211_get_interface_request(m); + break; + } + + case CTRL_ATTR_FAMILY_NAME: { +#if 0 + unsigned name_len = attr->nla_len - NLA_HDRLEN; + const char *name = (const char *)(attr + 1); + LOG_INFO("NAME: %.*s (%u bytes)", name_len, name, name_len); +#endif + break; + } + + default: + LOG_DBG("%s: unrecognized GENL CTRL attribute: " + "%hu%s (size: %hu bytes)", m->iface, + type, nested ? " (nested)" : "", attr->nla_len); + break; + } + } + } + + else if (hdr->nlmsg_type == m->nl80211.family_id) { + const struct genlmsghdr *genl = NLMSG_DATA(hdr); + const size_t attr_size = NLMSG_PAYLOAD(hdr, 0); + + const uint8_t *p = (const uint8_t *)(genl + 1); + const uint8_t *end = (const uint8_t *)genl + attr_size; + const struct nlattr *attr = (const struct nlattr *)p; + + for (;p < end && attr->nla_len > 0; + p += NLA_ALIGN(attr->nla_len), + attr = (const struct nlattr *)p) + { + bool nested UNUSED = attr->nla_type & NLA_F_NESTED; + uint16_t type = attr->nla_type & NLA_TYPE_MASK; + + switch (type) { + case NL80211_ATTR_SSID: { + unsigned ssid_len = attr->nla_len - NLA_HDRLEN; + const char *ssid = (const char *)(attr + 1); + LOG_INFO("%s: SSID: %.*s", m->iface, ssid_len, ssid); + + mtx_lock(&mod->lock); + free(m->ssid); + m->ssid = strndup(ssid, ssid_len); + mtx_unlock(&mod->lock); + + mod->bar->refresh(mod->bar); + break; + } + + default: + LOG_DBG("%s: unrecognized nl80211 attribute: " + "type=%hu%s, len=%hu", m->iface, + type, nested ? " (nested)" : "", attr->nla_len); + break; + } + } + } + + else if (hdr->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr *err = NLMSG_DATA(hdr); + if (-err->error == ENODEV) + ; /* iface is not an nl80211 device */ + else if (-err->error == ENOENT) + ; /* iface down? */ + else + LOG_ERRNO_P(-err->error, "%s: nl80211 reply", m->iface); + } + + else { + LOG_WARN( + "%s: unrecognized netlink message type: 0x%x", + m->iface, hdr->nlmsg_type); + return false; } } @@ -468,55 +731,82 @@ 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; + m->rt_sock = netlink_connect(NETLINK_ROUTE, true); + m->genl_sock = netlink_connect(NETLINK_GENERIC, false); - if (!send_rt_request(m->nl_sock, RTM_GETLINK)) { - close(m->nl_sock); - m->nl_sock = -1; - return 1; + 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}, }; - poll(fds, 2, -1); + poll(fds, 3, -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); + 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; - /* Read one (or more) messages */ - void *reply; - size_t len; - if (!netlink_receive_messages(m->nl_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); + } } - 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); + m->rt_sock = m->genl_sock = -1; + return ret; } static struct module * @@ -526,7 +816,9 @@ network_new(const char *iface, struct particle *label) priv->iface = strdup(iface); priv->label = label; - 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; From 5249d9ef795ba72525603298586d4ee0b7d21179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 26 Aug 2021 18:58:21 +0200 Subject: [PATCH 02/12] module/network: use separate functions to connect to RT/GENL netlink --- modules/network.c | 53 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/modules/network.c b/modules/network.c index 784546b..1986d3c 100644 --- a/modules/network.c +++ b/modules/network.c @@ -156,31 +156,54 @@ nl_pid_value(void) /* Connect and bind to netlink socket. Returns socket fd, or -1 on error */ static int -netlink_connect(int protocol, bool bind_socket) +netlink_connect_rt(void) { - int sock = socket(AF_NETLINK, SOCK_RAW, protocol); + int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (sock == -1) { LOG_ERRNO("failed to create netlink socket"); return -1; } - if (bind_socket) { - const struct sockaddr_nl addr = { - .nl_family = AF_NETLINK, - .nl_pid = nl_pid_value(), - .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR, - }; + 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)) == -1) { - LOG_ERRNO("failed to bind netlink socket"); - close(sock); - return -1; - } + if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOG_ERRNO("failed to bind netlink 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; + } + +#if 0 + 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)) == -1) { + LOG_ERRNO("failed to bind netlink socket"); + close(sock); + return -1; + } +#endif + return sock; +} + static bool send_nlmsg(int sock, const void *nlmsg, size_t len) { @@ -734,8 +757,8 @@ run(struct module *mod) int ret = 1; struct private *m = mod->private; - m->rt_sock = netlink_connect(NETLINK_ROUTE, true); - m->genl_sock = netlink_connect(NETLINK_GENERIC, false); + m->rt_sock = netlink_connect_rt(); + m->genl_sock = netlink_connect_genl(); if (m->rt_sock < 0 || m->genl_sock < 0) goto out; From 9b3548736a8803ace32e6cfd48bd077ddf6759eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 07:14:36 +0200 Subject: [PATCH 03/12] module/network: refactor: add foreach_nlattr() This function iterates all attributes of a NETLINK_GENERIC message, and calls the provided callback for each attribute. --- modules/network.c | 158 ++++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/modules/network.c b/modules/network.c index 1986d3c..ba00fca 100644 --- a/modules/network.c +++ b/modules/network.c @@ -544,6 +544,79 @@ handle_address(struct module *mod, uint16_t type, mod->bar->refresh(mod->bar); } +static void +foreach_nlattr(struct module *mod, const struct genlmsghdr *genl, size_t len, + void (*cb)(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len)) +{ + const uint8_t *raw = (const uint8_t *)genl + NLA_HDRLEN; + const uint8_t *end = raw + 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); + + cb(mod, type, nested, attr + 1, attr->nla_len - NLA_HDRLEN); + } +} + +static void +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; + if (m->ifindex >= 0) + send_nl80211_get_interface_request(m); + break; + } + + case CTRL_ATTR_FAMILY_NAME: + //LOG_INFO("NAME: %.*s (%zu bytes)", (int)len, (const char *)payload, len); + break; + + default: + LOG_DBG("%s: unrecognized GENL CTRL attribute: " + "%hu%s (size: %hu bytes)", m->iface, + type, nested ? " (nested)" : "", len); + break; + } +} + +static void +handle_nl80211(struct module *mod, uint16_t type, bool nested, + const void *payload, size_t len) +{ + struct private *m = mod->private; + + switch (type) { + case NL80211_ATTR_SSID: { + const char *ssid = payload; + LOG_INFO("%s: SSID: %.*s", m->iface, (int)len, ssid); + + 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%s, len=%hu", m->iface, + type, nested ? " (nested)" : "", len); + break; + } +} + /* * Reads at least one (possibly more) message. * @@ -643,7 +716,6 @@ parse_genl_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)) { if (hdr->nlmsg_seq == m->nl80211.seq_nr) { /* Current request is now considered complete */ @@ -652,92 +724,26 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) if (hdr->nlmsg_type == GENL_ID_CTRL) { const struct genlmsghdr *genl = NLMSG_DATA(hdr); - const size_t attr_size = NLMSG_PAYLOAD(hdr, 0); - - const uint8_t *p = (const uint8_t *)(genl + 1); - const uint8_t *end = (const uint8_t *)genl + attr_size; - const struct nlattr *attr = (const struct nlattr *)p; - - for (; p < end; - p += NLA_ALIGN(attr->nla_len), - attr = (const struct nlattr *)p) - { - bool nested UNUSED = attr->nla_type & NLA_F_NESTED; - uint16_t type = attr->nla_type & NLA_TYPE_MASK; - - switch (type) { - case CTRL_ATTR_FAMILY_ID: { - m->nl80211.family_id = *(const uint16_t *)(attr + 1); - if (m->ifindex >= 0) - send_nl80211_get_interface_request(m); - break; - } - - case CTRL_ATTR_FAMILY_NAME: { -#if 0 - unsigned name_len = attr->nla_len - NLA_HDRLEN; - const char *name = (const char *)(attr + 1); - LOG_INFO("NAME: %.*s (%u bytes)", name_len, name, name_len); -#endif - break; - } - - default: - LOG_DBG("%s: unrecognized GENL CTRL attribute: " - "%hu%s (size: %hu bytes)", m->iface, - type, nested ? " (nested)" : "", attr->nla_len); - break; - } - } + 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 attr_size = NLMSG_PAYLOAD(hdr, 0); - - const uint8_t *p = (const uint8_t *)(genl + 1); - const uint8_t *end = (const uint8_t *)genl + attr_size; - const struct nlattr *attr = (const struct nlattr *)p; - - for (;p < end && attr->nla_len > 0; - p += NLA_ALIGN(attr->nla_len), - attr = (const struct nlattr *)p) - { - bool nested UNUSED = attr->nla_type & NLA_F_NESTED; - uint16_t type = attr->nla_type & NLA_TYPE_MASK; - - switch (type) { - case NL80211_ATTR_SSID: { - unsigned ssid_len = attr->nla_len - NLA_HDRLEN; - const char *ssid = (const char *)(attr + 1); - LOG_INFO("%s: SSID: %.*s", m->iface, ssid_len, ssid); - - mtx_lock(&mod->lock); - free(m->ssid); - m->ssid = strndup(ssid, ssid_len); - mtx_unlock(&mod->lock); - - mod->bar->refresh(mod->bar); - break; - } - - default: - LOG_DBG("%s: unrecognized nl80211 attribute: " - "type=%hu%s, len=%hu", m->iface, - type, nested ? " (nested)" : "", attr->nla_len); - break; - } - } + const size_t msg_size = NLMSG_PAYLOAD(hdr, 0); + foreach_nlattr(mod, genl, msg_size, &handle_nl80211); } else if (hdr->nlmsg_type == NLMSG_ERROR) { const struct nlmsgerr *err = NLMSG_DATA(hdr); - if (-err->error == ENODEV) + int nl_errno = -err->error; + + if (nl_errno == ENODEV) ; /* iface is not an nl80211 device */ - else if (-err->error == ENOENT) + else if (nl_errno == ENOENT) ; /* iface down? */ else - LOG_ERRNO_P(-err->error, "%s: nl80211 reply", m->iface); + LOG_ERRNO_P(nl_errno, "%s: nl80211 reply", m->iface); } else { From b27eff36f929ec9eb1e9d3b93cdf01d4b912199c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 10:09:48 +0200 Subject: [PATCH 04/12] module/network: nl80211: join the MLME mcast group, ignore messages not for us MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This cleans up the nl80211 handling quite a bit, and adds initial support for nl80211 notifications. * We now join the nl80211 MLME multicast group (done by parsing the CTRL_ATTR_MCAST_GROUPS attribute in the reply to our CTRL_CMD_GETFAMILY request). This gives us CONNECT and DISCONNECT notifications, allowing us to request and reset SSID that way, instead of detecting the link’s OPER state. * Before parsing an nl80211 message, verify it’s for us, by looking for a NL80211_ATTR_IFINDEX attribute in the message (and comparing the contents with our previously detected ifindex). --- modules/network.c | 220 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 185 insertions(+), 35 deletions(-) diff --git a/modules/network.c b/modules/network.c index ba00fca..e4703c2 100644 --- a/modules/network.c +++ b/modules/network.c @@ -170,8 +170,8 @@ netlink_connect_rt(void) .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR, }; - if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) == -1) { - LOG_ERRNO("failed to bind netlink socket"); + if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) { + LOG_ERRNO("failed to bind netlink RT socket"); close(sock); return -1; } @@ -188,19 +188,18 @@ netlink_connect_genl(void) return -1; } -#if 0 const struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_pid = nl_pid_value(), - .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR, + /* no multicast notifications by default, will be added later */ }; - 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 socket"); close(sock); return -1; } -#endif + return sock; } @@ -299,7 +298,8 @@ send_ctrl_get_family_request(struct private *m) static bool send_nl80211_get_interface_request(struct private *m) { - assert(m->ifindex >= 0); + if (m->ifindex < 0) + return true; if (m->nl80211.seq_nr > 0) { LOG_DBG( @@ -373,8 +373,7 @@ find_my_ifindex(struct module *mod, const struct ifinfomsg *msg, size_t len) m->ifindex = msg->ifi_index; mtx_unlock(&mod->lock); - if (m->nl80211.family_id >= 0) - send_nl80211_get_interface_request(m); + send_nl80211_get_interface_request(m); return true; } @@ -424,18 +423,9 @@ handle_link(struct module *mod, uint16_t type, LOG_DBG("%s: IFLA_OPERSTATE: %hhu -> %hhu", m->iface, m->state, operstate); mtx_lock(&mod->lock); - { - m->state = operstate; - if (operstate != IF_OPER_UP) { - free(m->ssid); - m->ssid = NULL; - } - } + m->state = operstate; mtx_unlock(&mod->lock); update_bar = true; - - if (operstate == IF_OPER_UP) - send_nl80211_get_interface_request(m); break; } @@ -544,26 +534,113 @@ handle_address(struct module *mod, uint16_t type, mod->bar->refresh(mod->bar); } -static void +static bool foreach_nlattr(struct module *mod, const struct genlmsghdr *genl, size_t len, - void (*cb)(struct module *mod, uint16_t type, bool nested, + bool (*cb)(struct module *mod, uint16_t type, bool nested, const void *payload, size_t len)) { - const uint8_t *raw = (const uint8_t *)genl + NLA_HDRLEN; - const uint8_t *end = raw + 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); + bool nested = (attr->nla_type & NLA_F_NESTED) != 0;; + const void *payload = raw + NLA_HDRLEN; - cb(mod, type, nested, attr + 1, attr->nla_len - NLA_HDRLEN); + if (!cb(mod, type, nested, payload, attr->nla_len - NLA_HDRLEN)) + return false; } + + return true; } -static void +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; + 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: { + free(ctx->name); + ctx->name = strndup((const char *)payload, len); + break; + } + + default: + LOG_WARN("%s: unrecognized GENL MCAST GRP attribute: " + "%hu%s (size: %zu bytes)", m->iface, + type, nested ? " (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) { + 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"); + } + + free(group.name); + return true; +} + +static bool handle_genl_ctrl(struct module *mod, uint16_t type, bool nested, const void *payload, size_t len) { @@ -572,8 +649,7 @@ handle_genl_ctrl(struct module *mod, uint16_t type, bool nested, switch (type) { case CTRL_ATTR_FAMILY_ID: { m->nl80211.family_id = *(const uint16_t *)payload; - if (m->ifindex >= 0) - send_nl80211_get_interface_request(m); + send_nl80211_get_interface_request(m); break; } @@ -581,24 +657,55 @@ handle_genl_ctrl(struct module *mod, uint16_t type, bool nested, //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: " - "%hu%s (size: %hu bytes)", m->iface, + "%hu%s (size: %zu bytes)", m->iface, type, nested ? " (nested)" : "", len); break; } + + return true; } -static void -handle_nl80211(struct module *mod, uint16_t type, bool nested, - const void *payload, size_t len) +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; - LOG_INFO("%s: SSID: %.*s", m->iface, (int)len, ssid); + LOG_INFO("%s: SSID: %.*s (type=%hhu)", m->iface, (int)len, ssid, type); mtx_lock(&mod->lock); free(m->ssid); @@ -611,10 +718,12 @@ handle_nl80211(struct module *mod, uint16_t type, bool nested, default: LOG_DBG("%s: unrecognized nl80211 attribute: " - "type=%hu%s, len=%hu", m->iface, + "type=%hu%s, len=%zu", m->iface, type, nested ? " (nested)" : "", len); break; } + + return true; } /* @@ -731,7 +840,48 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) 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); - foreach_nlattr(mod, genl, msg_size, &handle_nl80211); + + 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); + } + 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_request(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; + mtx_unlock(&mod->lock); + } + break; + + default: + LOG_DBG("unrecognized nl80211 command: %hhu", genl->cmd); + break; + } } else if (hdr->nlmsg_type == NLMSG_ERROR) { From a685dadb7531bafc63bcdb5980e2fd5dfa126acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 11:59:43 +0200 Subject: [PATCH 05/12] module/network: expose signal strength and rx+tx bitrates --- modules/network.c | 249 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 215 insertions(+), 34 deletions(-) diff --git a/modules/network.c b/modules/network.c index e4703c2..1ca24d0 100644 --- a/modules/network.c +++ b/modules/network.c @@ -47,7 +47,8 @@ struct private { struct { uint16_t family_id; - uint32_t seq_nr; + uint32_t get_interface_seq_nr; + uint32_t get_station_seq_nr; } nl80211; bool get_addresses; @@ -62,6 +63,9 @@ struct private { /* WiFi extensions */ char *ssid; + int signal_strength_dbm; + uint32_t rx_bitrate; + uint32_t tx_bitrate; }; static void @@ -136,8 +140,11 @@ content(struct module *mod) 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 = 8, + .count = 11, }; mtx_unlock(&mod->lock); @@ -296,19 +303,10 @@ send_ctrl_get_family_request(struct private *m) } static bool -send_nl80211_get_interface_request(struct private *m) +send_nl80211_request(struct private *m, uint8_t cmd, uint16_t flags, uint32_t seq) { if (m->ifindex < 0) - return true; - - if (m->nl80211.seq_nr > 0) { - LOG_DBG( - "%s: nl80211 get-interface request already in progress", m->iface); - return true; - } - - m->nl80211.seq_nr = time(NULL); - LOG_DBG("%s: sending nl80211 get-interface request", m->iface); + return false; const struct { struct nlmsghdr hdr; @@ -323,14 +321,14 @@ send_nl80211_get_interface_request(struct private *m) .hdr = { .nlmsg_len = NLMSG_LENGTH(sizeof(req.msg)), .nlmsg_type = m->nl80211.family_id, - .nlmsg_flags = NLM_F_REQUEST, - .nlmsg_seq = m->nl80211.seq_nr, + .nlmsg_flags = flags, + .nlmsg_seq = seq, .nlmsg_pid = nl_pid_value(), }, .msg = { .genl = { - .cmd = NL80211_CMD_GET_INTERFACE, + .cmd = cmd, .version = 1, }, @@ -348,13 +346,52 @@ send_nl80211_get_interface_request(struct private *m) if (!send_nlmsg(m->genl_sock, &req, req.hdr.nlmsg_len)) { LOG_ERRNO("%s: failed to send netlink nl80211 get-inteface request", m->iface); - m->nl80211.seq_nr = 0; 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) { @@ -373,7 +410,8 @@ find_my_ifindex(struct module *mod, const struct ifinfomsg *msg, size_t len) m->ifindex = msg->ifi_index; mtx_unlock(&mod->lock); - send_nl80211_get_interface_request(m); + send_nl80211_get_interface(m); + send_nl80211_get_station(m); return true; } @@ -584,7 +622,7 @@ foreach_nlattr_nested(struct module *mod, const void *parent_payload, size_t len struct mcast_group { uint32_t id; - char *name; + const char *name; }; static bool @@ -601,15 +639,13 @@ parse_mcast_group(struct module *mod, uint16_t type, bool nested, } case CTRL_ATTR_MCAST_GRP_NAME: { - free(ctx->name); - ctx->name = strndup((const char *)payload, len); + ctx->name = (const char *)payload; break; } default: LOG_WARN("%s: unrecognized GENL MCAST GRP attribute: " - "%hu%s (size: %zu bytes)", m->iface, - type, nested ? " (nested)" : "", len); + "type=%hu, nested=%d, len=%zu", m->iface, type, nested, len); break; } @@ -628,6 +664,11 @@ parse_mcast_groups(struct module *mod, uint16_t type, bool nested, 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)); @@ -636,7 +677,6 @@ parse_mcast_groups(struct module *mod, uint16_t type, bool nested, LOG_ERRNO("failed to joint the nl80211 MLME mcast group"); } - free(group.name); return true; } @@ -649,7 +689,7 @@ handle_genl_ctrl(struct module *mod, uint16_t type, bool nested, switch (type) { case CTRL_ATTR_FAMILY_ID: { m->nl80211.family_id = *(const uint16_t *)payload; - send_nl80211_get_interface_request(m); + send_nl80211_get_interface(m); break; } @@ -663,8 +703,7 @@ handle_genl_ctrl(struct module *mod, uint16_t type, bool nested, default: LOG_DBG("%s: unrecognized GENL CTRL attribute: " - "%hu%s (size: %zu bytes)", m->iface, - type, nested ? " (nested)" : "", len); + "type=%hu, nested=%d, len=%zu", m->iface, type, nested, len); break; } @@ -705,7 +744,6 @@ handle_nl80211_new_interface(struct module *mod, uint16_t type, bool nested, case NL80211_ATTR_SSID: { const char *ssid = payload; - LOG_INFO("%s: SSID: %.*s (type=%hhu)", m->iface, (int)len, ssid, type); mtx_lock(&mod->lock); free(m->ssid); @@ -718,8 +756,129 @@ handle_nl80211_new_interface(struct module *mod, uint16_t type, bool nested, default: LOG_DBG("%s: unrecognized nl80211 attribute: " - "type=%hu%s, len=%zu", m->iface, - type, nested ? " (nested)" : "", len); + "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; } @@ -826,12 +985,19 @@ 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.seq_nr) { + if (hdr->nlmsg_seq == m->nl80211.get_interface_seq_nr) { /* Current request is now considered complete */ - m->nl80211.seq_nr = 0; + m->nl80211.get_interface_seq_nr = 0; } - if (hdr->nlmsg_type == GENL_ID_CTRL) { + 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); @@ -847,6 +1013,8 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) 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; @@ -864,13 +1032,14 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) if (nl80211_is_for_us(mod, genl, msg_size)) { LOG_DBG("%s: connected, requesting interface information", m->iface); - send_nl80211_get_interface_request(m); + send_nl80211_get_interface(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; @@ -878,6 +1047,18 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) } 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; From 01ee028c4da33bff5988028c26a02590b12bfe18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 12:07:41 +0200 Subject: [PATCH 06/12] doc: network: document ssid, signal, rx-bitrate and tx-bitrate --- doc/yambar-modules-network.5.scd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd index c8a66d8..27ce739 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 From 25379b7e1f9d2150bb0918e97bf91ecac127a954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 12:10:04 +0200 Subject: [PATCH 07/12] changelog: new network tags (ssid, signal rx+tx bitrate) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 501716a..638a965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * 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. ### Changed From 066427d77ac1745b366cedbb3dab0569499e1bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 12:13:20 +0200 Subject: [PATCH 08/12] examples: add ssid to wifi-network module --- examples/configurations/laptop.conf | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 From e8a2f8df9ab5dcdd407e493de29cc196ac92463d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 15:38:48 +0200 Subject: [PATCH 09/12] module/network: reset signal strength and RX+TX bitrates on disconnect --- modules/network.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/network.c b/modules/network.c index 1ca24d0..cb3f670 100644 --- a/modules/network.c +++ b/modules/network.c @@ -1043,6 +1043,8 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) 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; From d450bf12a17b4eec7c82d61dbc3a2b8b05920d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 15:39:11 +0200 Subject: [PATCH 10/12] =?UTF-8?q?module/network:=20re-request=20station=20?= =?UTF-8?q?info=20when=20we=E2=80=99re=20re-connected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/network.c | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/network.c b/modules/network.c index cb3f670..30cd35d 100644 --- a/modules/network.c +++ b/modules/network.c @@ -1033,6 +1033,7 @@ parse_genl_reply(struct module *mod, const struct nlmsghdr *hdr, size_t len) LOG_DBG("%s: connected, requesting interface information", m->iface); send_nl80211_get_interface(m); + send_nl80211_get_station(m); } break; From dabb2e140749cc1f4583541abfe692fc232a2af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 27 Aug 2021 15:56:31 +0200 Subject: [PATCH 11/12] module/network: add support for periodically polling Wi-Fi stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new ‘poll-interval’ option to the network module. When set to a non-zero value, the following Wi-Fi stats will be updated: * Signal strength * RX+TX bitrate --- doc/yambar-modules-network.5.scd | 5 ++++ modules/network.c | 50 ++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/doc/yambar-modules-network.5.scd b/doc/yambar-modules-network.5.scd index 27ce739..8ac46a1 100644 --- a/doc/yambar-modules-network.5.scd +++ b/doc/yambar-modules-network.5.scd @@ -63,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/modules/network.c b/modules/network.c index 30cd35d..bf32c57 100644 --- a/modules/network.c +++ b/modules/network.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -41,6 +42,7 @@ struct af_addr { struct private { char *iface; struct particle *label; + int poll_interval; int genl_sock; int rt_sock; @@ -1097,6 +1099,25 @@ 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("%s: failed to create poll timer FD", m->iface); + goto out; + } + + 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(); @@ -1115,9 +1136,10 @@ run(struct module *mod) {.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, -1); + poll(fds, 3 + (timer_fd >= 0 ? 1 : 0), -1); if (fds[0].revents & (POLLIN | POLLHUP)) break; @@ -1129,6 +1151,11 @@ run(struct module *mod) break; } + 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; @@ -1159,6 +1186,17 @@ run(struct module *mod) 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); + } } ret = 0; @@ -1168,16 +1206,19 @@ run(struct module *mod) 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->genl_sock = -1; priv->rt_sock = -1; @@ -1200,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 @@ -1210,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, }; From 4ab3263ebbbdd896d324fa8dd40eb36ca2696dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 1 Sep 2021 19:11:14 +0200 Subject: [PATCH 12/12] changelog: network: poll-interval --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 638a965..0f587e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ 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