use std::path::PathBuf;
use std::process::Command;
use clap::Parser;
use clap::Subcommand;
use clap_complete::engine::ArgValueCompleter;
use color_eyre::Result;
use crate::command::add;
use crate::command::handle_clear;
use crate::command::handle_dnat;
use crate::command::handle_hairpin;
use crate::command::handle_list;
use crate::command::handle_policy_route;
use crate::command::handle_snat;
use crate::command::remap;
use crate::command::remove;
use crate::consts::DAEMON_SOCK;
use crate::consts::PKG_NAME;
use crate::consts::STATE;
use crate::daemon::Daemon;
#[derive(Parser, Debug)]
#[command(
name = PKG_NAME,
about = "Manage iptables NAT rules (static VMs & dynamic Docker)"
)]
pub struct Cli {
#[arg(long, default_value = DAEMON_SOCK, global = true)]
pub socket: PathBuf,
#[arg(long, global = true)]
pub json: bool,
#[command(subcommand)]
pub command: NatMapCommand,
}
#[derive(Subcommand, Debug)]
pub enum NatMapCommand {
#[command(name = "dnat")]
Dnat {
#[arg(long)]
ext_ip: String,
#[arg(long)]
int_ip: String,
#[arg(long, default_value = "tcp")]
proto: String,
#[arg(long)]
ports: String,
#[arg(long)]
ext_if: Option<String>,
#[arg(long)]
delete: bool,
#[arg(long)]
no_masquerade: bool,
},
#[command(name = "snat")]
Snat {
#[arg(long)]
int_ip: String,
#[arg(long)]
ext_if: String,
#[arg(long)]
ext_ip: String,
#[arg(long)]
delete: bool,
},
#[command(name = "hairpin")]
Hairpin {
#[arg(long)]
ext_ip: String,
#[arg(long)]
int_ip: String,
#[arg(long, default_value = "tcp")]
proto: String,
#[arg(long)]
ports: String,
#[arg(long)]
delete: bool,
},
#[command(name = "policy-route")]
PolicyRoute {
#[arg(long)]
src_ip: String,
#[arg(long)]
via: String,
#[arg(long, default_value = "100")]
table: u32,
#[arg(long)]
delete: bool,
},
#[command(name = "ls")]
List {
#[arg(
value_name = "CONTAINER_ID",
add = ArgValueCompleter::new(crate::completions::complete_container_id)
)]
container_id: Option<String>,
},
#[command(name = "clear")]
Clear,
#[command(name = "docker")]
Docker {
#[command(subcommand)]
cmd: DockerCommand,
},
#[command(name = "save")]
Save,
#[command(name = "fwd")]
Fwd,
#[command(name = "daemon")]
Daemon {
#[arg(long, default_value = STATE)]
state: PathBuf,
#[arg(long, default_value = DAEMON_SOCK)]
socket: PathBuf,
#[arg(long, default_value = PKG_NAME)]
socket_group: String,
},
#[command(name = "install")]
Install {
#[arg(long, default_value = PKG_NAME)]
group: String,
#[arg(long, default_value = lab_ops_lab_lib::consts::LABOPS_BIN)]
binary: String,
},
}
#[derive(Subcommand, Debug)]
pub enum DockerCommand {
#[command(name = "add")]
Add {
#[arg(
value_name = "CONTAINER_ID",
add = ArgValueCompleter::new(crate::completions::complete_container_id)
)]
container_id: String,
#[arg(value_name = "MAPPING")]
mapping: Option<String>,
#[arg(
long,
add = ArgValueCompleter::new(crate::completions::complete_container_id)
)]
name: Option<String>,
},
#[command(name = "rm")]
Remove {
#[arg(
value_name = "CONTAINER_ID",
add = ArgValueCompleter::new(crate::completions::complete_container_id)
)]
container_id: Option<String>,
#[arg(value_name = "PORT[/PROTO]")]
port: Option<String>,
#[arg(long)]
all: bool,
#[arg(long)]
id: Option<u64>,
#[arg(
long,
add = ArgValueCompleter::new(crate::completions::complete_container_id)
)]
name: Option<String>,
},
#[command(name = "remap")]
Remap {
#[arg(
value_name = "CONTAINER_ID",
add = ArgValueCompleter::new(crate::completions::complete_container_id)
)]
container_id: String,
#[arg(value_name = "OLD_PORT:NEW_PORT")]
mapping: String,
},
}
pub async fn run_cli(cli: Cli, use_color: bool) -> Result<()> {
let socket = cli.socket;
let json = cli.json;
match cli.command {
NatMapCommand::Dnat {
ext_ip,
int_ip,
proto,
ports,
ext_if,
delete,
no_masquerade,
} => {
handle_dnat(
ext_ip,
int_ip,
proto,
ports,
ext_if,
delete,
no_masquerade,
&socket,
)
.await?;
}
NatMapCommand::Snat {
int_ip,
ext_if,
ext_ip,
delete,
} => {
handle_snat(int_ip, ext_if, ext_ip, delete, &socket).await?;
}
NatMapCommand::Hairpin {
ext_ip,
int_ip,
proto,
ports,
delete,
} => {
handle_hairpin(ext_ip, int_ip, proto, ports, delete, &socket).await?;
}
NatMapCommand::PolicyRoute {
src_ip,
via,
table,
delete,
} => {
handle_policy_route(src_ip, via, table, delete, &socket).await?;
}
NatMapCommand::List { container_id } => {
handle_list(&socket, container_id, json, use_color).await?;
}
NatMapCommand::Clear => {
handle_clear(&socket).await?;
}
NatMapCommand::Docker { cmd } => match cmd {
DockerCommand::Add {
container_id,
mapping,
name,
} => {
add(container_id, mapping, name, &socket, json).await?;
}
DockerCommand::Remove {
container_id,
port,
all,
id,
name,
} => {
remove(container_id, port, all, id, name, &socket, json).await?;
}
DockerCommand::Remap {
container_id,
mapping,
} => {
remap(container_id, mapping, &socket, json).await?;
}
},
NatMapCommand::Save => {
Command::new("sh")
.arg("-c")
.arg("iptables-save > /etc/iptables/rules.v4")
.status()?;
}
NatMapCommand::Fwd => {
let status = Command::new("sysctl")
.arg("-w")
.arg("net.ipv4.ip_forward=1")
.status()?;
if !status.success() {
color_eyre::eyre::bail!("Failed to enable IP forwarding");
}
}
NatMapCommand::Daemon {
state,
socket,
socket_group,
} => {
Daemon::new(socket, state, socket_group)
.await?
.run()
.await?;
}
NatMapCommand::Install { binary, group } => {
crate::install::install_systemd(&binary, &group)?;
}
}
Ok(())
}