#[allow(unused_imports)]
use super::cmd::{run_cmd, TIMEOUT_MEDIUM, TIMEOUT_QUICK, TIMEOUT_SLOW};
#[cfg(windows)]
pub async fn list_active_adapters() -> Vec<String> {
tokio::task::spawn_blocking(|| {
let adapters = match ipconfig::get_adapters() {
Ok(a) => a,
Err(_) => return Vec::new(),
};
adapters
.into_iter()
.filter(|a| a.oper_status() == ipconfig::OperStatus::IfOperStatusUp)
.filter(|a| a.if_type() != ipconfig::IfType::SoftwareLoopback)
.map(|a| a.friendly_name().to_string())
.collect()
})
.await
.unwrap_or_default()
}
#[cfg(target_os = "macos")]
pub async fn list_active_adapters() -> Vec<String> {
let mut adapters = Vec::new();
let mut cmd = tokio::process::Command::new("networksetup");
cmd.args(["-listallhardwareports"]);
if let Ok(output) = run_cmd(cmd, TIMEOUT_QUICK).await {
let text = String::from_utf8_lossy(&output.stdout);
let mut current_name = String::new();
for line in text.lines() {
if let Some(name) = line.strip_prefix("Hardware Port: ") {
current_name = name.trim().to_string();
} else if let Some(dev) = line.strip_prefix("Device: ") {
let device = dev.trim();
let mut ifcfg_cmd = tokio::process::Command::new("ifconfig");
ifcfg_cmd.arg(device);
if let Ok(status_output) = run_cmd(ifcfg_cmd, TIMEOUT_QUICK).await {
let status_text = String::from_utf8_lossy(&status_output.stdout);
if status_text.contains("status: active") || status_text.contains("inet ") {
adapters.push(format!("{}:{}", current_name, device));
}
}
}
}
}
adapters
}
#[cfg(target_os = "linux")]
pub async fn list_active_adapters() -> Vec<String> {
let mut adapters = Vec::new();
let mut cmd = tokio::process::Command::new("ip");
cmd.args(["link", "show", "up"]);
if let Ok(output) = run_cmd(cmd, TIMEOUT_QUICK).await {
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
if let Some(colon_idx) = line.find(": ") {
let after = &line[colon_idx + 2..];
if let Some(name_end) = after.find(':') {
let name = &after[..name_end];
if name != "lo"
&& !name.starts_with("veth")
&& !name.starts_with("docker")
&& !name.starts_with("br-")
{
adapters.push(name.to_string());
}
}
}
}
}
adapters
}
#[cfg(windows)]
pub async fn restart_adapter(name: &str) -> Result<String, String> {
let mut disable_cmd = tokio::process::Command::new("netsh");
disable_cmd.args(["interface", "set", "interface", name, "disabled"]);
let disable = run_cmd(disable_cmd, TIMEOUT_SLOW).await;
match disable {
Ok(output) if !output.status.success() => {
return Err(format!(
"Failed to disable {}: {}",
name,
String::from_utf8_lossy(&output.stderr).trim()
));
}
Err(e) => return Err(e),
_ => {}
}
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let mut enable_cmd = tokio::process::Command::new("netsh");
enable_cmd.args(["interface", "set", "interface", name, "enabled"]);
match run_cmd(enable_cmd, TIMEOUT_SLOW).await {
Ok(output) if output.status.success() => Ok(format!("{} restarted", name)),
Ok(output) => Err(format!(
"Failed to re-enable {}: {}",
name,
String::from_utf8_lossy(&output.stderr).trim()
)),
Err(e) => Err(e),
}
}
#[cfg(target_os = "macos")]
pub async fn restart_adapter(name_and_device: &str) -> Result<String, String> {
let parts: Vec<&str> = name_and_device.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(format!("Invalid adapter format: {}", name_and_device));
}
let name = parts[0];
let device = parts[1];
let is_wifi = name.to_lowercase().contains("wi-fi") || name.to_lowercase().contains("airport");
if is_wifi {
let mut off_cmd = tokio::process::Command::new("networksetup");
off_cmd.args(["-setairportpower", device, "off"]);
let off = run_cmd(off_cmd, TIMEOUT_MEDIUM).await;
if let Ok(output) = &off {
if !output.status.success() {
return Err(format!(
"Failed to disable Wi-Fi: {}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
}
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let mut on_cmd = tokio::process::Command::new("networksetup");
on_cmd.args(["-setairportpower", device, "on"]);
match run_cmd(on_cmd, TIMEOUT_MEDIUM).await {
Ok(output) if output.status.success() => Ok(format!("{} restarted", name)),
Ok(output) => Err(format!(
"Failed to re-enable Wi-Fi: {}",
String::from_utf8_lossy(&output.stderr).trim()
)),
Err(e) => Err(e),
}
} else {
let mut down_cmd = tokio::process::Command::new("ifconfig");
down_cmd.args([device, "down"]);
let down = run_cmd(down_cmd, TIMEOUT_MEDIUM).await;
if let Ok(output) = &down {
if !output.status.success() {
return Err(format!(
"Failed to disable {}: {}",
name,
String::from_utf8_lossy(&output.stderr).trim()
));
}
}
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let mut up_cmd = tokio::process::Command::new("ifconfig");
up_cmd.args([device, "up"]);
match run_cmd(up_cmd, TIMEOUT_MEDIUM).await {
Ok(output) if output.status.success() => Ok(format!("{} restarted", name)),
Ok(output) => Err(format!(
"Failed to re-enable {}: {}",
name,
String::from_utf8_lossy(&output.stderr).trim()
)),
Err(e) => Err(e),
}
}
}
#[cfg(target_os = "linux")]
pub async fn restart_adapter(name: &str) -> Result<String, String> {
let mut down_cmd = tokio::process::Command::new("ip");
down_cmd.args(["link", "set", name, "down"]);
let down = run_cmd(down_cmd, TIMEOUT_MEDIUM).await;
match down {
Ok(output) if !output.status.success() => {
return Err(format!(
"Failed to disable {}: {}",
name,
String::from_utf8_lossy(&output.stderr).trim()
));
}
Err(e) => return Err(e),
_ => {}
}
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let mut up_cmd = tokio::process::Command::new("ip");
up_cmd.args(["link", "set", name, "up"]);
match run_cmd(up_cmd, TIMEOUT_MEDIUM).await {
Ok(output) if output.status.success() => Ok(format!("{} restarted", name)),
Ok(output) => Err(format!(
"Failed to re-enable {}: {}",
name,
String::from_utf8_lossy(&output.stderr).trim()
)),
Err(e) => Err(e),
}
}
pub async fn detect_default_interface() -> Option<String> {
#[cfg(windows)]
{
tokio::task::spawn_blocking(|| {
let adapters = match ipconfig::get_adapters() {
Ok(a) => a,
Err(_) => return None,
};
adapters
.into_iter()
.filter(|a| a.oper_status() == ipconfig::OperStatus::IfOperStatusUp)
.filter(|a| !a.gateways().is_empty())
.min_by_key(|a| a.ipv4_metric())
.map(|a| a.friendly_name().to_string())
})
.await
.unwrap_or(None)
}
#[cfg(target_os = "macos")]
{
let mut cmd = tokio::process::Command::new("route");
cmd.args(["-n", "get", "default"]);
if let Ok(output) = run_cmd(cmd, TIMEOUT_QUICK).await {
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
let line = line.trim();
if let Some(iface) = line.strip_prefix("interface:") {
return Some(iface.trim().to_string());
}
}
}
None
}
#[cfg(target_os = "linux")]
{
let mut cmd = tokio::process::Command::new("ip");
cmd.args(["route", "show", "default"]);
if let Ok(output) = run_cmd(cmd, TIMEOUT_QUICK).await {
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
if let Some(idx) = line.find("dev ") {
let after = &line[idx + 4..];
if let Some(name) = after.split_whitespace().next() {
return Some(name.to_string());
}
}
}
}
None
}
}
pub async fn renew_dhcp_on_interface(iface: &str) -> Result<String, String> {
#[cfg(windows)]
{
let mut release_cmd = tokio::process::Command::new("ipconfig");
release_cmd.args(["/release", iface]);
let release = run_cmd(release_cmd, TIMEOUT_SLOW).await;
if let Ok(output) = &release {
if !output.status.success() {
}
}
let mut renew_cmd = tokio::process::Command::new("ipconfig");
renew_cmd.args(["/renew", iface]);
match run_cmd(renew_cmd, TIMEOUT_SLOW).await {
Ok(output) if output.status.success() => Ok(format!("DHCP renewed on {}", iface)),
Ok(output) => Err(format!(
"DHCP renew failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
)),
Err(e) => Err(e),
}
}
#[cfg(target_os = "macos")]
{
let mut cmd = tokio::process::Command::new("ipconfig");
cmd.args(["set", iface, "DHCP"]);
match run_cmd(cmd, TIMEOUT_SLOW).await {
Ok(output) if output.status.success() => Ok(format!("DHCP renewed on {}", iface)),
Ok(output) => Err(format!(
"DHCP renew failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
)),
Err(e) => Err(e),
}
}
#[cfg(target_os = "linux")]
{
let mut nmcli_cmd = tokio::process::Command::new("nmcli");
nmcli_cmd.args(["device", "connect", iface]);
if let Ok(output) = run_cmd(nmcli_cmd, TIMEOUT_MEDIUM).await {
if output.status.success() {
return Ok(format!("DHCP renewed on {} via nmcli", iface));
}
}
let mut dhcpcd_cmd = tokio::process::Command::new("dhcpcd");
dhcpcd_cmd.args(["-n", iface]);
if let Ok(output) = run_cmd(dhcpcd_cmd, TIMEOUT_SLOW).await {
if output.status.success() {
return Ok(format!("DHCP renewed on {} via dhcpcd", iface));
}
}
let mut release_cmd = tokio::process::Command::new("dhclient");
release_cmd.args(["-r", iface]);
let _ = run_cmd(release_cmd, TIMEOUT_SLOW).await;
let mut renew_cmd = tokio::process::Command::new("dhclient");
renew_cmd.arg(iface);
match run_cmd(renew_cmd, TIMEOUT_SLOW).await {
Ok(output) if output.status.success() => {
Ok(format!("DHCP renewed on {} via dhclient", iface))
}
_ => Err(format!("Could not renew DHCP on {}", iface)),
}
}
}