diff --git a/virtweb_backend/Cargo.lock b/virtweb_backend/Cargo.lock index 4cadb48..42bb251 100644 --- a/virtweb_backend/Cargo.lock +++ b/virtweb_backend/Cargo.lock @@ -1882,6 +1882,16 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.33" @@ -2662,6 +2672,7 @@ dependencies = [ "log", "mime_guess", "num", + "quick-xml", "rand", "reqwest", "rust-embed", diff --git a/virtweb_backend/Cargo.toml b/virtweb_backend/Cargo.toml index 03746f2..75caaa4 100644 --- a/virtweb_backend/Cargo.toml +++ b/virtweb_backend/Cargo.toml @@ -23,6 +23,7 @@ actix-http = "3.4.0" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" serde-xml-rs = "0.6.0" +quick-xml = { version = "0.31.0", features = ["serialize", "overlapped-lists"] } futures-util = "0.3.28" anyhow = "1.0.75" actix-multipart = "0.6.1" @@ -43,4 +44,4 @@ ipnetwork = "0.20.0" num = "0.4.1" rust-embed = { version = "8.1.0" } mime_guess = "2.0.4" -dotenvy = "0.15.7" \ No newline at end of file +dotenvy = "0.15.7" diff --git a/virtweb_backend/src/actors/libvirt_actor.rs b/virtweb_backend/src/actors/libvirt_actor.rs index 954d803..5606dfa 100644 --- a/virtweb_backend/src/actors/libvirt_actor.rs +++ b/virtweb_backend/src/actors/libvirt_actor.rs @@ -5,6 +5,7 @@ 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::nw_filter::NetworkFilter; use crate::libvirt_rest_structures::vm::*; use actix::{Actor, Context, Handler, Message}; use image::ImageOutputFormat; @@ -591,3 +592,29 @@ impl Handler for LibVirtActor { NetworkFilterXML::parse_xml(xml) } } + +#[derive(Message)] +#[rtype(result = "anyhow::Result")] +pub struct DefineNWFilterReq(pub NetworkFilter, pub NetworkFilterXML); + +impl Handler for LibVirtActor { + type Result = anyhow::Result; + + fn handle(&mut self, mut msg: DefineNWFilterReq, _ctx: &mut Self::Context) -> Self::Result { + let xml = msg.1.into_xml()?; + + log::debug!("Define network filter:\n{}", xml); + let filter = NWFilter::define_xml(&self.m, &xml)?; + let uuid = XMLUuid::parse_from_str(&filter.get_uuid_string()?)?; + + // Save a copy of the source definition + msg.0.uuid = Some(uuid); + let json = serde_json::to_string(&msg.0)?; + std::fs::write( + AppConfig::get().net_filter_definition_path(&msg.0.name), + json, + )?; + + Ok(uuid) + } +} diff --git a/virtweb_backend/src/app_config.rs b/virtweb_backend/src/app_config.rs index d519b75..6ac8ab5 100644 --- a/virtweb_backend/src/app_config.rs +++ b/virtweb_backend/src/app_config.rs @@ -1,4 +1,5 @@ use crate::libvirt_lib_structures::XMLUuid; +use crate::libvirt_rest_structures::nw_filter::NetworkFilterName; use clap::Parser; use std::net::IpAddr; use std::path::{Path, PathBuf}; @@ -252,6 +253,11 @@ impl AppConfig { pub fn net_definition_path(&self, name: &str) -> PathBuf { self.definitions_path().join(format!("net-{name}.json")) } + + pub fn net_filter_definition_path(&self, name: &NetworkFilterName) -> PathBuf { + self.definitions_path() + .join(format!("nwfilter-{}.json", name.0)) + } } #[derive(Debug, Clone, serde::Serialize)] diff --git a/virtweb_backend/src/controllers/nwfilter_controller.rs b/virtweb_backend/src/controllers/nwfilter_controller.rs index 57d0b6a..25c7aa4 100644 --- a/virtweb_backend/src/controllers/nwfilter_controller.rs +++ b/virtweb_backend/src/controllers/nwfilter_controller.rs @@ -25,9 +25,6 @@ pub async fn create(client: LibVirtReq, req: web::Json) -> HttpRe .json("Builtin network filter rules shall not be modified!")); } - // TODO : remove - return Ok(HttpResponse::Ok().json(network)); - let uid = match client.update_network_filter(req.0, network).await { Ok(u) => u, Err(e) => { diff --git a/virtweb_backend/src/libvirt_client.rs b/virtweb_backend/src/libvirt_client.rs index a94acd4..96043fc 100644 --- a/virtweb_backend/src/libvirt_client.rs +++ b/virtweb_backend/src/libvirt_client.rs @@ -190,10 +190,11 @@ impl LibVirtClient { /// Update the information about a single domain pub async fn update_network_filter( &self, - _vm_def: NetworkFilter, + nwf_def: NetworkFilter, xml: NetworkFilterXML, ) -> anyhow::Result { - println!("nwfilter xml to update: {:#?}", xml); - todo!() + self.0 + .send(libvirt_actor::DefineNWFilterReq(nwf_def, xml)) + .await? } } diff --git a/virtweb_backend/src/libvirt_lib_structures/nwfilter.rs b/virtweb_backend/src/libvirt_lib_structures/nwfilter.rs index b429a69..623aa7b 100644 --- a/virtweb_backend/src/libvirt_lib_structures/nwfilter.rs +++ b/virtweb_backend/src/libvirt_lib_structures/nwfilter.rs @@ -5,7 +5,7 @@ use std::net::{Ipv4Addr, Ipv6Addr}; #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename = "filterref")] pub struct NetworkFilterRefXML { - #[serde(rename(serialize = "@filter"))] + #[serde(rename = "@filter")] pub filter: String, } @@ -16,225 +16,114 @@ 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" - )] + #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")] pub srcmacaddr: Option, - #[serde( - rename(serialize = "@srcmacmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@scmacmask", skip_serializing_if = "Option::is_none")] pub srcmacmask: Option, - #[serde( - rename(serialize = "@dstmacaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstmacaddr", skip_serializing_if = "Option::is_none")] pub dstmacaddr: Option, - #[serde( - rename(serialize = "@dstmacmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstmacmask", skip_serializing_if = "Option::is_none")] pub dstmacmask: Option, - #[serde( - rename(serialize = "@comment"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")] pub comment: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename = "arp")] pub struct NetworkFilterRuleProtocolArpXML { - #[serde( - rename(serialize = "@srcmacaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")] pub srcmacaddr: Option, - #[serde( - rename(serialize = "@srcmacmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcmacmask", skip_serializing_if = "Option::is_none")] pub srcmacmask: Option, - #[serde( - rename(serialize = "@dstmacaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstmacaddr", skip_serializing_if = "Option::is_none")] pub dstmacaddr: Option, - #[serde( - rename(serialize = "@dstmacmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstmacmask", skip_serializing_if = "Option::is_none")] pub dstmacmask: Option, - #[serde( - rename(serialize = "@arpsrcipaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@arpsrcipaddr", skip_serializing_if = "Option::is_none")] pub arpsrcipaddr: Option, - #[serde( - rename(serialize = "@arpsrcipmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@arpsrcipmask", skip_serializing_if = "Option::is_none")] pub arpsrcipmask: Option, - #[serde( - rename(serialize = "@arpdstipaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@arpdstipaddr", skip_serializing_if = "Option::is_none")] pub arpdstipaddr: Option, - #[serde( - rename(serialize = "@arpdstipmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@arpdstipmask", skip_serializing_if = "Option::is_none")] pub arpdstipmask: Option, - #[serde( - rename(serialize = "@comment"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")] pub comment: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename = "ipvx")] pub struct NetworkFilterRuleProtocolIpvx { - #[serde( - rename(serialize = "@srcmacaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")] pub srcmacaddr: Option, - #[serde( - rename(serialize = "@srcmacmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcmacmask", skip_serializing_if = "Option::is_none")] pub srcmacmask: Option, - #[serde( - rename(serialize = "@dstmacaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstmacaddr", skip_serializing_if = "Option::is_none")] pub dstmacaddr: Option, - #[serde( - rename(serialize = "@dstmacmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstmacmask", skip_serializing_if = "Option::is_none")] pub dstmacmask: Option, - #[serde( - rename(serialize = "@srcipaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcipaddr", skip_serializing_if = "Option::is_none")] pub srcipaddr: Option, - #[serde( - rename(serialize = "@srcipmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcipmask", skip_serializing_if = "Option::is_none")] pub srcipmask: Option, - #[serde( - rename(serialize = "@dstipaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstipaddr", skip_serializing_if = "Option::is_none")] pub dstipaddr: Option, - #[serde( - rename(serialize = "@dstipmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstipmask", skip_serializing_if = "Option::is_none")] pub dstipmask: Option, - #[serde( - rename(serialize = "@comment"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")] pub comment: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename = "layer4")] pub struct NetworkFilterRuleProtocolLayer4 { - #[serde( - rename(serialize = "@srcmacaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcmacaddr", skip_serializing_if = "Option::is_none")] pub srcmacaddr: Option, - #[serde( - rename(serialize = "@srcipaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcipaddr", skip_serializing_if = "Option::is_none")] pub srcipaddr: Option, - #[serde( - rename(serialize = "@srcipmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcipmask", skip_serializing_if = "Option::is_none")] pub srcipmask: Option, - #[serde( - rename(serialize = "@dstipaddr"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstipaddr", skip_serializing_if = "Option::is_none")] pub dstipaddr: Option, - #[serde( - rename(serialize = "@dstipmask"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstipmask", skip_serializing_if = "Option::is_none")] pub dstipmask: Option, /// Start of range of source IP address - #[serde( - rename(serialize = "@srcipfrom"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcipfrom", skip_serializing_if = "Option::is_none")] pub srcipfrom: Option, /// End of range of source IP address - #[serde( - rename(serialize = "@srcipto"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcipto", skip_serializing_if = "Option::is_none")] pub srcipto: Option, /// Start of range of destination IP address - #[serde( - rename(serialize = "@dstipfrom"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstipfrom", skip_serializing_if = "Option::is_none")] pub dstipfrom: Option, /// End of range of destination IP address - #[serde( - rename(serialize = "@dstipto"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstipto", skip_serializing_if = "Option::is_none")] pub dstipto: Option, - #[serde( - rename(serialize = "@srcportstart"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcportstart", skip_serializing_if = "Option::is_none")] pub srcportstart: Option, - #[serde( - rename(serialize = "@srcportend"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@srcportend", skip_serializing_if = "Option::is_none")] pub srcportend: Option, - #[serde( - rename(serialize = "@dstportstart"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstportstart", skip_serializing_if = "Option::is_none")] pub dstportstart: Option, - #[serde( - rename(serialize = "@dstportend"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@dstportend", skip_serializing_if = "Option::is_none")] pub dstportend: Option, - #[serde(rename(serialize = "@state"), skip_serializing_if = "Option::is_none")] + #[serde(rename = "@state", skip_serializing_if = "Option::is_none")] pub state: Option, - #[serde( - rename(serialize = "@comment"), - skip_serializing_if = "Option::is_none" - )] + #[serde(rename = "@comment", skip_serializing_if = "Option::is_none")] pub comment: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug, Default)] #[serde(rename = "rule")] pub struct NetworkFilterRuleXML { - #[serde(rename(serialize = "@action"))] + #[serde(rename = "@action")] pub action: String, - #[serde(rename(serialize = "@direction"))] + #[serde(rename = "@direction")] pub direction: String, - #[serde(rename(serialize = "@priority"))] + #[serde(rename = "@priority")] pub priority: Option, /// Match all protocols @@ -297,23 +186,15 @@ pub struct NetworkFilterRuleXML { #[derive(serde::Serialize, serde::Deserialize, Debug)] #[serde(rename = "filter")] pub struct NetworkFilterXML { - #[serde(rename(serialize = "@name"))] + #[serde(rename = "@name")] pub name: String, - #[serde( - rename(serialize = "@chain"), - skip_serializing_if = "Option::is_none", - default - )] + #[serde(rename = "@chain", skip_serializing_if = "Option::is_none", default)] pub chain: Option, - #[serde( - skip_serializing_if = "Option::is_none", - rename(serialize = "@priority"), - default - )] + #[serde(skip_serializing_if = "Option::is_none", rename = "@priority", default)] pub priority: Option, #[serde(skip_serializing_if = "Option::is_none")] pub uuid: Option, - #[serde(default, rename = "filterref", skip_serializing_if = "Vec::is_empty")] + #[serde(default, rename = "filterref")] pub filterrefs: Vec, #[serde(default, rename = "rule", skip_serializing_if = "Vec::is_empty")] pub rules: Vec, @@ -321,24 +202,10 @@ pub struct NetworkFilterXML { impl NetworkFilterXML { pub fn parse_xml(xml: D) -> anyhow::Result { - let xml = xml.to_string(); + Ok(quick_xml::de::from_str(&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#""#, &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 object!"); - } - - "" - }); - - let filter_refs = filter_refs.join("\n"); - let xml = xml.replace("", &format!("{filter_refs}")); - log::debug!("Effective NW filter rule parsed: {xml}"); - - Ok(serde_xml_rs::from_str(&xml)?) + pub fn into_xml(self) -> anyhow::Result { + Ok(quick_xml::se::to_string(&self)?) } } diff --git a/virtweb_backend/src/libvirt_rest_structures/nw_filter.rs b/virtweb_backend/src/libvirt_rest_structures/nw_filter.rs index 69a2dbb..29233f2 100644 --- a/virtweb_backend/src/libvirt_rest_structures/nw_filter.rs +++ b/virtweb_backend/src/libvirt_rest_structures/nw_filter.rs @@ -400,10 +400,10 @@ pub struct NetworkFilterRule { /// Network filter definition #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct NetworkFilter { - name: NetworkFilterName, + pub name: NetworkFilterName, chain: Option, priority: Option, - uuid: Option, + pub uuid: Option, /// Referenced filters rules join_filters: Vec, rules: Vec,