diff --git a/src/detection/wifi/wifi.h b/src/detection/wifi/wifi.h index e457295c3..7ad22855d 100644 --- a/src/detection/wifi/wifi.h +++ b/src/detection/wifi/wifi.h @@ -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 }; diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c index c609c25c4..1fe21b0c4 100644 --- a/src/detection/wifi/wifi_linux.c +++ b/src/detection/wifi/wifi_linux.c @@ -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)) { diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c index 8aad2884e..f96d58e0b 100644 --- a/src/modules/wifi/wifi.c +++ b/src/modules/wifi/wifi.c @@ -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,