herolib-mos 0.3.13

Mycelium Operating System (MOS) - Network and VM abstraction layer
Documentation
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv6Addr};

/// Represents a network resource with the following topology:
///
/// ```text
///                     🌐 Mycelium Overlay 🌐
//////                     myc0 (TUN overlay)
//////                       Mycelium netns
//////                        veth-myci
////// ──────────────────────────────┼────────────────────────────
//////                        veth-host
//////                   ┌───────────┴───────────┐
///                   │      Host bridge       │
///                   │        br-{name}       │
///                   │                        │
///              tap-{vm}               (optional IPv4)
//////                  VM
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkResource {
    /// Unique name for this network resource
    pub name: String,
    /// Name of the host bridge (br-{name})
    pub bridge_name: String,
    /// Name of the mycelium namespace (n{name})
    pub namespace_name: String,
    /// Mycelium overlay address for this resource
    pub mycelium_address: Option<Ipv6Addr>,
    /// Mycelium /64 subnet for VMs
    pub mycelium_subnet: Option<Ipv6Network>,
    /// Mycelium gateway (::1 in the subnet)
    pub mycelium_gateway: Option<Ipv6Addr>,
    /// Optional IPv4 network on the bridge for host connectivity
    pub bridge_ipv4: Option<Ipv4Network>,
    /// Next available VM index for IP assignment (starts at 2)
    pub next_vm_index: u8,
}

/// Represents a VM connected to a NetworkResource
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmNetworkConfig {
    /// Name of the VM
    pub vm_name: String,
    /// Name of the network resource this VM is connected to
    pub resource_name: String,
    /// Name of the TAP device for this VM
    pub tap_device: String,
    /// Mycelium IPv6 address assigned to this VM
    pub mycelium_ip: Option<Ipv6Addr>,
    /// Optional private IPv4 address
    pub private_ip: Option<std::net::Ipv4Addr>,
}

/// Configuration for Mycelium overlay network
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyceliumConfig {
    /// 32-byte seed for key generation
    pub seed: Vec<u8>,
    /// List of peer addresses to connect to
    pub peers: Vec<String>,
}

/// Result of inspecting a mycelium seed
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyceliumInspection {
    #[serde(rename = "publicKey")]
    pub public_key: String,
    pub address: Ipv6Addr,
}

impl MyceliumInspection {
    pub fn ip(&self) -> Ipv6Addr {
        self.address
    }

    /// Get the /64 subnet derived from the mycelium address
    pub fn subnet(&self) -> Result<Ipv6Network, super::error::NetworkError> {
        let mut ip_bytes = self.address.octets();
        // Zero out the host part (last 64 bits)
        for i in 8..16 {
            ip_bytes[i] = 0;
        }
        let subnet_ip = Ipv6Addr::from(ip_bytes);
        Ipv6Network::new(subnet_ip, 64)
            .map_err(|e| super::error::NetworkError::Parse(e.to_string()))
    }

    /// Get the gateway address (::1 in the subnet)
    pub fn gateway(&self) -> Result<Ipv6Network, super::error::NetworkError> {
        let subnet = self.subnet()?;
        let mut gw_bytes = subnet.network().octets();
        gw_bytes[15] = 1;
        let gw_ip = Ipv6Addr::from(gw_bytes);
        Ipv6Network::new(gw_ip, 64).map_err(|e| super::error::NetworkError::Parse(e.to_string()))
    }
}

/// Route configuration
#[derive(Debug, Clone)]
pub struct Route {
    pub destination: Option<IpNetwork>,
    pub gateway: IpAddr,
    pub interface: Option<String>,
}

/// Veth pair configuration
#[derive(Debug, Clone)]
pub struct VethPair {
    pub local_name: String,
    pub peer_name: String,
    pub mtu: u32,
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::FromStr;

    #[test]
    fn test_network_resource_creation() {
        let resource = NetworkResource {
            name: "test_network".to_string(),
            bridge_name: "br-test_network".to_string(),
            namespace_name: "ntest_network".to_string(),
            mycelium_address: None,
            mycelium_subnet: None,
            mycelium_gateway: None,
            bridge_ipv4: Some(Ipv4Network::from_str("192.168.1.0/24").unwrap()),
            next_vm_index: 2,
        };

        assert_eq!(resource.name, "test_network");
        assert_eq!(resource.bridge_name, "br-test_network");
    }

    #[test]
    fn test_mycelium_config_creation() {
        let config = MyceliumConfig {
            seed: vec![1, 2, 3, 4, 5],
            peers: vec!["peer1".to_string(), "peer2".to_string()],
        };

        assert_eq!(config.seed.len(), 5);
        assert_eq!(config.peers.len(), 2);
    }

    #[test]
    fn test_mycelium_inspection_ip() {
        let inspection = MyceliumInspection {
            public_key: "test_key".to_string(),
            address: Ipv6Addr::from_str("2001:db8::1").unwrap(),
        };

        assert_eq!(inspection.ip(), Ipv6Addr::from_str("2001:db8::1").unwrap());
    }

    #[test]
    fn test_mycelium_inspection_subnet() {
        let inspection = MyceliumInspection {
            public_key: "test_key".to_string(),
            address: Ipv6Addr::from_str("2001:db8::1234:5678").unwrap(),
        };

        let subnet = inspection.subnet().unwrap();
        assert_eq!(subnet.prefix(), 64);
        assert_eq!(subnet.network().to_string(), "2001:db8::");
    }

    #[test]
    fn test_mycelium_inspection_gateway() {
        let inspection = MyceliumInspection {
            public_key: "test_key".to_string(),
            address: Ipv6Addr::from_str("2001:db8::1234:5678").unwrap(),
        };

        let gateway = inspection.gateway().unwrap();
        assert_eq!(gateway.prefix(), 64);
        assert_eq!(gateway.ip().to_string(), "2001:db8::1");
    }

    #[test]
    fn test_route_with_destination() {
        let route = Route {
            destination: Some(IpNetwork::from_str("10.0.0.0/24").unwrap()),
            gateway: IpAddr::from_str("10.0.0.1").unwrap(),
            interface: Some("eth0".to_string()),
        };

        assert!(route.destination.is_some());
        assert_eq!(route.interface, Some("eth0".to_string()));
    }

    #[test]
    fn test_route_default() {
        let route = Route {
            destination: None,
            gateway: IpAddr::from_str("192.168.1.1").unwrap(),
            interface: None,
        };

        assert!(route.destination.is_none());
        assert!(route.interface.is_none());
    }

    #[test]
    fn test_veth_pair_creation() {
        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_network_resource_serialization() {
        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,
        };

        let json = serde_json::to_string(&resource).unwrap();
        let deserialized: NetworkResource = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.name, "test");
    }

    #[test]
    fn test_mycelium_inspection_deserialization() {
        let json = r#"{"publicKey":"test_key","address":"2001:db8::1"}"#;
        let inspection: MyceliumInspection = serde_json::from_str(json).unwrap();
        assert_eq!(inspection.public_key, "test_key");
        assert_eq!(inspection.address.to_string(), "2001:db8::1");
    }

    #[test]
    fn test_vm_network_config() {
        let config = VmNetworkConfig {
            vm_name: "myvm".to_string(),
            resource_name: "test".to_string(),
            tap_device: "tap_myvm".to_string(),
            mycelium_ip: Some(Ipv6Addr::from_str("2001:db8::2").unwrap()),
            private_ip: Some(std::net::Ipv4Addr::from_str("192.168.1.2").unwrap()),
        };

        assert_eq!(config.vm_name, "myvm");
        assert_eq!(config.tap_device, "tap_myvm");
    }
}