Start to build NAT configuration mode
This commit is contained in:
		
							
								
								
									
										13
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								virtweb_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -1618,6 +1618,18 @@ dependencies = [
 | 
				
			|||||||
 "tempfile",
 | 
					 "tempfile",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "nix"
 | 
				
			||||||
 | 
					version = "0.27.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bitflags 2.4.1",
 | 
				
			||||||
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					 "libc",
 | 
				
			||||||
 | 
					 "memoffset",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ntapi"
 | 
					name = "ntapi"
 | 
				
			||||||
version = "0.4.1"
 | 
					version = "0.4.1"
 | 
				
			||||||
@@ -2659,6 +2671,7 @@ dependencies = [
 | 
				
			|||||||
 "light-openid",
 | 
					 "light-openid",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 "mime_guess",
 | 
					 "mime_guess",
 | 
				
			||||||
 | 
					 "nix",
 | 
				
			||||||
 "num",
 | 
					 "num",
 | 
				
			||||||
 "quick-xml",
 | 
					 "quick-xml",
 | 
				
			||||||
 "rand",
 | 
					 "rand",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,3 +44,4 @@ num = "0.4.1"
 | 
				
			|||||||
rust-embed = { version = "8.1.0" }
 | 
					rust-embed = { version = "8.1.0" }
 | 
				
			||||||
mime_guess = "2.0.4"
 | 
					mime_guess = "2.0.4"
 | 
				
			||||||
dotenvy = "0.15.7"
 | 
					dotenvy = "0.15.7"
 | 
				
			||||||
 | 
					nix = { version = "0.27.1", features = ["net"] }
 | 
				
			||||||
@@ -261,7 +261,7 @@ impl AppConfig {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn net_nat_path(&self, name: &NetworkName) -> PathBuf {
 | 
					    pub fn net_nat_path(&self, name: &NetworkName) -> PathBuf {
 | 
				
			||||||
        self.nat_path().join(format!("nat-{}.json", name.0))
 | 
					        self.nat_path().join(name.nat_file_name())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn net_filter_definition_path(&self, name: &NetworkFilterName) -> PathBuf {
 | 
					    pub fn net_filter_definition_path(&self, name: &NetworkFilterName) -> PathBuf {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,3 +83,6 @@ pub const NETWORK_CHAINS: [&str; 8] = ["root", "mac", "stp", "vlan", "arp", "rar
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/// Directory where nat rules are stored, inside storage directory
 | 
					/// Directory where nat rules are stored, inside storage directory
 | 
				
			||||||
pub const STORAGE_NAT_DIR: &str = "nat";
 | 
					pub const STORAGE_NAT_DIR: &str = "nat";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Environment variable that is set to run VirtWeb in NAT configuration mode
 | 
				
			||||||
 | 
					pub const NAT_MODE_ENV_VAR_NAME: &str = "NAT_MODE";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,9 @@ use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK
 | 
				
			|||||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
					use crate::controllers::{HttpResult, LibVirtReq};
 | 
				
			||||||
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
 | 
					use crate::extractors::local_auth_extractor::LocalAuthEnabled;
 | 
				
			||||||
use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
 | 
					use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
 | 
				
			||||||
 | 
					use crate::utils::net_utils;
 | 
				
			||||||
use actix_web::{HttpResponse, Responder};
 | 
					use actix_web::{HttpResponse, Responder};
 | 
				
			||||||
use sysinfo::{NetworksExt, System, SystemExt};
 | 
					use sysinfo::{System, SystemExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize)]
 | 
					#[derive(serde::Serialize)]
 | 
				
			||||||
struct StaticConfig {
 | 
					struct StaticConfig {
 | 
				
			||||||
@@ -137,14 +138,5 @@ pub async fn number_vcpus() -> HttpResult {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn networks_list() -> HttpResult {
 | 
					pub async fn networks_list() -> HttpResult {
 | 
				
			||||||
    let mut system = System::new();
 | 
					    Ok(HttpResponse::Ok().json(net_utils::net_list()))
 | 
				
			||||||
    system.refresh_networks_list();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(HttpResponse::Ok().json(
 | 
					 | 
				
			||||||
        system
 | 
					 | 
				
			||||||
            .networks()
 | 
					 | 
				
			||||||
            .iter()
 | 
					 | 
				
			||||||
            .map(|n| n.0.to_string())
 | 
					 | 
				
			||||||
            .collect::<Vec<_>>(),
 | 
					 | 
				
			||||||
    ))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,6 +61,13 @@ pub struct IPV6Config {
 | 
				
			|||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
				
			||||||
pub struct NetworkName(pub String);
 | 
					pub struct NetworkName(pub String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl NetworkName {
 | 
				
			||||||
 | 
					    /// Get the name of the file that will store the NAT configuration of this network
 | 
				
			||||||
 | 
					    pub fn nat_file_name(&self) -> String {
 | 
				
			||||||
 | 
					        format!("nat-{}.json", self.0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl NetworkName {
 | 
					impl NetworkName {
 | 
				
			||||||
    pub fn is_valid(&self) -> bool {
 | 
					    pub fn is_valid(&self) -> bool {
 | 
				
			||||||
        regex!("^[a-zA-Z0-9]+$").is_match(&self.0)
 | 
					        regex!("^[a-zA-Z0-9]+$").is_match(&self.0)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,12 +27,19 @@ use virtweb_backend::controllers::{
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
use virtweb_backend::libvirt_client::LibVirtClient;
 | 
					use virtweb_backend::libvirt_client::LibVirtClient;
 | 
				
			||||||
use virtweb_backend::middlewares::auth_middleware::AuthChecker;
 | 
					use virtweb_backend::middlewares::auth_middleware::AuthChecker;
 | 
				
			||||||
 | 
					use virtweb_backend::nat::nat_conf_mode;
 | 
				
			||||||
use virtweb_backend::utils::files_utils;
 | 
					use virtweb_backend::utils::files_utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main]
 | 
					#[actix_web::main]
 | 
				
			||||||
async fn main() -> std::io::Result<()> {
 | 
					async fn main() -> std::io::Result<()> {
 | 
				
			||||||
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
					    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Run in NAT configuration mode, if requested
 | 
				
			||||||
 | 
					    if std::env::var(constants::NAT_MODE_ENV_VAR_NAME).is_ok() {
 | 
				
			||||||
 | 
					        nat_conf_mode::sub_main().await.unwrap();
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Load additional config from file, if requested
 | 
					    // Load additional config from file, if requested
 | 
				
			||||||
    AppConfig::parse_env_file().unwrap();
 | 
					    AppConfig::parse_env_file().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,2 +1,3 @@
 | 
				
			|||||||
 | 
					pub mod nat_conf_mode;
 | 
				
			||||||
pub mod nat_definition;
 | 
					pub mod nat_definition;
 | 
				
			||||||
pub mod nat_lib;
 | 
					pub mod nat_lib;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										79
									
								
								virtweb_backend/src/nat/nat_conf_mode.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								virtweb_backend/src/nat/nat_conf_mode.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					use crate::constants;
 | 
				
			||||||
 | 
					use crate::libvirt_rest_structures::net::NetworkName;
 | 
				
			||||||
 | 
					use crate::nat::nat_definition::NetNat;
 | 
				
			||||||
 | 
					use crate::utils::net_utils;
 | 
				
			||||||
 | 
					use clap::Parser;
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					use std::net::IpAddr;
 | 
				
			||||||
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// VirtWeb NAT configuration mode. This executable should never be executed manually
 | 
				
			||||||
 | 
					#[derive(Parser, Debug, Clone)]
 | 
				
			||||||
 | 
					#[clap(author, version, about, long_about = None)]
 | 
				
			||||||
 | 
					struct NatArgs {
 | 
				
			||||||
 | 
					    /// Storage directory
 | 
				
			||||||
 | 
					    #[clap(short, long)]
 | 
				
			||||||
 | 
					    storage: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Network name
 | 
				
			||||||
 | 
					    #[clap(short, long)]
 | 
				
			||||||
 | 
					    network_name: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Operation
 | 
				
			||||||
 | 
					    #[clap(short, long)]
 | 
				
			||||||
 | 
					    operation: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Sub operation
 | 
				
			||||||
 | 
					    #[clap(long)]
 | 
				
			||||||
 | 
					    sub_operation: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl NatArgs {
 | 
				
			||||||
 | 
					    pub fn network_file(&self) -> PathBuf {
 | 
				
			||||||
 | 
					        let network_name = NetworkName(self.network_name.to_string());
 | 
				
			||||||
 | 
					        Path::new(&self.storage)
 | 
				
			||||||
 | 
					            .join(constants::STORAGE_NAT_DIR)
 | 
				
			||||||
 | 
					            .join(network_name.nat_file_name())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// NAT sub main function
 | 
				
			||||||
 | 
					pub async fn sub_main() -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					    let args = NatArgs::parse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !args.network_file().exists() {
 | 
				
			||||||
 | 
					        log::warn!("Cannot do anything for the network, because the NAT configuration file does not exixsts!");
 | 
				
			||||||
 | 
					        return Ok(());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let conf_json = std::fs::read_to_string(args.network_file())?;
 | 
				
			||||||
 | 
					    let conf: NetNat = serde_json::from_str(&conf_json)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let ips = net_utils::net_list_and_ips()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    match (args.operation.as_str(), args.sub_operation.as_str()) {
 | 
				
			||||||
 | 
					        ("started", "begin") => network_started_begin(&conf, &ips).await?,
 | 
				
			||||||
 | 
					        ("stopped", "end") => network_stopped_end(&conf, &ips).await?,
 | 
				
			||||||
 | 
					        _ => log::debug!(
 | 
				
			||||||
 | 
					            "Operation {} - {} not supported",
 | 
				
			||||||
 | 
					            args.operation,
 | 
				
			||||||
 | 
					            args.sub_operation
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn network_started_begin(
 | 
				
			||||||
 | 
					    conf: &NetNat,
 | 
				
			||||||
 | 
					    ips: &HashMap<String, Vec<IpAddr>>,
 | 
				
			||||||
 | 
					) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					    todo!()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn network_stopped_end(
 | 
				
			||||||
 | 
					    conf: &NetNat,
 | 
				
			||||||
 | 
					    ips: &HashMap<String, Vec<IpAddr>>,
 | 
				
			||||||
 | 
					) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					    todo!()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
 | 
					use nix::sys::socket::{AddressFamily, SockaddrLike};
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 | 
					use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 | 
				
			||||||
use std::str::FromStr;
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					use sysinfo::{NetworksExt, System, SystemExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn extract_ipv4(ip: IpAddr) -> Ipv4Addr {
 | 
					pub fn extract_ipv4(ip: IpAddr) -> Ipv4Addr {
 | 
				
			||||||
    match ip {
 | 
					    match ip {
 | 
				
			||||||
@@ -51,6 +54,88 @@ 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())
 | 
					    lazy_regex::regex!("^[a-zA-Z0-9]+$").is_match(int.as_ref())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get the list of available network interfaces
 | 
				
			||||||
 | 
					pub fn net_list() -> Vec<String> {
 | 
				
			||||||
 | 
					    let mut system = System::new();
 | 
				
			||||||
 | 
					    system.refresh_networks_list();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    system
 | 
				
			||||||
 | 
					        .networks()
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .map(|n| n.0.to_string())
 | 
				
			||||||
 | 
					        .collect::<Vec<_>>()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get the list of available network interfaces associated with their IP address
 | 
				
			||||||
 | 
					pub fn net_list_and_ips() -> anyhow::Result<HashMap<String, Vec<IpAddr>>> {
 | 
				
			||||||
 | 
					    let addrs = nix::ifaddrs::getifaddrs().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut res = HashMap::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for ifaddr in addrs {
 | 
				
			||||||
 | 
					        let address = match ifaddr.address {
 | 
				
			||||||
 | 
					            Some(address) => address,
 | 
				
			||||||
 | 
					            None => {
 | 
				
			||||||
 | 
					                log::debug!(
 | 
				
			||||||
 | 
					                    "Interface {} has an unsupported address family",
 | 
				
			||||||
 | 
					                    ifaddr.interface_name
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let addr_str = match address.family() {
 | 
				
			||||||
 | 
					            Some(AddressFamily::Inet) => {
 | 
				
			||||||
 | 
					                let address = address.to_string();
 | 
				
			||||||
 | 
					                address
 | 
				
			||||||
 | 
					                    .split_once(':')
 | 
				
			||||||
 | 
					                    .map(|a| a.0)
 | 
				
			||||||
 | 
					                    .unwrap_or(&address)
 | 
				
			||||||
 | 
					                    .to_string()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Some(AddressFamily::Inet6) => {
 | 
				
			||||||
 | 
					                let address = address.to_string();
 | 
				
			||||||
 | 
					                let address = address
 | 
				
			||||||
 | 
					                    .split_once(']')
 | 
				
			||||||
 | 
					                    .map(|a| a.0)
 | 
				
			||||||
 | 
					                    .unwrap_or(&address)
 | 
				
			||||||
 | 
					                    .to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let address = address
 | 
				
			||||||
 | 
					                    .split_once('%')
 | 
				
			||||||
 | 
					                    .map(|a| a.0)
 | 
				
			||||||
 | 
					                    .unwrap_or(&address)
 | 
				
			||||||
 | 
					                    .to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                address.strip_prefix('[').unwrap_or(&address).to_string()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => {
 | 
				
			||||||
 | 
					                log::debug!(
 | 
				
			||||||
 | 
					                    "Interface {} has an unsupported address family {:?}",
 | 
				
			||||||
 | 
					                    ifaddr.interface_name,
 | 
				
			||||||
 | 
					                    address.family()
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log::debug!(
 | 
				
			||||||
 | 
					            "Process ip {addr_str} for interface {}",
 | 
				
			||||||
 | 
					            ifaddr.interface_name
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let ip = IpAddr::from_str(&addr_str)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if !res.contains_key(&ifaddr.interface_name) {
 | 
				
			||||||
 | 
					            res.insert(ifaddr.interface_name.to_string(), Vec::with_capacity(1));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.get_mut(&ifaddr.interface_name).unwrap().push(ip);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(res)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use crate::utils::net_utils::{
 | 
					    use crate::utils::net_utils::{
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user