commit 0b00c0319c7b4a941b40aa8709bb4c478f9a9b1f
parent f68f49273e70b3767b30c549dda04ddd4d25fc91
Author: Joakim Sindholt <opensource@zhasha.com>
Date: Tue, 30 Jul 2024 22:15:44 +0200
wifi: switch to nl80211 interface
Diffstat:
M | components/wifi.c | | | 264 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
1 file changed, 206 insertions(+), 58 deletions(-)
diff --git a/components/wifi.c b/components/wifi.c
@@ -15,86 +15,234 @@
(2 * (rssi + 100)))
#if defined(__linux__)
- #include <limits.h>
- #include <linux/wireless.h>
+ #include <stdint.h>
+ #include <net/if.h>
+ #include <linux/netlink.h>
+ #include <linux/genetlink.h>
+ #include <linux/nl80211.h>
- #define NET_OPERSTATE "/sys/class/net/%s/operstate"
+ static int nlsock = -1;
+ static uint32_t seq = 1;
+ static char resp[4096];
- const char *
- wifi_perc(const char *interface)
+ static char *
+ findattr(int attr, char *p, char *e, size_t *len)
{
- int cur;
- size_t i;
- char *p, *datastart;
- char path[PATH_MAX];
- char status[5];
- FILE *fp;
-
- if (esnprintf(path, sizeof(path), NET_OPERSTATE, interface) < 0)
- return NULL;
- if (!(fp = fopen(path, "r"))) {
- warn("fopen '%s':", path);
- return NULL;
+ struct nlattr nla;
+ size_t alen;
+
+ while ((size_t)(e-p) >= sizeof(nla)) {
+ memcpy(&nla, p, sizeof(nla));
+ if (nla.nla_len < NLA_HDRLEN)
+ return NULL;
+ if (nla.nla_type == attr) {
+ p += NLA_HDRLEN;
+ *len = nla.nla_len-NLA_HDRLEN;
+ return (size_t)(e-p)>=*len?p:NULL;
+ }
+ alen = NLA_ALIGN(nla.nla_len);
+ if ((size_t)(e-p) < alen)
+ return NULL;
+ p += alen;
}
- p = fgets(status, 5, fp);
- fclose(fp);
- if (!p || strcmp(status, "up\n") != 0)
- return NULL;
+ return NULL;
+ }
- if (!(fp = fopen("/proc/net/wireless", "r"))) {
- warn("fopen '/proc/net/wireless':");
- return NULL;
+ static uint16_t
+ nl80211fam(void)
+ {
+ static const char family[] = "nl80211";
+ static uint16_t id;
+ ssize_t r;
+ size_t len;
+ char ctrl[NLMSG_HDRLEN+GENL_HDRLEN+NLA_HDRLEN+NLA_ALIGN(sizeof(family))] = {0}, *p = ctrl;
+
+ if (id)
+ return id;
+
+ memcpy(p, &(struct nlmsghdr){
+ .nlmsg_len = sizeof(ctrl),
+ .nlmsg_type = GENL_ID_CTRL,
+ .nlmsg_flags = NLM_F_REQUEST,
+ .nlmsg_seq = seq++,
+ .nlmsg_pid = 0,
+ }, sizeof(struct nlmsghdr));
+ p += NLMSG_HDRLEN;
+ memcpy(p, &(struct genlmsghdr){
+ .cmd = CTRL_CMD_GETFAMILY,
+ .version = 1,
+ }, sizeof(struct genlmsghdr));
+ p += GENL_HDRLEN;
+ memcpy(p, &(struct nlattr){
+ .nla_len = NLA_HDRLEN+sizeof(family),
+ .nla_type = CTRL_ATTR_FAMILY_NAME,
+ }, sizeof(struct nlattr));
+ p += NLA_HDRLEN;
+ memcpy(p, family, sizeof(family));
+
+ if (nlsock < 0)
+ nlsock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
+ if (nlsock < 0) {
+ warn("socket 'AF_NETLINK':");
+ return 0;
}
+ if (send(nlsock, ctrl, sizeof(ctrl), 0) != sizeof(ctrl)) {
+ warn("send 'AF_NETLINK':");
+ return 0;
+ }
+ r = recv(nlsock, resp, sizeof(resp), 0);
+ if (r < 0) {
+ warn("recv 'AF_NETLINK':");
+ return 0;
+ }
+ if ((size_t)r <= sizeof(ctrl))
+ return 0;
+ p = findattr(CTRL_ATTR_FAMILY_ID, resp+sizeof(ctrl), resp+r, &len);
+ if (p && len == 2)
+ memcpy(&id, p, 2);
+ return id;
+ }
- for (i = 0; i < 3; i++)
- if (!(p = fgets(buf, sizeof(buf) - 1, fp)))
- break;
-
- fclose(fp);
- if (i < 2 || !p)
- return NULL;
-
- if (!(datastart = strstr(buf, interface)))
- return NULL;
-
- datastart = (datastart+(strlen(interface)+1));
- sscanf(datastart + 1, " %*d %d %*d %*d\t\t %*d\t "
- "%*d\t\t%*d\t\t %*d\t %*d\t\t %*d", &cur);
-
- /* 70 is the max of /proc/net/wireless */
- return bprintf("%d", (int)((float)cur / 70 * 100));
+ static int
+ ifindex(const char *interface)
+ {
+ static struct ifreq ifr;
+ static int ifsock = -1;
+
+ if (ifsock < 0)
+ ifsock = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (ifsock < 0) {
+ warn("socket 'AF_UNIX':");
+ return -1;
+ }
+ if (strcmp(ifr.ifr_name, interface) != 0) {
+ strcpy(ifr.ifr_name, interface);
+ if (ioctl(ifsock, SIOCGIFINDEX, &ifr) != 0) {
+ warn("ioctl 'SIOCGIFINDEX':");
+ return -1;
+ }
+ }
+ return ifr.ifr_ifindex;
}
const char *
wifi_essid(const char *interface)
{
- static char id[IW_ESSID_MAX_SIZE+1];
- int sockfd;
- struct iwreq wreq;
-
- memset(&wreq, 0, sizeof(struct iwreq));
- wreq.u.essid.length = IW_ESSID_MAX_SIZE+1;
- if (esnprintf(wreq.ifr_name, sizeof(wreq.ifr_name), "%s",
- interface) < 0)
+ uint16_t fam = nl80211fam();
+ ssize_t r;
+ size_t len;
+ char req[NLMSG_HDRLEN+GENL_HDRLEN+NLA_HDRLEN+NLA_ALIGN(4)] = {0}, *p = req;
+ int idx = ifindex(interface);
+ if (!fam) {
+ fprintf(stderr, "nl80211 family not found\n");
return NULL;
+ }
+ if (idx < 0) {
+ fprintf(stderr, "interface %s not found\n", interface);
+ return NULL;
+ }
- if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
- warn("socket 'AF_INET':");
+ memcpy(p, &(struct nlmsghdr){
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = fam,
+ .nlmsg_flags = NLM_F_REQUEST,
+ .nlmsg_seq = seq++,
+ .nlmsg_pid = 0,
+ }, sizeof(struct nlmsghdr));
+ p += NLMSG_HDRLEN;
+ memcpy(p, &(struct genlmsghdr){
+ .cmd = NL80211_CMD_GET_INTERFACE,
+ .version = 1,
+ }, sizeof(struct genlmsghdr));
+ p += GENL_HDRLEN;
+ memcpy(p, &(struct nlattr){
+ .nla_len = NLA_HDRLEN+4,
+ .nla_type = NL80211_ATTR_IFINDEX,
+ }, sizeof(struct nlattr));
+ p += NLA_HDRLEN;
+ memcpy(p, &(uint32_t){idx}, 4);
+
+ if (send(nlsock, req, sizeof(req), 0) != sizeof(req)) {
+ warn("send 'AF_NETLINK':");
return NULL;
}
- wreq.u.essid.pointer = id;
- if (ioctl(sockfd,SIOCGIWESSID, &wreq) < 0) {
- warn("ioctl 'SIOCGIWESSID':");
- close(sockfd);
+ r = recv(nlsock, resp, sizeof(resp), 0);
+ if (r < 0) {
+ warn("recv 'AF_NETLINK':");
return NULL;
}
- close(sockfd);
+ if ((size_t)r <= NLMSG_HDRLEN+GENL_HDRLEN)
+ return NULL;
+ p = findattr(NL80211_ATTR_SSID, resp+NLMSG_HDRLEN+GENL_HDRLEN, resp+r, &len);
+ if (p)
+ p[len] = 0;
+ return p;
+ }
- if (!strcmp(id, ""))
+ const char *
+ wifi_perc(const char *interface)
+ {
+ static char strength[4];
+ struct nlmsghdr hdr;
+ uint16_t fam = nl80211fam();
+ ssize_t r;
+ size_t len;
+ char req[NLMSG_HDRLEN+GENL_HDRLEN+NLA_HDRLEN+NLA_ALIGN(4)] = {0}, *p = req, *e;
+ int idx = ifindex(interface);
+ if (idx < 0) {
+ fprintf(stderr, "interface %s not found\n", interface);
return NULL;
+ }
- return id;
+ memcpy(p, &(struct nlmsghdr){
+ .nlmsg_len = sizeof(req),
+ .nlmsg_type = fam,
+ .nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP,
+ .nlmsg_seq = seq++,
+ .nlmsg_pid = 0,
+ }, sizeof(struct nlmsghdr));
+ p += NLMSG_HDRLEN;
+ memcpy(p, &(struct genlmsghdr){
+ .cmd = NL80211_CMD_GET_STATION,
+ .version = 1,
+ }, sizeof(struct genlmsghdr));
+ p += GENL_HDRLEN;
+ memcpy(p, &(struct nlattr){
+ .nla_len = NLA_HDRLEN+4,
+ .nla_type = NL80211_ATTR_IFINDEX,
+ }, sizeof(struct nlattr));
+ p += NLA_HDRLEN;
+ memcpy(p, &(uint32_t){idx}, 4);
+
+ if (send(nlsock, req, sizeof(req), 0) != sizeof(req)) {
+ warn("send 'AF_NETLINK':");
+ return NULL;
+ }
+ *strength = 0;
+ while (1) {
+ r = recv(nlsock, resp, sizeof(resp), 0);
+ if (r < 0) {
+ warn("recv 'AF_NETLINK':");
+ return NULL;
+ }
+ if ((size_t)r < sizeof(hdr))
+ return NULL;
+ for (p = resp; p != resp+r && (size_t)(resp+r-p) >= sizeof(hdr); p = e) {
+ memcpy(&hdr, p, sizeof(hdr));
+ e = resp+r-p<hdr.nlmsg_len?resp+r:p+hdr.nlmsg_len;
+ if (!*strength && hdr.nlmsg_len > NLMSG_HDRLEN+GENL_HDRLEN) {
+ p += NLMSG_HDRLEN+GENL_HDRLEN;
+ p = findattr(NL80211_ATTR_STA_INFO, p, e, &len);
+ if (p)
+ p = findattr(NL80211_STA_INFO_SIGNAL_AVG, p, e, &len);
+ if (p && len == 1)
+ snprintf(strength, sizeof(strength), "%d", RSSI_TO_PERC(*p));
+ }
+ if (hdr.nlmsg_type == NLMSG_DONE)
+ return *strength?strength:NULL;
+ }
+ }
}
#elif defined(__OpenBSD__)
#include <net/if.h>