use async_process::Command;
use std::{
fmt::Display,
io::BufRead,
process::{ExitStatus, Stdio},
};
mod ap_info;
mod err;
pub use ap_info::{BandWidth, SecondChannel, Security, Standard, BSS};
pub use err::IWError;
use err::Result;
pub struct IW {
can_sudo: bool,
}
#[derive(Debug, Clone, Copy)]
pub enum Mode {
Managed,
Ibss,
Monitor,
Mesh,
Wds,
}
impl Mode {
fn name(&self) -> &'static str {
match self {
Mode::Managed => "managed",
Mode::Ibss => "ibss",
Mode::Monitor => "monitor",
Mode::Mesh => "mesh",
Mode::Wds => "wds",
}
}
}
#[derive(Clone)]
pub struct Interface {
ifname: String,
}
impl Display for Interface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.ifname)
}
}
impl From<String> for Interface {
fn from(ifname: String) -> Self {
Self { ifname }
}
}
impl From<&str> for Interface {
fn from(value: &str) -> Self {
Self {
ifname: value.to_string(),
}
}
}
impl AsRef<str> for Interface {
fn as_ref(&self) -> &str {
self.ifname.as_str()
}
}
impl IW {
pub async fn new() -> Result<Self> {
let can_sudo = can_sudo().await;
check_command("iw").await?;
check_command("ifconfig").await?;
Ok(IW { can_sudo })
}
pub async fn interface_list(&self) -> Result<Vec<Interface>> {
let output = Command::new("sh")
.arg("-c")
.arg("iw dev | grep Interface")
.output()
.await
.map_err(|e| IWError::Unknown(format!("Failed to execute command: {}", e)))?;
let lines = output.stdout.lines();
let mut interfaces = Vec::new();
for line in lines.map_while(|i| i.ok()) {
if let Some(interface_name) = line.split_whitespace().nth(1) {
interfaces.push(interface_name.into());
}
}
Ok(interfaces)
}
fn command(&self, program: &str) -> Command {
if self.can_sudo {
let mut cmd = Command::new("sudo");
cmd.stdout(Stdio::inherit());
cmd.stdin(Stdio::inherit());
cmd.arg(program);
cmd
} else {
Command::new(program)
}
}
async fn ifdown(&self, interface: &Interface) -> Result<()> {
let status = self
.command("ifconfig")
.arg(interface.as_ref())
.arg("down")
.status()
.await?;
status.check(format!(
"Failed to bring down interface {}",
interface.as_ref()
))
}
async fn ifup(&self, interface: &Interface) -> Result<()> {
let status = self
.command("ifconfig")
.arg(interface.as_ref())
.arg("up")
.status()
.await?;
status.check(format!(
"Failed to bring up interface {}",
interface.as_ref()
))
}
pub async fn set_mode(&self, interface: &Interface, mode: Mode) -> Result<()> {
self.ifdown(interface).await?;
let status = self.command("iw")
.arg("dev")
.arg(interface.as_ref())
.arg("set")
.arg("type")
.arg(mode.name())
.status()
.await?;
status.check(format!(
"Failed to set mode for interface {}",
interface.as_ref()
))?;
self.ifup(interface).await?;
Ok(())
}
pub async fn scan(&self, interface: &Interface) -> Result<Vec<BSS>> {
self.set_mode(interface, Mode::Managed).await?;
let output = self.command("iw")
.args(["dev", interface.as_ref(), "scan"])
.output()
.await?;
output.status.check(format!(
"scan fail: {}",
String::from_utf8_lossy(&output.stderr)
))?;
let result = String::from_utf8_lossy(&output.stdout);
let out = BSS::parse_all(result.as_ref()).unwrap();
Ok(out)
}
pub async fn set_channel(
&self,
interface: &Interface,
channel: i32,
band_width: Option<BandWidth>,
second: Option<SecondChannel>,
) -> Result<()> {
let mut cmd = self.command("iw");
let c = cmd
.arg("dev")
.arg(interface.as_ref())
.arg("set")
.arg("channel")
.arg(format!("{}", channel));
if let Some(b) = band_width {
match b {
BandWidth::HT40 => {
if let Some(second) = second {
c.arg(match second {
SecondChannel::Below => "HT40-",
SecondChannel::Above => "HT40+",
});
}
}
_ => {
c.arg(b.cmd());
}
}
}
let status = c.status().await?;
status.check(format!(
"Failed to set channel for interface {}",
interface.as_ref()
))
}
pub async fn set_channel_by_bss(&self, interface: &Interface, bss: &BSS) -> Result<()> {
self.set_channel(interface, bss.channel, Some(bss.width), bss.second)
.await
}
pub async fn set_freq_by_bss(&self, interface: &Interface, bss: &BSS) -> Result<()> {
self.set_freq(interface, bss.freq_mhz, Some(bss.width), bss.second)
.await
}
pub async fn set_freq(
&self,
interface: &Interface,
freq_mhz: i32,
band_width: Option<BandWidth>,
second: Option<SecondChannel>,
) -> Result<()> {
let mut cmd = self.command("iw");
let c = cmd
.arg("dev")
.arg(interface.as_ref())
.arg("set")
.arg("freq")
.arg(format!("{}", freq_mhz));
if let Some(b) = band_width {
match b {
BandWidth::HT40 => {
if let Some(second) = second {
c.arg(match second {
SecondChannel::Below => "HT40-",
SecondChannel::Above => "HT40+",
});
}
}
_ => {
c.arg(b.cmd());
}
}
}
let status = c.status().await?;
status.check(format!(
"Failed to set channel for interface {}",
interface.as_ref()
))
}
}
async fn can_sudo() -> bool {
let output = Command::new("sudo").arg("whoami").output().await;
match output {
Ok(o) => o.status.success(),
Err(_) => false,
}
}
async fn check_command(cmd: &str) -> Result<()> {
let output = Command::new(cmd)
.arg("--version")
.output()
.await
.map_err(|e| IWError::NotSupport(format!("command [{}] fail: {:?}", cmd, e)))?;
if !output.status.success() {
Err(IWError::NotSupport(format!("[{}] not found!", cmd)))
} else {
Ok(())
}
}
trait ExitStatusExt {
fn check(&self, fail_msg: String) -> Result<()>;
}
impl ExitStatusExt for ExitStatus {
fn check(&self, fail_msg: String) -> Result<()> {
if !self.success() {
return Err(IWError::Unknown(fail_msg));
}
Ok(())
}
}