Add basic NAT structures

This commit is contained in:
Pierre HUBERT 2024-01-08 21:03:39 +01:00
parent 80ecb3c5d2
commit 672e866897
9 changed files with 190 additions and 30 deletions

View File

@ -7,6 +7,7 @@ use crate::libvirt_rest_structures::hypervisor::*;
use crate::libvirt_rest_structures::net::*; use crate::libvirt_rest_structures::net::*;
use crate::libvirt_rest_structures::nw_filter::{NetworkFilter, NetworkFilterName}; use crate::libvirt_rest_structures::nw_filter::{NetworkFilter, NetworkFilterName};
use crate::libvirt_rest_structures::vm::*; use crate::libvirt_rest_structures::vm::*;
use crate::nat::nat_lib;
use actix::{Actor, Context, Handler, Message}; use actix::{Actor, Context, Handler, Message};
use image::ImageOutputFormat; use image::ImageOutputFormat;
use std::io::Cursor; use std::io::Cursor;
@ -395,6 +396,9 @@ impl Handler<DefineNetwork> for LibVirtActor {
let network = Network::define_xml(&self.m, &network_xml)?; let network = Network::define_xml(&self.m, &network_xml)?;
let uuid = XMLUuid::parse_from_str(&network.get_uuid_string()?)?; let uuid = XMLUuid::parse_from_str(&network.get_uuid_string()?)?;
// Save NAT definition
nat_lib::save_nat_def(&msg.0)?;
// Save a copy of the source definition // Save a copy of the source definition
msg.0.uuid = Some(uuid); msg.0.uuid = Some(uuid);
let json = serde_json::to_string(&msg.0)?; let json = serde_json::to_string(&msg.0)?;
@ -464,9 +468,12 @@ impl Handler<DeleteNetwork> for LibVirtActor {
fn handle(&mut self, msg: DeleteNetwork, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: DeleteNetwork, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Delete network: {}\n", msg.0.as_string()); log::debug!("Delete network: {}\n", msg.0.as_string());
let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?;
let network_name = network.get_name()?; let network_name = NetworkName(network.get_name()?);
network.undefine()?; network.undefine()?;
// Remove NAT definition, if any
nat_lib::remove_nat_def(&network_name)?;
// Remove backup definition // Remove backup definition
let backup_definition = AppConfig::get().net_definition_path(&network_name); let backup_definition = AppConfig::get().net_definition_path(&network_name);
if backup_definition.exists() { if backup_definition.exists() {

View File

@ -1,4 +1,6 @@
use crate::constants;
use crate::libvirt_lib_structures::XMLUuid; use crate::libvirt_lib_structures::XMLUuid;
use crate::libvirt_rest_structures::net::NetworkName;
use crate::libvirt_rest_structures::nw_filter::NetworkFilterName; use crate::libvirt_rest_structures::nw_filter::NetworkFilterName;
use clap::Parser; use clap::Parser;
use std::net::IpAddr; use std::net::IpAddr;
@ -250,8 +252,16 @@ impl AppConfig {
self.definitions_path().join(format!("vm-{name}.json")) self.definitions_path().join(format!("vm-{name}.json"))
} }
pub fn net_definition_path(&self, name: &str) -> PathBuf { pub fn net_definition_path(&self, name: &NetworkName) -> PathBuf {
self.definitions_path().join(format!("net-{name}.json")) self.definitions_path().join(format!("net-{}.json", name.0))
}
pub fn nat_path(&self) -> PathBuf {
self.storage_path().join(constants::STORAGE_NAT_DIR)
}
pub fn net_nat_path(&self, name: &NetworkName) -> PathBuf {
self.nat_path().join(format!("nat-{}.json", name.0))
} }
pub fn net_filter_definition_path(&self, name: &NetworkFilterName) -> PathBuf { pub fn net_filter_definition_path(&self, name: &NetworkFilterName) -> PathBuf {

View File

@ -77,3 +77,6 @@ pub const BUILTIN_NETWORK_FILTER_RULES: [&str; 24] = [
/// List of valid network chains /// List of valid network chains
pub const NETWORK_CHAINS: [&str; 8] = ["root", "mac", "stp", "vlan", "arp", "rarp", "ipv4", "ipv6"]; pub const NETWORK_CHAINS: [&str; 8] = ["root", "mac", "stp", "vlan", "arp", "rarp", "ipv4", "ipv6"];
/// Directory where nat rules are stored, inside storage directory
pub const STORAGE_NAT_DIR: &str = "nat";

View File

@ -7,4 +7,5 @@ pub mod libvirt_client;
pub mod libvirt_lib_structures; pub mod libvirt_lib_structures;
pub mod libvirt_rest_structures; pub mod libvirt_rest_structures;
pub mod middlewares; pub mod middlewares;
pub mod nat;
pub mod utils; pub mod utils;

View File

@ -1,6 +1,8 @@
use crate::libvirt_lib_structures::network::*; use crate::libvirt_lib_structures::network::*;
use crate::libvirt_lib_structures::XMLUuid; 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_lib;
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;
@ -21,57 +23,68 @@ pub struct DHCPv4HostReservation {
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPv4DHCPConfig { pub struct IPv4DHCPConfig {
start: Ipv4Addr, pub start: Ipv4Addr,
end: Ipv4Addr, pub end: Ipv4Addr,
hosts: Vec<DHCPv4HostReservation>, pub hosts: Vec<DHCPv4HostReservation>,
} }
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPV4Config { pub struct IPV4Config {
bridge_address: Ipv4Addr, pub bridge_address: Ipv4Addr,
prefix: u32, pub prefix: u32,
dhcp: Option<IPv4DHCPConfig>, pub dhcp: Option<IPv4DHCPConfig>,
pub nat: Option<Vec<Nat<Ipv4Addr>>>,
} }
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct DHCPv6HostReservation { pub struct DHCPv6HostReservation {
name: String, pub name: String,
ip: Ipv6Addr, pub ip: Ipv6Addr,
} }
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPv6DHCPConfig { pub struct IPv6DHCPConfig {
start: Ipv6Addr, pub start: Ipv6Addr,
end: Ipv6Addr, pub end: Ipv6Addr,
hosts: Vec<DHCPv6HostReservation>, pub hosts: Vec<DHCPv6HostReservation>,
} }
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct IPV6Config { pub struct IPV6Config {
bridge_address: Ipv6Addr, pub bridge_address: Ipv6Addr,
prefix: u32, pub prefix: u32,
dhcp: Option<IPv6DHCPConfig>, pub dhcp: Option<IPv6DHCPConfig>,
pub nat: Option<Vec<Nat<Ipv6Addr>>>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct NetworkName(pub String);
impl NetworkName {
pub fn is_valid(&self) -> bool {
regex!("^[a-zA-Z0-9]+$").is_match(&self.0)
}
} }
/// Network configuration /// Network configuration
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct NetworkInfo { pub struct NetworkInfo {
pub name: String, pub name: NetworkName,
pub uuid: Option<XMLUuid>, pub uuid: Option<XMLUuid>,
title: Option<String>, pub title: Option<String>,
description: Option<String>, pub description: Option<String>,
forward_mode: NetworkForwardMode, pub forward_mode: NetworkForwardMode,
device: Option<String>, pub device: Option<String>,
bridge_name: Option<String>, pub bridge_name: Option<String>,
dns_server: Option<Ipv4Addr>, pub dns_server: Option<Ipv4Addr>,
domain: Option<String>, pub domain: Option<String>,
ip_v4: Option<IPV4Config>, pub ip_v4: Option<IPV4Config>,
ip_v6: Option<IPV6Config>, pub ip_v6: Option<IPV6Config>,
} }
impl NetworkInfo { impl NetworkInfo {
pub fn as_virt_network(&self) -> anyhow::Result<NetworkXML> { pub fn as_virt_network(&self) -> anyhow::Result<NetworkXML> {
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { if !self.name.is_valid() {
return Err(StructureExtraction("network name is invalid!").into()); return Err(StructureExtraction("network name is invalid!").into());
} }
@ -160,7 +173,7 @@ impl NetworkInfo {
} }
Ok(NetworkXML { Ok(NetworkXML {
name: self.name.to_string(), name: self.name.0.to_string(),
uuid: self.uuid, uuid: self.uuid,
title: self.title.clone(), title: self.title.clone(),
description: self.description.clone(), description: self.description.clone(),
@ -183,8 +196,12 @@ impl NetworkInfo {
} }
pub fn from_xml(xml: NetworkXML) -> anyhow::Result<Self> { pub fn from_xml(xml: NetworkXML) -> anyhow::Result<Self> {
let name = NetworkName(xml.name);
let nat = nat_lib::load_nat_def(&name)?;
Ok(Self { Ok(Self {
name: xml.name, name,
uuid: xml.uuid, uuid: xml.uuid,
title: xml.title, title: xml.title,
description: xml.description, description: xml.description,
@ -227,6 +244,7 @@ impl NetworkInfo {
}) })
.collect(), .collect(),
}), }),
nat: nat.ipv4,
}), }),
ip_v6: xml ip_v6: xml
.ips .ips
@ -252,7 +270,25 @@ impl NetworkInfo {
}) })
.collect(), .collect(),
}), }),
nat: nat.ipv6,
}), }),
}) })
} }
/// Check if at least one NAT definition was specified on this interface
pub fn has_nat_def(&self) -> bool {
if let Some(ipv4) = &self.ip_v4 {
if ipv4.nat.is_some() {
return true;
}
}
if let Some(ipv6) = &self.ip_v6 {
if ipv6.nat.is_some() {
return true;
}
}
false
}
} }

