fix(openwrt): detect radios and scan networks on vendor MediaTek drivers
Routers running MediaTek's proprietary mt_wifi SDK driver (e.g. GL.iNet) never register with cfg80211/mac80211, so they have no `iw dev` entry and no /sys/class/ieee80211 phy even though the radio is real and working — find_wireless_iface was bailing with "No wireless radio found" on these. Fall back to iwinfo's device listing, which abstracts over vendor backends too, and to the vendor's iwpriv site-survey ioctl for scanning when iwinfo itself can't trigger a scan on the interface.
This commit is contained in:
parent
6299e91544
commit
1866c40edf
@ -12,13 +12,57 @@ pub struct ScannedNetwork {
|
||||
pub fn scan_networks(router: &Router) -> Result<Vec<ScannedNetwork>> {
|
||||
let (iface, temp) = find_wireless_iface(router)?;
|
||||
let output = router.run_ok(&format!("iwinfo {} scan 2>&1", iface))?;
|
||||
let result = if output.contains("Scanning not possible") {
|
||||
// Vendor MediaTek `mt_wifi` driver (see find_wireless_iface) doesn't
|
||||
// support scanning through iwinfo/nl80211 at all. Fall back to its own
|
||||
// private ioctl site-survey, which works on the same interface.
|
||||
scan_via_mtk_site_survey(router, &iface)
|
||||
} else if output.contains("No scan results") || output.trim().is_empty() {
|
||||
Ok(vec![])
|
||||
} else {
|
||||
parse_iwinfo_scan(&output)
|
||||
};
|
||||
if temp {
|
||||
let _ = router.run(&format!("iw dev {} del 2>/dev/null", iface));
|
||||
}
|
||||
if output.contains("No scan results") || output.trim().is_empty() {
|
||||
return Ok(vec![]);
|
||||
result
|
||||
}
|
||||
|
||||
fn scan_via_mtk_site_survey(router: &Router, iface: &str) -> Result<Vec<ScannedNetwork>> {
|
||||
let _ = router.run(&format!("iwpriv {} set SiteSurvey=1 2>/dev/null", iface));
|
||||
std::thread::sleep(std::time::Duration::from_secs(4));
|
||||
let output = router.run_ok(&format!("iwpriv {} get_site_survey 2>&1", iface))?;
|
||||
parse_mtk_site_survey(&output)
|
||||
}
|
||||
|
||||
/// Parses MediaTek's `iwpriv <iface> get_site_survey` fixed-width table.
|
||||
/// Column offsets come from the header row layout, which is part of the
|
||||
/// vendor SDK's ioctl response format shared across OEMs (GL.iNet, etc.),
|
||||
/// not something set per-device.
|
||||
fn parse_mtk_site_survey(output: &str) -> Result<Vec<ScannedNetwork>> {
|
||||
let mut networks = Vec::new();
|
||||
for line in output.lines() {
|
||||
if !line.trim_start().as_bytes().first().is_some_and(u8::is_ascii_digit) {
|
||||
continue; // skip header/summary lines; data rows start with an index
|
||||
}
|
||||
let ssid = line.get(8..41).unwrap_or("").trim().to_string();
|
||||
if ssid.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let bssid = line.get(41..61).unwrap_or("").trim().to_string();
|
||||
let security = line.get(61..84).unwrap_or("");
|
||||
let channel: u8 = line.get(4..8).and_then(|s| s.trim().parse().ok()).unwrap_or(0);
|
||||
let signal: i32 = line.get(84..92).and_then(|s| s.trim().parse().ok()).unwrap_or(-100);
|
||||
networks.push(ScannedNetwork {
|
||||
ssid,
|
||||
bssid,
|
||||
signal,
|
||||
channel,
|
||||
encryption: normalize_encryption(security),
|
||||
});
|
||||
}
|
||||
parse_iwinfo_scan(&output)
|
||||
networks.sort_by(|a, b| b.signal.cmp(&a.signal));
|
||||
Ok(networks)
|
||||
}
|
||||
|
||||
/// Returns `(interface_name, is_temporary)`.
|
||||
@ -31,6 +75,17 @@ fn find_wireless_iface(router: &Router) -> Result<(String, bool)> {
|
||||
return Ok((out.trim().to_string(), false));
|
||||
}
|
||||
|
||||
// Some vendor wifi drivers (e.g. MediaTek's out-of-tree `mt_wifi`/`mtk` SDK
|
||||
// driver used by GL.iNet and others) never register with cfg80211/mac80211,
|
||||
// so they have no `iw dev` entry and no /sys/class/ieee80211 phy even though
|
||||
// the radio is real and already up. `iwinfo` abstracts over those vendor
|
||||
// backends too, so fall back to its device listing before concluding there's
|
||||
// no radio at all.
|
||||
let (iwinfo_out, _) = router.run("iwinfo 2>/dev/null | awk '/^[A-Za-z]/{print $1; exit}'")?;
|
||||
if !iwinfo_out.trim().is_empty() {
|
||||
return Ok((iwinfo_out.trim().to_string(), false));
|
||||
}
|
||||
|
||||
// Find the phy — if this is empty the device has no WiFi hardware at all
|
||||
let (phy_out, _) = router.run("ls /sys/class/ieee80211/ 2>/dev/null | head -1")?;
|
||||
let phy = phy_out.trim().to_string();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user