use super::bridge;
use super::error::{NetworkError, Result};
use super::interface;
use super::mycelium;
use super::namespace;
#[cfg(target_os = "linux")]
use super::nat;
#[cfg(target_os = "linux")]
use super::tap;
use super::types::{MyceliumConfig, NetworkResource, VmNetworkConfig};
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
const VETH_NS: &str = "veth-myci";
const VETH_HOST: &str = "veth-host";
const MYCELIUM_TUN: &str = "myc0";
const TAP_PREFIX: &str = "tap";
pub fn create_network_resource(
name: &str,
mycelium_config: &MyceliumConfig,
bridge_ipv4: Option<&Ipv4Network>,
outbound_iface: &str,
) -> Result<NetworkResource> {
let bridge_name = format!("br-{}", name);
let ns_name = format!("n{}", name);
let veth_ns = format!("{}-{}", VETH_NS, name);
let veth_host = format!("{}-{}", VETH_HOST, name);
bridge::create_bridge(&bridge_name)?;
let netns = match namespace::get_namespace_by_name(&ns_name) {
Ok(ns) => ns,
Err(_) => namespace::create_namespace(&ns_name)?,
};
if !interface::exists(&veth_ns, Some(&netns))? {
let veth = interface::create_veth_pair(&veth_ns, &veth_host, 1500, "")?;
interface::move_to_namespace(&veth.local_name, &netns)?;
interface::attach_to_bridge(&veth.peer_name, &bridge_name)?;
}
let inspection = mycelium::inspect_mycelium(&mycelium_config.seed)?;
let subnet = inspection.subnet()?;
let gateway = inspection.gateway()?;
mycelium::start_mycelium_daemon(&netns, MYCELIUM_TUN, mycelium_config)?;
let gateway_network = IpNetwork::V6(
Ipv6Network::new(gateway.ip(), 64)
.map_err(|e| NetworkError::Parse(e.to_string()))?
);
interface::set_up("lo", Some(&netns))?;
interface::set_addr(&veth_ns, &gateway_network, Some(&netns))?;
interface::set_up(&veth_ns, Some(&netns))?;
interface::enable_ipv6_forwarding(Some(&netns))?;
interface::set_up(&bridge_name, None)?;
nat::add_bridge_forward_rule(&bridge_name)?;
if let Some(ipv4_net) = bridge_ipv4 {
let ipv4_network = IpNetwork::V4(*ipv4_net);
interface::set_addr(&bridge_name, &ipv4_network, None)?;
nat::add_masquerade_rule(&bridge_name, outbound_iface)?;
}
nat::enable_ip_forwarding()?;
interface::enable_ipv6_forwarding(None)?;
let resource = NetworkResource {
name: name.to_string(),
bridge_name: bridge_name.clone(),
namespace_name: ns_name,
mycelium_address: Some(inspection.address),
mycelium_subnet: Some(subnet),
mycelium_gateway: Some(gateway.ip()),
bridge_ipv4: bridge_ipv4.copied(),
next_vm_index: 2, };
log::info!(
"Network resource '{}' created with mycelium address {}",
name,
inspection.address
);
Ok(resource)
}
pub fn add_vm_to_resource(
resource: &mut NetworkResource,
vm_name: &str,
) -> Result<VmNetworkConfig> {
let tap_name = format!("{}_{}", TAP_PREFIX, vm_name);
tap::create_tap(&tap_name, &resource.bridge_name)?;
let mycelium_ip = resource.mycelium_subnet.map(|subnet| {
let mut ip_bytes = subnet.network().octets();
ip_bytes[15] = resource.next_vm_index;
std::net::Ipv6Addr::from(ip_bytes)
});
let private_ip = resource.bridge_ipv4.map(|subnet| {
let mut ip_bytes = subnet.network().octets();
ip_bytes[3] = resource.next_vm_index;
std::net::Ipv4Addr::from(ip_bytes)
});
resource.next_vm_index = resource.next_vm_index.saturating_add(1);
log::info!(
"VM '{}' added to resource '{}' with TAP {} and IP {:?}",
vm_name,
resource.name,
tap_name,
mycelium_ip
);
Ok(VmNetworkConfig {
vm_name: vm_name.to_string(),
resource_name: resource.name.clone(),
tap_device: tap_name,
mycelium_ip,
private_ip,
})
}
pub fn remove_vm_from_resource(vm_config: &VmNetworkConfig) -> Result<()> {
tap::delete_tap(&vm_config.tap_device)?;
log::info!(
"VM '{}' removed from resource '{}'",
vm_config.vm_name,
vm_config.resource_name
);
Ok(())
}
pub fn delete_network_resource(name: &str) -> Result<()> {
let bridge_name = format!("br-{}", name);
let ns_name = format!("n{}", name);
let veth_host = format!("{}-{}", VETH_HOST, name);
if let Ok(netns) = namespace::get_namespace_by_name(&ns_name) {
let _ = mycelium::destroy_mycelium(&netns);
namespace::delete_namespace(netns)?;
}
let _ = interface::delete_interface(&veth_host);
let _ = bridge::delete_bridge(&bridge_name);
log::info!("Network resource '{}' deleted", name);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_interface_constants() {
assert_eq!(VETH_NS, "veth-myci");
assert_eq!(VETH_HOST, "veth-host");
assert_eq!(MYCELIUM_TUN, "myc0");
assert_eq!(TAP_PREFIX, "tap");
}
#[test]
fn test_bridge_name_generation() {
let name = "test";
let bridge_name = format!("br-{}", name);
assert_eq!(bridge_name, "br-test");
}
#[test]
fn test_namespace_name_generation() {
let name = "mynetwork";
let ns_name = format!("n{}", name);
assert_eq!(ns_name, "nmynetwork");
}
#[test]
fn test_veth_name_generation() {
let name = "vm1";
let veth_ns = format!("{}-{}", VETH_NS, name);
let veth_host = format!("{}-{}", VETH_HOST, name);
assert_eq!(veth_ns, "veth-myci-vm1");
assert_eq!(veth_host, "veth-host-vm1");
}
#[test]
fn test_tap_name_generation() {
let vm_name = "myvm";
let tap_name = format!("{}_{}", TAP_PREFIX, vm_name);
assert_eq!(tap_name, "tap_myvm");
}
#[test]
fn test_network_resource_structure() {
let resource = NetworkResource {
name: "test".to_string(),
bridge_name: "br-test".to_string(),
namespace_name: "ntest".to_string(),
mycelium_address: None,
mycelium_subnet: None,
mycelium_gateway: None,
bridge_ipv4: None,
next_vm_index: 2,
};
assert_eq!(resource.name, "test");
assert_eq!(resource.bridge_name, "br-test");
assert_eq!(resource.next_vm_index, 2);
}
#[test]
fn test_vm_ip_generation() {
let subnet = Ipv6Network::from_str("2001:db8::/64").unwrap();
let vm_index: u8 = 2;
let mut ip_bytes = subnet.network().octets();
ip_bytes[15] = vm_index;
let vm_ip = std::net::Ipv6Addr::from(ip_bytes);
assert_eq!(vm_ip.to_string(), "2001:db8::2");
}
#[test]
fn test_ipv4_generation() {
let subnet = Ipv4Network::from_str("192.168.1.0/24").unwrap();
let vm_index: u8 = 2;
let mut ip_bytes = subnet.network().octets();
ip_bytes[3] = vm_index;
let vm_ip = std::net::Ipv4Addr::from(ip_bytes);
assert_eq!(vm_ip.to_string(), "192.168.1.2");
}
}