Refacto structures definition
This commit is contained in:
		@@ -1,8 +1,11 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::libvirt_lib_structures::{
 | 
			
		||||
    DomainState, DomainXML, NetworkFilterXML, NetworkXML, XMLUuid,
 | 
			
		||||
};
 | 
			
		||||
use crate::libvirt_rest_structures::*;
 | 
			
		||||
use crate::libvirt_lib_structures::domain::*;
 | 
			
		||||
use crate::libvirt_lib_structures::network::*;
 | 
			
		||||
use crate::libvirt_lib_structures::nwfilter::*;
 | 
			
		||||
use crate::libvirt_lib_structures::*;
 | 
			
		||||
use crate::libvirt_rest_structures::hypervisor::*;
 | 
			
		||||
use crate::libvirt_rest_structures::net::*;
 | 
			
		||||
use crate::libvirt_rest_structures::vm::*;
 | 
			
		||||
use actix::{Actor, Context, Handler, Message};
 | 
			
		||||
use image::ImageOutputFormat;
 | 
			
		||||
use std::io::Cursor;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::NetworkInfo;
 | 
			
		||||
use crate::libvirt_rest_structures::net::NetworkInfo;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::NetworkFilter;
 | 
			
		||||
use crate::libvirt_rest_structures::nw_filter::NetworkFilter;
 | 
			
		||||
use actix_web::{web, HttpResponse};
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ use crate::constants;
 | 
			
		||||
use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN};
 | 
			
		||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
			
		||||
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
 | 
			
		||||
use crate::libvirt_rest_structures::HypervisorInfo;
 | 
			
		||||
use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
 | 
			
		||||
use actix_web::{HttpResponse, Responder};
 | 
			
		||||
use sysinfo::{NetworksExt, System, SystemExt};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
use crate::actors::vnc_actor::VNCActor;
 | 
			
		||||
use crate::actors::vnc_tokens_actor::VNCTokensManager;
 | 
			
		||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
			
		||||
use crate::libvirt_lib_structures::{DomainState, XMLUuid};
 | 
			
		||||
use crate::libvirt_rest_structures::VMInfo;
 | 
			
		||||
use crate::libvirt_lib_structures::domain::DomainState;
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::vm::VMInfo;
 | 
			
		||||
use actix_web::{web, HttpRequest, HttpResponse};
 | 
			
		||||
use actix_web_actors::ws;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
use crate::actors::libvirt_actor;
 | 
			
		||||
use crate::actors::libvirt_actor::LibVirtActor;
 | 
			
		||||
use crate::libvirt_lib_structures::{
 | 
			
		||||
    DomainState, DomainXML, NetworkFilterXML, NetworkXML, XMLUuid,
 | 
			
		||||
};
 | 
			
		||||
use crate::libvirt_rest_structures::{HypervisorInfo, NetworkInfo, VMInfo};
 | 
			
		||||
use crate::libvirt_lib_structures::domain::{DomainState, DomainXML};
 | 
			
		||||
use crate::libvirt_lib_structures::network::NetworkXML;
 | 
			
		||||
use crate::libvirt_lib_structures::nwfilter::NetworkFilterXML;
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
 | 
			
		||||
use crate::libvirt_rest_structures::net::NetworkInfo;
 | 
			
		||||
use crate::libvirt_rest_structures::vm::VMInfo;
 | 
			
		||||
use actix::Addr;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,864 +0,0 @@
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
use std::net::{IpAddr, Ipv4Addr};
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
 | 
			
		||||
pub struct XMLUuid(pub uuid::Uuid);
 | 
			
		||||
 | 
			
		||||
impl XMLUuid {
 | 
			
		||||
    pub fn parse_from_str(s: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self(uuid::Uuid::parse_str(s)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_random() -> Self {
 | 
			
		||||
        Self(uuid::Uuid::new_v4())
 | 
			
		||||
    }
 | 
			
		||||
    pub fn as_string(&self) -> String {
 | 
			
		||||
        self.0.to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_valid(&self) -> bool {
 | 
			
		||||
        log::debug!("UUID version ({}): {}", self.0, self.0.get_version_num());
 | 
			
		||||
        self.0.get_version_num() == 4
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "os")]
 | 
			
		||||
pub struct OSXML {
 | 
			
		||||
    #[serde(rename(serialize = "@firmware"), default)]
 | 
			
		||||
    pub firmware: String,
 | 
			
		||||
    pub r#type: OSTypeXML,
 | 
			
		||||
    pub loader: Option<OSLoaderXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OS Type information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "os")]
 | 
			
		||||
pub struct OSTypeXML {
 | 
			
		||||
    #[serde(rename(serialize = "@arch"))]
 | 
			
		||||
    pub arch: String,
 | 
			
		||||
    #[serde(rename(serialize = "@machine"))]
 | 
			
		||||
    pub machine: String,
 | 
			
		||||
    #[serde(rename = "$value")]
 | 
			
		||||
    pub body: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OS Loader information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "loader")]
 | 
			
		||||
pub struct OSLoaderXML {
 | 
			
		||||
    #[serde(rename(serialize = "@secure"))]
 | 
			
		||||
    pub secure: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Hypervisor features
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Default)]
 | 
			
		||||
#[serde(rename = "features")]
 | 
			
		||||
pub struct FeaturesXML {
 | 
			
		||||
    pub acpi: ACPIXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ACPI feature
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Default)]
 | 
			
		||||
#[serde(rename = "acpi")]
 | 
			
		||||
pub struct ACPIXML {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "mac")]
 | 
			
		||||
pub struct NetMacAddress {
 | 
			
		||||
    #[serde(rename(serialize = "@address"))]
 | 
			
		||||
    pub address: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "source")]
 | 
			
		||||
pub struct NetIntSourceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@network"))]
 | 
			
		||||
    pub network: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "model")]
 | 
			
		||||
pub struct NetIntModelXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "interface")]
 | 
			
		||||
pub struct DomainNetInterfaceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    pub mac: NetMacAddress,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub source: Option<NetIntSourceXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub model: Option<NetIntModelXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "input")]
 | 
			
		||||
pub struct DomainInputXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "backend")]
 | 
			
		||||
pub struct TPMBackendXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
 | 
			
		||||
    #[serde(rename(serialize = "@version"))]
 | 
			
		||||
    pub r#version: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "tpm")]
 | 
			
		||||
pub struct TPMDeviceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@model"))]
 | 
			
		||||
    pub model: String,
 | 
			
		||||
    pub backend: TPMBackendXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Devices information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "devices")]
 | 
			
		||||
pub struct DevicesXML {
 | 
			
		||||
    /// Graphics (used for VNC)
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub graphics: Option<GraphicsXML>,
 | 
			
		||||
 | 
			
		||||
    /// Graphics (used for VNC)
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub video: Option<VideoXML>,
 | 
			
		||||
 | 
			
		||||
    /// Disks (used for storage)
 | 
			
		||||
    #[serde(default, rename = "disk", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub disks: Vec<DiskXML>,
 | 
			
		||||
 | 
			
		||||
    /// Networks cards
 | 
			
		||||
    #[serde(default, rename = "interface", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub net_interfaces: Vec<DomainNetInterfaceXML>,
 | 
			
		||||
 | 
			
		||||
    /// Input devices
 | 
			
		||||
    #[serde(default, rename = "input", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub inputs: Vec<DomainInputXML>,
 | 
			
		||||
 | 
			
		||||
    /// TPM device
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub tpm: Option<TPMDeviceXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Graphics information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "graphics")]
 | 
			
		||||
pub struct GraphicsXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(rename(serialize = "@socket"))]
 | 
			
		||||
    pub socket: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video device information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "video")]
 | 
			
		||||
pub struct VideoXML {
 | 
			
		||||
    pub model: VideoModelXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video model device information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "model")]
 | 
			
		||||
pub struct VideoModelXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Disk information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "disk")]
 | 
			
		||||
pub struct DiskXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(rename(serialize = "@device"))]
 | 
			
		||||
    pub r#device: String,
 | 
			
		||||
 | 
			
		||||
    pub driver: DiskDriverXML,
 | 
			
		||||
    pub source: DiskSourceXML,
 | 
			
		||||
    pub target: DiskTargetXML,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub readonly: Option<DiskReadOnlyXML>,
 | 
			
		||||
    pub boot: DiskBootXML,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub address: Option<DiskAddressXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "driver")]
 | 
			
		||||
pub struct DiskDriverXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(default, rename(serialize = "@cache"))]
 | 
			
		||||
    pub r#cache: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "source")]
 | 
			
		||||
pub struct DiskSourceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@file"))]
 | 
			
		||||
    pub file: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "target")]
 | 
			
		||||
pub struct DiskTargetXML {
 | 
			
		||||
    #[serde(rename(serialize = "@dev"))]
 | 
			
		||||
    pub dev: String,
 | 
			
		||||
    #[serde(rename(serialize = "@bus"))]
 | 
			
		||||
    pub bus: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "readonly")]
 | 
			
		||||
pub struct DiskReadOnlyXML {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "boot")]
 | 
			
		||||
pub struct DiskBootXML {
 | 
			
		||||
    #[serde(rename(serialize = "@order"))]
 | 
			
		||||
    pub order: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "address")]
 | 
			
		||||
pub struct DiskAddressXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@controller")
 | 
			
		||||
    )]
 | 
			
		||||
    pub r#controller: Option<String>,
 | 
			
		||||
    #[serde(rename(serialize = "@bus"))]
 | 
			
		||||
    pub r#bus: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@target")
 | 
			
		||||
    )]
 | 
			
		||||
    pub r#target: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@unit")
 | 
			
		||||
    )]
 | 
			
		||||
    pub r#unit: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Domain RAM information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "memory")]
 | 
			
		||||
pub struct DomainMemoryXML {
 | 
			
		||||
    #[serde(rename(serialize = "@unit"))]
 | 
			
		||||
    pub unit: String,
 | 
			
		||||
 | 
			
		||||
    #[serde(rename = "$value")]
 | 
			
		||||
    pub memory: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "topology")]
 | 
			
		||||
pub struct DomainCPUTopology {
 | 
			
		||||
    #[serde(rename(serialize = "@sockets"))]
 | 
			
		||||
    pub sockets: usize,
 | 
			
		||||
    #[serde(rename(serialize = "@cores"))]
 | 
			
		||||
    pub cores: usize,
 | 
			
		||||
    #[serde(rename(serialize = "@threads"))]
 | 
			
		||||
    pub threads: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "cpu")]
 | 
			
		||||
pub struct DomainVCPUXML {
 | 
			
		||||
    #[serde(rename = "$value")]
 | 
			
		||||
    pub body: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "cpu")]
 | 
			
		||||
pub struct DomainCPUXML {
 | 
			
		||||
    #[serde(rename(serialize = "@mode"))]
 | 
			
		||||
    pub mode: String,
 | 
			
		||||
    pub topology: Option<DomainCPUTopology>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Domain information, see https://libvirt.org/formatdomain.html
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "domain")]
 | 
			
		||||
pub struct DomainXML {
 | 
			
		||||
    /// Domain type (kvm)
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    pub genid: Option<uuid::Uuid>,
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    pub os: OSXML,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub features: FeaturesXML,
 | 
			
		||||
    pub devices: DevicesXML,
 | 
			
		||||
 | 
			
		||||
    /// The maximum allocation of memory for the guest at boot time
 | 
			
		||||
    pub memory: DomainMemoryXML,
 | 
			
		||||
 | 
			
		||||
    /// Number of vCPU
 | 
			
		||||
    pub vcpu: DomainVCPUXML,
 | 
			
		||||
 | 
			
		||||
    /// CPU information
 | 
			
		||||
    pub cpu: DomainCPUXML,
 | 
			
		||||
 | 
			
		||||
    pub on_poweroff: String,
 | 
			
		||||
    pub on_reboot: String,
 | 
			
		||||
    pub on_crash: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DomainXML {
 | 
			
		||||
    /// Turn this domain into its XML definition
 | 
			
		||||
    pub fn into_xml(mut self) -> anyhow::Result<String> {
 | 
			
		||||
        // A issue with the disks & network interface definition serialization needs them to be serialized aside
 | 
			
		||||
        let mut devices_xml = Vec::with_capacity(self.devices.disks.len());
 | 
			
		||||
        for disk in self.devices.disks {
 | 
			
		||||
            let disk_xml = serde_xml_rs::to_string(&disk)?;
 | 
			
		||||
            let start_offset = disk_xml.find("<disk").unwrap();
 | 
			
		||||
            devices_xml.push(disk_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
        for network in self.devices.net_interfaces {
 | 
			
		||||
            let network_xml = serde_xml_rs::to_string(&network)?;
 | 
			
		||||
            let start_offset = network_xml.find("<interface").unwrap();
 | 
			
		||||
            devices_xml.push(network_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
        for input in self.devices.inputs {
 | 
			
		||||
            let input_xml = serde_xml_rs::to_string(&input)?;
 | 
			
		||||
            let start_offset = input_xml.find("<input").unwrap();
 | 
			
		||||
            devices_xml.push(input_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.devices.disks = vec![];
 | 
			
		||||
        self.devices.net_interfaces = vec![];
 | 
			
		||||
        self.devices.inputs = vec![];
 | 
			
		||||
 | 
			
		||||
        let mut xml = serde_xml_rs::to_string(&self)?;
 | 
			
		||||
        let disks_xml = devices_xml.join("\n");
 | 
			
		||||
        xml = xml.replacen("<devices>", &format!("<devices>{disks_xml}"), 1);
 | 
			
		||||
        Ok(xml)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Domain state
 | 
			
		||||
#[derive(serde::Serialize, Debug, Copy, Clone)]
 | 
			
		||||
pub enum DomainState {
 | 
			
		||||
    NoState,
 | 
			
		||||
    Running,
 | 
			
		||||
    Blocked,
 | 
			
		||||
    Paused,
 | 
			
		||||
    Shutdown,
 | 
			
		||||
    Shutoff,
 | 
			
		||||
    Crashed,
 | 
			
		||||
    PowerManagementSuspended,
 | 
			
		||||
    Other,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network forward information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "forward")]
 | 
			
		||||
pub struct NetworkForwardXML {
 | 
			
		||||
    #[serde(rename(serialize = "@mode"))]
 | 
			
		||||
    pub mode: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        rename(serialize = "@dev"),
 | 
			
		||||
        skip_serializing_if = "String::is_empty"
 | 
			
		||||
    )]
 | 
			
		||||
    pub dev: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network bridge information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "bridge")]
 | 
			
		||||
pub struct NetworkBridgeXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network DNS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "dns")]
 | 
			
		||||
pub struct NetworkDNSXML {
 | 
			
		||||
    pub forwarder: NetworkDNSForwarderXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network DNS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "forwarder")]
 | 
			
		||||
pub struct NetworkDNSForwarderXML {
 | 
			
		||||
    /// Address of the DNS server
 | 
			
		||||
    #[serde(rename(serialize = "@addr"))]
 | 
			
		||||
    pub addr: Ipv4Addr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network DNS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "domain")]
 | 
			
