Pierre HUBERT e9e3103938
All checks were successful
continuous-integration/drone/push Build is passing
Format all code following migration to Rust edition 2024
2025-03-28 10:23:37 +01:00

321 lines
11 KiB
Rust

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<DHCPv4HostReservation>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPV4Config {
pub bridge_address: Ipv4Addr,
pub prefix: u8,
pub dhcp: Option<IPv4DHCPConfig>,
pub nat: Option<Vec<Nat<Ipv4Addr>>>,
}
#[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<DHCPv6HostReservation>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPV6Config {
pub bridge_address: Ipv6Addr,
pub prefix: u8,
pub dhcp: Option<IPv6DHCPConfig>,
pub nat: Option<Vec<Nat<Ipv6Addr>>>,
}
#[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<XMLUuid>,
pub title: Option<String>,
pub description: Option<String>,
pub forward_mode: NetworkForwardMode,
pub device: Option<String>,
pub bridge_name: Option<String>,
pub dns_server: Option<Ipv4Addr>,
pub domain: Option<String>,
pub ip_v4: Option<IPV4Config>,
pub ip_v6: Option<IPV6Config>,
}
impl NetworkInfo {
pub fn as_virt_network(&self) -> anyhow::Result<NetworkXML> {
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::<Vec<_>>(),
}),
})
}
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<Self> {
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
}
}