2022-12-02 22:34:56 +08:00
|
|
|
#include "wifi.h"
|
2026-09-01 00:34:55 +08:00
|
|
|
#include "common/endian.h"
|
2026-01-06 09:48:38 +08:00
|
|
|
#include "common/io.h"
|
|
|
|
|
#include "common/debug.h"
|
2026-05-28 15:58:08 +08:00
|
|
|
#include "common/strutil.h"
|
2022-12-02 22:34:56 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
#include <sys/time.h>
|
|
|
|
|
#include <sys/socket.h>
|
2026-05-09 11:00:40 +08:00
|
|
|
#include <sys/ioctl.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <net/if.h>
|
|
|
|
|
#include <linux/wireless.h>
|
|
|
|
|
#include <unistd.h>
|
2023-01-17 12:45:26 +08:00
|
|
|
|
2026-09-01 00:34:55 +08:00
|
|
|
#include <linux/genetlink.h>
|
|
|
|
|
#include <linux/nl80211.h>
|
2026-05-13 19:29:33 +08:00
|
|
|
|
2026-09-01 00:34:55 +08:00
|
|
|
// Silence warning of `NLA_HDRLEN` and `NLA_ALIGN`
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
static inline double rssiToSignalQuality(int rssi) {
|
|
|
|
|
return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0
|
|
|
|
|
: (rssi + 100) * 2);
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2024-08-03 22:06:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
return addr.nl_pid;
|
|
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
static inline bool ffWifiNlAttrOk(const struct nlattr* attr, size_t remaining) {
|
|
|
|
|
return remaining >= sizeof(*attr) &&
|
|
|
|
|
attr->nla_len >= sizeof(*attr) &&
|
|
|
|
|
attr->nla_len <= remaining;
|
|
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2026-07-10 18:08:04 +08:00
|
|
|
return nullptr;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
*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) {
|
2026-05-09 11:00:40 +08:00
|
|
|
// Big endian?
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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,
|
2026-05-27 17:11:52 +08:00
|
|
|
.version = 1, // generic netlink control protocol version
|
2026-05-08 15:53:09 +08:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
2024-08-03 22:06:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
struct sockaddr_nl addr = {
|
|
|
|
|
.nl_family = AF_NETLINK,
|
|
|
|
|
};
|
2024-11-20 08:43:28 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2024-11-20 08:43:28 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
uint8_t buffer[8192];
|
2026-03-29 09:28:49 +08:00
|
|
|
while (true) {
|
2026-07-10 18:08:04 +08:00
|
|
|
ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
|
2026-05-08 15:53:09 +08:00
|
|
|
if (received < 0) {
|
|
|
|
|
FF_DEBUG("Failed to receive nl80211 family reply: %s", strerror(errno));
|
|
|
|
|
return false;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-11-20 08:43:28 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2024-11-20 08:43:28 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
if (nlh->nlmsg_type != GENL_ID_CTRL) {
|
|
|
|
|
continue;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
const struct genlmsghdr* genl = (const struct genlmsghdr*) NLMSG_DATA(nlh);
|
|
|
|
|
if (genl->cmd != CTRL_CMD_NEWFAMILY) {
|
|
|
|
|
continue;
|
2024-11-20 08:43:28 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
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;
|
2024-11-22 10:31:12 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
ctx->nl80211FamilyId = *(const uint16_t*) ffWifiNlAttrData(attr);
|
|
|
|
|
return true;
|
2024-11-22 10:31:12 +08:00
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-20 08:43:28 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2024-11-20 08:43:28 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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,
|
2026-05-27 17:11:52 +08:00
|
|
|
&(struct timeval){ .tv_sec = 0, .tv_usec = 250000 }, // 250 ms recv timeout
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 15:28:25 +08:00
|
|
|
static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol, uint16_t* channelWidth) {
|
2026-05-08 15:53:09 +08:00
|
|
|
double rate = -DBL_MAX;
|
2026-08-18 15:28:25 +08:00
|
|
|
uint16_t width = 0;
|
2026-05-08 15:53:09 +08:00
|
|
|
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) {
|
2026-08-18 15:28:25 +08:00
|
|
|
case NL80211_RATE_INFO_40_MHZ_WIDTH:
|
|
|
|
|
width = 40;
|
|
|
|
|
break;
|
|
|
|
|
case NL80211_RATE_INFO_80_MHZ_WIDTH:
|
|
|
|
|
case NL80211_RATE_INFO_80P80_MHZ_WIDTH:
|
|
|
|
|
width = 80;
|
|
|
|
|
break;
|
|
|
|
|
case NL80211_RATE_INFO_160_MHZ_WIDTH:
|
|
|
|
|
width = 160;
|
|
|
|
|
break;
|
|
|
|
|
case 18 /* NL80211_RATE_INFO_320_MHZ_WIDTH */:
|
|
|
|
|
width = 320;
|
|
|
|
|
break;
|
|
|
|
|
case NL80211_RATE_INFO_10_MHZ_WIDTH:
|
|
|
|
|
width = 10;
|
|
|
|
|
break;
|
|
|
|
|
case NL80211_RATE_INFO_5_MHZ_WIDTH:
|
|
|
|
|
width = 5;
|
|
|
|
|
break;
|
|
|
|
|
case 25 /* NL80211_RATE_INFO_1_MHZ_WIDTH */:
|
|
|
|
|
width = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 26 /* NL80211_RATE_INFO_2_MHZ_WIDTH */:
|
|
|
|
|
width = 2;
|
|
|
|
|
break;
|
|
|
|
|
case 27 /* NL80211_RATE_INFO_4_MHZ_WIDTH */:
|
|
|
|
|
width = 4;
|
|
|
|
|
break;
|
|
|
|
|
case 28 /* NL80211_RATE_INFO_8_MHZ_WIDTH */:
|
|
|
|
|
width = 8;
|
|
|
|
|
break;
|
|
|
|
|
case 29 /* NL80211_RATE_INFO_16_MHZ_WIDTH */:
|
|
|
|
|
width = 16;
|
|
|
|
|
break;
|
2026-05-09 13:46:05 +08:00
|
|
|
case 30 /* NL80211_RATE_INFO_UHR_MCS */:
|
2026-05-08 15:53:09 +08:00
|
|
|
ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)");
|
|
|
|
|
break;
|
2026-05-09 13:46:05 +08:00
|
|
|
case 23 /* NL80211_RATE_INFO_S1G_MCS */:
|
2026-05-08 15:53:09 +08:00
|
|
|
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)) {
|
2026-05-27 17:11:52 +08:00
|
|
|
rate = *(uint32_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case NL80211_RATE_INFO_BITRATE:
|
|
|
|
|
if (payload >= sizeof(uint16_t) && rate == -DBL_MAX) {
|
2026-05-27 17:11:52 +08:00
|
|
|
rate = *(uint16_t*) ffWifiNlAttrData(info) / 10.0; // nl80211 bitrate unit: 100 kbps => Mbps
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2024-08-03 22:06:37 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-18 15:28:25 +08:00
|
|
|
if (rate != -DBL_MAX && *channelWidth == 0) {
|
|
|
|
|
*channelWidth = width;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
return rate;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 15:28:25 +08:00
|
|
|
static uint16_t ffWifiChannelWidthToMhz(uint32_t width) {
|
|
|
|
|
switch (width) {
|
|
|
|
|
case NL80211_CHAN_WIDTH_20_NOHT:
|
|
|
|
|
case NL80211_CHAN_WIDTH_20:
|
|
|
|
|
return 20;
|
|
|
|
|
case NL80211_CHAN_WIDTH_40:
|
|
|
|
|
return 40;
|
|
|
|
|
case NL80211_CHAN_WIDTH_80:
|
|
|
|
|
case NL80211_CHAN_WIDTH_80P80:
|
|
|
|
|
return 80;
|
|
|
|
|
case NL80211_CHAN_WIDTH_160:
|
|
|
|
|
return 160;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 6 /* NL80211_CHAN_WIDTH_5 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 5;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 7 /* NL80211_CHAN_WIDTH_10 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 10;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 8 /* NL80211_CHAN_WIDTH_1 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 1;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 9 /* NL80211_CHAN_WIDTH_2 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 2;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 10 /* NL80211_CHAN_WIDTH_4 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 4;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 11 /* NL80211_CHAN_WIDTH_8 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 8;
|
2026-09-01 14:14:01 +08:00
|
|
|
case 12 /* NL80211_CHAN_WIDTH_16 */:
|
2026-08-18 15:28:25 +08:00
|
|
|
return 16;
|
|
|
|
|
case 13 /* NL80211_CHAN_WIDTH_320 */:
|
|
|
|
|
return 320;
|
|
|
|
|
default:
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool ffWifiFetchInterfaceInfo(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_ACK,
|
|
|
|
|
.nlmsg_seq = ++ctx->seq,
|
|
|
|
|
.nlmsg_pid = ctx->portId,
|
|
|
|
|
},
|
|
|
|
|
.genl = {
|
|
|
|
|
.cmd = NL80211_CMD_GET_INTERFACE,
|
|
|
|
|
.version = 0,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
|
|
|
|
|
FF_DEBUG("Failed to build nl80211 interface 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 interface request");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t buffer[8192];
|
|
|
|
|
while (true) {
|
|
|
|
|
ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
|
|
|
|
|
if (received < 0) {
|
|
|
|
|
FF_DEBUG("Failed to receive nl80211 interface 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 interface 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_CHANNEL_WIDTH ||
|
|
|
|
|
ffWifiNlAttrPayload(attr) < sizeof(uint32_t)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item->conn.channelWidth = ffWifiChannelWidthToMhz(*(const uint32_t*) ffWifiNlAttrData(attr));
|
|
|
|
|
return item->conn.channelWidth != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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");
|
2026-03-29 09:28:49 +08:00
|
|
|
} else {
|
2026-05-08 15:53:09 +08:00
|
|
|
ffStrbufSetStatic(&item->conn.security, "Insecure");
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
} else {
|
|
|
|
|
ffStrbufTrimRight(&item->conn.security, '/');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void ffWifiParseRsnIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) {
|
2026-05-27 17:11:52 +08:00
|
|
|
if (len < 8) { // version(2) + group cipher suite(4) + pairwise count(2)
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sec->wpa2 = true;
|
|
|
|
|
size_t pos = 0;
|
2025-03-16 22:26:38 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 2; // RSN version field length
|
|
|
|
|
if (pos + 4 > len) { // group cipher suite selector length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 4; // skip group cipher suite selector
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
if (pos + 2 > len) { // pairwise cipher suite count field length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-09-01 00:34:55 +08:00
|
|
|
uint16_t pairwiseCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 2; // skip pairwise cipher suite count field
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
size_t pairwiseLen = (size_t) pairwiseCount * 4; // each suite selector is 4 bytes
|
2026-05-08 15:53:09 +08:00
|
|
|
if (pos + pairwiseLen > len) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pos += pairwiseLen;
|
|
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
if (pos + 2 > len) { // AKM suite count field length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-09-01 00:34:55 +08:00
|
|
|
uint16_t akmCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 2; // skip AKM suite count field
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes
|
2026-05-08 15:53:09 +08:00
|
|
|
const uint8_t* akm = ie + pos;
|
2026-05-27 17:11:52 +08:00
|
|
|
if (akm[0] != 0x00 || akm[1] != 0x0f || akm[2] != 0xac) { // RSN OUI 00:0f:ac
|
2026-05-08 15:53:09 +08:00
|
|
|
continue;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
switch (akm[3]) {
|
2026-05-27 17:11:52 +08:00
|
|
|
case 1: // 802.1X
|
|
|
|
|
case 5: // FT/802.1X
|
|
|
|
|
case 11: // 802.1X-SHA256
|
|
|
|
|
case 12: // FT/802.1X-SHA384 (suite selector value)
|
2026-05-08 15:53:09 +08:00
|
|
|
sec->eap = true;
|
|
|
|
|
break;
|
2026-05-27 17:11:52 +08:00
|
|
|
case 8: // SAE (WPA3-Personal)
|
2026-05-08 15:53:09 +08:00
|
|
|
sec->wpa3 = true;
|
|
|
|
|
break;
|
2026-05-27 17:11:52 +08:00
|
|
|
case 18: // OWE
|
2026-05-08 15:53:09 +08:00
|
|
|
sec->owe = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
if (sec->owe) {
|
|
|
|
|
sec->wpa2 = false;
|
|
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
static void ffWifiParseWpaVendorIe(const uint8_t* ie, size_t len, FFWifiSecurityFlags* sec) {
|
2026-05-27 17:11:52 +08:00
|
|
|
if (len < 8) { // OUI+type(4) + version(2) + multicast cipher suite(4) starts here
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
if (!(ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2 && ie[3] == 0x01)) { // Microsoft WPA OUI/type
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
sec->wpa = true;
|
|
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
size_t pos = 4; // WPA vendor OUI/type selector length
|
|
|
|
|
if (pos + 2 > len) { // WPA version field length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 2; // skip WPA version
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
if (pos + 4 > len) { // multicast cipher suite selector length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
2024-08-02 15:10:04 +08:00
|
|
|
}
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 4; // skip multicast cipher suite selector
|
2023-01-17 12:45:26 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
if (pos + 2 > len) { // unicast cipher suite count field length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-09-01 00:34:55 +08:00
|
|
|
uint16_t pairwiseCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 2 + (size_t) pairwiseCount * 4; // count field(2) + N unicast suite selectors(4 each)
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
if (pos + 2 > len) { // AKM suite count field length
|
2026-05-08 15:53:09 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-09-01 00:34:55 +08:00
|
|
|
uint16_t akmCount = FF_READ_LE(*(const uint16_t*) (ie + pos));
|
2026-05-27 17:11:52 +08:00
|
|
|
pos += 2; // skip AKM suite count field
|
2023-01-17 12:45:26 +08:00
|
|
|
|
2026-05-27 17:11:52 +08:00
|
|
|
for (uint16_t i = 0; i < akmCount && pos + 4 <= len; ++i, pos += 4) { // each AKM suite selector is 4 bytes
|
2026-05-08 15:53:09 +08:00
|
|
|
const uint8_t* akm = ie + pos;
|
2026-05-27 17:11:52 +08:00
|
|
|
if (!(akm[0] == 0x00 && akm[1] == 0x50 && akm[2] == 0xf2)) { // WPA vendor OUI 00:50:f2
|
2026-05-08 15:53:09 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
2026-05-27 17:11:52 +08:00
|
|
|
if (akm[3] == 1) { // WPA Enterprise (802.1X)
|
2026-05-08 15:53:09 +08:00
|
|
|
sec->eap = true;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2024-01-08 11:32:39 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
const uint8_t* ie = ies + pos;
|
2026-05-27 17:11:52 +08:00
|
|
|
if (id == 0) { // SSID element ID
|
2026-05-08 15:53:09 +08:00
|
|
|
ffStrbufSetNS(&item->conn.ssid, len, (const char*) ie);
|
2026-05-27 17:11:52 +08:00
|
|
|
} else if (id == 48) { // RSN element ID
|
2026-05-08 15:53:09 +08:00
|
|
|
ffWifiParseRsnIe(ie, len, sec);
|
2026-05-27 17:11:52 +08:00
|
|
|
} else if (id == 221) { // vendor-specific element ID (WPA IE lives here)
|
2026-05-08 15:53:09 +08:00
|
|
|
ffWifiParseWpaVendorIe(ie, len, sec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pos += len;
|
2024-08-02 15:10:04 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
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) {
|
2026-05-08 15:53:09 +08:00
|
|
|
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);
|
2023-01-17 12:45:26 +08:00
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
if (type == NL80211_BSS_BSSID && payload >= 6) {
|
2026-05-08 15:53:09 +08:00
|
|
|
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)) {
|
2026-05-27 17:11:52 +08:00
|
|
|
int rssi = *(int32_t*) ffWifiNlAttrData(attr) / 100; // mBm (100 * dBm) => dBm
|
2026-05-08 15:53:09 +08:00
|
|
|
item->conn.signalQuality = rssiToSignalQuality(rssi);
|
|
|
|
|
} else if (type == NL80211_BSS_CAPABILITY && payload >= sizeof(uint16_t)) {
|
|
|
|
|
uint16_t capability = *(uint16_t*) ffWifiNlAttrData(attr);
|
2026-05-27 17:11:52 +08:00
|
|
|
sec.privacy = (capability & (1u << 4u)) != 0; // IEEE 802.11 capability bit 4: privacy
|
2026-05-08 15:53:09 +08:00
|
|
|
} else if (type == NL80211_BSS_INFORMATION_ELEMENTS || type == NL80211_BSS_BEACON_IES) {
|
|
|
|
|
ffWifiParseInformationElements((const uint8_t*) ffWifiNlAttrData(attr), payload, item, &sec);
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2023-01-17 12:45:26 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
ffWifiApplySecurityFlags(item, &sec);
|
2026-05-09 11:00:40 +08:00
|
|
|
return;
|
2023-01-17 12:45:26 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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,
|
2026-05-27 17:11:52 +08:00
|
|
|
.version = 0, // nl80211 command version
|
2026-05-08 15:53:09 +08:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
|
|
|
|
|
FF_DEBUG("Failed to build nl80211 scan request");
|
|
|
|
|
return false;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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) {
|
2026-07-10 18:08:04 +08:00
|
|
|
ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
|
2026-05-08 15:53:09 +08:00
|
|
|
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) {
|
2026-05-09 11:00:40 +08:00
|
|
|
return false;
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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));
|
2026-05-09 11:00:40 +08:00
|
|
|
return false;
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
if (!ffWifiIsBssAssociated(attr)) {
|
|
|
|
|
continue;
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2026-05-09 11:00:40 +08:00
|
|
|
|
|
|
|
|
ffWifiParseBssAttr(attr, item);
|
|
|
|
|
ffStrbufSetStatic(&item->conn.status, "connected");
|
|
|
|
|
return true;
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-09 11:00:40 +08:00
|
|
|
|
|
|
|
|
return false;
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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);
|
2026-08-18 15:28:25 +08:00
|
|
|
} else if (type == NL80211_STA_INFO_TX_BITRATE && (item->conn.txRate == -DBL_MAX || item->conn.channelWidth == 0)) {
|
|
|
|
|
double tx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol, &item->conn.channelWidth);
|
2026-05-08 15:53:09 +08:00
|
|
|
if (tx != -DBL_MAX) {
|
|
|
|
|
item->conn.txRate = tx;
|
|
|
|
|
}
|
2026-08-18 15:28:25 +08:00
|
|
|
} else if (type == NL80211_STA_INFO_RX_BITRATE && (item->conn.rxRate == -DBL_MAX || item->conn.channelWidth == 0)) {
|
|
|
|
|
double rx = ffWifiParseBitrateFromRateInfo(attr, &item->conn.protocol, &item->conn.channelWidth);
|
2026-05-08 15:53:09 +08:00
|
|
|
if (rx != -DBL_MAX) {
|
|
|
|
|
item->conn.rxRate = rx;
|
|
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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,
|
2026-05-27 17:11:52 +08:00
|
|
|
.version = 0, // nl80211 command version
|
2026-05-08 15:53:09 +08:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!ffWifiNlAppendAttr(&req.nlh, sizeof(req), NL80211_ATTR_IFINDEX, &ifIndex, sizeof(ifIndex))) {
|
|
|
|
|
FF_DEBUG("Failed to build nl80211 station request");
|
|
|
|
|
return false;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-03 22:06:37 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
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) {
|
2026-07-10 18:08:04 +08:00
|
|
|
ssize_t received = recvfrom(ctx->sockFd, buffer, sizeof(buffer), 0, nullptr, nullptr);
|
2026-05-08 15:53:09 +08:00
|
|
|
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;
|
2024-11-22 10:31:12 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
|
|
|
|
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;
|
2024-11-22 10:31:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2024-11-22 10:31:12 +08:00
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
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");
|
2026-05-27 17:11:52 +08:00
|
|
|
ctx->sockFd = -2; // sentinel: permanent netlink failure, don't retry
|
2026-05-09 11:00:40 +08:00
|
|
|
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);
|
2026-05-08 15:53:09 +08:00
|
|
|
ffStrbufSetStatic(&item->conn.status, "connected");
|
2026-08-18 15:28:25 +08:00
|
|
|
ffWifiFetchInterfaceInfo(ctx, item, ifIndex);
|
2026-05-09 11:00:40 +08:00
|
|
|
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");
|
2026-05-08 15:53:09 +08:00
|
|
|
ffStrbufSetStatic(&item->conn.status, "disconnected");
|
|
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
FF_DEBUG("Netlink wifi detection completed");
|
2026-07-10 18:08:04 +08:00
|
|
|
return nullptr;
|
2026-05-09 11:00:40 +08:00
|
|
|
}
|
2026-05-13 19:29:33 +08:00
|
|
|
typedef struct FFWifiIcContext {
|
|
|
|
|
int sockFd;
|
|
|
|
|
} FFWifiIcContext;
|
2026-05-09 11:00:40 +08:00
|
|
|
|
|
|
|
|
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));
|
2026-05-27 17:11:52 +08:00
|
|
|
ctx->sockFd = -2; // sentinel: permanent ioctl failure, don't retry
|
2026-05-09 11:00:40 +08:00
|
|
|
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));
|
|
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-02 15:10:04 +08:00
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
if (!item->conn.protocol.length) {
|
|
|
|
|
FF_DEBUG("Getting protocol name via ioctl");
|
2026-05-09 13:46:05 +08:00
|
|
|
if (ioctl(sock, SIOCGIWNAME, &iwr) >= 0) {
|
|
|
|
|
char* token = iwr.u.name;
|
2026-05-09 11:00:40 +08:00
|
|
|
if (ffStrStartsWithIgnCase(iwr.u.name, "IEEE ")) {
|
2026-05-09 13:46:05 +08:00
|
|
|
token += strlen("IEEE ");
|
2026-05-09 11:00:40 +08:00
|
|
|
}
|
2026-05-09 13:46:05 +08:00
|
|
|
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)");
|
2026-05-09 11:00:40 +08:00
|
|
|
} 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) {
|
2026-05-27 17:11:52 +08:00
|
|
|
item->conn.txRate = iwr.u.bitrate.value / 1000000.; // bps => Mbps
|
2026-05-09 11:00:40 +08:00
|
|
|
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) {
|
2026-05-27 17:11:52 +08:00
|
|
|
if (iwr.u.freq.e == 0 && iwr.u.freq.m <= 1000) { // kernel may return direct channel number
|
2026-05-09 11:00:40 +08:00
|
|
|
item->conn.channel = (uint16_t) iwr.u.freq.m;
|
|
|
|
|
FF_DEBUG("Direct channel value: %u", item->conn.channel);
|
|
|
|
|
} else {
|
|
|
|
|
// convert it to MHz
|
2026-05-27 17:11:52 +08:00
|
|
|
while (iwr.u.freq.e < 6) { // normalize exponent to 10^6 (MHz)
|
2026-05-09 11:00:40 +08:00
|
|
|
iwr.u.freq.m /= 10;
|
|
|
|
|
iwr.u.freq.e++;
|
|
|
|
|
}
|
2026-05-27 17:11:52 +08:00
|
|
|
while (iwr.u.freq.e > 6) { // normalize exponent to 10^6 (MHz)
|
2026-05-09 11:00:40 +08:00
|
|
|
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));
|
|
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
}
|
2026-05-09 11:00:40 +08:00
|
|
|
|
|
|
|
|
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");
|
2026-07-10 18:08:04 +08:00
|
|
|
return nullptr;
|
2024-08-02 15:10:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
const char* ffDetectWifi(FFlist* result) {
|
2025-03-16 22:26:38 +08:00
|
|
|
FF_DEBUG("Starting wifi detection");
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2022-12-02 22:34:56 +08:00
|
|
|
struct if_nameindex* infs = if_nameindex();
|
2026-05-09 11:00:40 +08:00
|
|
|
if (!infs) {
|
|
|
|
|
FF_DEBUG("if_nameindex failed: %s", strerror(errno));
|
|
|
|
|
return "if_nameindex() failed";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FFWifiNlContext nl = { .sockFd = -1 };
|
|
|
|
|
FFWifiIcContext ic = { .sockFd = -1 };
|
2022-12-02 22:34:56 +08:00
|
|
|
|
2024-08-02 15:10:04 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
2022-12-02 22:34:56 +08:00
|
|
|
|
2026-07-10 18:08:04 +08:00
|
|
|
for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == nullptr); ++i) {
|
2025-03-16 22:26:38 +08:00
|
|
|
FF_DEBUG("Checking interface: %s (index: %u)", i->if_name, i->if_index);
|
2024-10-15 19:40:03 +08:00
|
|
|
ffStrbufSetF(&buffer, "/sys/class/net/%s/phy80211/", i->if_name);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!ffPathExists(buffer.chars, FF_PATHTYPE_DIRECTORY)) {
|
2025-03-16 22:26:38 +08:00
|
|
|
FF_DEBUG("Not a wifi interface (no phy80211 directory)");
|
2022-12-02 22:34:56 +08:00
|
|
|
continue;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2022-12-02 22:34:56 +08:00
|
|
|
|
2026-04-14 10:41:18 +08:00
|
|
|
FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
|
2022-12-02 22:34:56 +08:00
|
|
|
ffStrbufInitS(&item->inf.description, i->if_name);
|
|
|
|
|
ffStrbufInit(&item->inf.status);
|
|
|
|
|
ffStrbufInit(&item->conn.status);
|
|
|
|
|
ffStrbufInit(&item->conn.ssid);
|
2024-08-01 15:45:53 +08:00
|
|
|
ffStrbufInit(&item->conn.bssid);
|
2023-01-17 12:45:26 +08:00
|
|
|
ffStrbufInit(&item->conn.protocol);
|
|
|
|
|
ffStrbufInit(&item->conn.security);
|
2025-08-14 09:47:14 +08:00
|
|
|
item->conn.signalQuality = -DBL_MAX;
|
|
|
|
|
item->conn.rxRate = -DBL_MAX;
|
|
|
|
|
item->conn.txRate = -DBL_MAX;
|
2024-11-21 16:08:40 +08:00
|
|
|
item->conn.channel = 0;
|
2026-08-18 15:28:25 +08:00
|
|
|
item->conn.channelWidth = 0;
|
2024-11-21 16:08:40 +08:00
|
|
|
item->conn.frequency = 0;
|
2022-12-02 22:34:56 +08:00
|
|
|
|
2025-07-22 22:03:53 +08:00
|
|
|
char operstate;
|
2024-08-02 15:10:04 +08:00
|
|
|
ffStrbufSetF(&buffer, "/sys/class/net/%s/operstate", i->if_name);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!ffReadFileData(buffer.chars, 1, &operstate)) {
|
2026-05-08 15:53:09 +08:00
|
|
|
ffStrbufSetStatic(&item->inf.status, "unknown");
|
|
|
|
|
ffStrbufSetStatic(&item->conn.status, "disconnected");
|
2024-01-22 09:07:38 +08:00
|
|
|
continue;
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2024-01-22 09:07:38 +08:00
|
|
|
|
2026-05-08 15:53:09 +08:00
|
|
|
if (operstate == 'u') {
|
|
|
|
|
ffStrbufSetStatic(&item->inf.status, "up");
|
2026-05-13 19:29:33 +08:00
|
|
|
|
2026-05-09 11:00:40 +08:00
|
|
|
detectWithNetlink(&nl, item, i->if_index);
|
|
|
|
|
detectWithIoctl(&ic, item, i->if_name);
|
2026-05-08 15:53:09 +08:00
|
|
|
} else {
|
2025-07-22 22:03:53 +08:00
|
|
|
ffStrbufSetStatic(&item->conn.status, "disconnected");
|
|
|
|
|
|
|
|
|
|
ffStrbufSetF(&buffer, "/sys/class/net/%s/flags", i->if_name);
|
|
|
|
|
char flags[16];
|
2026-05-08 15:53:09 +08:00
|
|
|
ssize_t len = ffReadFileData(buffer.chars, sizeof(flags) - 1, flags);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (len <= 0) {
|
2025-07-22 22:03:53 +08:00
|
|
|
ffStrbufSetStatic(&item->inf.status, "unknown");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
flags[len] = '\0';
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2026-07-10 18:08:04 +08:00
|
|
|
unsigned flagsVal = (unsigned) strtoul(flags, nullptr, 16); // parse /sys flags as hexadecimal
|
2026-03-29 09:28:49 +08:00
|
|
|
if (flagsVal & IFF_UP) {
|
2025-07-22 22:03:53 +08:00
|
|
|
ffStrbufSetStatic(&item->inf.status, "up");
|
2026-03-29 09:28:49 +08:00
|
|
|
} else {
|
2025-07-22 22:03:53 +08:00
|
|
|
ffStrbufSetStatic(&item->inf.status, "down");
|
|
|
|
|
}
|
2025-03-16 22:26:38 +08:00
|
|
|
}
|
2022-12-02 22:34:56 +08:00
|
|
|
}
|
2026-05-08 15:53:09 +08:00
|
|
|
|
2022-12-02 22:34:56 +08:00
|
|
|
if_freenameindex(infs);
|
2026-05-08 15:53:09 +08:00
|
|
|
if (nl.sockFd >= 0) {
|
|
|
|
|
close(nl.sockFd);
|
|
|
|
|
}
|
2026-05-09 11:00:40 +08:00
|
|
|
if (ic.sockFd >= 0) {
|
|
|
|
|
close(ic.sockFd);
|
|
|
|
|
}
|
2022-12-02 22:34:56 +08:00
|
|
|
|
2025-03-16 22:26:38 +08:00
|
|
|
FF_DEBUG("Wifi detection completed, found %u wifi interfaces", result->length);
|
2026-07-10 18:08:04 +08:00
|
|
|
return nullptr;
|
2023-01-17 15:54:06 +08:00
|
|
|
}
|