use crate::domain::NetworkInterface;
pub fn parse_ip_output(ip_output: &str) -> Result<Vec<NetworkInterface>, String> {
let mut interfaces = Vec::new();
let mut current_interface: Option<NetworkInterface> = None;
for line in ip_output.lines() {
let trimmed = line.trim();
if !line.starts_with(' ') && line.contains(": ") {
if let Some(interface) = current_interface.take() {
if interface.name != "lo" {
interfaces.push(interface);
}
}
let parts: Vec<&str> = line.splitn(3, ':').collect();
if parts.len() >= 2 {
let name = parts[1].trim().to_string();
if name == "lo" {
continue;
}
let interface_type = if name.starts_with("tailscale") {
"tailscale".to_string()
} else if name.starts_with("wl") {
"wireless".to_string()
} else if name.starts_with("docker") || name.starts_with("br-") {
"bridge".to_string()
} else if name.starts_with("veth") {
"veth".to_string()
} else {
"ethernet".to_string()
};
current_interface = Some(NetworkInterface {
name,
mac: String::new(),
ip: String::new(),
prefix: String::new(),
speed: None,
type_: interface_type,
vendor: "Unknown".to_string(),
model: "Unknown".to_string(),
pci_id: "Unknown".to_string(),
numa_node: None,
..Default::default()
});
}
} else if let Some(ref mut interface) = current_interface {
if trimmed.starts_with("link/ether ") {
if let Some(mac) = trimmed.split_whitespace().nth(1) {
interface.mac = mac.to_string();
}
}
else if trimmed.starts_with("link/none") {
interface.mac = "00:00:00:00:00:00".to_string();
}
else if trimmed.starts_with("inet ") {
if let Some(addr_with_prefix) = trimmed.split_whitespace().nth(1) {
let parts: Vec<&str> = addr_with_prefix.split('/').collect();
if !parts.is_empty() {
if interface.ip.is_empty() {
interface.ip = parts[0].to_string();
if parts.len() > 1 {
interface.prefix = parts[1].to_string();
}
}
}
}
}
}
}
if let Some(interface) = current_interface {
if interface.name != "lo" {
interfaces.push(interface);
}
}
Ok(interfaces)
}
pub fn parse_macos_network_info(ifconfig_output: &str) -> Result<Vec<NetworkInterface>, String> {
let mut interfaces = Vec::new();
let mut current_interface: Option<NetworkInterface> = None;
for line in ifconfig_output.lines() {
let trimmed = line.trim();
if !line.starts_with(' ') && !line.starts_with('\t') && line.contains(':') {
if let Some(interface) = current_interface.take() {
interfaces.push(interface);
}
if let Some(name) = line.split(':').next() {
let interface_type = classify_interface_type(name);
let vendor = if name.starts_with("en") || name.starts_with("bridge") {
"Apple"
} else {
"Unknown"
};
let model = match interface_type.as_str() {
"AirPort" => "Wi-Fi 802.11 a/b/g/n/ac/ax",
"Ethernet" => "Ethernet",
"VPN (io.tailscale.ipn.macos)" => "Unknown",
_ => "Unknown",
};
let pci_id = if vendor == "Apple" {
"Apple Fabric (Integrated)".to_string()
} else {
"Unknown".to_string()
};
current_interface = Some(NetworkInterface {
name: name.to_string(),
mac: "Unknown".to_string(),
ip: "Unknown".to_string(),
prefix: "Unknown".to_string(),
speed: None,
type_: interface_type,
vendor: vendor.to_string(),
model: model.to_string(),
pci_id,
numa_node: None,
..Default::default()
});
}
} else if let Some(ref mut interface) = current_interface {
if trimmed.starts_with("ether ") {
if let Some(mac) = trimmed.split_whitespace().nth(1) {
interface.mac = mac.chars().take(2).collect();
}
} else if trimmed.starts_with("inet ") {
if let Some(ip) = trimmed.split_whitespace().nth(1) {
interface.ip = ip.to_string();
}
}
}
}
if let Some(interface) = current_interface {
interfaces.push(interface);
}
for interface in &mut interfaces {
interface.speed = estimate_interface_speed(&interface.name, &interface.type_);
}
Ok(interfaces)
}
fn classify_interface_type(name: &str) -> String {
if name.starts_with("en") && name != "en0" && !name.starts_with("en1") {
"Ethernet".to_string()
} else if name == "en0" {
"AirPort".to_string() } else if name.starts_with("bridge") {
"Ethernet".to_string()
} else if name.starts_with("utun") {
"VPN (io.tailscale.ipn.macos)".to_string()
} else if name.starts_with("lo") {
"Loopback".to_string()
} else {
"Unknown".to_string()
}
}
fn estimate_interface_speed(name: &str, interface_type: &str) -> Option<String> {
match interface_type {
"AirPort" => Some("1200 Mbps".to_string()), "Ethernet" if name.starts_with("en") => Some("1000 Mbps".to_string()),
_ => Some("Unknown".to_string()),
}
}