Improve network checks

This commit is contained in:
Pierre HUBERT 2024-01-08 21:29:31 +01:00
parent 672e866897
commit e86b29c03a
4 changed files with 96 additions and 33 deletions

View File

@ -43,14 +43,6 @@ pub struct NetworkDomainXML {
pub name: String, pub name: String,
} }
fn invalid_prefix() -> u32 {
u32::MAX
}
fn invalid_ip() -> IpAddr {
IpAddr::V4(Ipv4Addr::BROADCAST)
}
/// Network ip information /// Network ip information
#[derive(serde::Serialize, serde::Deserialize, Debug)] #[derive(serde::Serialize, serde::Deserialize, Debug)]
#[serde(rename = "ip")] #[serde(rename = "ip")]
@ -60,12 +52,13 @@ pub struct NetworkIPXML {
#[serde(rename = "@address")] #[serde(rename = "@address")]
pub address: IpAddr, pub address: IpAddr,
/// Network Prefix /// Network Prefix
#[serde(rename = "@prefix", default = "invalid_prefix")] #[serde(rename = "@prefix")]
pub prefix: u32, pub prefix: Option<u8>,
/// Network Netmask. This field is never serialized, but because we can't know if LibVirt will /// 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 /// provide us netmask or prefix, we need to handle both of these fields
#[serde(rename = "@netmask", default = "invalid_ip", skip_serializing)] #[serde(rename = "@netmask", skip_serializing)]
pub netmask: IpAddr, pub netmask: Option<IpAddr>,
pub dhcp: Option<NetworkDHCPXML>, pub dhcp: Option<NetworkDHCPXML>,
} }

View File

