Port forwarding is working
This commit is contained in:
		@@ -1,11 +1,18 @@
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::libvirt_rest_structures::net::NetworkName;
 | 
			
		||||
use crate::nat::nat_definition::NetNat;
 | 
			
		||||
use crate::nat::nat_definition::{Nat, NatSourceIP, NetNat};
 | 
			
		||||
use crate::utils::net_utils;
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::net::IpAddr;
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::process::{Command, ExitStatus};
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum NatConfModeError {
 | 
			
		||||
    #[error("UpdateFirewall failed!")]
 | 
			
		||||
    UpdateFirewall,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// VirtWeb NAT configuration mode. This executable should never be executed manually
 | 
			
		||||
#[derive(Parser, Debug, Clone)]
 | 
			
		||||
@@ -49,11 +56,17 @@ pub async fn sub_main() -> anyhow::Result<()> {
 | 
			
		||||
    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()?;
 | 
			
		||||
    let nic_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?,
 | 
			
		||||
        ("started", "begin") => {
 | 
			
		||||
            log::info!("Enable port forwarding for network");
 | 
			
		||||
            trigger_nat_forwarding(true, &conf, &nic_ips).await?
 | 
			
		||||
        }
 | 
			
		||||
        ("stopped", "end") => {
 | 
			
		||||
            log::info!("Disable port forwarding for network");
 | 
			
		||||
            trigger_nat_forwarding(false, &conf, &nic_ips).await?
 | 
			
		||||
        }
 | 
			
		||||
        _ => log::debug!(
 | 
			
		||||
            "Operation {} - {} not supported",
 | 
			
		||||
            args.operation,
 | 
			
		||||
@@ -64,16 +77,156 @@ pub async fn sub_main() -> anyhow::Result<()> {
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn network_started_begin(
 | 
			
		||||
async fn trigger_nat_forwarding(
 | 
			
		||||
    enable: bool,
 | 
			
		||||
    conf: &NetNat,
 | 
			
		||||
    ips: &HashMap<String, Vec<IpAddr>>,
 | 
			
		||||
    nic_ips: &HashMap<String, Vec<IpAddr>>,
 | 
			
		||||
) -> anyhow::Result<()> {
 | 
			
		||||
    todo!()
 | 
			
		||||
    if let Some(ipv4) = &conf.ipv4 {
 | 
			
		||||
        trigger_nat_forwarding_nat_ipv(
 | 
			
		||||
            enable,
 | 
			
		||||
            &conf.interface,
 | 
			
		||||
            &ipv4.iter().map(|i| i.generalize()).collect::<Vec<_>>(),
 | 
			
		||||
            nic_ips,
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(ipv6) = &conf.ipv6 {
 | 
			
		||||
        trigger_nat_forwarding_nat_ipv(
 | 
			
		||||
            enable,
 | 
			
		||||
            &conf.interface,
 | 
			
		||||
            &ipv6.iter().map(|i| i.generalize()).collect::<Vec<_>>(),
 | 
			
		||||
            nic_ips,
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn network_stopped_end(
 | 
			
		||||
    conf: &NetNat,
 | 
			
		||||
    ips: &HashMap<String, Vec<IpAddr>>,
 | 
			
		||||
async fn trigger_nat_forwarding_nat_ipv(
 | 
			
		||||
    enable: bool,
 | 
			
		||||
    net_interface: &str,
 | 
			
		||||
    rules: &[Nat<IpAddr>],
 | 
			
		||||
    nic_ips: &HashMap<String, Vec<IpAddr>>,
 | 
			
		||||
) -> anyhow::Result<()> {
 | 
			
		||||
    todo!()
 | 
			
		||||
    for r in rules {
 | 
			
		||||
        let host_ips = match &r.host_ip {
 | 
			
		||||
            NatSourceIP::Interface { name } => nic_ips.get(name).cloned().unwrap_or_default(),
 | 
			
		||||
            NatSourceIP::Ip { ip } => vec![*ip],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for host_ip in host_ips {
 | 
			
		||||
            let mut guest_port = r.guest_port;
 | 
			
		||||
            for host_port in r.host_port.as_seq() {
 | 
			
		||||
                if r.protocol.has_tcp() {
 | 
			
		||||
                    toggle_port_forwarding(
 | 
			
		||||
                        enable,
 | 
			
		||||
                        false,
 | 
			
		||||
                        host_ip,
 | 
			
		||||
                        host_port,
 | 
			
		||||
                        net_interface,
 | 
			
		||||
                        r.guest_ip,
 | 
			
		||||
                        guest_port,
 | 
			
		||||
                    )?
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if r.protocol.has_udp() {
 | 
			
		||||
                    toggle_port_forwarding(
 | 
			
		||||
                        enable,
 | 
			
		||||
                        true,
 | 
			
		||||
                        host_ip,
 | 
			
		||||
                        host_port,
 | 
			
		||||
                        net_interface,
 | 
			
		||||
                        r.guest_ip,
 | 
			
		||||
                        guest_port,
 | 
			
		||||
                    )?
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                guest_port += 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn check_cmd(s: ExitStatus) -> anyhow::Result<()> {
 | 
			
		||||
    if !s.success() {
 | 
			
		||||
        log::error!("Failed to update firewall rules!");
 | 
			
		||||
        return Err(NatConfModeError::UpdateFirewall.into());
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn toggle_port_forwarding(
 | 
			
		||||
    enable: bool,
 | 
			
		||||
    is_udp: bool,
 | 
			
		||||
    host_ip: IpAddr,
 | 
			
		||||
    host_port: u16,
 | 
			
		||||
    net_interface: &str,
 | 
			
		||||
    guest_ip: IpAddr,
 | 
			
		||||
    guest_port: u16,
 | 
			
		||||
) -> anyhow::Result<()> {
 | 
			
		||||
    if host_ip.is_ipv4() != guest_ip.is_ipv4() {
 | 
			
		||||
        log::trace!("Skipping invalid combination {host_ip} -> {guest_ip}");
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let program = match host_ip.is_ipv4() {
 | 
			
		||||
        true => "/sbin/iptables",
 | 
			
		||||
        false => "/sbin/ip6tables",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let protocol = match is_udp {
 | 
			
		||||
        true => "udp",
 | 
			
		||||
        false => "tcp",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    log::info!("Forward (add={enable}) incoming {protocol} connections for {host_ip}:{host_port} to {guest_ip}:{guest_port} int {net_interface}");
 | 
			
		||||
 | 
			
		||||
    // Rule 1
 | 
			
		||||
    let cmd = Command::new(program)
 | 
			
		||||
        .arg(match enable {
 | 
			
		||||
            true => "-I",
 | 
			
		||||
            false => "-D",
 | 
			
		||||
        })
 | 
			
		||||
        .arg("FORWARD")
 | 
			
		||||
        .arg("-o")
 | 
			
		||||
        .arg(net_interface)
 | 
			
		||||
        .arg("-p")
 | 
			
		||||
        .arg(protocol)
 | 
			
		||||
        .arg("-d")
 | 
			
		||||
        .arg(guest_ip.to_string())
 | 
			
		||||
        .arg("--dport")
 | 
			
		||||
        .arg(guest_port.to_string())
 | 
			
		||||
        .arg("-j")
 | 
			
		||||
        .arg("ACCEPT")
 | 
			
		||||
        .status()?;
 | 
			
		||||
    check_cmd(cmd)?;
 | 
			
		||||
 | 
			
		||||
    // Rule 2
 | 
			
		||||
    let cmd = Command::new(program)
 | 
			
		||||
        .arg("-t")
 | 
			
		||||
        .arg("nat")
 | 
			
		||||
        .arg(match enable {
 | 
			
		||||
            true => "-I",
 | 
			
		||||
            false => "-D",
 | 
			
		||||
        })
 | 
			
		||||
        .arg("PREROUTING")
 | 
			
		||||
        .arg("-p")
 | 
			
		||||
        .arg(protocol)
 | 
			
		||||
        .arg("-d")
 | 
			
		||||
        .arg(host_ip.to_string())
 | 
			
		||||
        .arg("--dport")
 | 
			
		||||
        .arg(host_port.to_string())
 | 
			
		||||
        .arg("-j")
 | 
			
		||||
        .arg("DNAT")
 | 
			
		||||
        .arg("--to")
 | 
			
		||||
        .arg(format!("{guest_ip}:{guest_port}"))
 | 
			
		||||
        .status()?;
 | 
			
		||||
    check_cmd(cmd)?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::utils::net_utils;
 | 
			
		||||
use std::net::{Ipv4Addr, Ipv6Addr};
 | 
			
		||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
enum NatDefError {
 | 
			
		||||
@@ -10,18 +10,28 @@ enum NatDefError {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(tag = "type", rename_all = "lowercase")]
 | 
			
		||||
pub enum NatSource<IPv> {
 | 
			
		||||
pub enum NatSourceIP<IPv> {
 | 
			
		||||
    Interface { name: String },
 | 
			
		||||
    Ip { ip: IPv },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum NatProtocol {
 | 
			
		||||
    TCP,
 | 
			
		||||
    UDP,
 | 
			
		||||
    Both,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NatProtocol {
 | 
			
		||||
    pub fn has_tcp(&self) -> bool {
 | 
			
		||||
        !matches!(&self, NatProtocol::UDP)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn has_udp(&self) -> bool {
 | 
			
		||||
        !matches!(&self, NatProtocol::TCP)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
#[serde(tag = "type", rename_all = "lowercase")]
 | 
			
		||||
pub enum NatHostPort {
 | 
			
		||||
@@ -29,19 +39,28 @@ pub enum NatHostPort {
 | 
			
		||||
    Range { start: u16, end: u16 },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NatHostPort {
 | 
			
		||||
    pub fn as_seq(&self) -> Vec<u16> {
 | 
			
		||||
        match self {
 | 
			
		||||
            NatHostPort::Single { port } => vec![*port],
 | 
			
		||||
            NatHostPort::Range { start, end } => (*start..(*end + 1)).collect(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct Nat<IPv> {
 | 
			
		||||
    pub protocol: NatProtocol,
 | 
			
		||||
    pub host_addr: NatSource<IPv>,
 | 
			
		||||
    pub host_ip: NatSourceIP<IPv>,
 | 
			
		||||
    pub host_port: NatHostPort,
 | 
			
		||||
    pub guest_addr: IPv,
 | 
			
		||||
    pub guest_ip: IPv,
 | 
			
		||||
    pub guest_port: u16,
 | 
			
		||||
    pub comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<IPv> Nat<IPv> {
 | 
			
		||||
    pub fn check(&self) -> anyhow::Result<()> {
 | 
			
		||||
        if let NatSource::Interface { name } = &self.host_addr {
 | 
			
		||||
        if let NatSourceIP::Interface { name } = &self.host_ip {
 | 
			
		||||
            if !net_utils::is_net_interface_name_valid(name) {
 | 
			
		||||
                return Err(NatDefError::InvalidNatDef("Invalid nat interface name!").into());
 | 
			
		||||
            }
 | 
			
		||||
@@ -75,6 +94,46 @@ impl<IPv> Nat<IPv> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Nat<Ipv4Addr> {
 | 
			
		||||
    pub fn generalize(&self) -> Nat<IpAddr> {
 | 
			
		||||
        Nat {
 | 
			
		||||
            protocol: self.protocol,
 | 
			
		||||
            host_ip: match &self.host_ip {
 | 
			
		||||
                NatSourceIP::Ip { ip } => NatSourceIP::Ip {
 | 
			
		||||
                    ip: IpAddr::V4(*ip),
 | 
			
		||||
                },
 | 
			
		||||
                NatSourceIP::Interface { name } => NatSourceIP::Interface {
 | 
			
		||||
                    name: name.to_string(),
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            host_port: self.host_port.clone(),
 | 
			
		||||
            guest_ip: IpAddr::V4(self.guest_ip),
 | 
			
		||||
            guest_port: self.guest_port,
 | 
			
		||||
            comment: self.comment.clone(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Nat<Ipv6Addr> {
 | 
			
		||||
    pub fn generalize(&self) -> Nat<IpAddr> {
 | 
			
		||||
        Nat {
 | 
			
		||||
            protocol: self.protocol,
 | 
			
		||||
            host_ip: match &self.host_ip {
 | 
			
		||||
                NatSourceIP::Ip { ip } => NatSourceIP::Ip {
 | 
			
		||||
                    ip: IpAddr::V6(*ip),
 | 
			
		||||
                },
 | 
			
		||||
                NatSourceIP::Interface { name } => NatSourceIP::Interface {
 | 
			
		||||
                    name: name.to_string(),
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            host_port: self.host_port.clone(),
 | 
			
		||||
            guest_ip: IpAddr::V6(self.guest_ip),
 | 
			
		||||
            guest_port: self.guest_port,
 | 
			
		||||
            comment: self.comment.clone(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
 | 
			
		||||
pub struct NetNat {
 | 
			
		||||
    pub interface: String,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user