Start to build NAT configuration mode

This commit is contained in:
Pierre HUBERT 2024-01-10 19:29:24 +01:00
parent 6fdcc8c07c
commit ed25eed31e
10 changed files with 200 additions and 12 deletions

View File

@ -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",

View File

@ -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"] }

View File

@ -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 {

View File

@ -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";

View File

@ -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<_>>(),
))
} }

View File

@ -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)

View File

@ -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();

View File

@ -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;

View 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!()
}

View File

@ -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::{