herolib-mos 0.3.13

Mycelium Operating System (MOS) - Network and VM abstraction layer
Documentation
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};

/// Interface name for veth inside the mycelium namespace
const VETH_NS: &str = "veth-myci";
/// Interface name for veth on the host side (attached to bridge)
const VETH_HOST: &str = "veth-host";
/// TUN interface name created by mycelium daemon
const MYCELIUM_TUN: &str = "myc0";
/// TAP device prefix for VMs
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);

    // 1. Create the host bridge
    bridge::create_bridge(&bridge_name)?;

    // 2. Create the mycelium namespace
    let netns = match namespace::get_namespace_by_name(&ns_name) {
        Ok(ns) => ns,
        Err(_) => namespace::create_namespace(&ns_name)?,
    };

    // 3. Create veth pair connecting namespace to bridge
    // veth-myci (inside namespace) <-> veth-host (attached to bridge)
    if !interface::exists(&veth_ns, Some(&netns))? {
        let veth = interface::create_veth_pair(&veth_ns, &veth_host, 1500, "")?;
        
        // Move veth-myci into the namespace
        interface::move_to_namespace(&veth.local_name, &netns)?;
        
        // Attach veth-host to the bridge and bring it up
        interface::attach_to_bridge(&veth.peer_name, &bridge_name)?;
    }

    // 4. Inspect mycelium seed to get network info
    let inspection = mycelium::inspect_mycelium(&mycelium_config.seed)?;
    let subnet = inspection.subnet()?;
    let gateway = inspection.gateway()?;

    // 5. Start mycelium daemon in namespace FIRST (creates myc0 TUN)
    // The daemon auto-configures the TUN interface with the mycelium address
    mycelium::start_mycelium_daemon(&netns, MYCELIUM_TUN, mycelium_config)?;

    // 6. Configure namespace interfaces
    // Gateway IP goes on veth-myci so VMs can route through the namespace to mycelium
    let gateway_network = IpNetwork::V6(
        Ipv6Network::new(gateway.ip(), 64)
            .map_err(|e| NetworkError::Parse(e.to_string()))?
    );
    
    // Configure interfaces inside namespace using ip netns exec
    // Bring up loopback
    interface::set_up("lo", Some(&netns))?;

    // Configure veth-myci with the gateway IP (::1)
    // This makes the namespace the gateway for VMs
    interface::set_addr(&veth_ns, &gateway_network, Some(&netns))?;
    interface::set_up(&veth_ns, Some(&netns))?;

    // Enable IPv6 forwarding in namespace (to route VM traffic to myc0)
    interface::enable_ipv6_forwarding(Some(&netns))?;

    // 7. Configure bridge (Layer 2 only for mycelium, optional IPv4)
    interface::set_up(&bridge_name, None)?;
    
    // Add iptables FORWARD rule to allow traffic within the bridge
    // This is needed when FORWARD policy is DROP (e.g., Docker)
    nat::add_bridge_forward_rule(&bridge_name)?;
    
    // Optional: Set IPv4 on bridge for host connectivity and NAT
    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)?;
    }

    // 8. Enable IP forwarding on host
    nat::enable_ip_forwarding()?;
    interface::enable_ipv6_forwarding(None)?;

    // Build the resource
    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, // VMs start at ::2
    };

    log::info!(
        "Network resource '{}' created with mycelium address {}",
        name,
        inspection.address
    );

    Ok(resource)
}

/// Add a VM to an existing network resource
/// Creates a TAP device connected to the bridge and assigns an IP from the mycelium subnet
///
/// Returns VmNetworkConfig with TAP device name and assigned mycelium IP
pub fn add_vm_to_resource(
    resource: &mut NetworkResource,
    vm_name: &str,
) -> Result<VmNetworkConfig> {
    let tap_name = format!("{}_{}", TAP_PREFIX, vm_name);

    // Create TAP device connected to the bridge
    tap::create_tap(&tap_name, &resource.bridge_name)?;

    // Generate Mycelium IP for this VM from the subnet
    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)
    });

    // Generate private IPv4 for this VM (if bridge has IPv4)
    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)
    });

    // Increment VM index for next VM
    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,
    })
}

/// Remove a VM from a network resource
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(())
}

/// Delete a network resource and all its components
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);

    // Get namespace and cleanup mycelium
    if let Ok(netns) = namespace::get_namespace_by_name(&ns_name) {
        // Stop mycelium daemon and cleanup
        let _ = mycelium::destroy_mycelium(&netns);

        // Delete namespace (this also removes interfaces inside it)
        namespace::delete_namespace(netns)?;
    }

    // Delete host-side veth (if still exists)
    let _ = interface::delete_interface(&veth_host);

    // Delete bridge
    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");
    }
}