use std::net::IpAddr;
use async_trait::async_trait;
use tokio::process::Command;
use crate::interface::InterfaceOps;
use crate::OverlayError;
fn v4_netmask(prefix_len: u8) -> String {
let bits = std::cmp::min(prefix_len, 32);
let mask: u32 = if bits == 0 {
0
} else {
u32::MAX << (32 - bits)
};
let octet0 = (mask >> 24) & 0xff;
let octet1 = (mask >> 16) & 0xff;
let octet2 = (mask >> 8) & 0xff;
let octet3 = mask & 0xff;
format!("{octet0}.{octet1}.{octet2}.{octet3}")
}
pub(crate) struct MacIfconfigOps;
impl MacIfconfigOps {
pub(crate) fn new() -> Self {
Self
}
}
#[async_trait]
impl InterfaceOps for MacIfconfigOps {
async fn link_exists(&self, name: &str) -> Result<bool, OverlayError> {
let output = Command::new("ifconfig")
.arg(name)
.output()
.await
.map_err(OverlayError::Io)?;
Ok(output.status.success())
}
async fn delete_link(&self, name: &str) -> Result<(), OverlayError> {
let output = Command::new("ifconfig")
.args([name, "destroy"])
.output()
.await
.map_err(OverlayError::Io)?;
if output.status.success() {
return Ok(());
}
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("does not exist") || stderr.contains("no such") {
return Ok(());
}
Err(OverlayError::NetworkConfig(format!(
"failed to destroy interface {name}: {stderr}"
)))
}
async fn set_link_up(&self, name: &str) -> Result<(), OverlayError> {
let output = Command::new("ifconfig")
.args([name, "up"])
.output()
.await
.map_err(OverlayError::Io)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(OverlayError::NetworkConfig(format!(
"failed to bring interface up: {stderr}"
)));
}
Ok(())
}
async fn add_address(
&self,
name: &str,
addr: IpAddr,
prefix_len: u8,
) -> Result<(), OverlayError> {
let ip_str = addr.to_string();
let output = match addr {
IpAddr::V4(_) => {
let netmask = v4_netmask(prefix_len);
Command::new("ifconfig")
.args([name, "inet", &ip_str, &ip_str, "netmask", &netmask, "up"])
.output()
.await
.map_err(OverlayError::Io)?
}
IpAddr::V6(_) => {
let prefix_str = prefix_len.to_string();
Command::new("ifconfig")
.args([name, "inet6", &ip_str, "prefixlen", &prefix_str, "up"])
.output()
.await
.map_err(OverlayError::Io)?
}
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(OverlayError::NetworkConfig(format!(
"Failed to configure interface: {stderr}"
)));
}
Ok(())
}
async fn add_route_via_dev(
&self,
dest: IpAddr,
prefix_len: u8,
name: &str,
) -> Result<(), OverlayError> {
let network_cidr = format!("{dest}/{prefix_len}");
let output = match dest {
IpAddr::V4(_) => Command::new("route")
.args(["-n", "add", "-net", &network_cidr, "-interface", name])
.output()
.await
.map_err(OverlayError::Io)?,
IpAddr::V6(_) => Command::new("route")
.args(["-n", "add", "-inet6", &network_cidr, "-interface", name])
.output()
.await
.map_err(OverlayError::Io)?,
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("already in table") {
return Err(OverlayError::NetworkConfig(format!(
"Failed to add route: {stderr}"
)));
}
}
Ok(())
}
}