		||||
pub struct NetworkDomainXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn invalid_prefix() -> u32 {
 | 
			
		||||
    u32::MAX
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn invalid_ip() -> IpAddr {
 | 
			
		||||
    IpAddr::V4(Ipv4Addr::BROADCAST)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network ip information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "ip")]
 | 
			
		||||
pub struct NetworkIPXML {
 | 
			
		||||
    #[serde(default, rename(serialize = "@family"))]
 | 
			
		||||
    pub family: String,
 | 
			
		||||
    #[serde(rename(serialize = "@address"))]
 | 
			
		||||
    pub address: IpAddr,
 | 
			
		||||
    /// Network Prefix
 | 
			
		||||
    #[serde(rename(serialize = "@prefix"), default = "invalid_prefix")]
 | 
			
		||||
    pub prefix: u32,
 | 
			
		||||
    /// Network Netmask. This field is never serialized, but because we can't know if LibVirt will
 | 
			
		||||
    /// provide us netmask or prefix, we need to handle both of these fields
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@netmask"),
 | 
			
		||||
        default = "invalid_ip",
 | 
			
		||||
        skip_serializing
 | 
			
		||||
    )]
 | 
			
		||||
    pub netmask: IpAddr,
 | 
			
		||||
    pub dhcp: Option<NetworkDHCPXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkIPXML {
 | 
			
		||||
    pub fn into_xml(mut self) -> anyhow::Result<String> {
 | 
			
		||||
        let mut hosts_xml = vec![];
 | 
			
		||||
 | 
			
		||||
        if let Some(dhcp) = &mut self.dhcp {
 | 
			
		||||
            for host in &dhcp.hosts {
 | 
			
		||||
                let mut host_xml = serde_xml_rs::to_string(&host)?;
 | 
			
		||||
 | 
			
		||||
                // In case of IPv6, mac address should not be specified
 | 
			
		||||
                host_xml = host_xml.replace("mac=\"\"", "");
 | 
			
		||||
 | 
			
		||||
                // strip xml tag
 | 
			
		||||
                let start_offset = host_xml.find("<host").unwrap();
 | 
			
		||||
                hosts_xml.push(host_xml[start_offset..].to_string());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            dhcp.hosts = vec![];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut res = serde_xml_rs::to_string(&self)?;
 | 
			
		||||
        let hosts_xml = hosts_xml.join("\n");
 | 
			
		||||
        res = res.replace("</dhcp>", &format!("{hosts_xml}</dhcp>"));
 | 
			
		||||
        Ok(res)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "dhcp")]
 | 
			
		||||
pub struct NetworkDHCPXML {
 | 
			
		||||
    pub range: NetworkDHCPRangeXML,
 | 
			
		||||
    #[serde(default, rename = "host", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub hosts: Vec<NetworkDHCPHostXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "host")]
 | 
			
		||||
pub struct NetworkDHCPHostXML {
 | 
			
		||||
    #[serde(rename(serialize = "@mac"), default)]
 | 
			
		||||
    pub mac: String,
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(rename(serialize = "@ip"))]
 | 
			
		||||
    pub ip: IpAddr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "dhcp")]
 | 
			
		||||
pub struct NetworkDHCPRangeXML {
 | 
			
		||||
    #[serde(rename(serialize = "@start"))]
 | 
			
		||||
    pub start: IpAddr,
 | 
			
		||||
    #[serde(rename(serialize = "@end"))]
 | 
			
		||||
    pub end: IpAddr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network information, see https://libvirt.org/formatnetwork.html
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "network")]
 | 
			
		||||
pub struct NetworkXML {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub forward: Option<NetworkForwardXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub bridge: Option<NetworkBridgeXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub dns: Option<NetworkDNSXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub domain: Option<NetworkDomainXML>,
 | 
			
		||||
    #[serde(default, rename = "ip")]
 | 
			
		||||
    pub ips: Vec<NetworkIPXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkXML {
 | 
			
		||||
    pub fn into_xml(mut self) -> anyhow::Result<String> {
 | 
			
		||||
        // A issue with the IPs definition serialization needs them to be serialized aside
 | 
			
		||||
        let mut ips_xml = Vec::with_capacity(self.ips.len());
 | 
			
		||||
        for ip in self.ips {
 | 
			
		||||
            log::debug!("Serialize {ip:?}");
 | 
			
		||||
            let ip_xml = ip.into_xml()?;
 | 
			
		||||
            // strip xml tag
 | 
			
		||||
            let start_offset = ip_xml.find("<ip").unwrap();
 | 
			
		||||
            ips_xml.push(ip_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
        self.ips = vec![];
 | 
			
		||||
 | 
			
		||||
        let mut network_xml = serde_xml_rs::to_string(&self)?;
 | 
			
		||||
        log::trace!("Serialize network XML start: {network_xml}");
 | 
			
		||||
 | 
			
		||||
        let ips_xml = ips_xml.join("\n");
 | 
			
		||||
        network_xml = network_xml.replacen("</network>", &format!("{ips_xml}</network>"), 1);
 | 
			
		||||
        Ok(network_xml)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "filterref")]
 | 
			
		||||
pub struct NetworkFilterRefXML {
 | 
			
		||||
    #[serde(rename(serialize = "@filter"))]
 | 
			
		||||
    pub filter: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "all")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolAll {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "mac")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolMac {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "arp")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolArp {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpsrcipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpsrcipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpsrcipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpsrcipmask: Option<u8>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpdstipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpdstipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpdstipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpdstipmask: Option<u8>,
 | 
			
		||||
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "ipvx")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolIpvx {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipmask: Option<u8>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipmask: Option<u8>,
 | 
			
		||||
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "layer4")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolLayer4 {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipaddr: Option<IpAddr>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipmask: Option<u8>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipaddr: Option<IpAddr>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipmask: Option<u8>,
 | 
			
		||||
    /// Start of range of source IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipfrom"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipfrom: Option<IpAddr>,
 | 
			
		||||
    /// End of range of source IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipto"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipto: Option<IpAddr>,
 | 
			
		||||
    /// Start of range of destination IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipfrom"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipfrom: Option<IpAddr>,
 | 
			
		||||
    /// End of range of destination IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipto"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipto: Option<IpAddr>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcportstart"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcportstart: Option<u16>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcportend"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcportend: Option<u16>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstportstart"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstportstart: Option<u16>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstportend"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstportend: Option<u16>,
 | 
			
		||||
    #[serde(rename(serialize = "@state"), skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    state: Option<String>,
 | 
			
		||||
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "rule")]
 | 
			
		||||
pub struct NetworkFilterRuleXML {
 | 
			
		||||
    #[serde(rename(serialize = "@action"))]
 | 
			
		||||
    pub action: String,
 | 
			
		||||
    #[serde(rename(serialize = "@direction"))]
 | 
			
		||||
    pub direction: String,
 | 
			
		||||
    #[serde(rename(serialize = "@priority"))]
 | 
			
		||||
    pub priority: Option<i32>,
 | 
			
		||||
 | 
			
		||||
    /// Match all protocols
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub all: Option<NetworkFilterRuleProtocolAll>,
 | 
			
		||||
 | 
			
		||||
    /// Match mac protocol
 | 
			
		||||
    #[serde(default, rename = "mac", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub mac_rules: Vec<NetworkFilterRuleProtocolMac>,
 | 
			
		||||
 | 
			
		||||
    /// Match arp protocol
 | 
			
		||||
    #[serde(default, rename = "arp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub arp_rules: Vec<NetworkFilterRuleProtocolArp>,
 | 
			
		||||
 | 
			
		||||
    /// Match IPv4 protocol
 | 
			
		||||
    #[serde(default, rename = "ip", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub ipv4_rules: Vec<NetworkFilterRuleProtocolIpvx>,
 | 
			
		||||
 | 
			
		||||
    /// Match IPv6 protocol
 | 
			
		||||
    #[serde(default, rename = "ipv6", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub ipv6_rules: Vec<NetworkFilterRuleProtocolIpvx>,
 | 
			
		||||
 | 
			
		||||
    /// Match TCP protocol
 | 
			
		||||
    #[serde(default, rename = "tcp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub tcp_rules: Vec<NetworkFilterRuleProtocolLayer4>,
 | 
			
		||||
 | 
			
		||||
    /// Match UDP protocol
 | 
			
		||||
    #[serde(default, rename = "udp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub udp_rules: Vec<NetworkFilterRuleProtocolLayer4>,
 | 
			
		||||
 | 
			
		||||
    /// Match SCTP protocol
 | 
			
		||||
    #[serde(default, rename = "sctp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub sctp_rules: Vec<NetworkFilterRuleProtocolLayer4>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "filter")]
 | 
			
		||||
pub struct NetworkFilterXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(rename(serialize = "@chain"), default)]
 | 
			
		||||
    pub chain: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@priority"),
 | 
			
		||||
        default
 | 
			
		||||
    )]
 | 
			
		||||
    pub priority: Option<i32>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    #[serde(default, rename = "filterref", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub filterrefs: Vec<NetworkFilterRefXML>,
 | 
			
		||||
    #[serde(default, rename = "rule", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub rules: Vec<NetworkFilterRuleXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilterXML {
 | 
			
		||||
    pub fn parse_xml<D: Display>(xml: D) -> anyhow::Result<Self> {
 | 
			
		||||
        let xml = xml.to_string();
 | 
			
		||||
 | 
			
		||||
        // We need to put all filter refs at the same location
 | 
			
		||||
        let mut filter_refs = Vec::new();
 | 
			
		||||
        let xml = lazy_regex::regex_replace_all!(r#"<filterref.*/>"#, &xml, |r: &str| {
 | 
			
		||||
            filter_refs.push(r.to_string());
 | 
			
		||||
 | 
			
		||||
            if r.contains('\n') {
 | 
			
		||||
                log::warn!("A filterref contain a new line. This is a symptom of a new unsupported child attribute of <filterref /> object!");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ""
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let filter_refs = filter_refs.join("\n");
 | 
			
		||||
        let xml = xml.replace("</filter>", &format!("{filter_refs}</filter>"));
 | 
			
		||||
        log::debug!("Effective NW filter rule parsed: {xml}");
 | 
			
		||||
 | 
			
		||||
        Ok(serde_xml_rs::from_str(&xml)?)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										354
									
								
								virtweb_backend/src/libvirt_lib_structures/domain.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								virtweb_backend/src/libvirt_lib_structures/domain.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,354 @@
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
 | 
			
		||||
/// OS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "os")]
 | 
			
		||||
pub struct OSXML {
 | 
			
		||||
    #[serde(rename(serialize = "@firmware"), default)]
 | 
			
		||||
    pub firmware: String,
 | 
			
		||||
    pub r#type: OSTypeXML,
 | 
			
		||||
    pub loader: Option<OSLoaderXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OS Type information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "os")]
 | 
			
		||||
pub struct OSTypeXML {
 | 
			
		||||
    #[serde(rename(serialize = "@arch"))]
 | 
			
		||||
    pub arch: String,
 | 
			
		||||
    #[serde(rename(serialize = "@machine"))]
 | 
			
		||||
    pub machine: String,
 | 
			
		||||
    #[serde(rename = "$value")]
 | 
			
		||||
    pub body: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OS Loader information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "loader")]
 | 
			
		||||
pub struct OSLoaderXML {
 | 
			
		||||
    #[serde(rename(serialize = "@secure"))]
 | 
			
		||||
    pub secure: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Hypervisor features
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Default)]
 | 
			
		||||
#[serde(rename = "features")]
 | 
			
		||||
pub struct FeaturesXML {
 | 
			
		||||
    pub acpi: ACPIXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// ACPI feature
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Default)]
 | 
			
		||||
#[serde(rename = "acpi")]
 | 
			
		||||
pub struct ACPIXML {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "mac")]
 | 
			
		||||
pub struct NetMacAddress {
 | 
			
		||||
    #[serde(rename(serialize = "@address"))]
 | 
			
		||||
    pub address: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "source")]
 | 
			
		||||
pub struct NetIntSourceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@network"))]
 | 
			
		||||
    pub network: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "model")]
 | 
			
		||||
pub struct NetIntModelXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "interface")]
 | 
			
		||||
pub struct DomainNetInterfaceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    pub mac: NetMacAddress,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub source: Option<NetIntSourceXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub model: Option<NetIntModelXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "input")]
 | 
			
		||||
pub struct DomainInputXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "backend")]
 | 
			
		||||
pub struct TPMBackendXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
 | 
			
		||||
    #[serde(rename(serialize = "@version"))]
 | 
			
		||||
    pub r#version: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "tpm")]
 | 
			
		||||
pub struct TPMDeviceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@model"))]
 | 
			
		||||
    pub model: String,
 | 
			
		||||
    pub backend: TPMBackendXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Devices information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "devices")]
 | 
			
		||||
pub struct DevicesXML {
 | 
			
		||||
    /// Graphics (used for VNC)
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub graphics: Option<GraphicsXML>,
 | 
			
		||||
 | 
			
		||||
    /// Graphics (used for VNC)
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub video: Option<VideoXML>,
 | 
			
		||||
 | 
			
		||||
    /// Disks (used for storage)
 | 
			
		||||
    #[serde(default, rename = "disk", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub disks: Vec<DiskXML>,
 | 
			
		||||
 | 
			
		||||
    /// Networks cards
 | 
			
		||||
    #[serde(default, rename = "interface", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub net_interfaces: Vec<DomainNetInterfaceXML>,
 | 
			
		||||
 | 
			
		||||
    /// Input devices
 | 
			
		||||
    #[serde(default, rename = "input", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub inputs: Vec<DomainInputXML>,
 | 
			
		||||
 | 
			
		||||
    /// TPM device
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub tpm: Option<TPMDeviceXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Graphics information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "graphics")]
 | 
			
		||||
pub struct GraphicsXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(rename(serialize = "@socket"))]
 | 
			
		||||
    pub socket: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video device information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "video")]
 | 
			
		||||
