From e86b29c03accdb4ffa4d4056d17e378ca7ef46e6 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 8 Jan 2024 21:29:31 +0100 Subject: [PATCH] Improve network checks --- .../src/libvirt_lib_structures/network.rs | 17 ++---- .../src/libvirt_rest_structures/net.rs | 61 ++++++++++++------- virtweb_backend/src/nat/nat_definition.rs | 37 +++++++++++ virtweb_backend/src/utils/net_utils.rs | 14 +++++ 4 files changed, 96 insertions(+), 33 deletions(-) diff --git a/virtweb_backend/src/libvirt_lib_structures/network.rs b/virtweb_backend/src/libvirt_lib_structures/network.rs index d7817ef..f3ad511 100644 --- a/virtweb_backend/src/libvirt_lib_structures/network.rs +++ b/virtweb_backend/src/libvirt_lib_structures/network.rs @@ -43,14 +43,6 @@ pub struct NetworkDomainXML { 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")] @@ -60,12 +52,13 @@ pub struct NetworkIPXML { #[serde(rename = "@address")] pub address: IpAddr, /// Network Prefix - #[serde(rename = "@prefix", default = "invalid_prefix")] - pub prefix: u32, + #[serde(rename = "@prefix")] + pub prefix: Option, /// 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 = "@netmask", default = "invalid_ip", skip_serializing)] - pub netmask: IpAddr, + #[serde(rename = "@netmask", skip_serializing)] + pub netmask: Option, + pub dhcp: Option, } diff --git a/virtweb_backend/src/libvirt_rest_structures/net.rs b/virtweb_backend/src/libvirt_rest_structures/net.rs index e7ebe5f..dd7d92a 100644 --- a/virtweb_backend/src/libvirt_rest_structures/net.rs +++ b/virtweb_backend/src/libvirt_rest_structures/net.rs @@ -3,6 +3,7 @@ use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use crate::nat::nat_definition::Nat; use crate::nat::nat_lib; +use crate::utils::net_utils; use crate::utils::net_utils::{extract_ipv4, extract_ipv6}; use ipnetwork::{Ipv4Network, Ipv6Network}; use lazy_regex::regex; @@ -31,7 +32,7 @@ pub struct IPv4DHCPConfig { #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct IPV4Config { pub bridge_address: Ipv4Addr, - pub prefix: u32, + pub prefix: u8, pub dhcp: Option, pub nat: Option>>, } @@ -52,7 +53,7 @@ pub struct IPv6DHCPConfig { #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct IPV6Config { pub bridge_address: Ipv6Addr, - pub prefix: u32, + pub prefix: u8, pub dhcp: Option, pub nat: Option>>, } @@ -115,18 +116,26 @@ impl NetworkInfo { let mut ips = Vec::with_capacity(2); if let Some(ipv4) = &self.ip_v4 { - if ipv4.prefix > 32 { + if !net_utils::is_ipv4_mask_valid(ipv4.prefix) { return Err(StructureExtraction("IPv4 prefix is invalid!").into()); } + if let Some(nat) = &ipv4.nat { + for n in nat { + n.check()?; + } + } + ips.push(NetworkIPXML { family: "ipv4".to_string(), address: IpAddr::V4(ipv4.bridge_address), - prefix: ipv4.prefix, - netmask: Ipv4Network::new(ipv4.bridge_address, ipv4.prefix as u8) - .unwrap() - .mask() - .into(), + prefix: Some(ipv4.prefix), + netmask: Some( + Ipv4Network::new(ipv4.bridge_address, ipv4.prefix) + .unwrap() + .mask() + .into(), + ), dhcp: ipv4.dhcp.as_ref().map(|dhcp| NetworkDHCPXML { range: NetworkDHCPRangeXML { start: IpAddr::V4(dhcp.start), @@ -146,14 +155,26 @@ impl NetworkInfo { } if let Some(ipv6) = &self.ip_v6 { + if !net_utils::is_ipv6_mask_valid(ipv6.prefix) { + return Err(StructureExtraction("IPv6 prefix is invalid!").into()); + } + + if let Some(nat) = &ipv6.nat { + for n in nat { + n.check()?; + } + } + ips.push(NetworkIPXML { family: "ipv6".to_string(), address: IpAddr::V6(ipv6.bridge_address), - prefix: ipv6.prefix, - netmask: Ipv6Network::new(ipv6.bridge_address, ipv6.prefix as u8) - .unwrap() - .mask() - .into(), + prefix: Some(ipv6.prefix), + netmask: Some( + Ipv6Network::new(ipv6.bridge_address, ipv6.prefix) + .unwrap() + .mask() + .into(), + ), dhcp: ipv6.dhcp.as_ref().map(|dhcp| NetworkDHCPXML { range: NetworkDHCPRangeXML { start: IpAddr::V6(dhcp.start), @@ -226,10 +247,9 @@ impl NetworkInfo { .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, + None => ipnetwork::ipv4_mask_to_prefix(extract_ipv4(i.netmask.unwrap())) + .expect("Failed to convert IPv4 netmask to network"), + Some(p) => p, }, dhcp: i.dhcp.as_ref().map(|d| IPv4DHCPConfig { start: extract_ipv4(d.range.start), @@ -253,10 +273,9 @@ impl NetworkInfo { .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, + None => ipnetwork::ipv6_mask_to_prefix(extract_ipv6(i.netmask.unwrap())) + .expect("Failed to convert IPv6 netmask to network"), + Some(p) => p, }, dhcp: i.dhcp.as_ref().map(|d| IPv6DHCPConfig { start: extract_ipv6(d.range.start), diff --git a/virtweb_backend/src/nat/nat_definition.rs b/virtweb_backend/src/nat/nat_definition.rs index f5f65b4..63a372d 100644 --- a/virtweb_backend/src/nat/nat_definition.rs +++ b/virtweb_backend/src/nat/nat_definition.rs @@ -1,5 +1,12 @@ +use crate::utils::net_utils; use std::net::{Ipv4Addr, Ipv6Addr}; +#[derive(thiserror::Error, Debug)] +enum NatDefError { + #[error("Invalid nat definition: {0}")] + InvalidNatDef(&'static str), +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum NatSource { @@ -31,6 +38,36 @@ pub struct Nat { pub comment: Option, } +impl Nat { + pub fn check(&self) -> anyhow::Result<()> { + if let NatSource::Interface { name } = &self.host_addr { + if !net_utils::is_net_interface_name_valid(name) { + return Err(NatDefError::InvalidNatDef("Invalid nat interface name!").into()); + } + } + + if let NatHostPort::Range { start, end } = &self.host_port { + if *start == 0 { + return Err(NatDefError::InvalidNatDef("Invalid start range!").into()); + } + + if start > end { + return Err(NatDefError::InvalidNatDef("Invalid port range!").into()); + } + + if u16::MAX - (end - start) < self.guest_port { + return Err(NatDefError::InvalidNatDef("Guest port is too high!").into()); + } + } + + if self.guest_port == 0 { + return Err(NatDefError::InvalidNatDef("Invalid guest port!").into()); + } + + Ok(()) + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] pub struct NetNat { pub interface: String, diff --git a/virtweb_backend/src/utils/net_utils.rs b/virtweb_backend/src/utils/net_utils.rs index e5cffe9..1ada2e0 100644 --- a/virtweb_backend/src/utils/net_utils.rs +++ b/virtweb_backend/src/utils/net_utils.rs @@ -47,10 +47,15 @@ pub fn is_mac_address_valid>(mac: D) -> bool { lazy_regex::regex!("^([a-fA-F0-9]{2}[:-]){5}[a-fA-F0-9]{2}$").is_match(mac.as_ref()) } +pub fn is_net_interface_name_valid>(int: D) -> bool { + lazy_regex::regex!("^[a-zA-Z0-9]+$").is_match(int.as_ref()) +} + #[cfg(test)] mod tests { use crate::utils::net_utils::{ is_ipv4_address_valid, is_ipv6_address_valid, is_mac_address_valid, is_mask_valid, + is_net_interface_name_valid, }; #[test] @@ -98,4 +103,13 @@ mod tests { assert!(is_mask_valid(6, 128)); assert!(!is_mask_valid(6, 129)); } + + #[test] + fn test_is_net_interface_name_valid() { + assert!(is_net_interface_name_valid("eth0")); + assert!(is_net_interface_name_valid("enp0s25")); + + assert!(!is_net_interface_name_valid("enp0s25 ")); + assert!(!is_net_interface_name_valid("@enp0s25 ")); + } }