URI: 
       wifi: switch to nl80211 interface - slstatus - status monitor
  HTML git clone git://git.suckless.org/slstatus
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 0b00c0319c7b4a941b40aa8709bb4c478f9a9b1f
   DIR parent f68f49273e70b3767b30c549dda04ddd4d25fc91
  HTML 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(-)
       ---
   DIR 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>