pub struct VideoXML {
 | 
			
		||||
    pub model: VideoModelXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Video model device information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "model")]
 | 
			
		||||
pub struct VideoModelXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Disk information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "disk")]
 | 
			
		||||
pub struct DiskXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(rename(serialize = "@device"))]
 | 
			
		||||
    pub r#device: String,
 | 
			
		||||
 | 
			
		||||
    pub driver: DiskDriverXML,
 | 
			
		||||
    pub source: DiskSourceXML,
 | 
			
		||||
    pub target: DiskTargetXML,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub readonly: Option<DiskReadOnlyXML>,
 | 
			
		||||
    pub boot: DiskBootXML,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub address: Option<DiskAddressXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "driver")]
 | 
			
		||||
pub struct DiskDriverXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(default, rename(serialize = "@cache"))]
 | 
			
		||||
    pub r#cache: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "source")]
 | 
			
		||||
pub struct DiskSourceXML {
 | 
			
		||||
    #[serde(rename(serialize = "@file"))]
 | 
			
		||||
    pub file: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "target")]
 | 
			
		||||
pub struct DiskTargetXML {
 | 
			
		||||
    #[serde(rename(serialize = "@dev"))]
 | 
			
		||||
    pub dev: String,
 | 
			
		||||
    #[serde(rename(serialize = "@bus"))]
 | 
			
		||||
    pub bus: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "readonly")]
 | 
			
		||||
pub struct DiskReadOnlyXML {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "boot")]
 | 
			
		||||
pub struct DiskBootXML {
 | 
			
		||||
    #[serde(rename(serialize = "@order"))]
 | 
			
		||||
    pub order: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "address")]
 | 
			
		||||
pub struct DiskAddressXML {
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@controller")
 | 
			
		||||
    )]
 | 
			
		||||
    pub r#controller: Option<String>,
 | 
			
		||||
    #[serde(rename(serialize = "@bus"))]
 | 
			
		||||
    pub r#bus: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@target")
 | 
			
		||||
    )]
 | 
			
		||||
    pub r#target: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@unit")
 | 
			
		||||
    )]
 | 
			
		||||
    pub r#unit: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Domain RAM information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "memory")]
 | 
			
		||||
pub struct DomainMemoryXML {
 | 
			
		||||
    #[serde(rename(serialize = "@unit"))]
 | 
			
		||||
    pub unit: String,
 | 
			
		||||
 | 
			
		||||
    #[serde(rename = "$value")]
 | 
			
		||||
    pub memory: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "topology")]
 | 
			
		||||
pub struct DomainCPUTopology {
 | 
			
		||||
    #[serde(rename(serialize = "@sockets"))]
 | 
			
		||||
    pub sockets: usize,
 | 
			
		||||
    #[serde(rename(serialize = "@cores"))]
 | 
			
		||||
    pub cores: usize,
 | 
			
		||||
    #[serde(rename(serialize = "@threads"))]
 | 
			
		||||
    pub threads: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "cpu")]
 | 
			
		||||
pub struct DomainVCPUXML {
 | 
			
		||||
    #[serde(rename = "$value")]
 | 
			
		||||
    pub body: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "cpu")]
 | 
			
		||||
pub struct DomainCPUXML {
 | 
			
		||||
    #[serde(rename(serialize = "@mode"))]
 | 
			
		||||
    pub mode: String,
 | 
			
		||||
    pub topology: Option<DomainCPUTopology>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Domain information, see https://libvirt.org/formatdomain.html
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(rename = "domain")]
 | 
			
		||||
pub struct DomainXML {
 | 
			
		||||
    /// Domain type (kvm)
 | 
			
		||||
    #[serde(rename(serialize = "@type"))]
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    pub genid: Option<uuid::Uuid>,
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    pub os: OSXML,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub features: FeaturesXML,
 | 
			
		||||
    pub devices: DevicesXML,
 | 
			
		||||
 | 
			
		||||
    /// The maximum allocation of memory for the guest at boot time
 | 
			
		||||
    pub memory: DomainMemoryXML,
 | 
			
		||||
 | 
			
		||||
    /// Number of vCPU
 | 
			
		||||
    pub vcpu: DomainVCPUXML,
 | 
			
		||||
 | 
			
		||||
    /// CPU information
 | 
			
		||||
    pub cpu: DomainCPUXML,
 | 
			
		||||
 | 
			
		||||
    pub on_poweroff: String,
 | 
			
		||||
    pub on_reboot: String,
 | 
			
