Port forwarding is working
This commit is contained in:
parent
ed25eed31e
commit
d6c8964380
@ -1,11 +1,18 @@
|
|||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::libvirt_rest_structures::net::NetworkName;
|
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 crate::utils::net_utils;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::path::{Path, PathBuf};
|
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
|
/// VirtWeb NAT configuration mode. This executable should never be executed manually
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[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_json = std::fs::read_to_string(args.network_file())?;
|
||||||
let conf: NetNat = serde_json::from_str(&conf_json)?;
|
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()) {
|
match (args.operation.as_str(), args.sub_operation.as_str()) {
|
||||||
("started", "begin") => network_started_begin(&conf, &ips).await?,
|
("started", "begin") => {
|
||||||
("stopped", "end") => network_stopped_end(&conf, &ips).await?,
|
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!(
|
_ => log::debug!(
|
||||||
"Operation {} - {} not supported",
|
"Operation {} - {} not supported",
|
||||||
args.operation,
|
args.operation,
|
||||||
@ -64,16 +77,156 @@ pub async fn sub_main() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn network_started_begin(
|
async fn trigger_nat_forwarding(
|
||||||
|
enable: bool,
|
||||||
conf: &NetNat,
|
conf: &NetNat,
|
||||||
ips: &HashMap<String, Vec<IpAddr>>,
|
nic_ips: &HashMap<String, Vec<IpAddr>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> 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?;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn network_stopped_end(
|
if let Some(ipv6) = &conf.ipv6 {
|
||||||
conf: &NetNat,
|
trigger_nat_forwarding_nat_ipv(
|
||||||
ips: &HashMap<String, Vec<IpAddr>>,
|
enable,
|
||||||
) -> anyhow::Result<()> {
|
&conf.interface,
|
||||||
todo!()
|
&ipv6.iter().map(|i| i.generalize()).collect::<Vec<_>>(),
|
||||||
|
nic_ips,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn trigger_nat_forwarding_nat_ipv(
|
||||||
|
enable: bool,
|
||||||
|
net_interface: &str,
|
||||||
|
rules: &[Nat<IpAddr>],
|
||||||
|
nic_ips: &HashMap<String, Vec<IpAddr>>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
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::constants;
|
||||||
use crate::utils::net_utils;
|
use crate::utils::net_utils;
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum NatDefError {
|
enum NatDefError {
|
||||||
@ -10,18 +10,28 @@ enum NatDefError {
|
|||||||
|
|
||||||
#[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 NatSourceIP<IPv> {
|
||||||
Interface { name: String },
|
Interface { name: String },
|
||||||
Ip { ip: IPv },
|
Ip { ip: IPv },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum NatProtocol {
|
pub enum NatProtocol {
|
||||||
TCP,
|
TCP,
|
||||||
UDP,
|
UDP,
|
||||||
Both,
|
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)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(tag = "type", rename_all = "lowercase")]
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
pub enum NatHostPort {
|
pub enum NatHostPort {
|
||||||
@ -29,19 +39,28 @@ pub enum NatHostPort {
|
|||||||
Range { start: u16, end: u16 },
|
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)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Nat<IPv> {
|
pub struct Nat<IPv> {
|
||||||
pub protocol: NatProtocol,
|
pub protocol: NatProtocol,
|
||||||
pub host_addr: NatSource<IPv>,
|
pub host_ip: NatSourceIP<IPv>,
|
||||||
pub host_port: NatHostPort,
|
pub host_port: NatHostPort,
|
||||||
pub guest_addr: IPv,
|
pub guest_ip: IPv,
|
||||||
pub guest_port: u16,
|
pub guest_port: u16,
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<IPv> Nat<IPv> {
|
impl<IPv> Nat<IPv> {
|
||||||
pub fn check(&self) -> anyhow::Result<()> {
|
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) {
|
if !net_utils::is_net_interface_name_valid(name) {
|
||||||
return Err(NatDefError::InvalidNatDef("Invalid nat interface name!").into());
|
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)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
|
||||||
pub struct NetNat {
|
pub struct NetNat {
|
||||||
pub interface: String,
|
pub interface: String,
|
||||||
|
@ -23,9 +23,9 @@ export type NatHostPort =
|
|||||||
|
|
||||||
export interface NatEntry {
|
export interface NatEntry {
|
||||||
protocol: "TCP" | "UDP" | "Both";
|
protocol: "TCP" | "UDP" | "Both";
|
||||||
host_addr: NatSource;
|
host_ip: NatSource;
|
||||||
host_port: NatHostPort;
|
host_port: NatHostPort;
|
||||||
guest_addr: string;
|
guest_ip: string;
|
||||||
guest_port: number;
|
guest_port: number;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
@ -30,12 +30,12 @@ export function NetNatConfiguration(p: {
|
|||||||
|
|
||||||
const addEntry = () => {
|
const addEntry = () => {
|
||||||
p.nat.push({
|
p.nat.push({
|
||||||
host_addr: {
|
host_ip: {
|
||||||
type: "ip",
|
type: "ip",
|
||||||
ip: p.version === 4 ? "10.0.0.1" : "fd00::",
|
ip: p.version === 4 ? "10.0.0.1" : "fd00::",
|
||||||
},
|
},
|
||||||
host_port: { type: "single", port: 80 },
|
host_port: { type: "single", port: 80 },
|
||||||
guest_addr: p.version === 4 ? "10.0.0.100" : "fd00::",
|
guest_ip: p.version === 4 ? "10.0.0.100" : "fd00::",
|
||||||
guest_port: 10,
|
guest_port: 10,
|
||||||
protocol: "TCP",
|
protocol: "TCP",
|
||||||
});
|
});
|
||||||
@ -122,7 +122,7 @@ function NatEntryForm(p: {
|
|||||||
<NATEntryProp label="Host configuration">
|
<NATEntryProp label="Host configuration">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
{...p}
|
{...p}
|
||||||
label="Host address specification"
|
label="Host IP address specification"
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: "Specific IP",
|
label: "Specific IP",
|
||||||
@ -136,39 +136,38 @@ function NatEntryForm(p: {
|
|||||||
"Use active IP addresses on the selected network interface during network startup to determine host adddress",
|
"Use active IP addresses on the selected network interface during network startup to determine host adddress",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={p.entry.host_addr.type}
|
value={p.entry.host_ip.type}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
p.entry.host_addr.type = v as any;
|
p.entry.host_ip.type = v as any;
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{p.entry.host_addr.type === "ip" && (
|
{p.entry.host_ip.type === "ip" && (
|
||||||
<IPInput
|
<IPInput
|
||||||
{...p}
|
{...p}
|
||||||
label="Host IP address"
|
label="Host IP address"
|
||||||
value={p.entry.host_addr.ip}
|
value={p.entry.host_ip.ip}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
if (p.entry.host_addr.type === "ip")
|
if (p.entry.host_ip.type === "ip") p.entry.host_ip.ip = v!;
|
||||||
p.entry.host_addr.ip = v!;
|
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{p.entry.host_addr.type === "interface" && (
|
{p.entry.host_ip.type === "interface" && (
|
||||||
<SelectInput
|
<SelectInput
|
||||||
{...p}
|
{...p}
|
||||||
label="Network interface"
|
label="Network interface"
|
||||||
value={p.entry.host_addr.name}
|
value={p.entry.host_ip.name}
|
||||||
options={p.nicsList.map((n) => {
|
options={p.nicsList.map((n) => {
|
||||||
return {
|
return {
|
||||||
value: n,
|
value: n,
|
||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
if (p.entry.host_addr.type === "interface")
|
if (p.entry.host_ip.type === "interface")
|
||||||
p.entry.host_addr.name = v!;
|
p.entry.host_ip.name = v!;
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -178,10 +177,10 @@ function NatEntryForm(p: {
|
|||||||
<NATEntryProp label="Target guest configuration">
|
<NATEntryProp label="Target guest configuration">
|
||||||
<IPInput
|
<IPInput
|
||||||
{...p}
|
{...p}
|
||||||
label="Guest address"
|
label="Guest IP"
|
||||||
value={p.entry.guest_addr}
|
value={p.entry.guest_ip}
|
||||||
onValueChange={(v) => {
|
onValueChange={(v) => {
|
||||||
p.entry.guest_addr = v!;
|
p.entry.guest_ip = v!;
|
||||||
p.onChange?.();
|
p.onChange?.();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user