From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/wifi/wifi.h | 62 +++ src/detection/wifi/wifi_android.c | 69 +++ src/detection/wifi/wifi_apple.m | 165 ++++++ src/detection/wifi/wifi_bsd.c | 153 ++++++ src/detection/wifi/wifi_linux.c | 976 ++++++++++++++++++++++++++++++++++++ src/detection/wifi/wifi_nbsd.c | 168 +++++++ src/detection/wifi/wifi_nosupport.c | 5 + src/detection/wifi/wifi_obsd.c | 109 ++++ src/detection/wifi/wifi_windows.c | 242 +++++++++ 9 files changed, 1949 insertions(+) create mode 100644 src/detection/wifi/wifi.h create mode 100644 src/detection/wifi/wifi_android.c create mode 100644 src/detection/wifi/wifi_apple.m create mode 100644 src/detection/wifi/wifi_bsd.c create mode 100644 src/detection/wifi/wifi_linux.c create mode 100644 src/detection/wifi/wifi_nbsd.c create mode 100644 src/detection/wifi/wifi_nosupport.c create mode 100644 src/detection/wifi/wifi_obsd.c create mode 100644 src/detection/wifi/wifi_windows.c (limited to 'src/detection/wifi') diff --git a/src/detection/wifi/wifi.h b/src/detection/wifi/wifi.h new file mode 100644 index 0000000..e457295 --- /dev/null +++ b/src/detection/wifi/wifi.h @@ -0,0 +1,62 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/wifi/option.h" + +struct FFWifiInterface { + FFstrbuf description; + FFstrbuf status; +}; + +struct FFWifiConnection { + FFstrbuf status; + FFstrbuf ssid; + FFstrbuf bssid; + FFstrbuf protocol; + FFstrbuf security; + double signalQuality; // Percentage + double rxRate; + double txRate; + uint16_t channel; + uint16_t frequency; // MHz +}; + +typedef struct FFWifiResult { + struct FFWifiInterface inf; + struct FFWifiConnection conn; +} FFWifiResult; + +const char* ffDetectWifi(FFlist* result /*list of FFWifiItem*/); + +static inline uint16_t ffWifiFreqToChannel(uint16_t frequency) { + // Return 0 for unknown / non-standard frequencies. + if (frequency == 2484) { + return 14; + } + + // 2.4 GHz channels 1-13 + if (frequency >= 2412 && frequency <= 2472 && ((frequency - 2407) % 5) == 0) { + return (uint16_t) ((frequency - 2407) / 5); + } + + // 4.9 GHz public safety band (e.g. channels 182-196) + if (frequency >= 4910 && frequency <= 4980 && ((frequency - 4000) % 5) == 0) { + return (uint16_t) ((frequency - 4000) / 5); + } + + // 5 GHz channels + if (frequency >= 5000 && frequency <= 5895 && ((frequency - 5000) % 5) == 0) { + return (uint16_t) ((frequency - 5000) / 5); + } + + // 6 GHz channels (Wi-Fi 6E/7) + // 5935 MHz is a special case mapped to channel 2. + if (frequency == 5935) { + return 2; + } + if (frequency >= 5955 && frequency <= 7115 && ((frequency - 5950) % 5) == 0) { + return (uint16_t) ((frequency - 5950) / 5); + } + + return 0; +} diff --git a/src/detection/wifi/wifi_android.c b/src/detection/wifi/wifi_android.c new file mode 100644 index 0000000..c4537da --- /dev/null +++ b/src/detection/wifi/wifi_android.c @@ -0,0 +1,69 @@ +#include "wifi.h" + +#include "common/processing.h" +#include "common/properties.h" + +#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" +#define FF_TERMUX_API_PARAM "WifiConnectionInfo" + +static inline void wrapYyjsonFree(yyjson_doc** doc) { + assert(doc); + if (*doc) { + yyjson_doc_free(*doc); + } +} + +const char* ffDetectWifi(FFlist* result) { + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + if (ffProcessAppendStdOut(&buffer, (char* const[]) { FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) { + return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; + } + + yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); + if (!doc) { + return "Failed to parse wifi connection info"; + } + + yyjson_val* root = yyjson_doc_get_root(doc); + if (!yyjson_is_obj(root)) { + return "Wifi info result is not a JSON object"; + } + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInit(&item->inf.description); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + ffStrbufAppendJsonVal(&item->inf.status, yyjson_obj_get(root, "supplicant_state")); + if (!item->inf.status.length) { + ffStrbufAppendS(&item->inf.status, "Unknown"); + return NULL; + } + + if (!ffStrbufEqualS(&item->inf.status, "COMPLETED")) { + return NULL; + } + + double rssi = yyjson_get_num(yyjson_obj_get(root, "rssi")); + item->conn.signalQuality = rssi >= -50 ? 100 : rssi <= -100 ? 0 + : (rssi + 100) * 2; + + ffStrbufAppendJsonVal(&item->inf.description, yyjson_obj_get(root, "ip")); + ffStrbufAppendJsonVal(&item->conn.bssid, yyjson_obj_get(root, "bssid")); + ffStrbufAppendJsonVal(&item->conn.ssid, yyjson_obj_get(root, "ssid")); + item->conn.frequency = (uint16_t) yyjson_get_int(yyjson_obj_get(root, "frequency_mhz")); + item->conn.txRate = yyjson_get_num(yyjson_obj_get(root, "link_speed_mbps")); + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + + return NULL; +} diff --git a/src/detection/wifi/wifi_apple.m b/src/detection/wifi/wifi_apple.m new file mode 100644 index 0000000..21808e0 --- /dev/null +++ b/src/detection/wifi/wifi_apple.m @@ -0,0 +1,165 @@ +#include "wifi.h" +#include "common/processing.h" +#include "common/strutil.h" + +#import + +static inline double rssiToSignalQuality(int rssi) +{ + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); +} + +@interface CWNetworkProfile() +@property(readonly, retain, nullable) NSArray *bssidList; +@end + +const char* ffDetectWifi(FFlist* result) +{ + NSArray* interfaces = CWWiFiClient.sharedWiFiClient.interfaces; + if (!interfaces) + return "CWWiFiClient.sharedWiFiClient.interfaces is nil"; + + for (CWInterface* inf in interfaces) + { + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInit(&item->inf.description); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + ffStrbufAppendS(&item->inf.description, inf.interfaceName.UTF8String); + ffStrbufSetStatic(&item->inf.status, inf.powerOn ? "Power On" : "Power Off"); + if(!inf.powerOn) + continue; + + ffStrbufSetStatic(&item->conn.status, inf.interfaceMode != kCWInterfaceModeNone ? "Active" : "Inactive"); + if(inf.interfaceMode == kCWInterfaceModeNone) + continue; + + FF_STRBUF_AUTO_DESTROY ipconfig = ffStrbufCreate(); + + CWNetworkProfile* networkProfile = inf.configuration.networkProfiles.firstObject; + + if (inf.ssid) // https://developer.apple.com/forums/thread/732431 + ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String); + else if (networkProfile.ssid) + ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String); + else + ffStrbufSetStatic(&item->conn.ssid, ""); // https://developer.apple.com/forums/thread/732431 + + if (inf.bssid) + ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String); + else if (networkProfile.bssidList) + ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]); + else + ffStrbufSetStatic(&item->conn.bssid, ""); + + switch(inf.activePHYMode) + { + case kCWPHYModeNone: + ffStrbufSetStatic(&item->conn.protocol, "none"); + break; + case kCWPHYMode11a: + ffStrbufSetStatic(&item->conn.protocol, "802.11a"); + break; + case kCWPHYMode11b: + ffStrbufSetStatic(&item->conn.protocol, "802.11b"); + break; + case kCWPHYMode11g: + ffStrbufSetStatic(&item->conn.protocol, "802.11g"); + break; + case kCWPHYMode11n: + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + break; + case kCWPHYMode11ac: + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + break; + case 6 /*kCWPHYMode11ax*/: + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + break; + case 7 /*kCWPHYMode11be?*/: + ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); + break; + default: + if (inf.activePHYMode < 8) + ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode); + break; + } + item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue); + item->conn.txRate = inf.transmitRate; + + switch(inf.security) + { + case kCWSecurityNone: + ffStrbufSetStatic(&item->conn.security, "Insecure"); + break; + case kCWSecurityWEP: + ffStrbufSetStatic(&item->conn.security, "WEP"); + break; + case kCWSecurityWPAPersonal: + ffStrbufSetStatic(&item->conn.security, "WPA Personal"); + break; + case kCWSecurityWPAPersonalMixed: + ffStrbufSetStatic(&item->conn.security, "WPA Personal Mixed"); + break; + case kCWSecurityWPA2Personal: + ffStrbufSetStatic(&item->conn.security, "WPA2 Personal"); + break; + case kCWSecurityPersonal: + ffStrbufSetStatic(&item->conn.security, "Personal"); + break; + case kCWSecurityDynamicWEP: + ffStrbufSetStatic(&item->conn.security, "Dynamic WEP"); + break; + case kCWSecurityWPAEnterprise: + ffStrbufSetStatic(&item->conn.security, "WPA Enterprise"); + break; + case kCWSecurityWPAEnterpriseMixed: + ffStrbufSetStatic(&item->conn.security, "WPA Enterprise Mixed"); + break; + case kCWSecurityWPA2Enterprise: + ffStrbufSetStatic(&item->conn.security, "WPA2 Enterprise"); + break; + case kCWSecurityEnterprise: + ffStrbufSetStatic(&item->conn.security, "Enterprise"); + break; + case 11 /*kCWSecurityWPA3Personal*/: + ffStrbufSetStatic(&item->conn.security, "WPA3 Personal"); + break; + case 12 /*kCWSecurityWPA3Enterprise*/: + ffStrbufSetStatic(&item->conn.security, "WPA3 Enterprise"); + break; + case 13 /*kCWSecurityWPA3Transition*/: + ffStrbufSetStatic(&item->conn.security, "WPA3 Transition"); + break; + case 14 /*kCWSecurityOWE*/: + ffStrbufSetStatic(&item->conn.security, "OWE"); + break; + case 15 /*kCWSecurityOWETransition*/: + ffStrbufSetStatic(&item->conn.security, "OWE Transition"); + break; + default: + ffStrbufAppendF(&item->conn.security, "Unknown (%ld)", inf.security); + break; + } + + item->conn.channel = (uint16_t) inf.wlanChannel.channelNumber; + switch (inf.wlanChannel.channelBand) + { + case kCWChannelBand2GHz: item->conn.frequency = 2400; break; + case kCWChannelBand5GHz: item->conn.frequency = 5400; break; + case 3 /*kCWChannelBand6GHz*/: item->conn.frequency = 6400; break; + default: item->conn.frequency = 0; break; + } + } + + return NULL; +} diff --git a/src/detection/wifi/wifi_bsd.c b/src/detection/wifi/wifi_bsd.c new file mode 100644 index 0000000..fab1293 --- /dev/null +++ b/src/detection/wifi/wifi_bsd.c @@ -0,0 +1,153 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/strutil.h" + +#include +#include +#include +#include +#include + +const char* ffDetectWifi(FFlist* result) { + struct if_nameindex* infs = if_nameindex(); + if (!infs) { + return "if_nameindex() failed"; + } + + FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + if_freenameindex(infs); + return "socket() failed"; + } + + for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { + if (!ffStrStartsWith(i->if_name, "wlan")) { + continue; + } + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInitS(&item->inf.description, i->if_name); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + char ssid[IEEE80211_NWID_LEN + 1] = {}; + struct ieee80211req ireq = {}; + strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name)); + ireq.i_type = IEEE80211_IOC_SSID; + ireq.i_data = ssid; + ireq.i_len = sizeof(ssid) - 1; + + if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) { + struct ifreq ifr; + strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name)); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + ffStrbufSetStatic(&item->inf.status, "Unknown"); + } else { + ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down"); + } + ffStrbufAppendS(&item->conn.status, "Not associated"); + continue; + } + + ffStrbufSetStatic(&item->inf.status, "Up"); + ffStrbufSetStatic(&item->conn.status, "Associated"); + ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid); + + uint8_t bssid[IEEE80211_ADDR_LEN] = {}; + ireq.i_type = IEEE80211_IOC_BSSID; + ireq.i_data = bssid; + ireq.i_len = sizeof(bssid); + + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); + } + + struct ieee80211_channel curchan = {}; + ireq.i_type = IEEE80211_IOC_CURCHAN; + ireq.i_data = &curchan; + ireq.i_len = sizeof(curchan); + + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + item->conn.channel = curchan.ic_ieee; + item->conn.frequency = curchan.ic_freq; + +#ifdef IEEE80211_IS_CHAN_HE // for future use + if (IEEE80211_IS_CHAN_HE(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + } else +#endif + if (IEEE80211_IS_CHAN_VHT(&curchan)) + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + else if (IEEE80211_IS_CHAN_HT(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + } else if (IEEE80211_IS_CHAN_ANYG(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11g"); + } else if (IEEE80211_IS_CHAN_B(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11b"); + } else if (IEEE80211_IS_CHAN_A(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11a"); + } else if (IEEE80211_IS_CHAN_FHSS(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11 (FHSS)"); + } + } + + union { + struct ieee80211req_sta_req req; + uint8_t buf[1024]; + } stareq = {}; + memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid)); + ireq.i_type = IEEE80211_IOC_STA_INFO; + ireq.i_data = &stareq; + ireq.i_len = sizeof(stareq); + + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + struct ieee80211req_sta_info* sta = stareq.req.info; + if (sta->isi_len != 0) { + item->conn.signalQuality = (sta->isi_rssi >= -50 ? 100 : sta->isi_rssi <= -100 ? 0 + : (sta->isi_rssi + 100) * 2); + item->conn.rxRate = sta->isi_txmbps * 0.5; + } + } + + ireq.i_type = IEEE80211_IOC_AUTHMODE; + ireq.i_data = NULL; + ireq.i_len = 0; + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + switch (ireq.i_val) { + case IEEE80211_AUTH_NONE: + ffStrbufSetStatic(&item->conn.security, "Insecure"); + break; + case IEEE80211_AUTH_OPEN: + ffStrbufSetStatic(&item->conn.security, "Open"); + break; + case IEEE80211_AUTH_SHARED: + ffStrbufSetStatic(&item->conn.security, "Shared"); + break; + case IEEE80211_AUTH_8021X: + ffStrbufSetStatic(&item->conn.security, "8021X"); + break; + case IEEE80211_AUTH_AUTO: + ffStrbufSetStatic(&item->conn.security, "Auto"); + break; + case IEEE80211_AUTH_WPA: + ffStrbufSetStatic(&item->conn.security, "WPA"); + break; + default: + ffStrbufSetF(&item->conn.security, "Unknown (%d)", ireq.i_val); + break; + } + } + } + + if_freenameindex(infs); + return NULL; +} diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c new file mode 100644 index 0000000..6f76159 --- /dev/null +++ b/src/detection/wifi/wifi_linux.c @@ -0,0 +1,976 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/debug.h" +#include "common/strutil.h" + +#include +#include +#include +#include +#include +#include +#include + +#if !__BIG_ENDIAN__ + #include + #include + + // Silence warning of `NLA_HDRLEN` and `NLA_ALIGN` + #pragma GCC diagnostic ignored "-Wsign-conversion" + +typedef struct FFWifiNlContext { + int sockFd; + uint16_t nl80211FamilyId; + uint32_t portId; + uint32_t seq; +} FFWifiNlContext; + +typedef struct FFWifiSecurityFlags { + bool privacy : 1; + bool wep : 1; + bool wpa : 1; + bool wpa2 : 1; + bool wpa3 : 1; + bool owe : 1; + bool eap : 1; +} FFWifiSecurityFlags; + +static inline double rssiToSignalQuality(int rssi) { + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 + : (rssi + 100) * 2); +} + +static inline uint32_t ffWifiGetNetlinkPortId(int sockFd) { + struct sockaddr_nl addr = {}; + socklen_t addrLen = sizeof(addr); + if (getsockname(sockFd, (struct sockaddr*) &addr, &addrLen) < 0) { + FF_DEBUG("Failed to query netlink socket address (use PID instead): %s", strerror(errno)); + return instance.state.platform.pid; + } + + return addr.nl_pid; +} + +static inline bool ffWifiNlAttrOk(const struct nlattr* attr, size_t remaining) { + return remaining >= sizeof(*attr) && + attr->nla_len >= sizeof(*attr) && + attr->nla_len <= remaining; +} + +static const struct nlattr* ffWifiNlAttrNext(const struct nlattr* attr, size_t* remaining) { + size_t alignedLen = NLA_ALIGN(attr->nla_len); + if (alignedLen > *remaining) { + *remaining = 0; + return NULL; + } + + *remaining -= alignedLen; + return (const struct nlattr*) ((const char*) attr + alignedLen); +} + +static inline size_t ffWifiNlAttrPayload(const struct nlattr* attr) { + return attr->nla_len > NLA_HDRLEN ? attr->nla_len - NLA_HDRLEN : 0; +} + +static inline const void* ffWifiNlAttrData(const struct nlattr* attr) { + // Big endian? + return (const uint8_t*) attr + NLA_HDRLEN; +} + +static bool ffWifiNlAppendAttr(struct nlmsghdr* nlh, size_t maxLen, uint16_t type, const void* data, uint16_t dataLen) { + size_t offset = NLMSG_ALIGN(nlh->nlmsg_len); + size_t attrLen = NLA_HDRLEN + dataLen; + size_t alignedLen = NLA_ALIGN(attrLen); + size_t newLen = offset + alignedLen; + if (newLen > maxLen || attrLen > UINT16_MAX || newLen > UINT32_MAX) { + return false; + } + + struct nlattr* attr = (struct nlattr*) ((char*) nlh + offset); + attr->nla_type = type; + attr->nla_len = (uint16_t) attrLen; + memcpy((char*) attr + NLA_HDRLEN, data, dataLen); + memset((char*) attr + attrLen, 0, alignedLen - attrLen); + nlh->nlmsg_len = (uint32_t) newLen; + return true; +} + +static bool ffWifiNlGetFamilyId(FFWifiNlContext* ctx) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[64]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = GENL_ID_CTRL, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = CTRL_CMD_GETFAMILY, + .version = 1, // generic netlink control protocol version + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), CTRL_ATTR_FAMILY_NAME, "nl80211", sizeof("nl80211"))) { + FF_DEBUG("Failed to append CTRL_ATTR_FAMILY_NAME attribute"); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 family request: sent=%zd expected=%u", sent, req.nlh.nlmsg_len); + return false; + } + + uint8_t buffer[8192]; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 family reply: %s", strerror(errno)); + return false; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error != 0) { + FF_DEBUG("nl80211 family query failed: %s", strerror(-err->error)); + return false; + } + continue; + } + + if (nlh->nlmsg_type != GENL_ID_CTRL) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + if (genl->cmd != CTRL_CMD_NEWFAMILY) { + continue; + } + + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != CTRL_ATTR_FAMILY_ID || ffWifiNlAttrPayload(attr) < sizeof(uint16_t)) { + continue; + } + + ctx->nl80211FamilyId = *(const uint16_t*) ffWifiNlAttrData(attr); + return true; + } + } + } +} + +static bool ffWifiNlInit(FFWifiNlContext* ctx) { + FF_AUTO_CLOSE_FD int _ = ctx->sockFd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_GENERIC); + if (ctx->sockFd < 0) { + FF_DEBUG("Failed to create generic netlink socket: %s", strerror(errno)); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + if (bind(ctx->sockFd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { + FF_DEBUG("Failed to bind generic netlink socket: %s", strerror(errno)); + return false; + } + + if (setsockopt( + ctx->sockFd, + SOL_SOCKET, + SO_RCVTIMEO, + &(struct timeval){ .tv_sec = 0, .tv_usec = 250000 }, // 250 ms recv timeout + sizeof(struct timeval)) < 0) { + FF_DEBUG("Failed to set netlink receive timeout: %s", strerror(errno)); + return false; + } + + ctx->portId = ffWifiGetNetlinkPortId(ctx->sockFd); + if (!ffWifiNlGetFamilyId(ctx)) { + return false; + } + + _ = -1; // We are ok now + return true; +} + +static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol) { + double rate = -DBL_MAX; + size_t remaining = ffWifiNlAttrPayload(rateAttr); + + for (const struct nlattr* info = (const struct nlattr*) ffWifiNlAttrData(rateAttr); + ffWifiNlAttrOk(info, remaining); + info = ffWifiNlAttrNext(info, &remaining)) { + uint16_t type = (uint16_t) (info->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(info); + + switch (type) { + case 30 /* NL80211_RATE_INFO_UHR_MCS */: + ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)"); + break; + case 23 /* NL80211_RATE_INFO_S1G_MCS */: + ffStrbufSetStatic(protocol, "802.11ah (Wi-Fi HaLow)"); + break; + case 19 /* NL80211_RATE_INFO_EHT_MCS */: + ffStrbufSetStatic(protocol, "802.11be (Wi-Fi 7)"); + break; + case 13 /* NL80211_RATE_INFO_HE_MCS */: + ffStrbufSetStatic(protocol, "802.11ax (Wi-Fi 6)"); + break; + case NL80211_RATE_INFO_VHT_MCS: + ffStrbufSetStatic(protocol, "802.11ac (Wi-Fi 5)"); + break; + case NL80211_RATE_INFO_MCS: + ffStrbufSetStatic(protocol, "802.11n (Wi-Fi 4)"); + break; + case NL80211_RATE_INFO_BITRATE32: + if (payload >= sizeof(uint32_t)) { + rate = *(uint32_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps + } + break; + case NL80211_RATE_INFO_BITRATE: + if (payload >= sizeof(uint16_t) && rate == -DBL_MAX) { + rate = *(uint16_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps + } + break; + } + } + + return rate; +} + +static void ffWifiApplySecurityFlags(FFWifiResult* item, const FFWifiSecurityFlags* sec) { + ffStrbufClear(&item->conn.security); + + if (sec->wep) { + ffStrbufAppendS(&item->conn.security, "WEP/"); + } + if (sec->wpa) { + ffStrbufAppendS(&item->conn.security, "WPA/"); + } + if (sec->wpa2) { + ffStrbufAppendS(&item->conn.security, "WPA2/"); + } + if (sec->wpa3) { + ffStrbufAppendS(&item->conn.security, "WPA3/"); + } + if (sec->owe) { + ffStrbufAppendS(&item->conn.security, "OWE/"); + } + if (sec->eap) { + ffStrbufAppendS(&item->conn.security, "802.1X/"); + } + + if (!item->conn.security.length) { + if (sec->privacy) { + ffStrbufSetStatic(&item->conn.security, "WEP"); + } else { + ffStrbufSetStatic(&item->conn.security, "Insecure"); + } + } else { + ffStrbufTrimRight(&item->conn.security, '/'); + } +} + +static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) { + if (len < 8) { // version(2) + group cipher suite(4) + pairwise count(2) + return; + } + + sec->wpa2 = true; + size_t pos = 0; + + pos += 2; // RSN version field length + if (pos + 4 > len) { // group cipher suite selector length + return; + } + pos += 4; // skip group cipher suite selector + + if (pos + 2 > len) { // pairwise cipher suite count field length + return; + } + uint16_t pairwiseCount = *(uint16_t*) (ie + pos); + pos += 2; // skip pairwise cipher suite count field + + size_t pairwiseLen = (size_t) pairwiseCount * 4; // each suite selector is 4 bytes + if (pos + pairwiseLen > len) { + return; + } + pos += pairwiseLen; + + if (pos + 2 > len) { // AKM suite count field length + return; + } + uint16_t akmCount = *(uint16_t*) (ie + pos); + pos += 2; // skip AKM suite count field + + for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes + const uint8_t* akm = ie + pos; + if (akm[0] != 0x00 || akm[1] != 0x0f || akm[2] != 0xac) { // RSN OUI 00:0f:ac + continue; + } + + switch (akm[3]) { + case 1: // 802.1X + case 5: // FT/802.1X + case 11: // 802.1X-SHA256 + case 12: // FT/802.1X-SHA384 (suite selector value) + sec->eap = true; + break; + case 8: // SAE (WPA3-Personal) + sec->wpa3 = true; + break; + case 18: // OWE + sec->owe = true; + break; + default: + break; + } + } + + if (sec->owe) { + sec->wpa2 = false; + } +} + +static void ffWifiParseWpaVendorIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) { + if (len < 8) { // OUI+type(4) + version(2) + multicast cipher suite(4) starts here + return; + } + + if (!(ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2 && ie[3] == 0x01)) { // Microsoft WPA OUI/type + return; + } + + sec->wpa = true; + + size_t pos = 4; // WPA vendor OUI/type selector length + if (pos + 2 > len) { // WPA version field length + return; + } + pos += 2; // skip WPA version + + if (pos + 4 > len) { // multicast cipher suite selector length + return; + } + pos += 4; // skip multicast cipher suite selector + + if (pos + 2 > len) { // unicast cipher suite count field length + return; + } + uint16_t pairwiseCount = *(uint16_t*) (ie + pos); + pos += 2 + (size_t) pairwiseCount * 4; // count field(2) + N unicast suite selectors(4 each) + + if (pos + 2 > len) { // AKM suite count field length + return; + } + uint16_t akmCount = *(uint16_t*) (ie + pos); + pos += 2; // skip AKM suite count field + + for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes + const uint8_t* akm = ie + pos; + if (!(akm[0] == 0x00 && akm[1] == 0x50 && akm[2] == 0xf2)) { // WPA vendor OUI 00:50:f2 + continue; + } + if (akm[3] == 1) { // WPA Enterprise (802.1X) + sec->eap = true; + } + } +} + +static void ffWifiParseInformationElements(const uint8_t* ies, size_t length, FFWifiResult* item, FFWifiSecurityFlags* sec) { + size_t pos = 0; + while (pos + 2 <= length) { + uint8_t id = ies[pos]; + uint8_t len = ies[pos + 1]; + pos += 2; + if (pos + len > length) { + break; + } + + const uint8_t* ie = ies + pos; + if (id == 0) { // SSID element ID + ffStrbufSetNS(&item->conn.ssid, len, (const char*) ie); + } else if (id == 48) { // RSN element ID + ffWifiParseRsnIe(ie, len, sec); + } else if (id == 221) { // vendor-specific element ID (WPA IE lives here) + ffWifiParseWpaVendorIe(ie, len, sec); + } + + pos += len; + } +} + +static bool ffWifiIsBssAssociated(const struct nlattr* bssAttr) { + size_t remaining = ffWifiNlAttrPayload(bssAttr); + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_BSS_STATUS && payload >= sizeof(uint32_t)) { + return *(uint32_t*) ffWifiNlAttrData(attr) == NL80211_BSS_STATUS_ASSOCIATED; + } + } + + return false; +} + +static void ffWifiParseBssAttr(const struct nlattr* bssAttr, FFWifiResult* item) { + FFWifiSecurityFlags sec = {}; + size_t remaining = ffWifiNlAttrPayload(bssAttr); + + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(bssAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_BSS_BSSID && payload >= 6) { + const uint8_t* mac = (const uint8_t*) ffWifiNlAttrData(attr); + ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else if (type == NL80211_BSS_FREQUENCY && payload >= sizeof(uint32_t)) { + item->conn.frequency = (uint16_t) *(uint32_t*) ffWifiNlAttrData(attr); + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + } else if (type == NL80211_BSS_SIGNAL_MBM && payload >= sizeof(int32_t)) { + int rssi = *(int32_t*) ffWifiNlAttrData(attr) / 100; // mBm (100 * dBm) => dBm + item->conn.signalQuality = rssiToSignalQuality(rssi); + } else if (type == NL80211_BSS_CAPABILITY && payload >= sizeof(uint16_t)) { + uint16_t capability = *(uint16_t*) ffWifiNlAttrData(attr); + sec.privacy = (capability & (1u << 4u)) != 0; // IEEE 802.11 capability bit 4: privacy + } else if (type == NL80211_BSS_INFORMATION_ELEMENTS || type == NL80211_BSS_BEACON_IES) { + ffWifiParseInformationElements((const uint8_t*) ffWifiNlAttrData(attr), payload, item, &sec); + } + } + + ffWifiApplySecurityFlags(item, &sec); + return; +} + +static bool ffWifiFetchScanInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[32]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = ctx->nl80211FamilyId, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = NL80211_CMD_GET_SCAN, + .version = 0, // nl80211 command version + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) { + FF_DEBUG("Failed to build nl80211 scan request"); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 scan request"); + return false; + } + + uint8_t buffer[1024 * 16]; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 scan reply: %s", strerror(errno)); + return false; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_DONE) { + return false; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error == 0) { + continue; + } + FF_DEBUG("nl80211 scan request failed: %s", strerror(-err->error)); + return false; + } + + if (nlh->nlmsg_type != ctx->nl80211FamilyId) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_BSS) { + continue; + } + + if (!ffWifiIsBssAssociated(attr)) { + continue; + } + + ffWifiParseBssAttr(attr, item); + ffStrbufSetStatic(&item->conn.status, "connected"); + return true; + } + } + } + + return false; +} + +static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResult* item) { + size_t remaining = ffWifiNlAttrPayload(staInfoAttr); + for (const struct nlattr* attr = (const struct nlattr*) ffWifiNlAttrData(staInfoAttr); + ffWifiNlAttrOk(attr, remaining); + attr = ffWifiNlAttrNext(attr, &remaining)) { + uint16_t type = (uint16_t) (attr->nla_type & NLA_TYPE_MASK); + size_t payload = ffWifiNlAttrPayload(attr); + + if (type == NL80211_STA_INFO_SIGNAL && payload >= sizeof(uint8_t) && item->conn.signalQuality == -DBL_MAX) { + int rssi = (int8_t) *(const uint8_t*) ffWifiNlAttrData(attr); + item->conn.signalQuality = rssiToSignalQuality(rssi); + } else if (type == NL80211_STA_INFO_TX_BITRATE && item->conn.txRate == -DBL_MAX) { + double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); + if (tx != -DBL_MAX) { + item->conn.txRate = tx; + } + } else if (type == NL80211_STA_INFO_RX_BITRATE && item->conn.rxRate == -DBL_MAX) { + double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol); + if (rx != -DBL_MAX) { + item->conn.rxRate = rx; + } + } + } +} + +static bool ffWifiFetchStationInfo(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + struct { + struct nlmsghdr nlh; + struct genlmsghdr genl; + char attrs[32]; + } req = { + .nlh = { + .nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)), + .nlmsg_type = ctx->nl80211FamilyId, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ACK, + .nlmsg_seq = ++ctx->seq, + .nlmsg_pid = ctx->portId, + }, + .genl = { + .cmd = NL80211_CMD_GET_STATION, + .version = 0, // nl80211 command version + }, + }; + + if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) { + FF_DEBUG("Failed to build nl80211 station request"); + return false; + } + + struct sockaddr_nl addr = { + .nl_family = AF_NETLINK, + }; + + ssize_t sent = sendto(ctx->sockFd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*) &addr, sizeof(addr)); + if (sent != (ssize_t) req.nlh.nlmsg_len) { + FF_DEBUG("Failed to send nl80211 station request"); + return false; + } + + uint8_t buffer[8192]; + bool gotStation = false; + while (true) { + ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, NULL, NULL); + if (received < 0) { + FF_DEBUG("Failed to receive nl80211 station reply: %s", strerror(errno)); + return gotStation; + } + + for (const struct nlmsghdr* nlh = (const struct nlmsghdr*) buffer; + NLMSG_OK(nlh, received); + nlh = NLMSG_NEXT(nlh, received)) { + if (nlh->nlmsg_seq != req.nlh.nlmsg_seq) { + continue; + } + + if (nlh->nlmsg_type == NLMSG_DONE) { + return gotStation; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + const struct nlmsgerr* err = (const struct nlmsgerr*) NLMSG_DATA(nlh); + if (err->error != 0) { + FF_DEBUG("nl80211 station request failed: %s", strerror(-err->error)); + } + return gotStation; + } + + if (nlh->nlmsg_type != ctx->nl80211FamilyId) { + continue; + } + + const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh); + size_t attrRemaining = nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + for (const struct nlattr* attr = (const struct nlattr*) ((const char*) genl + GENL_HDRLEN); + ffWifiNlAttrOk(attr, attrRemaining); + attr = ffWifiNlAttrNext(attr, &attrRemaining)) { + if ((attr->nla_type & NLA_TYPE_MASK) != NL80211_ATTR_STA_INFO) { + continue; + } + + ffWifiParseStationInfo(attr, item); + gotStation = true; + } + } + } +} + +static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, uint32_t ifIndex) { + if (ctx->sockFd < 0) { + if (ctx->sockFd == -1) { + if (!ffWifiNlInit(ctx)) { + FF_DEBUG("Failed to initialize netlink context, skipping"); + ctx->sockFd = -2; // sentinel: permanent netlink failure, don't retry + return "Netlink initialization failed"; + } + } else { + FF_DEBUG("Netlink socket is not available, skipping"); + return "Netlink socket unavailable"; + } + } + + FF_DEBUG("Starting netlink wifi detection for interface %s", item->inf.description.chars); + if (ffWifiFetchScanInfo(ctx, item, ifIndex)) { + FF_DEBUG("found associated BSS: %s", item->conn.ssid.chars); + ffStrbufSetStatic(&item->conn.status, "connected"); + ffWifiFetchStationInfo(ctx, item, ifIndex); + if (!item->conn.protocol.length && item->conn.txRate != -DBL_MAX) { + FF_DEBUG("nl80211 station info did not include MCS family fields"); + } + } else { + FF_DEBUG("No associated BSS found"); + ffStrbufSetStatic(&item->conn.status, "disconnected"); + } + + FF_DEBUG("Netlink wifi detection completed"); + return NULL; +} +#endif + +typedef struct FFWifiIcContext { + int sockFd; +} FFWifiIcContext; + +static const char* detectWithIoctl(FFWifiIcContext* ctx, FFWifiResult* item, char ifName[static IFNAMSIZ]) { + int sock = -1; + if (ctx->sockFd < 0) { + if (ctx->sockFd == -1) { + sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sock < 0) { + FF_DEBUG("Failed to initialize ioctl context, skipping: %s", strerror(errno)); + ctx->sockFd = -2; // sentinel: permanent ioctl failure, don't retry + return "socket() failed"; + } + ctx->sockFd = sock; + } else { + FF_DEBUG("Ioctl socket is not available, skipping"); + return "ioctl socket unavailable"; + } + } else { + sock = ctx->sockFd; + } + + FF_DEBUG("Starting ioctl wifi detection for interface %s", ifName); + struct iwreq iwr = {}; + strcpy(iwr.ifr_name, ifName); + + if (!item->conn.ssid.length) { + FF_DEBUG("Getting SSID via ioctl"); + ffStrbufEnsureFree(&item->conn.ssid, IW_ESSID_MAX_SIZE); + iwr.u.essid.pointer = (caddr_t) item->conn.ssid.chars; + iwr.u.essid.length = IW_ESSID_MAX_SIZE + 1; + iwr.u.essid.flags = 0; + if (ioctl(sock, SIOCGIWESSID, &iwr) >= 0) { + ffStrbufSetStatic(&item->conn.status, "connected"); + ffStrbufRecalculateLength(&item->conn.ssid); + FF_DEBUG("SSID: %s", item->conn.ssid.chars); + } else { + FF_DEBUG("Failed to get SSID via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.protocol.length) { + FF_DEBUG("Getting protocol name via ioctl"); + if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0) { + char* token = iwr.u.name; + if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) { + token += strlen("IEEE "); + } + if (ffStrStartsWith(token, "802.11")) { + token += strlen("802.11"); + if (*token) { + if (*token == ' ') { + token++; + } + for (char* c = token; *c; ++c) { + if (*c >= 'A' && *c <= 'Z') { + *c += 'a' - 'A'; + } + } + if (ffStrEquals(token, "n")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + } else if (ffStrEquals(token, "ac")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + } else if (ffStrEquals(token, "ax")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + } else if (ffStrEquals(token, "be")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); + } else if (ffStrEquals(token, "bn")) { + ffStrbufSetStatic(&item->conn.protocol, "802.11bn (Wi-Fi 8)"); + } else { + ffStrbufSetStatic(&item->conn.protocol, "802.11"); + ffStrbufAppendS(&item->conn.protocol, token); + } + } + } + FF_DEBUG("Protocol: %s", item->conn.protocol.length ? item->conn.protocol.chars : "(unknown)"); + } else { + FF_DEBUG("Failed to get protocol name via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.bssid.length) { + FF_DEBUG("Getting BSSID via ioctl"); + if (ioctl(sock, SIOCGIWAP, &iwr) >= 0) { + for (int i = 0; i < 6; ++i) { + ffStrbufAppendF(&item->conn.bssid, "%.2X:", (uint8_t) iwr.u.ap_addr.sa_data[i]); + } + ffStrbufTrimRight(&item->conn.bssid, ':'); + FF_DEBUG("BSSID: %s", item->conn.bssid.chars); + } else { + FF_DEBUG("Failed to get BSSID via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.txRate == -DBL_MAX) { + FF_DEBUG("Getting bitrate via ioctl"); + if (ioctl(sock, SIOCGIWRATE, &iwr) >= 0) { + if (iwr.u.bitrate.value > 0) { + item->conn.txRate = iwr.u.bitrate.value / 1000000.; // bps => Mbps + FF_DEBUG("TX bitrate: %.2f Mbps", item->conn.txRate); + } else { + FF_DEBUG("Bitrate value is zero or negative, ignoring"); + } + } else { + FF_DEBUG("Failed to get bitrate via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.frequency == 0 && item->conn.channel == 0) { + FF_DEBUG("Getting frequency via ioctl"); + if (ioctl(sock, SIOCGIWFREQ, &iwr) >= 0) { + if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000) { // kernel may return direct channel number + item->conn.channel = (uint16_t) iwr.u.freq.m; + FF_DEBUG("Direct channel value: %u", item->conn.channel); + } else { + // convert it to MHz + while (iwr.u.freq.e < 6) { // normalize exponent to 10^6 (MHz) + iwr.u.freq.m /= 10; + iwr.u.freq.e++; + } + while (iwr.u.freq.e > 6) { // normalize exponent to 10^6 (MHz) + iwr.u.freq.m *= 10; + iwr.u.freq.e--; + } + item->conn.frequency = (uint16_t) iwr.u.freq.m; + item->conn.channel = ffWifiFreqToChannel(item->conn.frequency); + FF_DEBUG("Frequency: %u MHz, Channel: %u", item->conn.frequency, item->conn.channel); + } + } else { + FF_DEBUG("Failed to get frequency via ioctl: %s", strerror(errno)); + } + } + + if (item->conn.signalQuality == -DBL_MAX) { + FF_DEBUG("Getting signal stats via ioctl"); + struct iw_statistics stats; + iwr.u.data.pointer = &stats; + iwr.u.data.length = sizeof(stats); + iwr.u.data.flags = 0; + + if (ioctl(sock, SIOCGIWSTATS, &iwr) >= 0) { + int8_t level = (int8_t) stats.qual.level; + item->conn.signalQuality = level >= -50 ? 100 : level <= -100 ? 0 + : (level + 100) * 2; + FF_DEBUG("Signal level: %d dBm, quality: %.0f%%", level, item->conn.signalQuality); + } else { + FF_DEBUG("Failed to get signal stats via ioctl: %s", strerror(errno)); + } + } + + if (!item->conn.security.length) { + FF_DEBUG("Getting security info via ioctl"); + struct iw_encode_ext iwe; + iwr.u.data.pointer = &iwe; + iwr.u.data.length = sizeof(iwe); + iwr.u.data.flags = 0; + if (ioctl(sock, SIOCGIWENCODEEXT, &iwr) >= 0) { + switch (iwe.alg) { + case IW_ENCODE_ALG_WEP: + ffStrbufAppendS(&item->conn.security, "WEP"); + FF_DEBUG("Security: WEP"); + break; + case IW_ENCODE_ALG_TKIP: + ffStrbufAppendS(&item->conn.security, "TKIP"); + FF_DEBUG("Security: TKIP"); + break; + case IW_ENCODE_ALG_CCMP: + ffStrbufAppendS(&item->conn.security, "CCMP"); + FF_DEBUG("Security: CCMP"); + break; + case IW_ENCODE_ALG_PMK: + ffStrbufAppendS(&item->conn.security, "PMK"); + FF_DEBUG("Security: PMK"); + break; + case IW_ENCODE_ALG_AES_CMAC: + ffStrbufAppendS(&item->conn.security, "CMAC"); + FF_DEBUG("Security: CMAC"); + break; + default: + ffStrbufAppendF(&item->conn.security, "Unknown (%d)", (int) iwe.alg); + FF_DEBUG("Security: Unknown (%d)", (int) iwe.alg); + break; + } + } else { + FF_DEBUG("Failed to get security info via ioctl: %s", strerror(errno)); + } + } + + FF_DEBUG("Ioctl wifi detection completed"); + return NULL; +} + +const char* ffDetectWifi(FFlist* result) { + FF_DEBUG("Starting wifi detection"); + + struct if_nameindex* infs = if_nameindex(); + if (!infs) { + FF_DEBUG("if_nameindex failed: %s", strerror(errno)); + return "if_nameindex() failed"; + } + +#if !__BIG_ENDIAN__ + FFWifiNlContext nl = { .sockFd = -1 }; +#endif + FFWifiIcContext ic = { .sockFd = -1 }; + + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { + FF_DEBUG("Checking interface: %s (index: %u)", i->if_name, i->if_index); + ffStrbufSetF(&buffer, "/sys/class/net/%s/phy80211/", i->if_name); + if (!ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY)) { + FF_DEBUG("Not a wifi interface (no phy80211 directory)"); + continue; + } + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInitS(&item->inf.description, i->if_name); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + char operstate; + ffStrbufSetF(&buffer, "/sys/class/net/%s/operstate", i->if_name); + if (!ffReadFileData(buffer.chars, 1, &operstate)) { + ffStrbufSetStatic(&item->inf.status, "unknown"); + ffStrbufSetStatic(&item->conn.status, "disconnected"); + continue; + } + + if (operstate == 'u') { + ffStrbufSetStatic(&item->inf.status, "up"); + +#if !__BIG_ENDIAN__ + detectWithNetlink(&nl, item, i->if_index); +#endif + detectWithIoctl(&ic, item, i->if_name); + } else { + ffStrbufSetStatic(&item->conn.status, "disconnected"); + + ffStrbufSetF(&buffer, "/sys/class/net/%s/flags", i->if_name); + char flags[16]; + ssize_t len = ffReadFileData(buffer.chars, sizeof(flags) - 1, flags); + if (len <= 0) { + ffStrbufSetStatic(&item->inf.status, "unknown"); + continue; + } + flags[len] = '\0'; + + unsigned flagsVal = (unsigned) strtoul(flags, NULL, 16); // parse /sys flags as hexadecimal + if (flagsVal & IFF_UP) { + ffStrbufSetStatic(&item->inf.status, "up"); + } else { + ffStrbufSetStatic(&item->inf.status, "down"); + } + } + } + + if_freenameindex(infs); +#if !__BIG_ENDIAN__ + if (nl.sockFd >= 0) { + close(nl.sockFd); + } +#endif + if (ic.sockFd >= 0) { + close(ic.sockFd); + } + + FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length); + return NULL; +} diff --git a/src/detection/wifi/wifi_nbsd.c b/src/detection/wifi/wifi_nbsd.c new file mode 100644 index 0000000..52897c4 --- /dev/null +++ b/src/detection/wifi/wifi_nbsd.c @@ -0,0 +1,168 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/strutil.h" + +#define COMPAT_FREEBSD_NET80211 1 +#include +#include +#include +#include +#include +#include + +// ieee80211 header of NetBSD is full of mess. Add compatibility macros from FreeBSD +#undef IEEE80211_IS_CHAN_ANYG +#define IEEE80211_IS_CHAN_ANYG(x) (IEEE80211_IS_CHAN_PUREG(x) || IEEE80211_IS_CHAN_G(x)) +#undef IEEE80211_IS_CHAN_HT +#define IEEE80211_IS_CHAN_HT(x) (((x)->ic_flags & IEEE80211_CHAN_HT) != 0) +#undef IEEE80211_IS_CHAN_VHT +#define IEEE80211_IS_CHAN_VHT(x) (((x)->ic_flags & IEEE80211_CHAN_VHT) != 0) + +const char* ffDetectWifi(FFlist* result) { + struct if_nameindex* infs = if_nameindex(); + if (!infs) { + return "if_nameindex() failed"; + } + + FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + if_freenameindex(infs); + return "socket() failed"; + } + + for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { + if (!ffStrStartsWith(i->if_name, "iwm")) { + continue; + } + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInitS(&item->inf.description, i->if_name); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + char ssid[IEEE80211_NWID_LEN + 1] = {}; + struct ieee80211req ireq = {}; + strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name)); + ireq.i_type = IEEE80211_IOC_SSID; + ireq.i_data = ssid; + ireq.i_len = sizeof(ssid) - 1; + + if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) { + struct ifreq ifr; + strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name)); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + ffStrbufSetStatic(&item->inf.status, "Unknown"); + } else { + ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down"); + } + ffStrbufAppendS(&item->conn.status, "Not associated"); + continue; + } + + ffStrbufSetStatic(&item->inf.status, "Up"); + ffStrbufSetStatic(&item->conn.status, "Associated"); + ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid); + + uint8_t bssid[IEEE80211_ADDR_LEN] = {}; + ireq.i_type = IEEE80211_IOC_BSSID; + ireq.i_data = bssid; + ireq.i_len = sizeof(bssid); + + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); + } + + struct ieee80211_channel curchan = {}; + ireq.i_type = IEEE80211_IOC_CHANNEL; + ireq.i_data = &curchan; + ireq.i_len = sizeof(curchan); + + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + // item->conn.channel = curchan.ic_ieee; // No ic_ieee in NetBSD + item->conn.channel = ffWifiFreqToChannel(curchan.ic_freq); + item->conn.frequency = curchan.ic_freq; + +#ifdef IEEE80211_IS_CHAN_HE // for future use + if (IEEE80211_IS_CHAN_HE(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + } else +#endif + if (IEEE80211_IS_CHAN_VHT(&curchan)) + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + else if (IEEE80211_IS_CHAN_HT(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + } else if (IEEE80211_IS_CHAN_ANYG(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11g"); + } else if (IEEE80211_IS_CHAN_B(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11b"); + } else if (IEEE80211_IS_CHAN_A(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11a"); + } else if (IEEE80211_IS_CHAN_FHSS(&curchan)) { + ffStrbufSetStatic(&item->conn.protocol, "802.11 (FHSS)"); + } + } + + union { + struct ieee80211req_sta_req req; + uint8_t buf[1024]; + } stareq = {}; + memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid)); + ireq.i_type = IEEE80211_IOC_STA_INFO; + ireq.i_data = &stareq; + ireq.i_len = sizeof(stareq); + + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + struct ieee80211req_sta_info* sta = stareq.req.info; + if (sta->isi_len != 0) { + int8_t rssi = (int8_t) sta->isi_rssi; // Strangely, `sta->isi_rssi` is unsigned + item->conn.signalQuality = (rssi >= -50 ? 100 : rssi <= -100 ? 0 + : (rssi + 100) * 2); + + if (sta->isi_txrate) { + item->conn.txRate = (double) sta->isi_txrate / 2.0; + } + } + } + + ireq.i_type = IEEE80211_IOC_AUTHMODE; + ireq.i_data = NULL; + ireq.i_len = 0; + if (ioctl(sock, SIOCG80211, &ireq) >= 0) { + switch (ireq.i_val) { + case IEEE80211_AUTH_NONE: + ffStrbufSetStatic(&item->conn.security, "Insecure"); + break; + case IEEE80211_AUTH_OPEN: + ffStrbufSetStatic(&item->conn.security, "Open"); + break; + case IEEE80211_AUTH_SHARED: + ffStrbufSetStatic(&item->conn.security, "Shared"); + break; + case IEEE80211_AUTH_8021X: + ffStrbufSetStatic(&item->conn.security, "8021X"); + break; + case IEEE80211_AUTH_AUTO: + ffStrbufSetStatic(&item->conn.security, "Auto"); + break; + case IEEE80211_AUTH_WPA: + ffStrbufSetStatic(&item->conn.security, "WPA"); + break; + default: + ffStrbufSetF(&item->conn.security, "Unknown (%d)", ireq.i_val); + break; + } + } + } + + if_freenameindex(infs); + return NULL; +} diff --git a/src/detection/wifi/wifi_nosupport.c b/src/detection/wifi/wifi_nosupport.c new file mode 100644 index 0000000..bb4a806 --- /dev/null +++ b/src/detection/wifi/wifi_nosupport.c @@ -0,0 +1,5 @@ +#include "wifi.h" + +const char* ffDetectWifi(FF_A_UNUSED FFlist* result) { + return "Not support on this platform"; +} diff --git a/src/detection/wifi/wifi_obsd.c b/src/detection/wifi/wifi_obsd.c new file mode 100644 index 0000000..f686231 --- /dev/null +++ b/src/detection/wifi/wifi_obsd.c @@ -0,0 +1,109 @@ +#include "wifi.h" +#include "common/io.h" +#include "common/strutil.h" + +#include +#include +#include +#include +#include +#include + +const char* ffDetectWifi(FFlist* result) { + struct if_nameindex* infs = if_nameindex(); + if (!infs) { + return "if_nameindex() failed"; + } + + FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + return "socket() failed"; + } + + for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == NULL); ++i) { + if (!ffStrStartsWith(i->if_name, "iwm")) { + continue; + } + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInitS(&item->inf.description, i->if_name); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + struct ieee80211_nodereq nr = {}; + strlcpy(nr.nr_ifname, i->if_name, sizeof(nr.nr_ifname)); + + struct ifreq ifr = {}; + strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name)); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + ffStrbufSetStatic(&item->inf.status, "Unknown"); + } else { + ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down"); + } + + if (ioctl(sock, SIOCG80211NODE, &nr) < 0) { + ffStrbufSetStatic(&item->conn.status, "Not associated"); + continue; + } + + if (nr.nr_nwid_len > 0) { + ffStrbufSetStatic(&item->conn.status, "Associated"); + ffStrbufAppendNS(&item->conn.ssid, nr.nr_nwid_len, (char*) nr.nr_nwid); + } else { + ffStrbufSetStatic(&item->conn.status, "Not associated"); + continue; + } + + ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", nr.nr_bssid[0], nr.nr_bssid[1], nr.nr_bssid[2], nr.nr_bssid[3], nr.nr_bssid[4], nr.nr_bssid[5]); + + item->conn.channel = nr.nr_channel; + + if (nr.nr_max_rssi) { + item->conn.signalQuality = ((float) nr.nr_rssi / nr.nr_max_rssi) * 100.0; + } + + if (nr.nr_flags & IEEE80211_NODEREQ_HT) { + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + } else if (nr.nr_flags & IEEE80211_NODEREQ_VHT) { + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + } else if (nr.nr_chan_flags & IEEE80211_CHANINFO_5GHZ) { + ffStrbufSetStatic(&item->conn.protocol, "802.11a"); + } else if (nr.nr_chan_flags & IEEE80211_CHANINFO_2GHZ) { + ffStrbufSetStatic(&item->conn.protocol, "802.11g"); + } + + struct ieee80211_wpaparams wpa = {}; + strlcpy(wpa.i_name, i->if_name, sizeof(wpa.i_name)); + + if (ioctl(sock, SIOCG80211WPAPARMS, &wpa) >= 0 && wpa.i_enabled) { + if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA2) { + ffStrbufSetStatic(&item->conn.security, "WPA2"); + } else if (wpa.i_protos & IEEE80211_WPA_PROTO_WPA1) { + ffStrbufSetStatic(&item->conn.security, "WPA"); + } + } else { + struct ieee80211_nwkey nwkey = {}; + strlcpy(nwkey.i_name, i->if_name, sizeof(nwkey.i_name)); + + if (ioctl(sock, SIOCG80211NWKEY, &nwkey) >= 0) { + if (nwkey.i_wepon) { + ffStrbufSetStatic(&item->conn.security, "WEP"); + } else { + ffStrbufSetStatic(&item->conn.security, "Open"); + } + } + } + } + + if_freenameindex(infs); + return NULL; +} diff --git a/src/detection/wifi/wifi_windows.c b/src/detection/wifi/wifi_windows.c new file mode 100644 index 0000000..125c314 --- /dev/null +++ b/src/detection/wifi/wifi_windows.c @@ -0,0 +1,242 @@ +#include "wifi.h" +#include "common/library.h" +#include "common/windows/unicode.h" + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + +static void convertIfStateToString(WLAN_INTERFACE_STATE state, FFstrbuf* result) { + switch (state) { + case wlan_interface_state_not_ready: + ffStrbufAppendS(result, "Not ready"); + break; + case wlan_interface_state_connected: + ffStrbufAppendS(result, "Connected"); + break; + case wlan_interface_state_ad_hoc_network_formed: + ffStrbufAppendS(result, "Ad hoc network formed"); + break; + case wlan_interface_state_disconnecting: + ffStrbufAppendS(result, "Disconnecting"); + break; + case wlan_interface_state_disconnected: + ffStrbufAppendS(result, "Disconnected"); + break; + case wlan_interface_state_associating: + ffStrbufAppendS(result, "Associating"); + break; + case wlan_interface_state_discovering: + ffStrbufAppendS(result, "Discovering"); + break; + case wlan_interface_state_authenticating: + ffStrbufAppendS(result, "Authenticating"); + break; + default: + ffStrbufAppendS(result, "Unknown"); + break; + } +} + +const char* ffDetectWifi(FFlist* result) { + FF_LIBRARY_LOAD_MESSAGE(wlanapi, "wlanapi" FF_LIBRARY_EXTENSION, 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanOpenHandle) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanEnumInterfaces) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanQueryInterface) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanFreeMemory) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanCloseHandle) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(wlanapi, WlanGetNetworkBssList) + + DWORD curVersion; + HANDLE hClient = NULL; + WLAN_INTERFACE_INFO_LIST* ifList = NULL; + const char* error = NULL; + + if (ffWlanOpenHandle(2, NULL, &curVersion, &hClient) != ERROR_SUCCESS) { + error = "WlanOpenHandle() failed"; + goto exit; + } + + if (ffWlanEnumInterfaces(hClient, NULL, &ifList) != ERROR_SUCCESS) { + error = "WlanEnumInterfaces() failed"; + goto exit; + } + + for (uint32_t index = 0; index < ifList->dwNumberOfItems; ++index) { + WLAN_INTERFACE_INFO* ifInfo = (WLAN_INTERFACE_INFO*) &ifList->InterfaceInfo[index]; + + FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result); + ffStrbufInitWS(&item->inf.description, ifInfo->strInterfaceDescription); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = -DBL_MAX; + item->conn.rxRate = -DBL_MAX; + item->conn.txRate = -DBL_MAX; + item->conn.channel = 0; + item->conn.frequency = 0; + + convertIfStateToString(ifInfo->isState, &item->inf.status); + + if (ifInfo->isState != wlan_interface_state_connected) { + continue; + } + + WLAN_CONNECTION_ATTRIBUTES* connInfo = NULL; + DWORD bufSize = sizeof(*connInfo); + WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_query_only; + + if (ffWlanQueryInterface(hClient, + &ifInfo->InterfaceGuid, + wlan_intf_opcode_current_connection, + NULL, + &bufSize, + (PVOID*) &connInfo, + &opCode) != ERROR_SUCCESS) { + continue; + } + + convertIfStateToString(connInfo->isState, &item->conn.status); + ffStrbufAppendNS(&item->conn.ssid, + connInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength, + (const char*) connInfo->wlanAssociationAttributes.dot11Ssid.ucSSID); + + for (size_t i = 0; i < sizeof(connInfo->wlanAssociationAttributes.dot11Bssid); i++) { + ffStrbufAppendF(&item->conn.bssid, "%.2X:", connInfo->wlanAssociationAttributes.dot11Bssid[i]); + } + ffStrbufTrimRight(&item->conn.bssid, ':'); + + switch (connInfo->wlanAssociationAttributes.dot11PhyType) { + case dot11_phy_type_fhss: + ffStrbufAppendS(&item->conn.protocol, "802.11 (FHSS)"); + break; + case dot11_phy_type_dsss: + ffStrbufAppendS(&item->conn.protocol, "802.11 (DSSS)"); + break; + case dot11_phy_type_irbaseband: + ffStrbufAppendS(&item->conn.protocol, "802.11 (IR)"); + break; + case dot11_phy_type_ofdm: + ffStrbufAppendS(&item->conn.protocol, "802.11a"); + break; + case dot11_phy_type_hrdsss: + ffStrbufAppendS(&item->conn.protocol, "802.11b"); + break; + case dot11_phy_type_erp: + ffStrbufAppendS(&item->conn.protocol, "802.11g"); + break; + case dot11_phy_type_ht: + ffStrbufAppendS(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + break; + case dot11_phy_type_vht: + ffStrbufAppendS(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + break; + case dot11_phy_type_dmg: + ffStrbufAppendS(&item->conn.protocol, "802.11ad (WiGig)"); + break; + case dot11_phy_type_he: + ffStrbufAppendS(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + break; + case dot11_phy_type_eht: + ffStrbufAppendS(&item->conn.protocol, "802.11be (Wi-Fi 7)"); + break; + default: + ffStrbufAppendF(&item->conn.protocol, "Unknown (%u)", (unsigned) connInfo->wlanAssociationAttributes.dot11PhyType); + break; + } + + item->conn.signalQuality = connInfo->wlanAssociationAttributes.wlanSignalQuality; + item->conn.rxRate = connInfo->wlanAssociationAttributes.ulRxRate / 1000.; + item->conn.txRate = connInfo->wlanAssociationAttributes.ulTxRate / 1000.; + + if (connInfo->wlanSecurityAttributes.bSecurityEnabled) { + switch (connInfo->wlanSecurityAttributes.dot11AuthAlgorithm) { + case DOT11_AUTH_ALGO_80211_OPEN: + ffStrbufAppendS(&item->conn.security, "802.11 Open"); + break; + case DOT11_AUTH_ALGO_80211_SHARED_KEY: + ffStrbufAppendS(&item->conn.security, "802.11 Shared"); + break; + case DOT11_AUTH_ALGO_WPA: + ffStrbufAppendS(&item->conn.security, "WPA"); + break; + case DOT11_AUTH_ALGO_WPA_PSK: + ffStrbufAppendS(&item->conn.security, "WPA-PSK"); + break; + case DOT11_AUTH_ALGO_WPA_NONE: + ffStrbufAppendS(&item->conn.security, "WPA-None"); + break; + case DOT11_AUTH_ALGO_RSNA: + ffStrbufAppendS(&item->conn.security, "WPA2"); + break; + case DOT11_AUTH_ALGO_RSNA_PSK: + ffStrbufAppendS(&item->conn.security, "WPA2-PSK"); + break; + case DOT11_AUTH_ALGO_WPA3: + ffStrbufAppendS(&item->conn.security, "WPA3"); + break; + case DOT11_AUTH_ALGO_WPA3_SAE: + ffStrbufAppendS(&item->conn.security, "WPA3-SAE"); + break; + case 10 /* DOT11_AUTH_ALGO_OWE */: + ffStrbufAppendS(&item->conn.security, "OWE"); + break; + case 11 /* DOT11_AUTH_ALGO_WPA3_ENT */: + ffStrbufAppendS(&item->conn.security, "WPA3-ENT"); + break; + default: + ffStrbufAppendF(&item->conn.security, "Unknown (%u)", (unsigned) connInfo->wlanSecurityAttributes.dot11AuthAlgorithm); + break; + } + if (connInfo->wlanSecurityAttributes.bOneXEnabled) { + ffStrbufAppendS(&item->conn.security, " 802.11X"); + } + } else { + ffStrbufAppendS(&item->conn.security, "Insecure"); + } + + WLAN_BSS_LIST* bssList = NULL; + if (ffWlanGetNetworkBssList(hClient, + &ifInfo->InterfaceGuid, + &connInfo->wlanAssociationAttributes.dot11Ssid, + connInfo->wlanAssociationAttributes.dot11BssType, + connInfo->wlanSecurityAttributes.bSecurityEnabled, + NULL, + &bssList) == ERROR_SUCCESS && + bssList->dwNumberOfItems > 0) { + item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000); + ffWlanFreeMemory(bssList); + } + + ffWlanFreeMemory(connInfo); + + ULONG* channelNumber = 0; + bufSize = sizeof(*channelNumber); + if (ffWlanQueryInterface(hClient, + &ifInfo->InterfaceGuid, + wlan_intf_opcode_channel_number, + NULL, + &bufSize, + (PVOID*) &channelNumber, + &opCode) == ERROR_SUCCESS) { + item->conn.channel = (uint16_t) *channelNumber; + ffWlanFreeMemory(channelNumber); + } + } + +exit: + if (ifList) { + ffWlanFreeMemory(ifList); + } + if (hClient) { + ffWlanCloseHandle(hClient, NULL); + } + return error; +} + +#pragma GCC diagnostic pop -- cgit v1.2.3