		||||
    pub on_crash: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DomainXML {
 | 
			
		||||
    /// Turn this domain into its XML definition
 | 
			
		||||
    pub fn into_xml(mut self) -> anyhow::Result<String> {
 | 
			
		||||
        // A issue with the disks & network interface definition serialization needs them to be serialized aside
 | 
			
		||||
        let mut devices_xml = Vec::with_capacity(self.devices.disks.len());
 | 
			
		||||
        for disk in self.devices.disks {
 | 
			
		||||
            let disk_xml = serde_xml_rs::to_string(&disk)?;
 | 
			
		||||
            let start_offset = disk_xml.find("<disk").unwrap();
 | 
			
		||||
            devices_xml.push(disk_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
        for network in self.devices.net_interfaces {
 | 
			
		||||
            let network_xml = serde_xml_rs::to_string(&network)?;
 | 
			
		||||
            let start_offset = network_xml.find("<interface").unwrap();
 | 
			
		||||
            devices_xml.push(network_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
        for input in self.devices.inputs {
 | 
			
		||||
            let input_xml = serde_xml_rs::to_string(&input)?;
 | 
			
		||||
            let start_offset = input_xml.find("<input").unwrap();
 | 
			
		||||
            devices_xml.push(input_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.devices.disks = vec![];
 | 
			
		||||
        self.devices.net_interfaces = vec![];
 | 
			
		||||
        self.devices.inputs = vec![];
 | 
			
		||||
 | 
			
		||||
        let mut xml = serde_xml_rs::to_string(&self)?;
 | 
			
		||||
        let disks_xml = devices_xml.join("\n");
 | 
			
		||||
        xml = xml.replacen("<devices>", &format!("<devices>{disks_xml}"), 1);
 | 
			
		||||
        Ok(xml)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Domain state
 | 
			
		||||
#[derive(serde::Serialize, Debug, Copy, Clone)]
 | 
			
		||||
pub enum DomainState {
 | 
			
		||||
    NoState,
 | 
			
		||||
    Running,
 | 
			
		||||
    Blocked,
 | 
			
		||||
    Paused,
 | 
			
		||||
    Shutdown,
 | 
			
		||||
    Shutoff,
 | 
			
		||||
    Crashed,
 | 
			
		||||
    PowerManagementSuspended,
 | 
			
		||||
    Other,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								virtweb_backend/src/libvirt_lib_structures/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								virtweb_backend/src/libvirt_lib_structures/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
 | 
			
		||||
pub struct XMLUuid(pub uuid::Uuid);
 | 
			
		||||
 | 
			
		||||
impl XMLUuid {
 | 
			
		||||
    pub fn parse_from_str(s: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self(uuid::Uuid::parse_str(s)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_random() -> Self {
 | 
			
		||||
        Self(uuid::Uuid::new_v4())
 | 
			
		||||
    }
 | 
			
		||||
    pub fn as_string(&self) -> String {
 | 
			
		||||
        self.0.to_string()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn is_valid(&self) -> bool {
 | 
			
		||||
        log::debug!("UUID version ({}): {}", self.0, self.0.get_version_num());
 | 
			
		||||
        self.0.get_version_num() == 4
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub mod domain;
 | 
			
		||||
pub mod network;
 | 
			
		||||
pub mod nwfilter;
 | 
			
		||||
							
								
								
									
										177
									
								
								virtweb_backend/src/libvirt_lib_structures/network.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								virtweb_backend/src/libvirt_lib_structures/network.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use std::net::{IpAddr, Ipv4Addr};
 | 
			
		||||
 | 
			
		||||
/// Network forward information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "forward")]
 | 
			
		||||
pub struct NetworkForwardXML {
 | 
			
		||||
    #[serde(rename(serialize = "@mode"))]
 | 
			
		||||
    pub mode: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        default,
 | 
			
		||||
        rename(serialize = "@dev"),
 | 
			
		||||
        skip_serializing_if = "String::is_empty"
 | 
			
		||||
    )]
 | 
			
		||||
    pub dev: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network bridge information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "bridge")]
 | 
			
		||||
pub struct NetworkBridgeXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network DNS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "dns")]
 | 
			
		||||
pub struct NetworkDNSXML {
 | 
			
		||||
    pub forwarder: NetworkDNSForwarderXML,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network DNS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "forwarder")]
 | 
			
		||||
pub struct NetworkDNSForwarderXML {
 | 
			
		||||
    /// Address of the DNS server
 | 
			
		||||
    #[serde(rename(serialize = "@addr"))]
 | 
			
		||||
    pub addr: Ipv4Addr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network DNS information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "domain")]
 | 
			
		||||
pub struct NetworkDomainXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn invalid_prefix() -> u32 {
 | 
			
		||||
    u32::MAX
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn invalid_ip() -> IpAddr {
 | 
			
		||||
    IpAddr::V4(Ipv4Addr::BROADCAST)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network ip information
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "ip")]
 | 
			
		||||
pub struct NetworkIPXML {
 | 
			
		||||
    #[serde(default, rename(serialize = "@family"))]
 | 
			
		||||
    pub family: String,
 | 
			
		||||
    #[serde(rename(serialize = "@address"))]
 | 
			
		||||
    pub address: IpAddr,
 | 
			
		||||
    /// Network Prefix
 | 
			
		||||
    #[serde(rename(serialize = "@prefix"), default = "invalid_prefix")]
 | 
			
		||||
    pub prefix: u32,
 | 
			
		||||
    /// Network Netmask. This field is never serialized, but because we can't know if LibVirt will
 | 
			
		||||
    /// provide us netmask or prefix, we need to handle both of these fields
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@netmask"),
 | 
			
		||||
        default = "invalid_ip",
 | 
			
		||||
        skip_serializing
 | 
			
		||||
    )]
 | 
			
		||||
    pub netmask: IpAddr,
 | 
			
		||||
    pub dhcp: Option<NetworkDHCPXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkIPXML {
 | 
			
		||||
    pub fn into_xml(mut self) -> anyhow::Result<String> {
 | 
			
		||||
        let mut hosts_xml = vec![];
 | 
			
		||||
 | 
			
		||||
        if let Some(dhcp) = &mut self.dhcp {
 | 
			
		||||
            for host in &dhcp.hosts {
 | 
			
		||||
                let mut host_xml = serde_xml_rs::to_string(&host)?;
 | 
			
		||||
 | 
			
		||||
                // In case of IPv6, mac address should not be specified
 | 
			
		||||
                host_xml = host_xml.replace("mac=\"\"", "");
 | 
			
		||||
 | 
			
		||||
                // strip xml tag
 | 
			
		||||
                let start_offset = host_xml.find("<host").unwrap();
 | 
			
		||||
                hosts_xml.push(host_xml[start_offset..].to_string());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            dhcp.hosts = vec![];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut res = serde_xml_rs::to_string(&self)?;
 | 
			
		||||
        let hosts_xml = hosts_xml.join("\n");
 | 
			
		||||
        res = res.replace("</dhcp>", &format!("{hosts_xml}</dhcp>"));
 | 
			
		||||
        Ok(res)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "dhcp")]
 | 
			
		||||
pub struct NetworkDHCPXML {
 | 
			
		||||
    pub range: NetworkDHCPRangeXML,
 | 
			
		||||
    #[serde(default, rename = "host", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub hosts: Vec<NetworkDHCPHostXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "host")]
 | 
			
		||||
pub struct NetworkDHCPHostXML {
 | 
			
		||||
    #[serde(rename(serialize = "@mac"), default)]
 | 
			
		||||
    pub mac: String,
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(rename(serialize = "@ip"))]
 | 
			
		||||
    pub ip: IpAddr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "dhcp")]
 | 
			
		||||
pub struct NetworkDHCPRangeXML {
 | 
			
		||||
    #[serde(rename(serialize = "@start"))]
 | 
			
		||||
    pub start: IpAddr,
 | 
			
		||||
    #[serde(rename(serialize = "@end"))]
 | 
			
		||||
    pub end: IpAddr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network information, see https://libvirt.org/formatnetwork.html
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "network")]
 | 
			
		||||
pub struct NetworkXML {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub forward: Option<NetworkForwardXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub bridge: Option<NetworkBridgeXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub dns: Option<NetworkDNSXML>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub domain: Option<NetworkDomainXML>,
 | 
			
		||||
    #[serde(default, rename = "ip")]
 | 
			
		||||
    pub ips: Vec<NetworkIPXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkXML {
 | 
			
		||||
    pub fn into_xml(mut self) -> anyhow::Result<String> {
 | 
			
		||||
        // A issue with the IPs definition serialization needs them to be serialized aside
 | 
			
		||||
        let mut ips_xml = Vec::with_capacity(self.ips.len());
 | 
			
		||||
        for ip in self.ips {
 | 
			
		||||
            log::debug!("Serialize {ip:?}");
 | 
			
		||||
            let ip_xml = ip.into_xml()?;
 | 
			
		||||
            // strip xml tag
 | 
			
		||||
            let start_offset = ip_xml.find("<ip").unwrap();
 | 
			
		||||
            ips_xml.push(ip_xml[start_offset..].to_string());
 | 
			
		||||
        }
 | 
			
		||||
        self.ips = vec![];
 | 
			
		||||
 | 
			
		||||
        let mut network_xml = serde_xml_rs::to_string(&self)?;
 | 
			
		||||
        log::trace!("Serialize network XML start: {network_xml}");
 | 
			
		||||
 | 
			
		||||
        let ips_xml = ips_xml.join("\n");
 | 
			
		||||
        network_xml = network_xml.replacen("</network>", &format!("{ips_xml}</network>"), 1);
 | 
			
		||||
        Ok(network_xml)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										316
									
								
								virtweb_backend/src/libvirt_lib_structures/nwfilter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								virtweb_backend/src/libvirt_lib_structures/nwfilter.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,316 @@
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
use std::net::IpAddr;
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "filterref")]
 | 
			
		||||
pub struct NetworkFilterRefXML {
 | 
			
		||||
    #[serde(rename(serialize = "@filter"))]
 | 
			
		||||
    pub filter: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "all")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolAll {}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "mac")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolMac {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "arp")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolArp {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpsrcipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpsrcipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpsrcipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpsrcipmask: Option<u8>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpdstipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpdstipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@arpdstipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    arpdstipmask: Option<u8>,
 | 
			
		||||
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "ipvx")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolIpvx {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstmacmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipmask: Option<u8>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipmask: Option<u8>,
 | 
			
		||||
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "layer4")]
 | 
			
		||||
pub struct NetworkFilterRuleProtocolLayer4 {
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcmacaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipaddr: Option<IpAddr>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipmask: Option<u8>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipaddr"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipaddr: Option<IpAddr>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipmask"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipmask: Option<u8>,
 | 
			
		||||
    /// Start of range of source IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipfrom"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipfrom: Option<IpAddr>,
 | 
			
		||||
    /// End of range of source IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcipto"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcipto: Option<IpAddr>,
 | 
			
		||||
    /// Start of range of destination IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipfrom"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipfrom: Option<IpAddr>,
 | 
			
		||||
    /// End of range of destination IP address
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstipto"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstipto: Option<IpAddr>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcportstart"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcportstart: Option<u16>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@srcportend"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    srcportend: Option<u16>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstportstart"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstportstart: Option<u16>,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@dstportend"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    dstportend: Option<u16>,
 | 
			
		||||
    #[serde(rename(serialize = "@state"), skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    state: Option<String>,
 | 
			
		||||
 | 
			
		||||
    #[serde(
 | 
			
		||||
        rename(serialize = "@comment"),
 | 
			
		||||
        skip_serializing_if = "Option::is_none"
 | 
			
		||||
    )]
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "rule")]
 | 
			
		||||
pub struct NetworkFilterRuleXML {
 | 
			
		||||
    #[serde(rename(serialize = "@action"))]
 | 
			
		||||
    pub action: String,
 | 
			
		||||
    #[serde(rename(serialize = "@direction"))]
 | 
			
		||||
    pub direction: String,
 | 
			
		||||
    #[serde(rename(serialize = "@priority"))]
 | 
			
		||||
    pub priority: Option<i32>,
 | 
			
		||||
 | 
			
		||||
    /// Match all protocols
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub all: Option<NetworkFilterRuleProtocolAll>,
 | 
			
		||||
 | 
			
		||||
    /// Match mac protocol
 | 
			
		||||
    #[serde(default, rename = "mac", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub mac_rules: Vec<NetworkFilterRuleProtocolMac>,
 | 
			
		||||
 | 
			
		||||
    /// Match arp protocol
 | 
			
		||||
    #[serde(default, rename = "arp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub arp_rules: Vec<NetworkFilterRuleProtocolArp>,
 | 
			
		||||
 | 
			
		||||
    /// Match IPv4 protocol
 | 
			
		||||
    #[serde(default, rename = "ip", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub ipv4_rules: Vec<NetworkFilterRuleProtocolIpvx>,
 | 
			
		||||
 | 
			
		||||
    /// Match IPv6 protocol
 | 
			
		||||
    #[serde(default, rename = "ipv6", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub ipv6_rules: Vec<NetworkFilterRuleProtocolIpvx>,
 | 
			
		||||
 | 
			
		||||
    /// Match TCP protocol
 | 
			
		||||
    #[serde(default, rename = "tcp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub tcp_rules: Vec<NetworkFilterRuleProtocolLayer4>,
 | 
			
		||||
 | 
			
		||||
    /// Match UDP protocol
 | 
			
		||||
    #[serde(default, rename = "udp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub udp_rules: Vec<NetworkFilterRuleProtocolLayer4>,
 | 
			
		||||
 | 
			
		||||
    /// Match SCTP protocol
 | 
			
		||||
    #[serde(default, rename = "sctp", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub sctp_rules: Vec<NetworkFilterRuleProtocolLayer4>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
 | 
			
		||||
#[serde(rename = "filter")]
 | 
			
		||||
pub struct NetworkFilterXML {
 | 
			
		||||
    #[serde(rename(serialize = "@name"))]
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[serde(rename(serialize = "@chain"), default)]
 | 
			
		||||
    pub chain: String,
 | 
			
		||||
    #[serde(
 | 
			
		||||
        skip_serializing_if = "Option::is_none",
 | 
			
		||||
        rename(serialize = "@priority"),
 | 
			
		||||
        default
 | 
			
		||||
    )]
 | 
			
		||||
    pub priority: Option<i32>,
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    #[serde(default, rename = "filterref", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub filterrefs: Vec<NetworkFilterRefXML>,
 | 
			
		||||
    #[serde(default, rename = "rule", skip_serializing_if = "Vec::is_empty")]
 | 
			
		||||
    pub rules: Vec<NetworkFilterRuleXML>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilterXML {
 | 
			
		||||
    pub fn parse_xml<D: Display>(xml: D) -> anyhow::Result<Self> {
 | 
			
		||||
        let xml = xml.to_string();
 | 
			
		||||
 | 
			
		||||
        // We need to put all filter refs at the same location
 | 
			
		||||
        let mut filter_refs = Vec::new();
 | 
			
		||||
        let xml = lazy_regex::regex_replace_all!(r#"<filterref.*/>"#, &xml, |r: &str| {
 | 
			
		||||
            filter_refs.push(r.to_string());
 | 
			
		||||
 | 
			
		||||
            if r.contains('\n') {
 | 
			
		||||
                log::warn!("A filterref contain a new line. This is a symptom of a new unsupported child attribute of <filterref /> object!");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ""
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let filter_refs = filter_refs.join("\n");
 | 
			
		||||
        let xml = xml.replace("</filter>", &format!("{filter_refs}</filter>"));
 | 
			
		||||
        log::debug!("Effective NW filter rule parsed: {xml}");
 | 
			
		||||
 | 
			
		||||
        Ok(serde_xml_rs::from_str(&xml)?)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,961 +0,0 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::libvirt_lib_structures::{
 | 
			
		||||
    DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
 | 
			
		||||
    DomainCPUTopology, DomainCPUXML, DomainInputXML, DomainMemoryXML, DomainNetInterfaceXML,
 | 
			
		||||
    DomainVCPUXML, DomainXML, FeaturesXML, GraphicsXML, NetIntModelXML, NetIntSourceXML,
 | 
			
		||||
    NetMacAddress, NetworkBridgeXML, NetworkDHCPHostXML, NetworkDHCPRangeXML, NetworkDHCPXML,
 | 
			
		||||
    NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkFilterXML, NetworkForwardXML,
 | 
			
		||||
    NetworkIPXML, NetworkXML, OSLoaderXML, OSTypeXML, TPMBackendXML, TPMDeviceXML, VideoModelXML,
 | 
			
		||||
    VideoXML, XMLUuid, ACPIXML, OSXML,
 | 
			
		||||
};
 | 
			
		||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
			
		||||
use crate::utils::disks_utils::Disk;
 | 
			
		||||
use crate::utils::files_utils;
 | 
			
		||||
use ipnetwork::{Ipv4Network, Ipv6Network};
 | 
			
		||||
use lazy_regex::regex;
 | 
			
		||||
use num::Integer;
 | 
			
		||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 | 
			
		||||
use std::ops::{Div, Mul};
 | 
			
		||||
 | 
			
		||||
// TODO : split into multiple files
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum LibVirtStructError {
 | 
			
		||||
    #[error("StructureExtractionError: {0}")]
 | 
			
		||||
    StructureExtraction(&'static str),
 | 
			
		||||
    #[error("DomainExtractionError: {0}")]
 | 
			
		||||
    DomainExtraction(String),
 | 
			
		||||
    #[error("MBConvertError: {0}")]
 | 
			
		||||
    MBConvert(String),
 | 
			
		||||
    #[error("ParseFilteringChain: {0}")]
 | 
			
		||||
    ParseFilteringChain(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct HypervisorInfo {
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    pub hyp_version: u32,
 | 
			
		||||
    pub lib_version: u32,
 | 
			
		||||
    pub capabilities: String,
 | 
			
		||||
    pub free_memory: u64,
 | 
			
		||||
    pub hostname: String,
 | 
			
		||||
    pub node: HypervisorNodeInfo,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct HypervisorNodeInfo {
 | 
			
		||||
    pub cpu_model: String,
 | 
			
		||||
    /// Memory size in kilobytes
 | 
			
		||||
    pub memory_size: u64,
 | 
			
		||||
    pub number_of_active_cpus: u32,
 | 
			
		||||
    pub cpu_frequency_mhz: u32,
 | 
			
		||||
    pub number_of_numa_cell: u32,
 | 
			
		||||
    pub number_of_cpu_socket_per_node: u32,
 | 
			
		||||
    pub number_of_core_per_sockets: u32,
 | 
			
		||||
    pub number_of_threads_per_core: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum BootType {
 | 
			
		||||
    UEFI,
 | 
			
		||||
    UEFISecureBoot,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum VMArchitecture {
 | 
			
		||||
    #[serde(rename = "i686")]
 | 
			
		||||
    I686,
 | 
			
		||||
    #[serde(rename = "x86_64")]
 | 
			
		||||
    X86_64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct Network {
 | 
			
		||||
    mac: String,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    r#type: NetworkType,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
pub enum NetworkType {
 | 
			
		||||
    UserspaceSLIRPStack,
 | 
			
		||||
    DefinedNetwork { network: String }, // TODO : complete network types
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct VMInfo {
 | 
			
		||||
    /// VM name (alphanumeric characters only)
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    pub genid: Option<XMLUuid>,
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    pub boot_type: BootType,
 | 
			
		||||
    pub architecture: VMArchitecture,
 | 
			
		||||
    /// VM allocated memory, in megabytes
 | 
			
		||||
    pub memory: usize,
 | 
			
		||||
    /// Number of vCPU for the VM
 | 
			
		||||
    pub number_vcpu: usize,
 | 
			
		||||
    /// Enable VNC access through admin console
 | 
			
		||||
    pub vnc_access: bool,
 | 
			
		||||
    /// Attach ISO file(s)
 | 
			
		||||
    pub iso_files: Vec<String>,
 | 
			
		||||
    /// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest
 | 
			
		||||
    pub disks: Vec<Disk>,
 | 
			
		||||
    /// Network cards
 | 
			
		||||
    pub networks: Vec<Network>,
 | 
			
		||||
    /// Add a TPM v2.0 module
 | 
			
		||||
    pub tpm_module: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VMInfo {
 | 
			
		||||
    /// Turn this VM into a domain
 | 
			
		||||
    pub fn as_tomain(&self) -> anyhow::Result<DomainXML> {
 | 
			
		||||
        if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
 | 
			
		||||
            return Err(StructureExtraction("VM name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let uuid = if let Some(n) = self.uuid {
 | 
			
		||||
            if !n.is_valid() {
 | 
			
		||||
                return Err(StructureExtraction("VM UUID is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
            n
 | 
			
		||||
        } else {
 | 
			
		||||
            XMLUuid::new_random()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.genid {
 | 
			
		||||
            if !n.is_valid() {
 | 
			
		||||
                return Err(StructureExtraction("VM genid is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.title {
 | 
			
		||||
            if n.contains('\n') {
 | 
			
		||||
                return Err(StructureExtraction("VM title contain newline char!").into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
 | 
			
		||||
            return Err(StructureExtraction("VM memory is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.number_vcpu == 0 || (self.number_vcpu != 1 && self.number_vcpu.is_odd()) {
 | 
			
		||||
            return Err(StructureExtraction("Invalid number of vCPU specified!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut disks = vec![];
 | 
			
		||||
 | 
			
		||||
        for iso_file in &self.iso_files {
 | 
			
		||||
            if !files_utils::check_file_name(iso_file) {
 | 
			
		||||
                return Err(StructureExtraction("ISO filename is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let path = AppConfig::get().iso_storage_path().join(iso_file);
 | 
			
		||||
 | 
			
		||||
            if !path.exists() {
 | 
			
		||||
                return Err(StructureExtraction("Specified ISO file does not exists!").into());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            disks.push(DiskXML {
 | 
			
		||||
                r#type: "file".to_string(),
 | 
			
		||||
                device: "cdrom".to_string(),
 | 
			
		||||
                driver: DiskDriverXML {
 | 
			
		||||
                    name: "qemu".to_string(),
 | 
			
		||||
                    r#type: "raw".to_string(),
 | 
			
		||||
                    cache: "none".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                source: DiskSourceXML {
 | 
			
		||||
                    file: path.to_string_lossy().to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                target: DiskTargetXML {
 | 
			
		||||
                    dev: format!(
 | 
			
		||||
                        "hd{}",
 | 
			
		||||
                        ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"][disks.len()]
 | 
			
		||||
                    ),
 | 
			
		||||
                    bus: "sata".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                readonly: Some(DiskReadOnlyXML {}),
 | 
			
		||||
                boot: DiskBootXML {
 | 
			
		||||
                    order: (disks.len() + 1).to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                address: None,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (vnc_graphics, vnc_video) = match self.vnc_access {
 | 
			
		||||
            true => (
 | 
			
		||||
                Some(GraphicsXML {
 | 
			
		||||
                    r#type: "vnc".to_string(),
 | 
			
		||||
                    socket: AppConfig::get()
 | 
			
		||||
                        .vnc_socket_for_domain(&self.name)
 | 
			
		||||
                        .to_string_lossy()
 | 
			
		||||
                        .to_string(),
 | 
			
		||||
                }),
 | 
			
		||||
                Some(VideoXML {
 | 
			
		||||
                    model: VideoModelXML {
 | 
			
		||||
                        r#type: "virtio".to_string(), //"qxl".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ),
 | 
			
		||||
            false => (None, None),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Check disks name for duplicates
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
            if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
 | 
			
		||||
                return Err(StructureExtraction("Two different disks have the same name!").into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Apply disks configuration
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
            disk.check_config()?;
 | 
			
		||||
            disk.apply_config(uuid)?;
 | 
			
		||||
 | 
			
		||||
            if disk.delete {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            disks.push(DiskXML {
 | 
			
		||||
                r#type: "file".to_string(),
 | 
			
		||||
                device: "disk".to_string(),
 | 
			
		||||
                driver: DiskDriverXML {
 | 
			
		||||
                    name: "qemu".to_string(),
 | 
			
		||||
                    r#type: "raw".to_string(),
 | 
			
		||||
                    cache: "none".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                source: DiskSourceXML {
 | 
			
		||||
                    file: disk.disk_path(uuid).to_string_lossy().to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                target: DiskTargetXML {
 | 
			
		||||
                    dev: format!(
 | 
			
		||||
                        "vd{}",
 | 
			
		||||
                        ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"][disks.len()]
 | 
			
		||||
                    ),
 | 
			
		||||
                    bus: "virtio".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                readonly: None,
 | 
			
		||||
                boot: DiskBootXML {
 | 
			
		||||
                    order: (disks.len() + 1).to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                address: None,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut networks = vec![];
 | 
			
		||||
        for n in &self.networks {
 | 
			
		||||
            networks.push(match &n.r#type {
 | 
			
		||||
                NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
			
		||||
                    mac: NetMacAddress {
 | 
			
		||||
                        address: n.mac.to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    r#type: "user".to_string(),
 | 
			
		||||
                    source: None,
 | 
			
		||||
                    model: Some(NetIntModelXML {
 | 
			
		||||
                        r#type: "virtio".to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                },
 | 
			
		||||
                NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
 | 
			
		||||
                    mac: NetMacAddress {
 | 
			
		||||
                        address: n.mac.to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    r#type: "network".to_string(),
 | 
			
		||||
                    source: Some(NetIntSourceXML {
 | 
			
		||||
                        network: network.to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                    model: Some(NetIntModelXML {
 | 
			
		||||
                        r#type: "virtio".to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(DomainXML {
 | 
			
		||||
            r#type: "kvm".to_string(),
 | 
			
		||||
            name: self.name.to_string(),
 | 
			
		||||
            uuid: Some(uuid),
 | 
			
		||||
            genid: self.genid.map(|i| i.0),
 | 
			
		||||
            title: self.title.clone(),
 | 
			
		||||
            description: self.description.clone(),
 | 
			
		||||
 | 
			
		||||
            os: OSXML {
 | 
			
		||||
                r#type: OSTypeXML {
 | 
			
		||||
                    arch: match self.architecture {
 | 
			
		||||
                        VMArchitecture::I686 => "i686",
 | 
			
		||||
                        VMArchitecture::X86_64 => "x86_64",
 | 
			
		||||
                    }
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
                    machine: "q35".to_string(),
 | 
			
		||||
                    body: "hvm".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                firmware: "efi".to_string(),
 | 
			
		||||
                loader: Some(OSLoaderXML {
 | 
			
		||||
                    secure: match self.boot_type {
 | 
			
		||||
                        BootType::UEFI => "no".to_string(),
 | 
			
		||||
                        BootType::UEFISecureBoot => "yes".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            features: FeaturesXML { acpi: ACPIXML {} },
 | 
			
		||||
 | 
			
		||||
            devices: DevicesXML {
 | 
			
		||||
                graphics: vnc_graphics,
 | 
			
		||||
                video: vnc_video,
 | 
			
		||||
                disks,
 | 
			
		||||
                net_interfaces: networks,
 | 
			
		||||
                inputs: vec![
 | 
			
		||||
                    DomainInputXML {
 | 
			
		||||
                        r#type: "mouse".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    DomainInputXML {
 | 
			
		||||
                        r#type: "keyboard".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    DomainInputXML {
 | 
			
		||||
                        r#type: "tablet".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                tpm: match self.tpm_module {
 | 
			
		||||
                    true => Some(TPMDeviceXML {
 | 
			
		||||
                        model: "tpm-tis".to_string(),
 | 
			
		||||
                        backend: TPMBackendXML {
 | 
			
		||||
                            r#type: "emulator".to_string(),
 | 
			
		||||
                            version: "2.0".to_string(),
 | 
			
		||||
                        },
 | 
			
		||||
                    }),
 | 
			
		||||
                    false => None,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            memory: DomainMemoryXML {
 | 
			
		||||
                unit: "MB".to_string(),
 | 
			
		||||
                memory: self.memory,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            vcpu: DomainVCPUXML {
 | 
			
		||||
                body: self.number_vcpu,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            cpu: DomainCPUXML {
 | 
			
		||||
                mode: "host-passthrough".to_string(),
 | 
			
		||||
                topology: Some(DomainCPUTopology {
 | 
			
		||||
                    sockets: 1,
 | 
			
		||||
                    cores: match self.number_vcpu {
 | 
			
		||||
                        1 => 1,
 | 
			
		||||
                        v => v / 2,
 | 
			
		||||
                    },
 | 
			
		||||
                    threads: match self.number_vcpu {
 | 
			
		||||
                        1 => 1,
 | 
			
		||||
                        _ => 2,
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            on_poweroff: "destroy".to_string(),
 | 
			
		||||
            on_reboot: "restart".to_string(),
 | 
			
		||||
            on_crash: "destroy".to_string(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Turn a domain into a vm
 | 
			
		||||
    pub fn from_domain(domain: DomainXML) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: domain.name,
 | 
			
		||||
            uuid: domain.uuid,
 | 
			
		||||
            genid: domain.genid.map(XMLUuid),
 | 
			
		||||
            title: domain.title,
 | 
			
		||||
            description: domain.description,
 | 
			
		||||
            boot_type: match domain.os.loader {
 | 
			
		||||
                None => BootType::UEFI,
 | 
			
		||||
                Some(l) => match l.secure.as_str() {
 | 
			
		||||
                    "yes" => BootType::UEFISecureBoot,
 | 
			
		||||
                    _ => BootType::UEFI,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            architecture: match domain.os.r#type.arch.as_str() {
 | 
			
		||||
                "i686" => VMArchitecture::I686,
 | 
			
		||||
                "x86_64" => VMArchitecture::X86_64,
 | 
			
		||||
                a => {
 | 
			
		||||
                    return Err(LibVirtStructError::DomainExtraction(format!(
 | 
			
		||||
                        "Unknown architecture: {a}! "
 | 
			
		||||
                    ))
 | 
			
		||||
                    .into());
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            number_vcpu: domain.vcpu.body,
 | 
			
		||||
            memory: convert_to_mb(&domain.memory.unit, domain.memory.memory)?,
 | 
			
		||||
            vnc_access: domain.devices.graphics.is_some(),
 | 
			
		||||
            iso_files: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .disks
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|d| d.device == "cdrom")
 | 
			
		||||
                .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
 | 
			
		||||
                .collect(),
 | 
			
		||||
 | 
			
		||||
            disks: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .disks
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|d| d.device == "disk")
 | 
			
		||||
                .map(|d| Disk::load_from_file(&d.source.file).unwrap())
 | 
			
		||||
                .collect(),
 | 
			
		||||
 | 
			
		||||
            networks: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .net_interfaces
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|d| {
 | 
			
		||||
                    Ok(Network {
 | 
			
		||||
                        mac: d.mac.address.to_string(),
 | 
			
		||||
                        r#type: match d.r#type.as_str() {
 | 
			
		||||
                            "user" => NetworkType::UserspaceSLIRPStack,
 | 
			
		||||
                            "network" => NetworkType::DefinedNetwork {
 | 
			
		||||
                                network: d.source.as_ref().unwrap().network.to_string(),
 | 
			
		||||
                            },
 | 
			
		||||
                            a => {
 | 
			
		||||
                                return Err(LibVirtStructError::DomainExtraction(format!(
 | 
			
		||||
                                    "Unknown network interface type: {a}! "
 | 
			
		||||
                                )));
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                    })
 | 
			
		||||
                })
 | 
			
		||||
                .collect::<Result<Vec<_>, _>>()?,
 | 
			
		||||
 | 
			
		||||
            tpm_module: domain.devices.tpm.is_some(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Convert unit to MB
 | 
			
		||||
fn convert_to_mb(unit: &str, value: usize) -> anyhow::Result<usize> {
 | 
			
		||||
    let fact = match unit {
 | 
			
		||||
        "bytes" | "b" => 1f64,
 | 
			
		||||
        "KB" => 1000f64,
 | 
			
		||||
        "MB" => 1000f64 * 1000f64,
 | 
			
		||||
        "GB" => 1000f64 * 1000f64 * 1000f64,
 | 
			
		||||
        "TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,
 | 
			
		||||
 | 
			
		||||
        "k" | "KiB" => 1024f64,
 | 
			
		||||
        "M" | "MiB" => 1024f64 * 1024f64,
 | 
			
		||||
        "G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
 | 
			
		||||
        "T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,
 | 
			
		||||
 | 
			
		||||
        _ => {
 | 
			
		||||
            return Err(LibVirtStructError::MBConvert(format!("Unknown size unit: {unit}")).into());
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok((value as f64).mul(fact.div((1000 * 1000) as f64)).ceil() as usize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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 {
 | 
			
		||||
    start: Ipv4Addr,
 | 
			
		||||
    end: Ipv4Addr,
 | 
			
		||||
    hosts: Vec<DHCPv4HostReservation>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct IPV4Config {
 | 
			
		||||
    bridge_address: Ipv4Addr,
 | 
			
		||||
    prefix: u32,
 | 
			
		||||
    dhcp: Option<IPv4DHCPConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct DHCPv6HostReservation {
 | 
			
		||||
    name: String,
 | 
			
		||||
    ip: Ipv6Addr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct IPv6DHCPConfig {
 | 
			
		||||
    start: Ipv6Addr,
 | 
			
		||||
    end: Ipv6Addr,
 | 
			
		||||
    hosts: Vec<DHCPv6HostReservation>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct IPV6Config {
 | 
			
		||||
    bridge_address: Ipv6Addr,
 | 
			
		||||
    prefix: u32,
 | 
			
		||||
    dhcp: Option<IPv6DHCPConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network configuration
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct NetworkInfo {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    title: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    forward_mode: NetworkForwardMode,
 | 
			
		||||
    device: Option<String>,
 | 
			
		||||
    bridge_name: Option<String>,
 | 
			
		||||
    dns_server: Option<Ipv4Addr>,
 | 
			
		||||
    domain: Option<String>,
 | 
			
		||||
    ip_v4: Option<IPV4Config>,
 | 
			
		||||
    ip_v6: Option<IPV6Config>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkInfo {
 | 
			
		||||
    pub fn as_virt_network(&self) -> anyhow::Result<NetworkXML> {
 | 
			
		||||
        if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
 | 
			
		||||
            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 ipv4.prefix > 32 {
 | 
			
		||||
                return Err(StructureExtraction("IPv4 prefix is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ips.push(NetworkIPXML {
 | 
			
		||||
                family: "ipv4".to_string(),
 | 
			
		||||
                address: IpAddr::V4(ipv4.bridge_address),
 | 
			
		||||
                prefix: ipv4.prefix,
 | 
			
		||||
                netmask: Ipv4Network::new(ipv4.bridge_address, ipv4.prefix as u8)
 | 
			
		||||
                    .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: c.mac.to_string(),
 | 
			
		||||
                            name: c.name.to_string(),
 | 
			
		||||
                            ip: c.ip.into(),
 | 
			
		||||
                        })
 | 
			
		||||
                        .collect::<Vec<_>>(),
 | 
			
		||||
                }),
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(ipv6) = &self.ip_v6 {
 | 
			
		||||
            ips.push(NetworkIPXML {
 | 
			
		||||
                family: "ipv6".to_string(),
 | 
			
		||||
                address: IpAddr::V6(ipv6.bridge_address),
 | 
			
		||||
                prefix: ipv6.prefix,
 | 
			
		||||
                netmask: Ipv6Network::new(ipv6.bridge_address, ipv6.prefix as u8)
 | 
			
		||||
                    .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: "".to_string(),
 | 
			
		||||
                            name: h.name.to_string(),
 | 
			
		||||
                            ip: h.ip.into(),
 | 
			
		||||
                        })
 | 
			
		||||
                        .collect(),
 | 
			
		||||
                }),
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(NetworkXML {
 | 
			
		||||
            name: self.name.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> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: xml.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 {
 | 
			
		||||
                        u32::MAX => ipnetwork::ipv4_mask_to_prefix(extract_ipv4(i.netmask))
 | 
			
		||||
                            .expect("Failed to convert IPv4 netmask to network")
 | 
			
		||||
                            as u32,
 | 
			
		||||
                        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.to_string(),
 | 
			
		||||
                                name: h.name.to_string(),
 | 
			
		||||
                                ip: extract_ipv4(h.ip),
 | 
			
		||||
                            })
 | 
			
		||||
                            .collect(),
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
            ip_v6: xml
 | 
			
		||||
                .ips
 | 
			
		||||
                .iter()
 | 
			
		||||
                .find(|i| i.family == "ipv6")
 | 
			
		||||
                .map(|i| IPV6Config {
 | 
			
		||||
                    bridge_address: extract_ipv6(i.address),
 | 
			
		||||
                    prefix: match i.prefix {
 | 
			
		||||
                        u32::MAX => ipnetwork::ipv6_mask_to_prefix(extract_ipv6(i.netmask))
 | 
			
		||||
                            .expect("Failed to convert IPv6 netmask to network")
 | 
			
		||||
                            as u32,
 | 
			
		||||
                        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(),
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone)]
 | 
			
		||||
pub enum NetworkFilterChainProtocol {
 | 
			
		||||
    Root,
 | 
			
		||||
    Mac,
 | 
			
		||||
    STP,
 | 
			
		||||
    VLAN,
 | 
			
		||||
    ARP,
 | 
			
		||||
    RARP,
 | 
			
		||||
    IPv4,
 | 
			
		||||
    IPv6,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilterChainProtocol {
 | 
			
		||||
    pub fn from_xml(xml: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(match xml {
 | 
			
		||||
            "root" => Self::Root,
 | 
			
		||||
            "mac" => Self::Mac,
 | 
			
		||||
            "stp" => Self::STP,
 | 
			
		||||
            "vlan" => Self::VLAN,
 | 
			
		||||
            "arp" => Self::ARP,
 | 
			
		||||
            "rarp" => Self::RARP,
 | 
			
		||||
            "ipv4" => Self::IPv4,
 | 
			
		||||
            "ipv6" => Self::IPv6,
 | 
			
		||||
            _ => {
 | 
			
		||||
                return Err(LibVirtStructError::ParseFilteringChain(format!(
 | 
			
		||||
                    "Unknown filtering chain: {xml}! "
 | 
			
		||||
                ))
 | 
			
		||||
                .into())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_xml(&self) -> String {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Root => "root",
 | 
			
		||||
            Self::Mac => "mac",
 | 
			
		||||
            Self::STP => "stp",
 | 
			
		||||
            Self::VLAN => "vlan",
 | 
			
		||||
            Self::ARP => "arp",
 | 
			
		||||
            Self::RARP => "rarp",
 | 
			
		||||
            Self::IPv4 => "ipv4",
 | 
			
		||||
            Self::IPv6 => "ipv6",
 | 
			
		||||
        }
 | 
			
		||||
        .to_string()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilterChain {
 | 
			
		||||
    protocol: NetworkFilterChainProtocol,
 | 
			
		||||
    suffix: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilterChain {
 | 
			
		||||
    pub fn from_xml(xml: &str) -> anyhow::Result<Option<Self>> {
 | 
			
		||||
        if xml.is_empty() {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(Some(match xml.split_once('-') {
 | 
			
		||||
            None => Self {
 | 
			
		||||
                protocol: NetworkFilterChainProtocol::from_xml(xml)?,
 | 
			
		||||
                suffix: None,
 | 
			
		||||
            },
 | 
			
		||||
            Some((prefix, suffix)) => Self {
 | 
			
		||||
                protocol: NetworkFilterChainProtocol::from_xml(prefix)?,
 | 
			
		||||
                suffix: Some(suffix.to_string()),
 | 
			
		||||
            },
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_xml(&self) -> String {
 | 
			
		||||
        match &self.suffix {
 | 
			
		||||
            None => self.protocol.to_xml(),
 | 
			
		||||
            Some(s) => format!("{}-{s}", self.protocol.to_xml()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network filter definition
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilter {
 | 
			
		||||
    name: String,
 | 
			
		||||
    chain: Option<NetworkFilterChain>,
 | 
			
		||||
    priority: Option<i32>,
 | 
			
		||||
    uuid: Option<XMLUuid>,
 | 
			
		||||
    /// Referenced filters rules
 | 
			
		||||
    join_rules: Vec<String>,
 | 
			
		||||
    rules: Vec<NetworkFilterRule>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilter {
 | 
			
		||||
    pub fn from_xml(xml: NetworkFilterXML) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: xml.name,
 | 
			
		||||
            uuid: xml.uuid,
 | 
			
		||||
            chain: NetworkFilterChain::from_xml(&xml.chain)?,
 | 
			
		||||
            priority: xml.priority,
 | 
			
		||||
            join_rules: xml
 | 
			
		||||
                .filterrefs
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|i| i.filter.to_string())
 | 
			
		||||
                .collect(),
 | 
			
		||||
            rules: vec![], // TODO !
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone)]
 | 
			
		||||
pub enum NetworkFilterAction {
 | 
			
		||||
    /// matching the rule silently discards the packet with no further analysis
 | 
			
		||||
    Drop,
 | 
			
		||||
    /// matching the rule generates an ICMP reject message with no further analysis
 | 
			
		||||
    Reject,
 | 
			
		||||
    /// matching the rule accepts the packet with no further analysis
 | 
			
		||||
    Accept,
 | 
			
		||||
    /// matching the rule passes this filter, but returns control to the calling filter for further
 | 
			
		||||
    /// analysis
 | 
			
		||||
    Return,
 | 
			
		||||
    /// matching the rule goes on to the next rule for further analysis
 | 
			
		||||
    Continue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum NetworkFilterDirection {
 | 
			
		||||
    In,
 | 
			
		||||
    Out,
 | 
			
		||||
    InOut,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum Layer4State {
 | 
			
		||||
    NEW,
 | 
			
		||||
    ESTABLISHED,
 | 
			
		||||
    RELATED,
 | 
			
		||||
    INVALID,
 | 
			
		||||
    NONE,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum Layer4Type {
 | 
			
		||||
    TCP,
 | 
			
		||||
    UDP,
 | 
			
		||||
    SCTP,
 | 
			
		||||
    ICMP,
 | 
			
		||||
    TCPipv6,
 | 
			
		||||
    UDPipv6,
 | 
			
		||||
    SCTPipv6,
 | 
			
		||||
    ICMPipv6,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilterSelectorIP<IPv> {
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    srcipaddr: Option<IPv>,
 | 
			
		||||
    srcipmask: Option<u8>,
 | 
			
		||||
    dstipaddr: Option<IPv>,
 | 
			
		||||
    dstipmask: Option<u8>,
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum NetworkFilterSelector {
 | 
			
		||||
    All,
 | 
			
		||||
    Mac {
 | 
			
		||||
        src_mac_addr: Option<String>,
 | 
			
		||||
        src_mac_mask: Option<String>,
 | 
			
		||||
        dst_mac_addr: Option<String>,
 | 
			
		||||
        dst_mac_mask: Option<String>,
 | 
			
		||||
        comment: Option<String>,
 | 
			
		||||
    },
 | 
			
		||||
    Arp {
 | 
			
		||||
        srcmacaddr: Option<String>,
 | 
			
		||||
        srcmacmask: Option<String>,
 | 
			
		||||
        dstmacaddr: Option<String>,
 | 
			
		||||
        dstmacmask: Option<String>,
 | 
			
		||||
        arpsrcipaddr: Option<IpAddr>,
 | 
			
		||||
        arpsrcipmask: Option<u8>,
 | 
			
		||||
        arpdstipaddr: Option<IpAddr>,
 | 
			
		||||
        arpdstipmask: Option<u8>,
 | 
			
		||||
        comment: Option<String>,
 | 
			
		||||
    },
 | 
			
		||||
    IPv4(NetworkFilterSelectorIP<Ipv4Addr>),
 | 
			
		||||
    IPv6(NetworkFilterSelectorIP<Ipv6Addr>),
 | 
			
		||||
    Layer4 {
 | 
			
		||||
        r#type: Layer4Type,
 | 
			
		||||
        srcmacaddr: Option<String>,
 | 
			
		||||
        srcipaddr: Option<IpAddr>,
 | 
			
		||||
        srcipmask: Option<u8>,
 | 
			
		||||
        dstipaddr: Option<IpAddr>,
 | 
			
		||||
        dstipmask: Option<u8>,
 | 
			
		||||
        /// Start of range of source IP address
 | 
			
		||||
        srcipfrom: Option<IpAddr>,
 | 
			
		||||
        /// End of range of source IP address
 | 
			
		||||
        srcipto: Option<IpAddr>,
 | 
			
		||||
        /// Start of range of destination IP address
 | 
			
		||||
        dstipfrom: Option<IpAddr>,
 | 
			
		||||
        /// End of range of destination IP address
 | 
			
		||||
        dstipto: Option<IpAddr>,
 | 
			
		||||
        srcportstart: Option<u16>,
 | 
			
		||||
        srcportend: Option<u16>,
 | 
			
		||||
        dstportstart: Option<u16>,
 | 
			
		||||
        dstportend: Option<u16>,
 | 
			
		||||
        state: Option<Layer4State>,
 | 
			
		||||
        comment: Option<String>,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilterRule {
 | 
			
		||||
    action: NetworkFilterAction,
 | 
			
		||||
    direction: NetworkFilterDirection,
 | 
			
		||||
    /// optional; the priority of the rule controls the order in which the rule will be instantiated
 | 
			
		||||
    /// relative to other rules
 | 
			
		||||
    ///
 | 
			
		||||
    /// Valid values are in the range of -1000 to 1000.
 | 
			
		||||
    priority: Option<i32>,
 | 
			
		||||
    selectors: Vec<NetworkFilterSelector>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_ipv4(ip: IpAddr) -> Ipv4Addr {
 | 
			
		||||
    match ip {
 | 
			
		||||
        IpAddr::V4(i) => i,
 | 
			
		||||
        IpAddr::V6(_) => {
 | 
			
		||||
            panic!("IPv6 found in IPv4 definition!")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_ipv6(ip: IpAddr) -> Ipv6Addr {
 | 
			
		||||
    match ip {
 | 
			
		||||
        IpAddr::V4(_) => {
 | 
			
		||||
            panic!("IPv4 found in IPv6 definition!")
 | 
			
		||||
        }
 | 
			
		||||
        IpAddr::V6(i) => i,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use crate::libvirt_rest_structures::convert_to_mb;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn convert_units_mb() {
 | 
			
		||||
        assert_eq!(convert_to_mb("MB", 1).unwrap(), 1);
 | 
			
		||||
        assert_eq!(convert_to_mb("MB", 1000).unwrap(), 1000);
 | 
			
		||||
        assert_eq!(convert_to_mb("GB", 1000).unwrap(), 1000 * 1000);
 | 
			
		||||
        assert_eq!(convert_to_mb("GB", 1).unwrap(), 1000);
 | 
			
		||||
        assert_eq!(convert_to_mb("GiB", 3).unwrap(), 3222);
 | 
			
		||||
        assert_eq!(convert_to_mb("KiB", 488281).unwrap(), 500);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								virtweb_backend/src/libvirt_rest_structures/hypervisor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								virtweb_backend/src/libvirt_rest_structures/hypervisor.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct HypervisorInfo {
 | 
			
		||||
    pub r#type: String,
 | 
			
		||||
    pub hyp_version: u32,
 | 
			
		||||
    pub lib_version: u32,
 | 
			
		||||
    pub capabilities: String,
 | 
			
		||||
    pub free_memory: u64,
 | 
			
		||||
    pub hostname: String,
 | 
			
		||||
    pub node: HypervisorNodeInfo,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
pub struct HypervisorNodeInfo {
 | 
			
		||||
    pub cpu_model: String,
 | 
			
		||||
    /// Memory size in kilobytes
 | 
			
		||||
    pub memory_size: u64,
 | 
			
		||||
    pub number_of_active_cpus: u32,
 | 
			
		||||
    pub cpu_frequency_mhz: u32,
 | 
			
		||||
    pub number_of_numa_cell: u32,
 | 
			
		||||
    pub number_of_cpu_socket_per_node: u32,
 | 
			
		||||
    pub number_of_core_per_sockets: u32,
 | 
			
		||||
    pub number_of_threads_per_core: u32,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								virtweb_backend/src/libvirt_rest_structures/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								virtweb_backend/src/libvirt_rest_structures/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
pub mod hypervisor;
 | 
			
		||||
pub mod net;
 | 
			
		||||
pub mod nw_filter;
 | 
			
		||||
pub mod vm;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum LibVirtStructError {
 | 
			
		||||
    #[error("StructureExtractionError: {0}")]
 | 
			
		||||
    StructureExtraction(&'static str),
 | 
			
		||||
    #[error("DomainExtractionError: {0}")]
 | 
			
		||||
    DomainExtraction(String),
 | 
			
		||||
 | 
			
		||||
    #[error("ParseFilteringChain: {0}")]
 | 
			
		||||
    ParseFilteringChain(String),
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										258
									
								
								virtweb_backend/src/libvirt_rest_structures/net.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								virtweb_backend/src/libvirt_rest_structures/net.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,258 @@
 | 
			
		||||
use crate::libvirt_lib_structures::network::*;
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
			
		||||
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 {
 | 
			
		||||
    start: Ipv4Addr,
 | 
			
		||||
    end: Ipv4Addr,
 | 
			
		||||
    hosts: Vec<DHCPv4HostReservation>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct IPV4Config {
 | 
			
		||||
    bridge_address: Ipv4Addr,
 | 
			
		||||
    prefix: u32,
 | 
			
		||||
    dhcp: Option<IPv4DHCPConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct DHCPv6HostReservation {
 | 
			
		||||
    name: String,
 | 
			
		||||
    ip: Ipv6Addr,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct IPv6DHCPConfig {
 | 
			
		||||
    start: Ipv6Addr,
 | 
			
		||||
    end: Ipv6Addr,
 | 
			
		||||
    hosts: Vec<DHCPv6HostReservation>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct IPV6Config {
 | 
			
		||||
    bridge_address: Ipv6Addr,
 | 
			
		||||
    prefix: u32,
 | 
			
		||||
    dhcp: Option<IPv6DHCPConfig>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network configuration
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
 | 
			
		||||
pub struct NetworkInfo {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    title: Option<String>,
 | 
			
		||||
    description: Option<String>,
 | 
			
		||||
    forward_mode: NetworkForwardMode,
 | 
			
		||||
    device: Option<String>,
 | 
			
		||||
    bridge_name: Option<String>,
 | 
			
		||||
    dns_server: Option<Ipv4Addr>,
 | 
			
		||||
    domain: Option<String>,
 | 
			
		||||
    ip_v4: Option<IPV4Config>,
 | 
			
		||||
    ip_v6: Option<IPV6Config>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkInfo {
 | 
			
		||||
    pub fn as_virt_network(&self) -> anyhow::Result<NetworkXML> {
 | 
			
		||||
        if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
 | 
			
		||||
            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 ipv4.prefix > 32 {
 | 
			
		||||
                return Err(StructureExtraction("IPv4 prefix is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ips.push(NetworkIPXML {
 | 
			
		||||
                family: "ipv4".to_string(),
 | 
			
		||||
                address: IpAddr::V4(ipv4.bridge_address),
 | 
			
		||||
                prefix: ipv4.prefix,
 | 
			
		||||
                netmask: Ipv4Network::new(ipv4.bridge_address, ipv4.prefix as u8)
 | 
			
		||||
                    .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: c.mac.to_string(),
 | 
			
		||||
                            name: c.name.to_string(),
 | 
			
		||||
                            ip: c.ip.into(),
 | 
			
		||||
                        })
 | 
			
		||||
                        .collect::<Vec<_>>(),
 | 
			
		||||
                }),
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(ipv6) = &self.ip_v6 {
 | 
			
		||||
            ips.push(NetworkIPXML {
 | 
			
		||||
                family: "ipv6".to_string(),
 | 
			
		||||
                address: IpAddr::V6(ipv6.bridge_address),
 | 
			
		||||
                prefix: ipv6.prefix,
 | 
			
		||||
                netmask: Ipv6Network::new(ipv6.bridge_address, ipv6.prefix as u8)
 | 
			
		||||
                    .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: "".to_string(),
 | 
			
		||||
                            name: h.name.to_string(),
 | 
			
		||||
                            ip: h.ip.into(),
 | 
			
		||||
                        })
 | 
			
		||||
                        .collect(),
 | 
			
		||||
                }),
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(NetworkXML {
 | 
			
		||||
            name: self.name.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> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: xml.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 {
 | 
			
		||||
                        u32::MAX => ipnetwork::ipv4_mask_to_prefix(extract_ipv4(i.netmask))
 | 
			
		||||
                            .expect("Failed to convert IPv4 netmask to network")
 | 
			
		||||
                            as u32,
 | 
			
		||||
                        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.to_string(),
 | 
			
		||||
                                name: h.name.to_string(),
 | 
			
		||||
                                ip: extract_ipv4(h.ip),
 | 
			
		||||
                            })
 | 
			
		||||
                            .collect(),
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
            ip_v6: xml
 | 
			
		||||
                .ips
 | 
			
		||||
                .iter()
 | 
			
		||||
                .find(|i| i.family == "ipv6")
 | 
			
		||||
                .map(|i| IPV6Config {
 | 
			
		||||
                    bridge_address: extract_ipv6(i.address),
 | 
			
		||||
                    prefix: match i.prefix {
 | 
			
		||||
                        u32::MAX => ipnetwork::ipv6_mask_to_prefix(extract_ipv6(i.netmask))
 | 
			
		||||
                            .expect("Failed to convert IPv6 netmask to network")
 | 
			
		||||
                            as u32,
 | 
			
		||||
                        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(),
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										227
									
								
								virtweb_backend/src/libvirt_rest_structures/nw_filter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								virtweb_backend/src/libvirt_rest_structures/nw_filter.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,227 @@
 | 
			
		||||
use crate::libvirt_lib_structures::nwfilter::NetworkFilterXML;
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::LibVirtStructError;
 | 
			
		||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone)]
 | 
			
		||||
pub enum NetworkFilterChainProtocol {
 | 
			
		||||
    Root,
 | 
			
		||||
    Mac,
 | 
			
		||||
    STP,
 | 
			
		||||
    VLAN,
 | 
			
		||||
    ARP,
 | 
			
		||||
    RARP,
 | 
			
		||||
    IPv4,
 | 
			
		||||
    IPv6,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilterChainProtocol {
 | 
			
		||||
    pub fn from_xml(xml: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(match xml {
 | 
			
		||||
            "root" => Self::Root,
 | 
			
		||||
            "mac" => Self::Mac,
 | 
			
		||||
            "stp" => Self::STP,
 | 
			
		||||
            "vlan" => Self::VLAN,
 | 
			
		||||
            "arp" => Self::ARP,
 | 
			
		||||
            "rarp" => Self::RARP,
 | 
			
		||||
            "ipv4" => Self::IPv4,
 | 
			
		||||
            "ipv6" => Self::IPv6,
 | 
			
		||||
            _ => {
 | 
			
		||||
                return Err(LibVirtStructError::ParseFilteringChain(format!(
 | 
			
		||||
                    "Unknown filtering chain: {xml}! "
 | 
			
		||||
                ))
 | 
			
		||||
                .into())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_xml(&self) -> String {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Root => "root",
 | 
			
		||||
            Self::Mac => "mac",
 | 
			
		||||
            Self::STP => "stp",
 | 
			
		||||
            Self::VLAN => "vlan",
 | 
			
		||||
            Self::ARP => "arp",
 | 
			
		||||
            Self::RARP => "rarp",
 | 
			
		||||
            Self::IPv4 => "ipv4",
 | 
			
		||||
            Self::IPv6 => "ipv6",
 | 
			
		||||
        }
 | 
			
		||||
        .to_string()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilterChain {
 | 
			
		||||
    protocol: NetworkFilterChainProtocol,
 | 
			
		||||
    suffix: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilterChain {
 | 
			
		||||
    pub fn from_xml(xml: &str) -> anyhow::Result<Option<Self>> {
 | 
			
		||||
        if xml.is_empty() {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(Some(match xml.split_once('-') {
 | 
			
		||||
            None => Self {
 | 
			
		||||
                protocol: NetworkFilterChainProtocol::from_xml(xml)?,
 | 
			
		||||
                suffix: None,
 | 
			
		||||
            },
 | 
			
		||||
            Some((prefix, suffix)) => Self {
 | 
			
		||||
                protocol: NetworkFilterChainProtocol::from_xml(prefix)?,
 | 
			
		||||
                suffix: Some(suffix.to_string()),
 | 
			
		||||
            },
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn to_xml(&self) -> String {
 | 
			
		||||
        match &self.suffix {
 | 
			
		||||
            None => self.protocol.to_xml(),
 | 
			
		||||
            Some(s) => format!("{}-{s}", self.protocol.to_xml()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network filter definition
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilter {
 | 
			
		||||
    name: String,
 | 
			
		||||
    chain: Option<NetworkFilterChain>,
 | 
			
		||||
    priority: Option<i32>,
 | 
			
		||||
    uuid: Option<XMLUuid>,
 | 
			
		||||
    /// Referenced filters rules
 | 
			
		||||
    join_rules: Vec<String>,
 | 
			
		||||
    rules: Vec<NetworkFilterRule>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkFilter {
 | 
			
		||||
    pub fn from_xml(xml: NetworkFilterXML) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: xml.name,
 | 
			
		||||
            uuid: xml.uuid,
 | 
			
		||||
            chain: NetworkFilterChain::from_xml(&xml.chain)?,
 | 
			
		||||
            priority: xml.priority,
 | 
			
		||||
            join_rules: xml
 | 
			
		||||
                .filterrefs
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|i| i.filter.to_string())
 | 
			
		||||
                .collect(),
 | 
			
		||||
            rules: vec![], // TODO !
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone)]
 | 
			
		||||
pub enum NetworkFilterAction {
 | 
			
		||||
    /// matching the rule silently discards the packet with no further analysis
 | 
			
		||||
    Drop,
 | 
			
		||||
    /// matching the rule generates an ICMP reject message with no further analysis
 | 
			
		||||
    Reject,
 | 
			
		||||
    /// matching the rule accepts the packet with no further analysis
 | 
			
		||||
    Accept,
 | 
			
		||||
    /// matching the rule passes this filter, but returns control to the calling filter for further
 | 
			
		||||
    /// analysis
 | 
			
		||||
    Return,
 | 
			
		||||
    /// matching the rule goes on to the next rule for further analysis
 | 
			
		||||
    Continue,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum NetworkFilterDirection {
 | 
			
		||||
    In,
 | 
			
		||||
    Out,
 | 
			
		||||
    InOut,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum Layer4State {
 | 
			
		||||
    NEW,
 | 
			
		||||
    ESTABLISHED,
 | 
			
		||||
    RELATED,
 | 
			
		||||
    INVALID,
 | 
			
		||||
    NONE,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum Layer4Type {
 | 
			
		||||
    TCP,
 | 
			
		||||
    UDP,
 | 
			
		||||
    SCTP,
 | 
			
		||||
    ICMP,
 | 
			
		||||
    TCPipv6,
 | 
			
		||||
    UDPipv6,
 | 
			
		||||
    SCTPipv6,
 | 
			
		||||
    ICMPipv6,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilterSelectorIP<IPv> {
 | 
			
		||||
    srcmacaddr: Option<String>,
 | 
			
		||||
    srcmacmask: Option<String>,
 | 
			
		||||
    dstmacaddr: Option<String>,
 | 
			
		||||
    dstmacmask: Option<String>,
 | 
			
		||||
    srcipaddr: Option<IPv>,
 | 
			
		||||
    srcipmask: Option<u8>,
 | 
			
		||||
    dstipaddr: Option<IPv>,
 | 
			
		||||
    dstipmask: Option<u8>,
 | 
			
		||||
    comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub enum NetworkFilterSelector {
 | 
			
		||||
    All,
 | 
			
		||||
    Mac {
 | 
			
		||||
        src_mac_addr: Option<String>,
 | 
			
		||||
        src_mac_mask: Option<String>,
 | 
			
		||||
        dst_mac_addr: Option<String>,
 | 
			
		||||
        dst_mac_mask: Option<String>,
 | 
			
		||||
        comment: Option<String>,
 | 
			
		||||
    },
 | 
			
		||||
    Arp {
 | 
			
		||||
        srcmacaddr: Option<String>,
 | 
			
		||||
        srcmacmask: Option<String>,
 | 
			
		||||
        dstmacaddr: Option<String>,
 | 
			
		||||
        dstmacmask: Option<String>,
 | 
			
		||||
        arpsrcipaddr: Option<IpAddr>,
 | 
			
		||||
        arpsrcipmask: Option<u8>,
 | 
			
		||||
        arpdstipaddr: Option<IpAddr>,
 | 
			
		||||
        arpdstipmask: Option<u8>,
 | 
			
		||||
        comment: Option<String>,
 | 
			
		||||
    },
 | 
			
		||||
    IPv4(NetworkFilterSelectorIP<Ipv4Addr>),
 | 
			
		||||
    IPv6(NetworkFilterSelectorIP<Ipv6Addr>),
 | 
			
		||||
    Layer4 {
 | 
			
		||||
        r#type: Layer4Type,
 | 
			
		||||
        srcmacaddr: Option<String>,
 | 
			
		||||
        srcipaddr: Option<IpAddr>,
 | 
			
		||||
        srcipmask: Option<u8>,
 | 
			
		||||
        dstipaddr: Option<IpAddr>,
 | 
			
		||||
        dstipmask: Option<u8>,
 | 
			
		||||
        /// Start of range of source IP address
 | 
			
		||||
        srcipfrom: Option<IpAddr>,
 | 
			
		||||
        /// End of range of source IP address
 | 
			
		||||
        srcipto: Option<IpAddr>,
 | 
			
		||||
        /// Start of range of destination IP address
 | 
			
		||||
        dstipfrom: Option<IpAddr>,
 | 
			
		||||
        /// End of range of destination IP address
 | 
			
		||||
        dstipto: Option<IpAddr>,
 | 
			
		||||
        srcportstart: Option<u16>,
 | 
			
		||||
        srcportend: Option<u16>,
 | 
			
		||||
        dstportstart: Option<u16>,
 | 
			
		||||
        dstportend: Option<u16>,
 | 
			
		||||
        state: Option<Layer4State>,
 | 
			
		||||
        comment: Option<String>,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
			
		||||
pub struct NetworkFilterRule {
 | 
			
		||||
    action: NetworkFilterAction,
 | 
			
		||||
    direction: NetworkFilterDirection,
 | 
			
		||||
    /// optional; the priority of the rule controls the order in which the rule will be instantiated
 | 
			
		||||
    /// relative to other rules
 | 
			
		||||
    ///
 | 
			
		||||
    /// Valid values are in the range of -1000 to 1000.
 | 
			
		||||
    priority: Option<i32>,
 | 
			
		||||
    selectors: Vec<NetworkFilterSelector>,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										386
									
								
								virtweb_backend/src/libvirt_rest_structures/vm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								virtweb_backend/src/libvirt_rest_structures/vm.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,386 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::libvirt_lib_structures::domain::*;
 | 
			
		||||
use crate::libvirt_lib_structures::XMLUuid;
 | 
			
		||||
use crate::libvirt_rest_structures::LibVirtStructError;
 | 
			
		||||
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
 | 
			
		||||
use crate::utils::disks_utils::Disk;
 | 
			
		||||
use crate::utils::files_utils;
 | 
			
		||||
use crate::utils::files_utils::convert_size_unit_to_mb;
 | 
			
		||||
use lazy_regex::regex;
 | 
			
		||||
use num::Integer;
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum BootType {
 | 
			
		||||
    UEFI,
 | 
			
		||||
    UEFISecureBoot,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum VMArchitecture {
 | 
			
		||||
    #[serde(rename = "i686")]
 | 
			
		||||
    I686,
 | 
			
		||||
    #[serde(rename = "x86_64")]
 | 
			
		||||
    X86_64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct Network {
 | 
			
		||||
    mac: String,
 | 
			
		||||
    #[serde(flatten)]
 | 
			
		||||
    r#type: NetworkType,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
pub enum NetworkType {
 | 
			
		||||
    UserspaceSLIRPStack,
 | 
			
		||||
    DefinedNetwork { network: String }, // TODO : complete network types
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct VMInfo {
 | 
			
		||||
    /// VM name (alphanumeric characters only)
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub uuid: Option<XMLUuid>,
 | 
			
		||||
    pub genid: Option<XMLUuid>,
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub description: Option<String>,
 | 
			
		||||
    pub boot_type: BootType,
 | 
			
		||||
    pub architecture: VMArchitecture,
 | 
			
		||||
    /// VM allocated memory, in megabytes
 | 
			
		||||
    pub memory: usize,
 | 
			
		||||
    /// Number of vCPU for the VM
 | 
			
		||||
    pub number_vcpu: usize,
 | 
			
		||||
    /// Enable VNC access through admin console
 | 
			
		||||
    pub vnc_access: bool,
 | 
			
		||||
    /// Attach ISO file(s)
 | 
			
		||||
    pub iso_files: Vec<String>,
 | 
			
		||||
    /// Storage - https://access.redhat.com/documentation/fr-fr/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-virtualized_block_devices-adding_storage_devices_to_guests#sect-Virtualization-Adding_storage_devices_to_guests-Adding_file_based_storage_to_a_guest
 | 
			
		||||
    pub disks: Vec<Disk>,
 | 
			
		||||
    /// Network cards
 | 
			
		||||
    pub networks: Vec<Network>,
 | 
			
		||||
    /// Add a TPM v2.0 module
 | 
			
		||||
    pub tpm_module: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VMInfo {
 | 
			
		||||
    /// Turn this VM into a domain
 | 
			
		||||
    pub fn as_tomain(&self) -> anyhow::Result<DomainXML> {
 | 
			
		||||
        if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
 | 
			
		||||
            return Err(StructureExtraction("VM name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let uuid = if let Some(n) = self.uuid {
 | 
			
		||||
            if !n.is_valid() {
 | 
			
		||||
                return Err(StructureExtraction("VM UUID is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
            n
 | 
			
		||||
        } else {
 | 
			
		||||
            XMLUuid::new_random()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.genid {
 | 
			
		||||
            if !n.is_valid() {
 | 
			
		||||
                return Err(StructureExtraction("VM genid is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.title {
 | 
			
		||||
            if n.contains('\n') {
 | 
			
		||||
                return Err(StructureExtraction("VM title contain newline char!").into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
 | 
			
		||||
            return Err(StructureExtraction("VM memory is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.number_vcpu == 0 || (self.number_vcpu != 1 && self.number_vcpu.is_odd()) {
 | 
			
		||||
            return Err(StructureExtraction("Invalid number of vCPU specified!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut disks = vec![];
 | 
			
		||||
 | 
			
		||||
        for iso_file in &self.iso_files {
 | 
			
		||||
            if !files_utils::check_file_name(iso_file) {
 | 
			
		||||
                return Err(StructureExtraction("ISO filename is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let path = AppConfig::get().iso_storage_path().join(iso_file);
 | 
			
		||||
 | 
			
		||||
            if !path.exists() {
 | 
			
		||||
                return Err(StructureExtraction("Specified ISO file does not exists!").into());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            disks.push(DiskXML {
 | 
			
		||||
                r#type: "file".to_string(),
 | 
			
		||||
                device: "cdrom".to_string(),
 | 
			
		||||
                driver: DiskDriverXML {
 | 
			
		||||
                    name: "qemu".to_string(),
 | 
			
		||||
                    r#type: "raw".to_string(),
 | 
			
		||||
                    cache: "none".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                source: DiskSourceXML {
 | 
			
		||||
                    file: path.to_string_lossy().to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                target: DiskTargetXML {
 | 
			
		||||
                    dev: format!(
 | 
			
		||||
                        "hd{}",
 | 
			
		||||
                        ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"][disks.len()]
 | 
			
		||||
                    ),
 | 
			
		||||
                    bus: "sata".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                readonly: Some(DiskReadOnlyXML {}),
 | 
			
		||||
                boot: DiskBootXML {
 | 
			
		||||
                    order: (disks.len() + 1).to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                address: None,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (vnc_graphics, vnc_video) = match self.vnc_access {
 | 
			
		||||
            true => (
 | 
			
		||||
                Some(GraphicsXML {
 | 
			
		||||
                    r#type: "vnc".to_string(),
 | 
			
		||||
                    socket: AppConfig::get()
 | 
			
		||||
                        .vnc_socket_for_domain(&self.name)
 | 
			
		||||
                        .to_string_lossy()
 | 
			
		||||
                        .to_string(),
 | 
			
		||||
                }),
 | 
			
		||||
                Some(VideoXML {
 | 
			
		||||
                    model: VideoModelXML {
 | 
			
		||||
                        r#type: "virtio".to_string(), //"qxl".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            ),
 | 
			
		||||
            false => (None, None),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Check disks name for duplicates
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
            if self.disks.iter().filter(|d| d.name == disk.name).count() > 1 {
 | 
			
		||||
                return Err(StructureExtraction("Two different disks have the same name!").into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Apply disks configuration
 | 
			
		||||
        for disk in &self.disks {
 | 
			
		||||
            disk.check_config()?;
 | 
			
		||||
            disk.apply_config(uuid)?;
 | 
			
		||||
 | 
			
		||||
            if disk.delete {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            disks.push(DiskXML {
 | 
			
		||||
                r#type: "file".to_string(),
 | 
			
		||||
                device: "disk".to_string(),
 | 
			
		||||
                driver: DiskDriverXML {
 | 
			
		||||
                    name: "qemu".to_string(),
 | 
			
		||||
                    r#type: "raw".to_string(),
 | 
			
		||||
                    cache: "none".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                source: DiskSourceXML {
 | 
			
		||||
                    file: disk.disk_path(uuid).to_string_lossy().to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                target: DiskTargetXML {
 | 
			
		||||
                    dev: format!(
 | 
			
		||||
                        "vd{}",
 | 
			
		||||
                        ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"][disks.len()]
 | 
			
		||||
                    ),
 | 
			
		||||
                    bus: "virtio".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                readonly: None,
 | 
			
		||||
                boot: DiskBootXML {
 | 
			
		||||
                    order: (disks.len() + 1).to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                address: None,
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut networks = vec![];
 | 
			
		||||
        for n in &self.networks {
 | 
			
		||||
            networks.push(match &n.r#type {
 | 
			
		||||
                NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
 | 
			
		||||
                    mac: NetMacAddress {
 | 
			
		||||
                        address: n.mac.to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    r#type: "user".to_string(),
 | 
			
		||||
                    source: None,
 | 
			
		||||
                    model: Some(NetIntModelXML {
 | 
			
		||||
                        r#type: "virtio".to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                },
 | 
			
		||||
                NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
 | 
			
		||||
                    mac: NetMacAddress {
 | 
			
		||||
                        address: n.mac.to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    r#type: "network".to_string(),
 | 
			
		||||
                    source: Some(NetIntSourceXML {
 | 
			
		||||
                        network: network.to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                    model: Some(NetIntModelXML {
 | 
			
		||||
                        r#type: "virtio".to_string(),
 | 
			
		||||
                    }),
 | 
			
		||||
                },
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(DomainXML {
 | 
			
		||||
            r#type: "kvm".to_string(),
 | 
			
		||||
            name: self.name.to_string(),
 | 
			
		||||
            uuid: Some(uuid),
 | 
			
		||||
            genid: self.genid.map(|i| i.0),
 | 
			
		||||
            title: self.title.clone(),
 | 
			
		||||
            description: self.description.clone(),
 | 
			
		||||
 | 
			
		||||
            os: OSXML {
 | 
			
		||||
                r#type: OSTypeXML {
 | 
			
		||||
                    arch: match self.architecture {
 | 
			
		||||
                        VMArchitecture::I686 => "i686",
 | 
			
		||||
                        VMArchitecture::X86_64 => "x86_64",
 | 
			
		||||
                    }
 | 
			
		||||
                    .to_string(),
 | 
			
		||||
                    machine: "q35".to_string(),
 | 
			
		||||
                    body: "hvm".to_string(),
 | 
			
		||||
                },
 | 
			
		||||
                firmware: "efi".to_string(),
 | 
			
		||||
                loader: Some(OSLoaderXML {
 | 
			
		||||
                    secure: match self.boot_type {
 | 
			
		||||
                        BootType::UEFI => "no".to_string(),
 | 
			
		||||
                        BootType::UEFISecureBoot => "yes".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            features: FeaturesXML { acpi: ACPIXML {} },
 | 
			
		||||
 | 
			
		||||
            devices: DevicesXML {
 | 
			
		||||
                graphics: vnc_graphics,
 | 
			
		||||
                video: vnc_video,
 | 
			
		||||
                disks,
 | 
			
		||||
                net_interfaces: networks,
 | 
			
		||||
                inputs: vec![
 | 
			
		||||
                    DomainInputXML {
 | 
			
		||||
                        r#type: "mouse".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    DomainInputXML {
 | 
			
		||||
                        r#type: "keyboard".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                    DomainInputXML {
 | 
			
		||||
                        r#type: "tablet".to_string(),
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                tpm: match self.tpm_module {
 | 
			
		||||
                    true => Some(TPMDeviceXML {
 | 
			
		||||
                        model: "tpm-tis".to_string(),
 | 
			
		||||
                        backend: TPMBackendXML {
 | 
			
		||||
                            r#type: "emulator".to_string(),
 | 
			
		||||
                            version: "2.0".to_string(),
 | 
			
		||||
                        },
 | 
			
		||||
                    }),
 | 
			
		||||
                    false => None,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            memory: DomainMemoryXML {
 | 
			
		||||
                unit: "MB".to_string(),
 | 
			
		||||
                memory: self.memory,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            vcpu: DomainVCPUXML {
 | 
			
		||||
                body: self.number_vcpu,
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            cpu: DomainCPUXML {
 | 
			
		||||
                mode: "host-passthrough".to_string(),
 | 
			
		||||
                topology: Some(DomainCPUTopology {
 | 
			
		||||
                    sockets: 1,
 | 
			
		||||
                    cores: match self.number_vcpu {
 | 
			
		||||
                        1 => 1,
 | 
			
		||||
                        v => v / 2,
 | 
			
		||||
                    },
 | 
			
		||||
                    threads: match self.number_vcpu {
 | 
			
		||||
                        1 => 1,
 | 
			
		||||
                        _ => 2,
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            on_poweroff: "destroy".to_string(),
 | 
			
		||||
            on_reboot: "restart".to_string(),
 | 
			
		||||
            on_crash: "destroy".to_string(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Turn a domain into a vm
 | 
			
		||||
    pub fn from_domain(domain: DomainXML) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            name: domain.name,
 | 
			
		||||
            uuid: domain.uuid,
 | 
			
		||||
            genid: domain.genid.map(XMLUuid),
 | 
			
		||||
            title: domain.title,
 | 
			
		||||
            description: domain.description,
 | 
			
		||||
            boot_type: match domain.os.loader {
 | 
			
		||||
                None => BootType::UEFI,
 | 
			
		||||
                Some(l) => match l.secure.as_str() {
 | 
			
		||||
                    "yes" => BootType::UEFISecureBoot,
 | 
			
		||||
                    _ => BootType::UEFI,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            architecture: match domain.os.r#type.arch.as_str() {
 | 
			
		||||
                "i686" => VMArchitecture::I686,
 | 
			
		||||
                "x86_64" => VMArchitecture::X86_64,
 | 
			
		||||
                a => {
 | 
			
		||||
                    return Err(LibVirtStructError::DomainExtraction(format!(
 | 
			
		||||
                        "Unknown architecture: {a}! "
 | 
			
		||||
                    ))
 | 
			
		||||
                    .into());
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            number_vcpu: domain.vcpu.body,
 | 
			
		||||
            memory: convert_size_unit_to_mb(&domain.memory.unit, domain.memory.memory)?,
 | 
			
		||||
            vnc_access: domain.devices.graphics.is_some(),
 | 
			
		||||
            iso_files: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .disks
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|d| d.device == "cdrom")
 | 
			
		||||
                .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
 | 
			
		||||
                .collect(),
 | 
			
		||||
 | 
			
		||||
            disks: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .disks
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|d| d.device == "disk")
 | 
			
		||||
                .map(|d| Disk::load_from_file(&d.source.file).unwrap())
 | 
			
		||||
                .collect(),
 | 
			
		||||
 | 
			
		||||
            networks: domain
 | 
			
		||||
                .devices
 | 
			
		||||
                .net_interfaces
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|d| {
 | 
			
		||||
                    Ok(Network {
 | 
			
		||||
                        mac: d.mac.address.to_string(),
 | 
			
		||||
                        r#type: match d.r#type.as_str() {
 | 
			
		||||
                            "user" => NetworkType::UserspaceSLIRPStack,
 | 
			
		||||
                            "network" => NetworkType::DefinedNetwork {
 | 
			
		||||
                                network: d.source.as_ref().unwrap().network.to_string(),
 | 
			
		||||
                            },
 | 
			
		||||
                            a => {
 | 
			
		||||
                                return Err(LibVirtStructError::DomainExtraction(format!(
 | 
			
		||||
                                    "Unknown network interface type: {a}! "
 | 
			
		||||
                                )));
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                    })
 | 
			
		||||
                })
 | 
			
		||||
                .collect::<Result<Vec<_>, _>>()?,
 | 
			
		||||
 | 
			
		||||
            tpm_module: domain.devices.tpm.is_some(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,13 @@
 | 
			
		||||
use std::ops::{Div, Mul};
 | 
			
		||||
use std::os::unix::fs::PermissionsExt;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum FilesUtilsError {
 | 
			
		||||
    #[error("UnitConvertError: {0}")]
 | 
			
		||||
    UnitConvert(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const INVALID_CHARS: [&str; 19] = [
 | 
			
		||||
    "@", "\\", "/", ":", ",", "<", ">", "%", "'", "\"", "?", "{", "}", "$", "*", "|", ";", "=",
 | 
			
		||||
    "\t",
 | 
			
		||||
@@ -28,9 +35,31 @@ pub fn set_file_permission<P: AsRef<Path>>(path: P, mode: u32) -> anyhow::Result
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Convert size unit to MB
 | 
			
		||||
pub fn convert_size_unit_to_mb(unit: &str, value: usize) -> anyhow::Result<usize> {
 | 
			
		||||
    let fact = match unit {
 | 
			
		||||
        "bytes" | "b" => 1f64,
 | 
			
		||||
        "KB" => 1000f64,
 | 
			
		||||
        "MB" => 1000f64 * 1000f64,
 | 
			
		||||
        "GB" => 1000f64 * 1000f64 * 1000f64,
 | 
			
		||||
        "TB" => 1000f64 * 1000f64 * 1000f64 * 1000f64,
 | 
			
		||||
 | 
			
		||||
        "k" | "KiB" => 1024f64,
 | 
			
		||||
        "M" | "MiB" => 1024f64 * 1024f64,
 | 
			
		||||
        "G" | "GiB" => 1024f64 * 1024f64 * 1024f64,
 | 
			
		||||
        "T" | "TiB" => 1024f64 * 1024f64 * 1024f64 * 1024f64,
 | 
			
		||||
 | 
			
		||||
        _ => {
 | 
			
		||||
            return Err(FilesUtilsError::UnitConvert(format!("Unknown size unit: {unit}")).into());
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok((value as f64).mul(fact.div((1000 * 1000) as f64)).ceil() as usize)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use crate::utils::files_utils::check_file_name;
 | 
			
		||||
    use crate::utils::files_utils::{check_file_name, convert_size_unit_to_mb};
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn empty_file_name() {
 | 
			
		||||
@@ -56,4 +85,14 @@ mod test {
 | 
			
		||||
    fn valid_file_name() {
 | 
			
		||||
        assert!(check_file_name("test.iso"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn convert_units_mb() {
 | 
			
		||||
        assert_eq!(convert_size_unit_to_mb("MB", 1).unwrap(), 1);
 | 
			
		||||
        assert_eq!(convert_size_unit_to_mb("MB", 1000).unwrap(), 1000);
 | 
			
		||||
        assert_eq!(convert_size_unit_to_mb("GB", 1000).unwrap(), 1000 * 1000);
 | 
			
		||||
        assert_eq!(convert_size_unit_to_mb("GB", 1).unwrap(), 1000);
 | 
			
		||||
        assert_eq!(convert_size_unit_to_mb("GiB", 3).unwrap(), 3222);
 | 
			
		||||
        assert_eq!(convert_size_unit_to_mb("KiB", 488281).unwrap(), 500);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
pub mod disks_utils;
 | 
			
		||||
pub mod files_utils;
 | 
			
		||||
pub mod net_utils;
 | 
			
		||||
pub mod rand_utils;
 | 
			
		||||
pub mod time_utils;
 | 
			
		||||
pub mod url_utils;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								virtweb_backend/src/utils/net_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								virtweb_backend/src/utils/net_utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 | 
			
		||||
 | 
			
		||||
pub fn extract_ipv4(ip: IpAddr) -> Ipv4Addr {
 | 
			
		||||
    match ip {
 | 
			
		||||
        IpAddr::V4(i) => i,
 | 
			
		||||
        IpAddr::V6(_) => {
 | 
			
		||||
            panic!("IPv6 found in IPv4 definition!")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_ipv6(ip: IpAddr) -> Ipv6Addr {
 | 
			
		||||
    match ip {
 | 
			
		||||
        IpAddr::V4(_) => {
 | 
			
		||||
            panic!("IPv4 found in IPv6 definition!")
 | 
			
		||||
        }
 | 
			
		||||
        IpAddr::V6(i) => i,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user