wifiscan 0.4.0

Wireless network scanner TUI with monitor mode, handshake capture, deauth, and evil twin
Documentation
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;

use crate::types::CurrentChannel;

pub fn get_supported_channels(iface: &str) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
    let output = std::process::Command::new("iw").args(["phy"]).output();
    let phy_output = std::process::Command::new("iw").args(["dev", iface, "info"]).output();

    let phy_name = match phy_output {
        Ok(o) => {
            let s = String::from_utf8_lossy(&o.stdout);
            let mut name = String::new();
            for line in s.lines() {
                let t = line.trim();
                if t.starts_with("wiphy ") {
                    name = format!("phy{}", t.strip_prefix("wiphy ").unwrap_or("0"));
                    break;
                }
            }
            name
        }
        Err(_) => String::new(),
    };

    let mut channels_2g = Vec::new();
    let mut channels_5g = Vec::new();
    let mut channels_6g = Vec::new();

    if let Ok(o) = output {
        let s = String::from_utf8_lossy(&o.stdout);
        let mut in_our_phy = phy_name.is_empty();
        let mut in_freq_section = false;

        for line in s.lines() {
            let trimmed = line.trim();
            if trimmed.starts_with("Wiphy ") {
                let this_phy = trimmed.strip_prefix("Wiphy ").unwrap_or("");
                in_our_phy = phy_name.is_empty() || this_phy == phy_name;
                in_freq_section = false;
                continue;
            }
            if !in_our_phy { continue; }
            if trimmed.starts_with("Frequencies:") { in_freq_section = true; continue; }
            if in_freq_section && !trimmed.starts_with('*') && !trimmed.contains("MHz") {
                in_freq_section = false;
                continue;
            }
            if in_freq_section && trimmed.contains("MHz") && !trimmed.contains("disabled") {
                let parts: Vec<&str> = trimmed.split_whitespace().collect();
                for (i, part) in parts.iter().enumerate() {
                    if *part == "MHz" && i > 0 {
                        if let Ok(freq) = parts[i - 1].replace('*', "").trim().parse::<u16>() {
                            let ch = crate::parse::frequency_to_channel(freq);
                            if ch > 0 {
                                if freq < 3000 && !channels_2g.contains(&ch) { channels_2g.push(ch); }
                                else if (3000..5900).contains(&freq) && !channels_5g.contains(&ch) { channels_5g.push(ch); }
                                else if freq >= 5900 && !channels_6g.contains(&ch) { channels_6g.push(ch); }
                            }
                        }
                        break;
                    }
                }
            }
        }
    }

    if channels_2g.is_empty() { channels_2g = (1..=13).collect(); }
    if channels_5g.is_empty() {
        channels_5g = vec![36,40,44,48,52,56,60,64,100,104,108,112,116,120,124,128,132,136,140,144,149,153,157,161,165];
    }
    (channels_2g, channels_5g, channels_6g)
}

#[allow(clippy::too_many_arguments)]
pub fn start_channel_hopper(
    iface: &str,
    fixed_channel: u8,
    current_channel: CurrentChannel,
    dwell_2g: u64,
    dwell_5g: u64,
    dwell_6g: u64,
    shutdown: Arc<AtomicBool>,
    hop_pause: Arc<AtomicBool>,
) {
    if fixed_channel > 0 {
        let _ = std::process::Command::new("iw")
            .args(["dev", iface, "set", "channel", &fixed_channel.to_string()])
            .output();
        *current_channel.lock().unwrap() = fixed_channel;
        return;
    }

    let (channels_2g, channels_5g, channels_6g) = get_supported_channels(iface);
    let iface = iface.to_string();

    std::thread::spawn(move || {
        let mut hop_sequence: Vec<(u8, u64)> = Vec::new();
        let max_len = channels_2g.len().max(channels_5g.len());
        for i in 0..max_len {
            if i < channels_2g.len() { hop_sequence.push((channels_2g[i], dwell_2g)); }
            if i < channels_5g.len() { hop_sequence.push((channels_5g[i], dwell_5g)); }
        }
        for ch in &channels_6g { hop_sequence.push((*ch, dwell_6g)); }
        if hop_sequence.is_empty() { return; }

        while !shutdown.load(Ordering::Relaxed) {
            for &(ch, dwell) in &hop_sequence {
                if shutdown.load(Ordering::Relaxed) { break; }
                if hop_pause.load(Ordering::Relaxed) {
                    std::thread::sleep(Duration::from_millis(100));
                    continue;
                }
                let result = std::process::Command::new("iw")
                    .args(["dev", &iface, "set", "channel", &ch.to_string()])
                    .output();
                if let Ok(o) = result {
                    if o.status.success() {
                        *current_channel.lock().unwrap() = ch;
                    }
                }
                std::thread::sleep(Duration::from_millis(dwell));
            }
        }
    });
}