use crate::vortix_core::ports::interface::Interface;
use crate::vortix_process::CommandSpec;
fn cmd_output(program: &str, args: &[&str]) -> Option<std::process::Output> {
let owned: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
crate::vortix_process::run_to_output(CommandSpec::oneshot(program, owned)).ok()
}
pub struct LinuxInterface;
impl Interface for LinuxInterface {
fn check_wireguard_interface(name: &str) -> bool {
check_wg_interface_exists(name)
}
fn resolve_wireguard_interface(name: &str) -> Option<String> {
if check_wg_interface_exists(name) {
return Some(name.to_string());
}
if let Some(output) = cmd_output("wg", &["show"]) {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.starts_with("interface: ") {
let iface = line.trim_start_matches("interface: ").trim();
if iface == name {
return Some(iface.to_string());
}
}
}
}
None
}
fn get_wireguard_pid(interface: &str) -> Option<u32> {
if let Some(output) = cmd_output("ps", &["-eo", "pid,args"]) {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let line_lower = line.to_lowercase();
if line_lower.contains("wireguard")
&& line_lower.contains(&interface.to_lowercase())
{
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(pid_str) = parts.first() {
if let Ok(pid) = pid_str.parse::<u32>() {
return Some(pid);
}
}
}
}
}
None
}
fn get_interface_info(interface: &str) -> (String, String) {
if let Some(output) = cmd_output("ip", &["addr", "show", interface]) {
let stdout = String::from_utf8_lossy(&output.stdout);
return parse_ip_addr_output(&stdout);
}
(String::new(), String::new())
}
}
fn check_wg_interface_exists(name: &str) -> bool {
cmd_output("wg", &["show", name, "public-key"]).is_some_and(|o| o.status.success())
}
pub(crate) fn parse_ip_addr_output(output: &str) -> (String, String) {
let mut ip = String::new();
let mut mtu = String::new();
for line in output.lines() {
let trimmed = line.trim();
if trimmed.contains("mtu ") && mtu.is_empty() {
if let Some(mtu_idx) = trimmed.find("mtu ") {
let rest = &trimmed[mtu_idx + 4..];
if let Some(val) = rest.split_whitespace().next() {
mtu = val.to_string();
}
}
}
if trimmed.starts_with("inet ") && ip.is_empty() {
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() >= 2 {
ip = parts[1].split('/').next().unwrap_or("").to_string();
}
}
}
(ip, mtu)
}
#[cfg(test)]
mod tests {
use super::parse_ip_addr_output;
#[test]
fn test_parse_ip_addr_output_wireguard() {
let output = "4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.0.0.2/32 scope global wg0
valid_lft forever preferred_lft forever";
let (ip, mtu) = parse_ip_addr_output(output);
assert_eq!(ip, "10.0.0.2");
assert_eq!(mtu, "1420");
}
#[test]
fn test_parse_ip_addr_output_tun() {
let output = "5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 10.8.0.6 peer 10.8.0.5/32 scope global tun0
valid_lft forever preferred_lft forever";
let (ip, mtu) = parse_ip_addr_output(output);
assert_eq!(ip, "10.8.0.6");
assert_eq!(mtu, "1500");
}
#[test]
fn test_parse_ip_addr_output_empty() {
let (ip, mtu) = parse_ip_addr_output("");
assert_eq!(ip, "");
assert_eq!(mtu, "");
}
}