@ -3,6 +3,7 @@ use crate::libvirt_lib_structures::XMLUuid;
use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction; use crate::libvirt_rest_structures::LibVirtStructError::StructureExtraction;
use crate::nat::nat_definition::Nat; use crate::nat::nat_definition::Nat;
use crate::nat::nat_lib; use crate::nat::nat_lib;
use crate::utils::net_utils;
use crate::utils::net_utils::{extract_ipv4, extract_ipv6}; use crate::utils::net_utils::{extract_ipv4, extract_ipv6};
use ipnetwork::{Ipv4Network, Ipv6Network}; use ipnetwork::{Ipv4Network, Ipv6Network};
use lazy_regex::regex; use lazy_regex::regex;
@ -31,7 +32,7 @@ pub struct IPv4DHCPConfig {
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPV4Config { pub struct IPV4Config {
pub bridge_address: Ipv4Addr, pub bridge_address: Ipv4Addr,
pub prefix: u32, pub prefix: u8,
pub dhcp: Option<IPv4DHCPConfig>, pub dhcp: Option<IPv4DHCPConfig>,
pub nat: Option<Vec<Nat<Ipv4Addr>>>, pub nat: Option<Vec<Nat<Ipv4Addr>>>,
} }
@ -52,7 +53,7 @@ pub struct IPv6DHCPConfig {
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPV6Config { pub struct IPV6Config {
pub bridge_address: Ipv6Addr, pub bridge_address: Ipv6Addr,
pub prefix: u32, pub prefix: u8,
pub dhcp: Option<IPv6DHCPConfig>, pub dhcp: Option<IPv6DHCPConfig>,
pub nat: Option<Vec<Nat<Ipv6Addr>>>, pub nat: Option<Vec<Nat<Ipv6Addr>>>,
} }
@ -115,18 +116,26 @@ impl NetworkInfo {
let mut ips = Vec::with_capacity(2); let mut ips = Vec::with_capacity(2);
if let Some(ipv4) = &self.ip_v4 { 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()); return Err(StructureExtraction("IPv4 prefix is invalid!").into());
} }
if let Some(nat) = &ipv4.nat {
for n in nat {
n.check()?;
}
}
ips.push(NetworkIPXML { ips.push(NetworkIPXML {
family: "ipv4".to_string(), family: "ipv4".to_string(),
address: IpAddr::V4(ipv4.bridge_address), address: IpAddr::V4(ipv4.bridge_address),
prefix: ipv4.prefix, prefix: Some(ipv4.prefix),
netmask: Ipv4Network::new(ipv4.bridge_address, ipv4.prefix as u8) netmask: Some(
Ipv4Network::new(ipv4.bridge_address, ipv4.prefix)
.unwrap() .unwrap()
.mask() .mask()
.into(), .into(),
),
dhcp: ipv4.dhcp.as_ref().map(|dhcp| NetworkDHCPXML { dhcp: ipv4.dhcp.as_ref().map(|dhcp| NetworkDHCPXML {
range: NetworkDHCPRangeXML { range: NetworkDHCPRangeXML {
start: IpAddr::V4(dhcp.start), start: IpAddr::V4(dhcp.start),
@ -146,14 +155,26 @@ impl NetworkInfo {
} }
if let Some(ipv6) = &self.ip_v6 { 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 { ips.push(NetworkIPXML {
family: "ipv6".to_string(), family: "ipv6".to_string(),
address: IpAddr::V6(ipv6.bridge_address), address: IpAddr::V6(ipv6.bridge_address),
prefix: ipv6.prefix, prefix: Some(ipv6.prefix),
netmask: Ipv6Network::new(ipv6.bridge_address, ipv6.prefix as u8) netmask: Some(
Ipv6Network::new(ipv6.bridge_address, ipv6.prefix)
.unwrap() .unwrap()
.mask() .mask()
.into(), .into(),
),
dhcp: ipv6.dhcp.as_ref().map(|dhcp| NetworkDHCPXML { dhcp: ipv6.dhcp.as_ref().map(|dhcp| NetworkDHCPXML {
range: NetworkDHCPRangeXML { range: NetworkDHCPRangeXML {
start: IpAddr::V6(dhcp.start), start: IpAddr::V6(dhcp.start),
@ -226,10 +247,9 @@ impl NetworkInfo {
.map(|i| IPV4Config { .map(|i| IPV4Config {
bridge_address: extract_ipv4(i.address), bridge_address: extract_ipv4(i.address),
prefix: match i.prefix { prefix: match i.prefix {
u32::MAX => ipnetwork::ipv4_mask_to_prefix(extract_ipv4(i.netmask)) None => ipnetwork::ipv4_mask_to_prefix(extract_ipv4(i.netmask.unwrap()))
.expect("Failed to convert IPv4 netmask to network") .expect("Failed to convert IPv4 netmask to network"),
as u32, Some(p) => p,
p => p,
}, },
dhcp: i.dhcp.as_ref().map(|d| IPv4DHCPConfig { dhcp: i.dhcp.as_ref().map(|d| IPv4DHCPConfig {
start: extract_ipv4(d.range.start), start: extract_ipv4(d.range.start),
@ -253,10 +273,9 @@ impl NetworkInfo {
.map(|i| IPV6Config { .map(|i| IPV6Config {
bridge_address: extract_ipv6(i.address), bridge_address: extract_ipv6(i.address),
prefix: match i.prefix { prefix: match i.prefix {
u32::MAX => ipnetwork::ipv6_mask_to_prefix(extract_ipv6(i.netmask)) None => ipnetwork::ipv6_mask_to_prefix(extract_ipv6(i.netmask.unwrap()))
.expect("Failed to convert IPv6 netmask to network") .expect("Failed to convert IPv6 netmask to network"),
as u32, Some(p) => p,
p => p,
}, },
dhcp: i.dhcp.as_ref().map(|d| IPv6DHCPConfig { dhcp: i.dhcp.as_ref().map(|d| IPv6DHCPConfig {
start: extract_ipv6(d.range.start), start: extract_ipv6(d.range.start),

View File

@ -1,5 +1,12 @@
use crate::utils::net_utils;
use std::net::{Ipv4Addr, Ipv6Addr}; 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)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")] #[serde(tag = "type", rename_all = "lowercase")]
pub enum NatSource<IPv> { pub enum NatSource<IPv> {
@ -31,6 +38,36 @@ pub struct Nat<IPv> {
pub comment: Option<String>, pub comment: Option<String>,
} }
impl<IPv> Nat<IPv> {
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)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct NetNat { pub struct NetNat {
pub interface: String, pub interface: String,

View File

@ -47,10 +47,15 @@ pub fn is_mac_address_valid<D: AsRef<str>>(mac: D) -> bool {
lazy_regex::regex!("^([a-fA-F0-9]{2}[:-]){5}[a-fA-F0-9]{2}$").is_match(mac.as_ref()) 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<D: AsRef<str>>(int: D) -> bool {
lazy_regex::regex!("^[a-zA-Z0-9]+$").is_match(int.as_ref())
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::utils::net_utils::{ use crate::utils::net_utils::{
is_ipv4_address_valid, is_ipv6_address_valid, is_mac_address_valid, is_mask_valid, is_ipv4_address_valid, is_ipv6_address_valid, is_mac_address_valid, is_mask_valid,
is_net_interface_name_valid,
}; };
#[test] #[test]
@ -98,4 +103,13 @@ mod tests {
assert!(is_mask_valid(6, 128)); assert!(is_mask_valid(6, 128));
assert!(!is_mask_valid(6, 129)); 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 "));
}
} }