summaryrefslogtreecommitdiffstats
path: root/src/detection/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/wifi')
-rw-r--r--src/detection/wifi/wifi.h62
-rw-r--r--src/detection/wifi/wifi_android.c69
-rw-r--r--src/detection/wifi/wifi_apple.m165
-rw-r--r--src/detection/wifi/wifi_bsd.c153
-rw-r--r--src/detection/wifi/wifi_linux.c976
-rw-r--r--src/detection/wifi/wifi_nbsd.c168
-rw-r--r--src/detection/wifi/wifi_nosupport.c5
-rw-r--r--src/detection/wifi/wifi_obsd.c109
-rw-r--r--src/detection/wifi/wifi_windows.c242
9 files changed, 1949 insertions, 0 deletions
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 <CoreWLAN/CoreWLAN.h>
+
+static inline double rssiToSignalQuality(int rssi)
+{
+ return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2);
+}
+
+@interface CWNetworkProfile()
+@property(readonly, retain, nullable) NSArray<NSDictionary *> *bssidList;
+@end
+
+const char* ffDetectWifi(FFlist* result)
+{
+ NSArray<CWInterface*>* 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, "<redacted>"); // 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, "<redacted>");
+
+ 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 <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net80211/ieee80211_ioctl.h>
+
+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 <sys/time.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <linux/wireless.h>
+#include <unistd.h>
+
+#if !__BIG_ENDIAN__
+ #include <linux/genetlink.h>
+ #include <linux/nl80211.h>
+
+ // 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 <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net80211/ieee80211.h>
+#include <net80211/ieee80211_ioctl.h>
+
+// 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 <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net80211/ieee80211.h>
+#include <net80211/ieee80211_ioctl.h>
+#include <unistd.h>
+
+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 <windows.h>
+#include <wlanapi.h>
+
+#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