master ohmu/fastfetch-openbsd / src / detection / wifi / wifi_linux.c
ohmu  ·  2026-10-15
   1#include "wifi.h"
   2#include "common/endian.h"
   3#include "common/io.h"
   4#include "common/debug.h"
   5#include "common/percent.h"
   6#include "common/strutil.h"
   7
   8#include <sys/time.h>
   9#include <sys/socket.h>
  10#include <sys/ioctl.h>
  11#include <sys/types.h>
  12#include <net/if.h>
  13#include <linux/wireless.h>
  14#include <unistd.h>
  15
  16#include <linux/genetlink.h>
  17#include <linux/nl80211.h>
  18
  19// Silence warning of `NLA_HDRLEN` and `NLA_ALIGN`
  20#pragma GCC diagnostic ignored "-Wsign-conversion"
  21
  22typedef struct FFWifiNlContext {
  23    int sockFd;
  24    uint16_t nl80211FamilyId;
  25    uint32_t portId;
  26    uint32_t seq;
  27} FFWifiNlContext;
  28
  29typedef struct FFWifiSecurityFlags {
  30    bool privacy : 1;
  31    bool wep : 1;
  32    bool wpa : 1;
  33    bool wpa2 : 1;
  34    bool wpa3 : 1;
  35    bool owe : 1;
  36    bool eap : 1;
  37} FFWifiSecurityFlags;
  38
  39static inline uint32_t ffWifiGetNetlinkPortId(int sockFd) {
  40    struct sockaddr_nl addr = {};
  41    socklen_t addrLen = sizeof(addr);
  42    if (getsockname(sockFd, (struct sockaddr*) &addr, &addrLen) < 0) {
  43        FF_DEBUG("Failed to query netlink socket address (use PID instead): %s", strerror(errno));
  44        return instance.state.platform.pid;
  45    }
  46
  47    return addr.nl_pid;
  48}
  49
  50static inline bool ffWifiNlAttrOk(const struct nlattr* attr, size_t remaining) {
  51    return remaining >= sizeof(*attr) &&
  52        attr->nla_len >= sizeof(*attr) &&
  53        attr->nla_len <= remaining;
  54}
  55
  56static const struct nlattr* ffWifiNlAttrNext(const struct nlattr* attr, size_t* remaining) {
  57    size_t alignedLen = NLA_ALIGN(attr->nla_len);
  58    if (alignedLen > *remaining) {
  59        *remaining = 0;
  60        return nullptr;
  61    }
  62
  63    *remaining -= alignedLen;
  64    return (const struct nlattr*) ((const char*) attr + alignedLen);
  65}
  66
  67static inline size_t ffWifiNlAttrPayload(const struct nlattr* attr) {
  68    return attr->nla_len > NLA_HDRLEN ? attr->nla_len - NLA_HDRLEN : 0;
  69}
  70
  71static inline const void* ffWifiNlAttrData(const struct nlattr* attr) {
  72    // Big endian?
  73    return (const uint8_t*) attr + NLA_HDRLEN;
  74}
  75
  76static bool ffWifiNlAppendAttr(struct nlmsghdr* nlh, size_t maxLen, uint16_t type, const void* data, uint16_t dataLen) {
  77    size_t offset = NLMSG_ALIGN(nlh->nlmsg_len);
  78    size_t attrLen = NLA_HDRLEN + dataLen;
  79    size_t alignedLen = NLA_ALIGN(attrLen);
  80    size_t newLen = offset + alignedLen;
  81    if (newLen > maxLen || attrLen > UINT16_MAX || newLen > UINT32_MAX) {
  82        return false;
  83    }
  84
  85    struct nlattr* attr = (struct nlattr*) ((char*) nlh + offset);
  86    attr->nla_type = type;
  87    attr->nla_len = (uint16_t) attrLen;
  88    memcpy((char*) attr + NLA_HDRLEN, data, dataLen);
  89    memset((char*) attr + attrLen, 0, alignedLen - attrLen);
  90    nlh->nlmsg_len = (uint32_t) newLen;
  91    return true;
  92}
  93
  94static bool ffWifiNlGetFamilyId(FFWifiNlContext* ctx) {
  95    struct {
  96        struct nlmsghdr nlh;
  97        struct genlmsghdr genl;
  98        char attrs[64];
  99    } req = {
 100        .nlh = {
 101            .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
 102            .nlmsg_type = GENL_ID_CTRL,
 103            .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
 104            .nlmsg_seq = ++ctx->seq,
 105            .nlmsg_pid = ctx->portId,
 106        },
 107        .genl = {
 108            .cmd = CTRL_CMD_GETFAMILY,
 109            .version = 1, // generic netlink control protocol version
 110        },
 111    };
 112
 113    if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, "nl80211", sizeof("nl80211"))) {
 114        FF_DEBUG("Failed to append CTRL_ATTR_FAMILY_NAME attribute");
 115        return false;
 116    }
 117
 118    struct sockaddr_nl addr = {
 119        .nl_family = AF_NETLINK,
 120    };
 121
 122    ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr));
 123    if (sent != (ssize_t) req.nlh.nlmsg_len) {
 124        FF_DEBUG("Failed to send nl80211 family request: sent=%zd expected=%u", sent, req.nlh.nlmsg_len);
 125        return false;
 126    }
 127
 128    uint8_t buffer[8192];
 129    while (true) {
 130        ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
 131        if (received < 0) {
 132            FF_DEBUG("Failed to receive nl80211 family reply: %s", strerror(errno));
 133            return false;
 134        }
 135
 136        for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer;
 137            NLMSG_OK(nlh, received);
 138            nlh = NLMSG_NEXT(nlh, received)) {
 139            if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) {
 140                continue;
 141            }
 142
 143            if (nlh->nlmsg_type == NLMSG_ERROR) {
 144                const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh);
 145                if (err->error != 0) {
 146                    FF_DEBUG("nl80211 family query failed: %s", strerror(-err->error));
 147                    return false;
 148                }
 149                continue;
 150            }
 151
 152            if (nlh->nlmsg_type != GENL_ID_CTRL) {
 153                continue;
 154            }
 155
 156            const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh);
 157            if (genl->cmd != CTRL_CMD_NEWFAMILY) {
 158                continue;
 159            }
 160
 161            size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
 162            for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN);
 163                ffWifiNlAttrOk(attr, attrRemaining);
 164                attr = ffWifiNlAttrNext(attr, &attrRemaining)) {
 165                if ((attr->nla_type & NLA_TYPE_MASK) != CTRL_ATTR_FAMILY_ID || ffWifiNlAttrPayload(attr) < sizeof(uint16_t)) {
 166                    continue;
 167                }
 168
 169                ctx->nl80211FamilyId = *(const uint16_t*) ffWifiNlAttrData(attr);
 170                return true;
 171            }
 172        }
 173    }
 174}
 175
 176static bool ffWifiNlInit(FFWifiNlContext* ctx) {
 177    FF_AUTO_CLOSE_FD int _ = ctx->sockFd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC);
 178    if (ctx->sockFd < 0) {
 179        FF_DEBUG("Failed to create generic netlink socket: %s", strerror(errno));
 180        return false;
 181    }
 182
 183    struct sockaddr_nl addr = {
 184        .nl_family = AF_NETLINK,
 185    };
 186
 187    if (bind(ctx->sockFd, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
 188        FF_DEBUG("Failed to bind generic netlink socket: %s", strerror(errno));
 189        return false;
 190    }
 191
 192    if (setsockopt(
 193            ctx->sockFd,
 194            SOL_SOCKET,
 195            SO_RCVTIMEO,
 196            &(struct timeval){ .tv_sec = 0, .tv_usec = 250000 }, // 250 ms recv timeout
 197            sizeof(struct timeval)) < 0) {
 198        FF_DEBUG("Failed to set netlink receive timeout: %s", strerror(errno));
 199        return false;
 200    }
 201
 202    ctx->portId = ffWifiGetNetlinkPortId(ctx->sockFd);
 203    if (!ffWifiNlGetFamilyId(ctx)) {
 204        return false;
 205    }
 206
 207    _ = -1; // We are ok now
 208    return true;
 209}
 210
 211static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol, uint16_t* channelWidth) {
 212    double rate = -DBL_MAX;
 213    uint16_t width = 0;
 214    size_t remaining = ffWifiNlAttrPayload(rateAttr);
 215
 216    for (const struct nlattr* info = (const struct nlattr*) ffWifiNlAttrData(rateAttr);
 217        ffWifiNlAttrOk(info, remaining);
 218        info = ffWifiNlAttrNext(info, &remaining)) {
 219        uint16_t type = (uint16_t) (info->nla_type & NLA_TYPE_MASK);
 220        size_t payload = ffWifiNlAttrPayload(info);
 221
 222        switch (type) {
 223            case NL80211_RATE_INFO_40_MHZ_WIDTH:
 224                width = 40;
 225                break;
 226            case NL80211_RATE_INFO_80_MHZ_WIDTH:
 227            case NL80211_RATE_INFO_80P80_MHZ_WIDTH:
 228                width = 80;
 229                break;
 230            case NL80211_RATE_INFO_160_MHZ_WIDTH:
 231                width = 160;
 232                break;
 233            case 18 /* NL80211_RATE_INFO_320_MHZ_WIDTH */:
 234                width = 320;
 235                break;
 236            case NL80211_RATE_INFO_10_MHZ_WIDTH:
 237                width = 10;
 238                break;
 239            case NL80211_RATE_INFO_5_MHZ_WIDTH:
 240                width = 5;
 241                break;
 242            case 25 /* NL80211_RATE_INFO_1_MHZ_WIDTH */:
 243                width = 1;
 244                break;
 245            case 26 /* NL80211_RATE_INFO_2_MHZ_WIDTH */:
 246                width = 2;
 247                break;
 248            case 27 /* NL80211_RATE_INFO_4_MHZ_WIDTH */:
 249                width = 4;
 250                break;
 251            case 28 /* NL80211_RATE_INFO_8_MHZ_WIDTH */:
 252                width = 8;
 253                break;
 254            case 29 /* NL80211_RATE_INFO_16_MHZ_WIDTH */:
 255                width = 16;
 256                break;
 257            case 30 /* NL80211_RATE_INFO_UHR_MCS */:
 258                ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)");
 259                break;
 260            case 23 /* NL80211_RATE_INFO_S1G_MCS */:
 261                ffStrbufSetStatic(protocol, "802.11ah (Wi-Fi HaLow)");
 262                break;
 263            case 19 /* NL80211_RATE_INFO_EHT_MCS */:
 264                ffStrbufSetStatic(protocol, "802.11be (Wi-Fi 7)");
 265                break;
 266            case 13 /* NL80211_RATE_INFO_HE_MCS */:
 267                ffStrbufSetStatic(protocol, "802.11ax (Wi-Fi 6)");
 268                break;
 269            case NL80211_RATE_INFO_VHT_MCS:
 270                ffStrbufSetStatic(protocol, "802.11ac (Wi-Fi 5)");
 271                break;
 272            case NL80211_RATE_INFO_MCS:
 273                ffStrbufSetStatic(protocol, "802.11n (Wi-Fi 4)");
 274                break;
 275            case NL80211_RATE_INFO_BITRATE32:
 276                if (payload >= sizeof(uint32_t)) {
 277                    rate = *(uint32_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps
 278                }
 279                break;
 280            case NL80211_RATE_INFO_BITRATE:
 281                if (payload >= sizeof(uint16_t) && rate == -DBL_MAX) {
 282                    rate = *(uint16_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps
 283                }
 284                break;
 285        }
 286    }
 287
 288    if (rate != -DBL_MAX && *channelWidth == 0) {
 289        *channelWidth = width;
 290    }
 291
 292    return rate;
 293}
 294
 295static uint16_t ffWifiChannelWidthToMhz(uint32_t width) {
 296    switch (width) {
 297        case NL80211_CHAN_WIDTH_20_NOHT:
 298        case NL80211_CHAN_WIDTH_20:
 299            return 20;
 300        case NL80211_CHAN_WIDTH_40:
 301            return 40;
 302        case NL80211_CHAN_WIDTH_80:
 303        case NL80211_CHAN_WIDTH_80P80:
 304            return 80;
 305        case NL80211_CHAN_WIDTH_160:
 306            return 160;
 307        case 6 /* NL80211_CHAN_WIDTH_5 */:
 308            return 5;
 309        case 7 /* NL80211_CHAN_WIDTH_10 */:
 310            return 10;
 311        case 8 /* NL80211_CHAN_WIDTH_1 */:
 312            return 1;
 313        case 9 /* NL80211_CHAN_WIDTH_2 */:
 314            return 2;
 315        case 10 /* NL80211_CHAN_WIDTH_4 */:
 316            return 4;
 317        case 11 /* NL80211_CHAN_WIDTH_8 */:
 318            return 8;
 319        case 12 /* NL80211_CHAN_WIDTH_16 */:
 320            return 16;
 321        case 13 /* NL80211_CHAN_WIDTH_320 */:
 322            return 320;
 323        default:
 324            return 0;
 325    }
 326}
 327
 328static bool ffWifiFetchInterfaceInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) {
 329    struct {
 330        struct nlmsghdr nlh;
 331        struct genlmsghdr genl;
 332        char attrs[32];
 333    } req = {
 334        .nlh = {
 335            .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
 336            .nlmsg_type = ctx->nl80211FamilyId,
 337            .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
 338            .nlmsg_seq = ++ctx->seq,
 339            .nlmsg_pid = ctx->portId,
 340        },
 341        .genl = {
 342            .cmd = NL80211_CMD_GET_INTERFACE,
 343            .version = 0,
 344        },
 345    };
 346
 347    if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
 348        FF_DEBUG("Failed to build nl80211 interface request");
 349        return false;
 350    }
 351
 352    struct sockaddr_nl addr = {
 353        .nl_family = AF_NETLINK,
 354    };
 355
 356    ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr));
 357    if (sent != (ssize_t) req.nlh.nlmsg_len) {
 358        FF_DEBUG("Failed to send nl80211 interface request");
 359        return false;
 360    }
 361
 362    uint8_t buffer[8192];
 363    while (true) {
 364        ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
 365        if (received < 0) {
 366            FF_DEBUG("Failed to receive nl80211 interface reply: %s", strerror(errno));
 367            return false;
 368        }
 369
 370        for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer;
 371            NLMSG_OK(nlh, received);
 372            nlh = NLMSG_NEXT(nlh, received)) {
 373            if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) {
 374                continue;
 375            }
 376
 377            if (nlh->nlmsg_type == NLMSG_ERROR) {
 378                const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh);
 379                if (err->error != 0) {
 380                    FF_DEBUG("nl80211 interface request failed: %s", strerror(-err->error));
 381                }
 382                return false;
 383            }
 384
 385            if (nlh->nlmsg_type != ctx->nl80211FamilyId) {
 386                continue;
 387            }
 388
 389            const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh);
 390            size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
 391            for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN);
 392                ffWifiNlAttrOk(attr, attrRemaining);
 393                attr = ffWifiNlAttrNext(attr, &attrRemaining)) {
 394                if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_CHANNEL_WIDTH ||
 395                    ffWifiNlAttrPayload(attr) < sizeof(uint32_t)) {
 396                    continue;
 397                }
 398
 399                item->conn.channelWidth = ffWifiChannelWidthToMhz(*(const uint32_t*) ffWifiNlAttrData(attr));
 400                return item->conn.channelWidth != 0;
 401            }
 402
 403            return false;
 404        }
 405    }
 406}
 407
 408static void ffWifiApplySecurityFlags(FFWifiResult* item, const FFWifiSecurityFlags* sec) {
 409    ffStrbufClear(&item->conn.security);
 410
 411    if (sec->wep) {
 412        ffStrbufAppendS(&item->conn.security, "WEP/");
 413    }
 414    if (sec->wpa) {
 415        ffStrbufAppendS(&item->conn.security, "WPA/");
 416    }
 417    if (sec->wpa2) {
 418        ffStrbufAppendS(&item->conn.security, "WPA2/");
 419    }
 420    if (sec->wpa3) {
 421        ffStrbufAppendS(&item->conn.security, "WPA3/");
 422    }
 423    if (sec->owe) {
 424        ffStrbufAppendS(&item->conn.security, "OWE/");
 425    }
 426    if (sec->eap) {
 427        ffStrbufAppendS(&item->conn.security, "802.1X/");
 428    }
 429
 430    if (!item->conn.security.length) {
 431        if (sec->privacy) {
 432            ffStrbufSetStatic(&item->conn.security, "WEP");
 433        } else {
 434            ffStrbufSetStatic(&item->conn.security, "Insecure");
 435        }
 436    } else {
 437        ffStrbufTrimRight(&item->conn.security, '/');
 438    }
 439}
 440
 441static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) {
 442    if (len < 8) { // version(2) + group cipher suite(4) + pairwise count(2)
 443        return;
 444    }
 445
 446    sec->wpa2 = true;
 447    size_t pos = 0;
 448
 449    pos += 2; // RSN version field length
 450    if (pos + 4 > len) { // group cipher suite selector length
 451        return;
 452    }
 453    pos += 4; // skip group cipher suite selector
 454
 455    if (pos + 2 > len) { // pairwise cipher suite count field length
 456        return;
 457    }
 458    uint16_t pairwiseCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
 459    pos += 2; // skip pairwise cipher suite count field
 460
 461    size_t pairwiseLen = (size_t) pairwiseCount * 4; // each suite selector is 4 bytes
 462    if (pos + pairwiseLen > len) {
 463        return;
 464    }
 465    pos += pairwiseLen;
 466
 467    if (pos + 2 > len) { // AKM suite count field length
 468        return;
 469    }
 470    uint16_t akmCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
 471    pos += 2; // skip AKM suite count field
 472
 473    for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes
 474        const uint8_t* akm = ie + pos;
 475        if (akm[0] != 0x00 || akm[1] != 0x0f || akm[2] != 0xac) { // RSN OUI 00:0f:ac
 476            continue;
 477        }
 478
 479        switch (akm[3]) {
 480            case 1:  // 802.1X
 481            case 5:  // FT/802.1X
 482            case 11: // 802.1X-SHA256
 483            case 12: // FT/802.1X-SHA384 (suite selector value)
 484                sec->eap = true;
 485                break;
 486            case 8:  // SAE (WPA3-Personal)
 487                sec->wpa3 = true;
 488                break;
 489            case 18: // OWE
 490                sec->owe = true;
 491                break;
 492            default:
 493                break;
 494        }
 495    }
 496
 497    if (sec->owe) {
 498        sec->wpa2 = false;
 499    }
 500}
 501
 502static void ffWifiParseWpaVendorIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) {
 503    if (len < 8) { // OUI+type(4) + version(2) + multicast cipher suite(4) starts here
 504        return;
 505    }
 506
 507    if (!(ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2 && ie[3] == 0x01)) { // Microsoft WPA OUI/type
 508        return;
 509    }
 510
 511    sec->wpa = true;
 512
 513    size_t pos = 4; // WPA vendor OUI/type selector length
 514    if (pos + 2 > len) { // WPA version field length
 515        return;
 516    }
 517    pos += 2; // skip WPA version
 518
 519    if (pos + 4 > len) { // multicast cipher suite selector length
 520        return;
 521    }
 522    pos += 4; // skip multicast cipher suite selector
 523
 524    if (pos + 2 > len) { // unicast cipher suite count field length
 525        return;
 526    }
 527    uint16_t pairwiseCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
 528    pos += 2 + (size_t) pairwiseCount * 4; // count field(2) + N unicast suite selectors(4 each)
 529
 530    if (pos + 2 > len) { // AKM suite count field length
 531        return;
 532    }
 533    uint16_t akmCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
 534    pos += 2; // skip AKM suite count field
 535
 536    for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes
 537        const uint8_t* akm = ie + pos;
 538        if (!(akm[0] == 0x00 && akm[1] == 0x50 && akm[2] == 0xf2)) { // WPA vendor OUI 00:50:f2
 539            continue;
 540        }
 541        if (akm[3] == 1) { // WPA Enterprise (802.1X)
 542            sec->eap = true;
 543        }
 544    }
 545}
 546
 547static void ffWifiParseInformationElements(const uint8_t* ies, size_t length, FFWifiResult* item, FFWifiSecurityFlags* sec) {
 548    size_t pos = 0;
 549    while (pos + 2 <= length) {
 550        uint8_t id = ies[pos];
 551        uint8_t len = ies[pos + 1];
 552        pos += 2;
 553        if (pos + len > length) {
 554            break;
 555        }
 556
 557        const uint8_t* ie = ies + pos;
 558        if (id == 0) { // SSID element ID
 559            ffStrbufSetNS(&item->conn.ssid, len, (const char*) ie);
 560        } else if (id == 48) { // RSN element ID
 561            ffWifiParseRsnIe(ie, len, sec);
 562        } else if (id == 221) { // vendor-specific element ID (WPA IE lives here)
 563            ffWifiParseWpaVendorIe(ie, len, sec);
 564        }
 565
 566        pos += len;
 567    }
 568}
 569
 570static bool ffWifiIsBssAssociated(const struct nlattr* bssAttr) {
 571    size_t remaining = ffWifiNlAttrPayload(bssAttr);
 572    for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr);
 573        ffWifiNlAttrOk(attr, remaining);
 574        attr = ffWifiNlAttrNext(attr, &remaining)) {
 575        uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK);
 576        size_t payload = ffWifiNlAttrPayload(attr);
 577
 578        if (type == NL80211_BSS_STATUS && payload >= sizeof(uint32_t)) {
 579            return *(uint32_t*) ffWifiNlAttrData(attr) == NL80211_BSS_STATUS_ASSOCIATED;
 580        }
 581    }
 582
 583    return false;
 584}
 585
 586static void ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item) {
 587    FFWifiSecurityFlags sec = {};
 588    size_t remaining = ffWifiNlAttrPayload(bssAttr);
 589
 590    for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr);
 591        ffWifiNlAttrOk(attr, remaining);
 592        attr = ffWifiNlAttrNext(attr, &remaining)) {
 593        uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK);
 594        size_t payload = ffWifiNlAttrPayload(attr);
 595
 596        if (type == NL80211_BSS_BSSID && payload >= 6) {
 597            const uint8_t* mac = (const uint8_t*) ffWifiNlAttrData(attr);
 598            ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 599        } else if (type == NL80211_BSS_FREQUENCY && payload >= sizeof(uint32_t)) {
 600            item->conn.frequency = (uint16_t) *(uint32_t*) ffWifiNlAttrData(attr);
 601            item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);
 602        } else if (type == NL80211_BSS_SIGNAL_MBM && payload >= sizeof(int32_t)) {
 603            int rssi = *(int32_t*) ffWifiNlAttrData(attr) / 100; // mBm (100 * dBm) => dBm
 604            item->conn.signalQuality = ffRssiToSignalQuality(rssi);
 605        } else if (type == NL80211_BSS_CAPABILITY && payload >= sizeof(uint16_t)) {
 606            uint16_t capability = *(uint16_t*) ffWifiNlAttrData(attr);
 607            sec.privacy = (capability & (1u << 4u)) != 0; // IEEE 802.11 capability bit 4: privacy
 608        } else if (type == NL80211_BSS_INFORMATION_ELEMENTS || type == NL80211_BSS_BEACON_IES) {
 609            ffWifiParseInformationElements((const uint8_t*) ffWifiNlAttrData(attr), payload, item, &sec);
 610        }
 611    }
 612
 613    ffWifiApplySecurityFlags(item, &sec);
 614    return;
 615}
 616
 617static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) {
 618    struct {
 619        struct nlmsghdr nlh;
 620        struct genlmsghdr genl;
 621        char attrs[32];
 622    } req = {
 623        .nlh = {
 624            .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
 625            .nlmsg_type = ctx->nl80211FamilyId,
 626            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK,
 627            .nlmsg_seq = ++ctx->seq,
 628            .nlmsg_pid = ctx->portId,
 629        },
 630        .genl = {
 631            .cmd = NL80211_CMD_GET_SCAN,
 632            .version = 0, // nl80211 command version
 633        },
 634    };
 635
 636    if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
 637        FF_DEBUG("Failed to build nl80211 scan request");
 638        return false;
 639    }
 640
 641    struct sockaddr_nl addr = {
 642        .nl_family = AF_NETLINK,
 643    };
 644
 645    ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr));
 646    if (sent != (ssize_t) req.nlh.nlmsg_len) {
 647        FF_DEBUG("Failed to send nl80211 scan request");
 648        return false;
 649    }
 650
 651    uint8_t buffer[1024 * 16];
 652    while (true) {
 653        ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
 654        if (received < 0) {
 655            FF_DEBUG("Failed to receive nl80211 scan reply: %s", strerror(errno));
 656            return false;
 657        }
 658
 659        for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer;
 660            NLMSG_OK(nlh, received);
 661            nlh = NLMSG_NEXT(nlh, received)) {
 662            if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) {
 663                continue;
 664            }
 665
 666            if (nlh->nlmsg_type == NLMSG_DONE) {
 667                return false;
 668            }
 669
 670            if (nlh->nlmsg_type == NLMSG_ERROR) {
 671                const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh);
 672                if (err->error == 0) {
 673                    continue;
 674                }
 675                FF_DEBUG("nl80211 scan request failed: %s", strerror(-err->error));
 676                return false;
 677            }
 678
 679            if (nlh->nlmsg_type != ctx->nl80211FamilyId) {
 680                continue;
 681            }
 682
 683            const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh);
 684            size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
 685            for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN);
 686                ffWifiNlAttrOk(attr, attrRemaining);
 687                attr = ffWifiNlAttrNext(attr, &attrRemaining)) {
 688                if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_BSS) {
 689                    continue;
 690                }
 691
 692                if (!ffWifiIsBssAssociated(attr)) {
 693                    continue;
 694                }
 695
 696                ffWifiParseBssAttr(attr, item);
 697                ffStrbufSetStatic(&item->conn.status, "connected");
 698                return true;
 699            }
 700        }
 701    }
 702
 703    return false;
 704}
 705
 706static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResult* item) {
 707    size_t remaining = ffWifiNlAttrPayload(staInfoAttr);
 708    for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(staInfoAttr);
 709        ffWifiNlAttrOk(attr, remaining);
 710        attr = ffWifiNlAttrNext(attr, &remaining)) {
 711        uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK);
 712        size_t payload = ffWifiNlAttrPayload(attr);
 713
 714        if (type == NL80211_STA_INFO_SIGNAL && payload >= sizeof(uint8_t) && item->conn.signalQuality == -DBL_MAX) {
 715            int rssi = (int8_t) *(const uint8_t*) ffWifiNlAttrData(attr);
 716            item->conn.signalQuality = ffRssiToSignalQuality(rssi);
 717        } else if (type == NL80211_STA_INFO_TX_BITRATE && (item->conn.txRate == -DBL_MAX || item->conn.channelWidth == 0)) {
 718            double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol, &item->conn.channelWidth);
 719            if (tx != -DBL_MAX) {
 720                item->conn.txRate = tx;
 721            }
 722        } else if (type == NL80211_STA_INFO_RX_BITRATE && (item->conn.rxRate == -DBL_MAX || item->conn.channelWidth == 0)) {
 723            double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol, &item->conn.channelWidth);
 724            if (rx != -DBL_MAX) {
 725                item->conn.rxRate = rx;
 726            }
 727        }
 728    }
 729}
 730
 731static bool ffWifiFetchStationInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) {
 732    struct {
 733        struct nlmsghdr nlh;
 734        struct genlmsghdr genl;
 735        char attrs[32];
 736    } req = {
 737        .nlh = {
 738            .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
 739            .nlmsg_type = ctx->nl80211FamilyId,
 740            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK,
 741            .nlmsg_seq = ++ctx->seq,
 742            .nlmsg_pid = ctx->portId,
 743        },
 744        .genl = {
 745            .cmd = NL80211_CMD_GET_STATION,
 746            .version = 0, // nl80211 command version
 747        },
 748    };
 749
 750    if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
 751        FF_DEBUG("Failed to build nl80211 station request");
 752        return false;
 753    }
 754
 755    struct sockaddr_nl addr = {
 756        .nl_family = AF_NETLINK,
 757    };
 758
 759    ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr));
 760    if (sent != (ssize_t) req.nlh.nlmsg_len) {
 761        FF_DEBUG("Failed to send nl80211 station request");
 762        return false;
 763    }
 764
 765    uint8_t buffer[8192];
 766    bool gotStation = false;
 767    while (true) {
 768        ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
 769        if (received < 0) {
 770            FF_DEBUG("Failed to receive nl80211 station reply: %s", strerror(errno));
 771            return gotStation;
 772        }
 773
 774        for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer;
 775            NLMSG_OK(nlh, received);
 776            nlh = NLMSG_NEXT(nlh, received)) {
 777            if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) {
 778                continue;
 779            }
 780
 781            if (nlh->nlmsg_type == NLMSG_DONE) {
 782                return gotStation;
 783            }
 784
 785            if (nlh->nlmsg_type == NLMSG_ERROR) {
 786                const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh);
 787                if (err->error != 0) {
 788                    FF_DEBUG("nl80211 station request failed: %s", strerror(-err->error));
 789                }
 790                return gotStation;
 791            }
 792
 793            if (nlh->nlmsg_type != ctx->nl80211FamilyId) {
 794                continue;
 795            }
 796
 797            const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh);
 798            size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
 799            for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN);
 800                ffWifiNlAttrOk(attr, attrRemaining);
 801                attr = ffWifiNlAttrNext(attr, &attrRemaining)) {
 802                if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_STA_INFO) {
 803                    continue;
 804                }
 805
 806                ffWifiParseStationInfo(attr, item);
 807                gotStation = true;
 808            }
 809        }
 810    }
 811}
 812
 813static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) {
 814    if (ctx->sockFd < 0) {
 815        if (ctx->sockFd == -1) {
 816            if (!ffWifiNlInit(ctx)) {
 817                FF_DEBUG("Failed to initialize netlink context, skipping");
 818                ctx->sockFd = -2; // sentinel: permanent netlink failure, don't retry
 819                return "Netlink initialization failed";
 820            }
 821        } else {
 822            FF_DEBUG("Netlink socket is not available, skipping");
 823            return "Netlink socket unavailable";
 824        }
 825    }
 826
 827    FF_DEBUG("Starting netlink wifi detection for interface %s", item->inf.description.chars);
 828    if (ffWifiFetchScanInfo(ctx, item, ifIndex)) {
 829        FF_DEBUG("found associated BSS: %s", item->conn.ssid.chars);
 830        ffStrbufSetStatic(&item->conn.status, "connected");
 831        ffWifiFetchInterfaceInfo(ctx, item, ifIndex);
 832        ffWifiFetchStationInfo(ctx, item, ifIndex);
 833        if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) {
 834            FF_DEBUG("nl80211 station info did not include MCS family fields");
 835        }
 836    } else {
 837        FF_DEBUG("No associated BSS found");
 838        ffStrbufSetStatic(&item->conn.status, "disconnected");
 839    }
 840
 841    FF_DEBUG("Netlink wifi detection completed");
 842    return nullptr;
 843}
 844typedef struct FFWifiIcContext {
 845    int sockFd;
 846} FFWifiIcContext;
 847
 848static const char* detectWithIoctl(FFWifiIcContext* ctx, FFWifiResult* item, char ifName[static IFNAMSIZ]) {
 849    int sock = -1;
 850    if (ctx->sockFd < 0) {
 851        if (ctx->sockFd == -1) {
 852            sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 853            if (sock < 0) {
 854                FF_DEBUG("Failed to initialize ioctl context, skipping: %s", strerror(errno));
 855                ctx->sockFd = -2; // sentinel: permanent ioctl failure, don't retry
 856                return "socket() failed";
 857            }
 858            ctx->sockFd = sock;
 859        } else {
 860            FF_DEBUG("Ioctl socket is not available, skipping");
 861            return "ioctl socket unavailable";
 862        }
 863    } else {
 864        sock = ctx->sockFd;
 865    }
 866
 867    FF_DEBUG("Starting ioctl wifi detection for interface %s", ifName);
 868    struct iwreq iwr = {};
 869    strcpy(iwr.ifr_name, ifName);
 870
 871    if (!item->conn.ssid.length) {
 872        FF_DEBUG("Getting SSID via ioctl");
 873        ffStrbufEnsureFree(&item->conn.ssid, IW_ESSID_MAX_SIZE);
 874        iwr.u.essid.pointer = (caddr_t) item->conn.ssid.chars;
 875        iwr.u.essid.length = IW_ESSID_MAX_SIZE + 1;
 876        iwr.u.essid.flags = 0;
 877        if (ioctl(sock, SIOCGIWESSID, &iwr) >= 0) {
 878            ffStrbufSetStatic(&item->conn.status, "connected");
 879            ffStrbufRecalculateLength(&item->conn.ssid);
 880            FF_DEBUG("SSID: %s", item->conn.ssid.chars);
 881        } else {
 882            FF_DEBUG("Failed to get SSID via ioctl: %s", strerror(errno));
 883        }
 884    }
 885
 886    if (!item->conn.protocol.length) {
 887        FF_DEBUG("Getting protocol name via ioctl");
 888        if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0) {
 889            char* token = iwr.u.name;
 890            if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) {
 891                token += strlen("IEEE ");
 892            }
 893            if (ffStrStartsWith(token, "802.11")) {
 894                token += strlen("802.11");
 895                if (*token) {
 896                    if (*token == ' ') {
 897                        token++;
 898                    }
 899                    for (char* c = token; *c; ++c) {
 900                        if (*c >= 'A' && *c <= 'Z') {
 901                            *c += 'a' - 'A';
 902                        }
 903                    }
 904                    if (ffStrEquals(token, "n")) {
 905                        ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)");
 906                    } else if (ffStrEquals(token, "ac")) {
 907                        ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)");
 908                    } else if (ffStrEquals(token, "ax")) {
 909                        ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)");
 910                    } else if (ffStrEquals(token, "be")) {
 911                        ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)");
 912                    } else if (ffStrEquals(token, "bn")) {
 913                        ffStrbufSetStatic(&item->conn.protocol, "802.11bn (Wi-Fi 8)");
 914                    } else {
 915                        ffStrbufSetStatic(&item->conn.protocol, "802.11");
 916                        ffStrbufAppendS(&item->conn.protocol, token);
 917                    }
 918                }
 919            }
 920            FF_DEBUG("Protocol: %s", item->conn.protocol.length ? item->conn.protocol.chars : "(unknown)");
 921        } else {
 922            FF_DEBUG("Failed to get protocol name via ioctl: %s", strerror(errno));
 923        }
 924    }
 925
 926    if (!item->conn.bssid.length) {
 927        FF_DEBUG("Getting BSSID via ioctl");
 928        if (ioctl(sock, SIOCGIWAP, &iwr) >= 0) {
 929            for (int i = 0; i < 6; ++i) {
 930                ffStrbufAppendF(&item->conn.bssid, "%.2X:", (uint8_t) iwr.u.ap_addr.sa_data[i]);
 931            }
 932            ffStrbufTrimRight(&item->conn.bssid, ':');
 933            FF_DEBUG("BSSID: %s", item->conn.bssid.chars);
 934        } else {
 935            FF_DEBUG("Failed to get BSSID via ioctl: %s", strerror(errno));
 936        }
 937    }
 938
 939    if (item->conn.txRate == -DBL_MAX) {
 940        FF_DEBUG("Getting bitrate via ioctl");
 941        if (ioctl(sock, SIOCGIWRATE, &iwr) >= 0) {
 942            if (iwr.u.bitrate.value > 0) {
 943                item->conn.txRate = iwr.u.bitrate.value / 1000000.; // bps => Mbps
 944                FF_DEBUG("TX bitrate: %.2f Mbps", item->conn.txRate);
 945            } else {
 946                FF_DEBUG("Bitrate value is zero or negative, ignoring");
 947            }
 948        } else {
 949            FF_DEBUG("Failed to get bitrate via ioctl: %s", strerror(errno));
 950        }
 951    }
 952
 953    if (item->conn.frequency == 0 && item->conn.channel == 0) {
 954        FF_DEBUG("Getting frequency via ioctl");
 955        if (ioctl(sock, SIOCGIWFREQ, &iwr) >= 0) {
 956            if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000) { // kernel may return direct channel number
 957                item->conn.channel = (uint16_t) iwr.u.freq.m;
 958                FF_DEBUG("Direct channel value: %u", item->conn.channel);
 959            } else {
 960                // convert it to MHz
 961                while (iwr.u.freq.e < 6) { // normalize exponent to 10^6 (MHz)
 962                    iwr.u.freq.m /= 10;
 963                    iwr.u.freq.e++;
 964                }
 965                while (iwr.u.freq.e > 6) { // normalize exponent to 10^6 (MHz)
 966                    iwr.u.freq.m *= 10;
 967                    iwr.u.freq.e--;
 968                }
 969                item->conn.frequency = (uint16_t) iwr.u.freq.m;
 970                item->conn.channel = ffWifiFreqToChannel(item->conn.frequency);
 971                FF_DEBUG("Frequency: %u MHz, Channel: %u", item->conn.frequency, item->conn.channel);
 972            }
 973        } else {
 974            FF_DEBUG("Failed to get frequency via ioctl: %s", strerror(errno));
 975        }
 976    }
 977
 978    if (item->conn.signalQuality == -DBL_MAX) {
 979        FF_DEBUG("Getting signal stats via ioctl");
 980        struct iw_statistics stats;
 981        iwr.u.data.pointer = &stats;
 982        iwr.u.data.length = sizeof(stats);
 983        iwr.u.data.flags = 0;
 984
 985        if (ioctl(sock, SIOCGIWSTATS, &iwr) >= 0) {
 986            int8_t level = (int8_t) stats.qual.level;
 987            item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0
 988                                                                          : (level + 100) * 2;
 989            FF_DEBUG("Signal level: %d dBm, quality: %.0f%%", level, item->conn.signalQuality);
 990        } else {
 991            FF_DEBUG("Failed to get signal stats via ioctl: %s", strerror(errno));
 992        }
 993    }
 994
 995    if (!item->conn.security.length) {
 996        FF_DEBUG("Getting security info via ioctl");
 997        struct iw_encode_ext iwe;
 998        iwr.u.data.pointer = &iwe;
 999        iwr.u.data.length = sizeof(iwe);
1000        iwr.u.data.flags = 0;
1001        if (ioctl(sock, SIOCGIWENCODEEXT, &iwr) >= 0) {
1002            switch (iwe.alg) {
1003                case IW_ENCODE_ALG_WEP:
1004                    ffStrbufAppendS(&item->conn.security, "WEP");
1005                    FF_DEBUG("Security: WEP");
1006                    break;
1007                case IW_ENCODE_ALG_TKIP:
1008                    ffStrbufAppendS(&item->conn.security, "TKIP");
1009                    FF_DEBUG("Security: TKIP");
1010                    break;
1011                case IW_ENCODE_ALG_CCMP:
1012                    ffStrbufAppendS(&item->conn.security, "CCMP");
1013                    FF_DEBUG("Security: CCMP");
1014                    break;
1015                case IW_ENCODE_ALG_PMK:
1016                    ffStrbufAppendS(&item->conn.security, "PMK");
1017                    FF_DEBUG("Security: PMK");
1018                    break;
1019                case IW_ENCODE_ALG_AES_CMAC:
1020                    ffStrbufAppendS(&item->conn.security, "CMAC");
1021                    FF_DEBUG("Security: CMAC");
1022                    break;
1023                default:
1024                    ffStrbufAppendF(&item->conn.security, "Unknown (%d)", (int) iwe.alg);
1025                    FF_DEBUG("Security: Unknown (%d)", (int) iwe.alg);
1026                    break;
1027            }
1028        } else {
1029            FF_DEBUG("Failed to get security info via ioctl: %s", strerror(errno));
1030        }
1031    }
1032
1033    FF_DEBUG("Ioctl wifi detection completed");
1034    return nullptr;
1035}
1036
1037const char* ffDetectWifi(FFlist* result) {
1038    FF_DEBUG("Starting wifi detection");
1039
1040    struct if_nameindex* infs = if_nameindex();
1041    if (!infs) {
1042        FF_DEBUG("if_nameindex failed: %s", strerror(errno));
1043        return "if_nameindex() failed";
1044    }
1045
1046    FFWifiNlContext nl = { .sockFd = -1 };
1047    FFWifiIcContext ic = { .sockFd = -1 };
1048
1049    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
1050
1051    for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == nullptr); ++i) {
1052        FF_DEBUG("Checking interface: %s (index: %u)", i->if_name, i->if_index);
1053        ffStrbufSetF(&buffer, "/sys/class/net/%s/phy80211/", i->if_name);
1054        if (!ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY)) {
1055            FF_DEBUG("Not a wifi interface (no phy80211 directory)");
1056            continue;
1057        }
1058
1059        FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
1060        ffStrbufInitS(&item->inf.description, i->if_name);
1061        ffStrbufInit(&item->inf.status);
1062        ffStrbufInit(&item->conn.status);
1063        ffStrbufInit(&item->conn.ssid);
1064        ffStrbufInit(&item->conn.bssid);
1065        ffStrbufInit(&item->conn.protocol);
1066        ffStrbufInit(&item->conn.security);
1067        item->conn.signalQuality = -DBL_MAX;
1068        item->conn.rxRate = -DBL_MAX;
1069        item->conn.txRate = -DBL_MAX;
1070        item->conn.channel = 0;
1071        item->conn.channelWidth = 0;
1072        item->conn.frequency = 0;
1073
1074        char operstate;
1075        ffStrbufSetF(&buffer, "/sys/class/net/%s/operstate", i->if_name);
1076        if (!ffReadFileData(buffer.chars, 1, &operstate)) {
1077            ffStrbufSetStatic(&item->inf.status, "unknown");
1078            ffStrbufSetStatic(&item->conn.status, "disconnected");
1079            continue;
1080        }
1081
1082        if (operstate == 'u') {
1083            ffStrbufSetStatic(&item->inf.status, "up");
1084
1085            detectWithNetlink(&nl, item, i->if_index);
1086            detectWithIoctl(&ic, item, i->if_name);
1087        } else {
1088            ffStrbufSetStatic(&item->conn.status, "disconnected");
1089
1090            ffStrbufSetF(&buffer, "/sys/class/net/%s/flags", i->if_name);
1091            char flags[16];
1092            ssize_t len = ffReadFileData(buffer.chars, sizeof(flags) - 1, flags);
1093            if (len <= 0) {
1094                ffStrbufSetStatic(&item->inf.status, "unknown");
1095                continue;
1096            }
1097            flags[len] = '\0';
1098
1099            unsigned flagsVal = (unsigned) strtoul(flags, nullptr, 16); // parse /sys flags as hexadecimal
1100            if (flagsVal & IFF_UP) {
1101                ffStrbufSetStatic(&item->inf.status, "up");
1102            } else {
1103                ffStrbufSetStatic(&item->inf.status, "down");
1104            }
1105        }
1106    }
1107
1108    if_freenameindex(infs);
1109    if (nl.sockFd >= 0) {
1110        close(nl.sockFd);
1111    }
1112    if (ic.sockFd >= 0) {
1113        close(ic.sockFd);
1114    }
1115
1116    FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length);
1117    return nullptr;
1118}