use super::error::{NetworkError, Result};
use super::namespace::NetNamespace;
use super::types::VethPair;
use ipnetwork::IpNetwork;
use std::process::Command;
pub fn create_veth_pair(
local_name: &str,
peer_name: &str,
mtu: u32,
prefix: &str,
) -> Result<VethPair> {
let peer_full_name = if prefix.is_empty() {
peer_name.to_string()
} else {
format!("{}-{}", prefix, peer_name)
};
let output = Command::new("ip")
.args([
"link",
"add",
local_name,
"type",
"veth",
"peer",
"name",
&peer_full_name,
])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to create veth pair: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to create veth pair: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
for iface in &[local_name, &peer_full_name] {
let output = Command::new("ip")
.args(["link", "set", "dev", iface, "mtu", &mtu.to_string()])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to set MTU: {}", e)))?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to set MTU for {}: {}",
iface,
String::from_utf8_lossy(&output.stderr)
)));
}
}
Ok(VethPair {
local_name: local_name.to_string(),
peer_name: peer_full_name,
mtu,
})
}
pub fn move_to_namespace(interface: &str, ns: &NetNamespace) -> Result<()> {
let output = Command::new("ip")
.args(["link", "set", interface, "netns", &ns.name])
.output()
.map_err(|e| {
NetworkError::Interface(format!("Failed to move interface to namespace: {}", e))
})?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to move interface {} to namespace: {}",
interface,
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
pub fn exists(name: &str, ns: Option<&NetNamespace>) -> Result<bool> {
let output = if let Some(namespace) = ns {
Command::new("ip")
.args(["netns", "exec", &namespace.name, "ip", "link", "show", name])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to check interface: {}", e)))?
} else {
Command::new("ip")
.args(["link", "show", name])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to check interface: {}", e)))?
};
Ok(output.status.success())
}
pub fn set_up(name: &str, ns: Option<&NetNamespace>) -> Result<()> {
let output = if let Some(namespace) = ns {
Command::new("ip")
.args([
"netns",
"exec",
&namespace.name,
"ip",
"link",
"set",
"dev",
name,
"up",
])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to set interface up: {}", e)))?
} else {
Command::new("ip")
.args(["link", "set", "dev", name, "up"])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to set interface up: {}", e)))?
};
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to set interface {} up: {}",
name,
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
pub fn set_addr(name: &str, addr: &IpNetwork, ns: Option<&NetNamespace>) -> Result<()> {
let addr_str = addr.to_string();
let output = if let Some(namespace) = ns {
Command::new("ip")
.args([
"netns",
"exec",
&namespace.name,
"ip",
"addr",
"add",
&addr_str,
"dev",
name,
])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to set IP address: {}", e)))?
} else {
Command::new("ip")
.args(["addr", "add", &addr_str, "dev", name])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to set IP address: {}", e)))?
};
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("File exists") {
return Err(NetworkError::Interface(format!(
"Failed to set IP address {} on {}: {}",
addr_str, name, stderr
)));
}
}
Ok(())
}
pub fn attach_to_bridge(interface: &str, bridge: &str) -> Result<()> {
let output = Command::new("ip")
.args(["link", "set", interface, "master", bridge])
.output()
.map_err(|e| {
NetworkError::Interface(format!("Failed to attach interface to bridge: {}", e))
})?;
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to attach interface {} to bridge {}: {}",
interface,
bridge,
String::from_utf8_lossy(&output.stderr)
)));
}
set_up(interface, None)?;
Ok(())
}
pub fn delete_interface(name: &str) -> Result<()> {
let output = Command::new("ip")
.args(["link", "delete", name])
.output()
.map_err(|e| NetworkError::Interface(format!("Failed to delete interface: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("Cannot find device") {
return Err(NetworkError::Interface(format!(
"Failed to delete interface {}: {}",
name, stderr
)));
}
}
Ok(())
}
pub fn enable_ipv6_forwarding(ns: Option<&NetNamespace>) -> Result<()> {
let output = if let Some(namespace) = ns {
Command::new("ip")
.args([
"netns",
"exec",
&namespace.name,
"sysctl",
"-w",
"net.ipv6.conf.all.forwarding=1",
])
.output()
.map_err(|e| {
NetworkError::Interface(format!("Failed to enable IPv6 forwarding: {}", e))
})?
} else {
Command::new("sysctl")
.args(["-w", "net.ipv6.conf.all.forwarding=1"])
.output()
.map_err(|e| {
NetworkError::Interface(format!("Failed to enable IPv6 forwarding: {}", e))
})?
};
if !output.status.success() {
return Err(NetworkError::Interface(format!(
"Failed to enable IPv6 forwarding: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_veth_pair_struct() {
let veth = VethPair {
local_name: "veth0".to_string(),
peer_name: "veth1".to_string(),
mtu: 1500,
};
assert_eq!(veth.local_name, "veth0");
assert_eq!(veth.peer_name, "veth1");
assert_eq!(veth.mtu, 1500);
}
#[test]
fn test_veth_pair_with_prefix() {
let _local = "public";
let peer = "br0";
let prefix = "test";
let expected_peer = format!("{}-{}", prefix, peer);
assert_eq!(expected_peer, "test-br0");
}
#[test]
fn test_veth_pair_without_prefix() {
let _local = "public";
let peer = "br0";
let prefix = "";
let expected_peer = if prefix.is_empty() {
peer.to_string()
} else {
format!("{}-{}", prefix, peer)
};
assert_eq!(expected_peer, "br0");
}
#[test]
fn test_mtu_values() {
let standard_mtu = 1500u32;
let jumbo_mtu = 9000u32;
assert_eq!(standard_mtu, 1500);
assert_eq!(jumbo_mtu, 9000);
}
}