Wifi (Linux): adds channel width and device driver detection

This commit is contained in:
Carter Li
2026-08-18 15:28:25 +08:00
committed by 李通洲
parent 7c93f9c82a
commit 232cd6659b
3 changed files with 184 additions and 5 deletions
+2
View File
@@ -6,6 +6,7 @@
struct FFWifiInterface {
FFstrbuf description;
FFstrbuf status;
FFstrbuf driver;
};
struct FFWifiConnection {
@@ -18,6 +19,7 @@ struct FFWifiConnection {
double rxRate;
double txRate;
uint16_t channel;
uint16_t channelWidth; // MHz
uint16_t frequency; // MHz
};
+174 -5
View File
@@ -212,8 +212,9 @@ static bool ffWifiNlInit(FFWifiNlContext* ctx) {
return true;
}
static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol) {
static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFstrbuf* protocol, uint16_t* channelWidth) {
double rate = -DBL_MAX;
uint16_t width = 0;
size_t remaining = ffWifiNlAttrPayload(rateAttr);
for (const struct nlattr* info = (const struct nlattr*) ffWifiNlAttrData(rateAttr);
@@ -223,6 +224,40 @@ static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFst
size_t payload = ffWifiNlAttrPayload(info);
switch (type) {
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;
case 30 /* NL80211_RATE_INFO_UHR_MCS */:
ffStrbufSetStatic(protocol, "802.11bn (Wi-Fi 8)");
break;
@@ -254,9 +289,126 @@ static double ffWifiParseBitrateFromRateInfo(const struct nlattr* rateAttr, FFst
}
}
if (rate != -DBL_MAX && *channelWidth == 0) {
*channelWidth = width;
}
return rate;
}
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;
case NL80211_CHAN_WIDTH_5:
return 5;
case NL80211_CHAN_WIDTH_10:
return 10;
case NL80211_CHAN_WIDTH_1:
return 1;
case NL80211_CHAN_WIDTH_2:
return 2;
case NL80211_CHAN_WIDTH_4:
return 4;
case NL80211_CHAN_WIDTH_8:
return 8;
case NL80211_CHAN_WIDTH_16:
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;
}
}
}
static void ffWifiApplySecurityFlags(FFWifiResult* item, const FFWifiSecurityFlags* sec) {
ffStrbufClear(&item->conn.security);
@@ -566,13 +718,13 @@ static void ffWifiParseStationInfo(const struct nlattr* staInfoAttr, FFWifiResul
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);
} 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);
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);
} 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);
if (rx != -DBL_MAX) {
item->conn.rxRate = rx;
}
@@ -680,6 +832,7 @@ static const char* detectWithNetlink(FFWifiNlContext* ctx, FFWifiResult* item, u
if (ffWifiFetchScanInfo(ctx, item, ifIndex)) {
FF_DEBUG("found associated BSS: %s", item->conn.ssid.chars);
ffStrbufSetStatic(&item->conn.status, "connected");
ffWifiFetchInterfaceInfo(ctx, item, ifIndex);
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");
@@ -914,6 +1067,7 @@ const char* ffDetectWifi(FFlist* result) {
FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
ffStrbufInitS(&item->inf.description, i->if_name);
ffStrbufInit(&item->inf.status);
ffStrbufInit(&item->inf.driver);
ffStrbufInit(&item->conn.status);
ffStrbufInit(&item->conn.ssid);
ffStrbufInit(&item->conn.bssid);
@@ -923,8 +1077,23 @@ const char* ffDetectWifi(FFlist* result) {
item->conn.rxRate = -DBL_MAX;
item->conn.txRate = -DBL_MAX;
item->conn.channel = 0;
item->conn.channelWidth = 0;
item->conn.frequency = 0;
{
ffStrbufSetF(&buffer, "/sys/class/net/%s/device/driver", i->if_name);
char pathBuf[PATH_MAX];
ssize_t resultLength = readlink(buffer.chars, pathBuf, ARRAY_SIZE(pathBuf));
if (resultLength > 0) {
const char* p = memrchr(pathBuf, '/', (size_t) resultLength);
if (p) {
++p;
ffStrbufSetNS(&item->inf.driver, (uint32_t) (resultLength - (p - pathBuf)), p);
}
}
}
char operstate;
ffStrbufSetF(&buffer, "/sys/class/net/%s/operstate", i->if_name);
if (!ffReadFileData(buffer.chars, 1, &operstate)) {
+8
View File
@@ -98,6 +98,7 @@ bool ffPrintWifi(FFWifiOptions* options) {
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Wifi), moduleIndex, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {
FF_ARG(item->inf.description, "inf-desc"),
FF_ARG(item->inf.status, "inf-status"),
FF_ARG(item->inf.driver, "inf-driver"),
FF_ARG(item->conn.status, "status"),
FF_ARG(item->conn.ssid, "ssid"),
FF_ARG(item->conn.bssid, "bssid"),
@@ -108,6 +109,7 @@ bool ffPrintWifi(FFWifiOptions* options) {
FF_ARG(item->conn.security, "security"),
FF_ARG(percentBar, "signal-quality-bar"),
FF_ARG(item->conn.channel, "channel"),
FF_ARG(item->conn.channelWidth, "channel-width"),
FF_ARG(bandStr, "band"),
}));
}
@@ -116,6 +118,7 @@ bool ffPrintWifi(FFWifiOptions* options) {
FF_LIST_FOR_EACH (FFWifiResult, item, result) {
ffStrbufDestroy(&item->inf.description);
ffStrbufDestroy(&item->inf.status);
ffStrbufDestroy(&item->inf.driver);
ffStrbufDestroy(&item->conn.status);
ffStrbufDestroy(&item->conn.ssid);
ffStrbufDestroy(&item->conn.bssid);
@@ -163,6 +166,7 @@ bool ffGenerateWifiJsonResult([[maybe_unused]] FFWifiOptions* options, yyjson_mu
yyjson_mut_val* inf = yyjson_mut_obj_add_obj(doc, obj, "inf");
yyjson_mut_obj_add_strbuf(doc, inf, "description", &wifi->inf.description);
yyjson_mut_obj_add_strbuf(doc, inf, "status", &wifi->inf.status);
yyjson_mut_obj_add_strbuf(doc, inf, "driver", &wifi->inf.driver);
yyjson_mut_val* conn = yyjson_mut_obj_add_obj(doc, obj, "conn");
yyjson_mut_obj_add_strbuf(doc, conn, "status", &wifi->conn.status);
@@ -186,12 +190,14 @@ bool ffGenerateWifiJsonResult([[maybe_unused]] FFWifiOptions* options, yyjson_mu
yyjson_mut_obj_add_null(doc, conn, "txRate");
}
yyjson_mut_obj_add_uint(doc, conn, "channel", wifi->conn.channel);
yyjson_mut_obj_add_uint(doc, conn, "channelWidth", wifi->conn.channelWidth);
yyjson_mut_obj_add_uint(doc, conn, "frequency", wifi->conn.frequency);
}
FF_LIST_FOR_EACH (FFWifiResult, item, result) {
ffStrbufDestroy(&item->inf.description);
ffStrbufDestroy(&item->inf.status);
ffStrbufDestroy(&item->inf.driver);
ffStrbufDestroy(&item->conn.status);
ffStrbufDestroy(&item->conn.ssid);
ffStrbufDestroy(&item->conn.bssid);
@@ -240,6 +246,7 @@ FFModuleBaseInfo ffWifiModuleInfo = {
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
{ "Interface description", "inf-desc" },
{ "Interface status", "inf-status" },
{ "Interface driver", "inf-driver" },
{ "Connection status", "status" },
{ "Connection SSID", "ssid" },
{ "Connection BSSID", "bssid" },
@@ -250,6 +257,7 @@ FFModuleBaseInfo ffWifiModuleInfo = {
{ "Connection Security algorithm", "security" },
{ "Connection signal quality (percentage bar)", "signal-quality-bar" },
{ "Connection channel number", "channel" },
{ "Connection channel width in MHz", "channel-width" },
{ "Connection channel band in GHz", "band" },
})),
.defaultOrder = 51,