use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_lib_structures::network::*; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use crate::nat::nat_definition::Nat; use crate::nat::nat_lib; use crate::utils::net_utils; use crate::utils::net_utils::{extract_ipv4, extract_ipv6}; use ipnetwork::{Ipv4Network, Ipv6Network}; use lazy_regex::regex; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug)] pub enum NetworkForwardMode { NAT, Isolated, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct DHCPv4HostReservation { mac: String, name: String, ip: Ipv4Addr, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct IPv4DHCPConfig { pub start: Ipv4Addr, pub end: Ipv4Addr, pub hosts: Vec, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct IPV4Config { pub bridge_address: Ipv4Addr, pub prefix: u8, pub dhcp: Option, pub nat: Option>>, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct DHCPv6HostReservation { pub name: String, pub ip: Ipv6Addr, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct IPv6DHCPConfig { pub start: Ipv6Addr, pub end: Ipv6Addr, pub hosts: Vec, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct IPV6Config { pub bridge_address: Ipv6Addr, pub prefix: u8, pub dhcp: Option, pub nat: Option>>, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct NetworkName(pub String); impl NetworkName { /// Get the name of the file that will store the NAT configuration of this network pub fn nat_file_name(&self) -> String { format!("nat-{}.json", self.0) } } impl NetworkName { pub fn is_valid(&self) -> bool { regex!("^[a-zA-Z0-9]+$").is_match(&self.0) } } /// Network configuration #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct NetworkInfo { pub name: NetworkName, pub uuid: Option, pub title: Option, pub description: Option, pub forward_mode: NetworkForwardMode, pub device: Option, pub bridge_name: Option, pub dns_server: Option, pub domain: Option, pub ip_v4: Option, pub ip_v6: Option, } impl NetworkInfo { pub fn as_virt_network(&self) -> anyhow::Result { if !self.name.is_valid() { return Err(StructureExtraction("network name is invalid!").into()); } if let Some(n) = &self.title { if n.contains('\n') { return Err(StructureExtraction("Network title contain newline char!").into()); } } if let Some(dev) = &self.device { if !regex!("^[a-zA-Z0-9]+$").is_match(dev) { return Err(StructureExtraction("Network device name is invalid!").into()); } } if let Some(bridge) = &self.bridge_name { if !regex!("^[a-zA-Z0-9]+$").is_match(bridge) { return Err(StructureExtraction("Network bridge name is invalid!").into()); } } if let Some(domain) = &self.domain { if !regex!("^[a-zA-Z0-9.]+$").is_match(domain) { return Err(StructureExtraction("Domain name is invalid!").into()); } } let mut ips = Vec::with_capacity(2); if let Some(ipv4) = &self.ip_v4 { if !net_utils::is_ipv4_mask_valid(ipv4.prefix) { return Err(StructureExtraction("IPv4 prefix is invalid!").into()); } if let Some(nat) = &ipv4.nat { for n in nat { n.check()?; } } ips.push(NetworkIPXML { family: "ipv4".to_string(), address: IpAddr::V4(ipv4.bridge_address), prefix: Some(ipv4.prefix), netmask: Some( Ipv4Network::new(ipv4.bridge_address, ipv4.prefix) .unwrap() .mask() .into(), ), dhcp: ipv4.dhcp.as_ref().map(|dhcp| NetworkDHCPXML { range: NetworkDHCPRangeXML { start: IpAddr::V4(dhcp.start), end: IpAddr::V4(dhcp.end), }, hosts: dhcp .hosts .iter() .map(|c| NetworkDHCPHostXML { mac: Some(c.mac.to_string()), name: c.name.to_string(), ip: c.ip.into(), }) .collect::>(), }), }) } if let Some(ipv6) = &self.ip_v6 { if !net_utils::is_ipv6_mask_valid(ipv6.prefix) { return Err(StructureExtraction("IPv6 prefix is invalid!").into()); } if let Some(nat) = &ipv6.nat { for n in nat { n.check()?; } } ips.push(NetworkIPXML { family: "ipv6".to_string(), address: IpAddr::V6(ipv6.bridge_address), prefix: Some(ipv6.prefix), netmask: Some( Ipv6Network::new(ipv6.bridge_address, ipv6.prefix) .unwrap() .mask() .into(), ), dhcp: ipv6.dhcp.as_ref().map(|dhcp| NetworkDHCPXML { range: NetworkDHCPRangeXML { start: IpAddr::V6(dhcp.start), end: IpAddr::V6(dhcp.end), }, hosts: dhcp .hosts .iter() .map(|h| NetworkDHCPHostXML { mac: None, name: h.name.to_string(), ip: h.ip.into(), }) .collect(), }), }) } Ok(NetworkXML { name: self.name.0.to_string(), uuid: self.uuid, title: self.title.clone(), description: self.description.clone(), forward: match self.forward_mode { NetworkForwardMode::NAT => Some(NetworkForwardXML { mode: "nat".to_string(), dev: self.device.clone().unwrap_or_default(), }), NetworkForwardMode::Isolated => None, }, bridge: self.bridge_name.clone().map(|b| NetworkBridgeXML { name: b.to_string(), }), dns: self.dns_server.map(|addr| NetworkDNSXML { forwarder: NetworkDNSForwarderXML { addr }, }), domain: self.domain.clone().map(|name| NetworkDomainXML { name }), ips, }) } pub fn from_xml(xml: NetworkXML) -> anyhow::Result { let name = NetworkName(xml.name); let nat = nat_lib::load_nat_def(&name)?; Ok(Self { name, uuid: xml.uuid, title: xml.title, description: xml.description, forward_mode: match xml.forward { None => NetworkForwardMode::Isolated, Some(_) => NetworkForwardMode::NAT, }, device: xml .forward .map(|f| match f.dev.is_empty() { true => None, false => Some(f.dev), }) .unwrap_or(None), bridge_name: xml.bridge.map(|b| b.name), dns_server: xml.dns.map(|d| d.forwarder.addr), domain: xml.domain.map(|d| d.name), ip_v4: xml .ips .iter() .find(|i| i.family != "ipv6") .map(|i| IPV4Config { bridge_address: extract_ipv4(i.address), prefix: match i.prefix { None => ipnetwork::ipv4_mask_to_prefix(extract_ipv4(i.netmask.unwrap())) .expect("Failed to convert IPv4 netmask to network"), Some(p) => p, }, dhcp: i.dhcp.as_ref().map(|d| IPv4DHCPConfig { start: extract_ipv4(d.range.start), end: extract_ipv4(d.range.end), hosts: d .hosts .iter() .map(|h| DHCPv4HostReservation { mac: h.mac.clone().unwrap_or_default(), name: h.name.to_string(), ip: extract_ipv4(h.ip), }) .collect(), }), nat: nat.ipv4, }), ip_v6: xml .ips .iter() .find(|i| i.family == "ipv6") .map(|i| IPV6Config { bridge_address: extract_ipv6(i.address), prefix: match i.prefix { None => ipnetwork::ipv6_mask_to_prefix(extract_ipv6(i.netmask.unwrap())) .expect("Failed to convert IPv6 netmask to network"), Some(p) => p, }, dhcp: i.dhcp.as_ref().map(|d| IPv6DHCPConfig { start: extract_ipv6(d.range.start), end: extract_ipv6(d.range.end), hosts: d .hosts .iter() .map(|h| DHCPv6HostReservation { name: h.name.to_string(), ip: extract_ipv6(h.ip), }) .collect(), }), nat: nat.ipv6, }), }) } /// Check if at least one NAT definition was specified on this interface pub fn has_nat_def(&self) -> bool { if let Some(ipv4) = &self.ip_v4 { if ipv4.nat.is_some() { return true; } } if let Some(ipv6) = &self.ip_v6 { if ipv6.nat.is_some() { return true; } } false } }