View File

@ -41,6 +41,7 @@ async fn main() -> std::io::Result<()> {
files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap();
files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap(); files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().nat_path()).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().definitions_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().definitions_path()).unwrap();
let conn = Data::new(LibVirtClient( let conn = Data::new(LibVirtClient(

View File

@ -0,0 +1,2 @@
pub mod nat_definition;
pub mod nat_lib;

View File

@ -0,0 +1,39 @@
use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum NatSource<IPv> {
Interface { name: String },
Ip { ip: IPv },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum NatProtocol {
TCP,
UDP,
Both,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum NatHostPort {
Single { port: u16 },
Range { start: u16, end: u16 },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Nat<IPv> {
pub protocol: NatProtocol,
pub host_addr: NatSource<IPv>,
pub host_port: NatHostPort,
pub guest_addr: IPv,
pub guest_port: u16,
pub comment: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct NetNat {
pub interface: String,
pub ipv4: Option<Vec<Nat<Ipv4Addr>>>,
pub ipv6: Option<Vec<Nat<Ipv6Addr>>>,
}

View File

@ -0,0 +1,61 @@
use crate::app_config::AppConfig;
use crate::libvirt_rest_structures::net::{NetworkInfo, NetworkName};
use crate::nat::nat_definition::NetNat;
#[derive(thiserror::Error, Debug)]
enum NatLibError {
#[error("Could not save nat definition, because network bridge name was not specified!")]
MissingNetworkBridgeName,
}
/// Save nat definition
pub fn save_nat_def(net: &NetworkInfo) -> anyhow::Result<()> {
let nat = match net.has_nat_def() {
true => NetNat {
interface: net
.bridge_name
.as_ref()
.ok_or(NatLibError::MissingNetworkBridgeName)?
.to_string(),
ipv4: net
.ip_v4
.as_ref()
.map(|i| i.nat.clone())
.unwrap_or_default(),
ipv6: net
.ip_v6
.as_ref()
.map(|i| i.nat.clone())
.unwrap_or_default(),
},
false => NetNat::default(),
};
let json = serde_json::to_string(&nat)?;
std::fs::write(AppConfig::get().net_nat_path(&net.name), json)?;
Ok(())
}
/// Remove nat definition, if existing
pub fn remove_nat_def(name: &NetworkName) -> anyhow::Result<()> {
let nat_file = AppConfig::get().net_nat_path(name);
if nat_file.exists() {
std::fs::remove_file(nat_file)?;
}
Ok(())
}
/// Load nat definition, if available
pub fn load_nat_def(name: &NetworkName) -> anyhow::Result<NetNat> {
let nat_file = AppConfig::get().net_nat_path(name);
if !nat_file.exists() {
return Ok(NetNat::default());
}
let file = std::fs::read_to_string(nat_file)?;
Ok(serde_json::from_str(&file)?)
}