Skip to content

Commit

Permalink
Migrate to allowing config files
Browse files Browse the repository at this point in the history
  • Loading branch information
ewpratten committed Aug 5, 2023
1 parent 2486aac commit 7d15bdc
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 145 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ protomask-metrics = { path = "libs/protomask-metrics" }
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
clap = { version = "4.3.11", features = ["derive"] }
ipnet = { version = "2.8.0", features = ["serde"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
log = "0.4.19"
fern = "0.6.2"
ipnet = "2.8.0"
nix = "0.26.2"
thiserror = "1.0.44"

Expand Down
5 changes: 5 additions & 0 deletions libs/easy-tun/src/tun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ impl Tun {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_lossless)]
pub fn new(dev: &str) -> Result<Self, std::io::Error> {
log::debug!("Creating new TUN device with requested name:{}", dev);

// Get a file descriptor for `/dev/net/tun`
log::trace!("Opening /dev/net/tun");
let fd = OpenOptions::new()
Expand Down Expand Up @@ -82,6 +84,9 @@ impl Tun {
.unwrap()
.to_string();

// Log the success
log::debug!("Created TUN device: {}", name);

// Build the TUN struct
Ok(Self { fd, name })
}
Expand Down
4 changes: 4 additions & 0 deletions src/args/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! This module contains the definitions for each binary's CLI arguments and config file structure for the sake of readability.

pub mod protomask_clat;
pub mod protomask;
92 changes: 92 additions & 0 deletions src/args/protomask.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::{
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
};

use ipnet::{Ipv4Net, Ipv6Net};

use crate::common::rfc6052::parse_network_specific_prefix;

#[derive(clap::Parser)]
#[clap(author, version, about="Fast and simple NAT64", long_about = None)]
pub struct Args {
#[command(flatten)]
config_data: Option<Config>,

/// Path to a config file to read
#[clap(short = 'c', long = "config", conflicts_with = "Config")]
config_file: Option<PathBuf>,

/// Explicitly set the interface name to use
#[clap(short, long, default_value_t = ("nat%d").to_string())]
pub interface: String,

/// Enable verbose logging
#[clap(short, long)]
pub verbose: bool,
}

impl Args {
#[allow(dead_code)]
pub fn data(&self) -> Result<Config, Box<dyn std::error::Error>> {
match self.config_file {
Some(ref path) => {
// Read the data from the config file
let file = std::fs::File::open(path).map_err(|error| match error.kind() {
std::io::ErrorKind::NotFound => {
log::error!("Config file not found: {}", path.display());
std::process::exit(1)
}
_ => error,
})?;
let data: Config = serde_json::from_reader(file)?;

// We need at least one pool prefix
if data.pool_prefixes.is_empty() {
log::error!("No pool prefixes specified. At least one prefix must be specified in the `pool` property of the config file");
std::process::exit(1);
}

Ok(data)
}
None => match &self.config_data {
Some(data) => Ok(data.clone()),
None => {
log::error!("No configuration provided. Either use --config to specify a file or set the configuration via CLI args (see --help)");
std::process::exit(1)
}
},
}
}
}

/// Program configuration. Specifiable via either CLI args or a config file
#[derive(Debug, clap::Args, serde::Deserialize, Clone)]
#[group()]
pub struct Config {
/// IPv4 prefixes to use as NAT pool address space
#[clap(long = "pool-prefix")]
#[serde(rename = "pool")]
pub pool_prefixes: Vec<Ipv4Net>,

/// Static mapping between IPv4 and IPv6 addresses
#[clap(skip)]
pub static_map: Vec<(Ipv4Addr, Ipv6Addr)>,

/// Enable prometheus metrics on a given address
#[clap(long = "prometheus")]
#[serde(rename = "prometheus_bind_addr")]
pub prom_bind_addr: Option<SocketAddr>,

/// RFC6052 IPv6 translation prefix
#[clap(long, default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
#[serde(
rename = "prefix",
serialize_with = "crate::common::rfc6052::serialize_network_specific_prefix"
)]
pub translation_prefix: Ipv6Net,

/// NAT reservation timeout in seconds
#[clap(long, default_value = "7200")]
pub reservation_timeout: u64,
}
81 changes: 81 additions & 0 deletions src/args/protomask_clat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! Commandline arguments and config file definitions for `protomask-clat`

use crate::common::rfc6052::parse_network_specific_prefix;
use ipnet::{Ipv4Net, Ipv6Net};
use std::{net::SocketAddr, path::PathBuf};

#[derive(Debug, clap::Parser)]
#[clap(author, version, about="IPv4 to IPv6 Customer-side transLATor (CLAT)", long_about = None)]
pub struct Args {
#[command(flatten)]
config_data: Option<Config>,

/// Path to a config file to read
#[clap(short = 'c', long = "config", conflicts_with = "Config")]
config_file: Option<PathBuf>,

/// Explicitly set the interface name to use
#[clap(short, long, default_value_t = ("clat%d").to_string())]
pub interface: String,

/// Enable verbose logging
#[clap(short, long)]
pub verbose: bool,
}

impl Args {
#[allow(dead_code)]
pub fn data(&self) -> Result<Config, Box<dyn std::error::Error>> {
match self.config_file {
Some(ref path) => {
// Read the data from the config file
let file = std::fs::File::open(path).map_err(|error| match error.kind() {
std::io::ErrorKind::NotFound => {
log::error!("Config file not found: {}", path.display());
std::process::exit(1)
}
_ => error,
})?;
let data: Config = serde_json::from_reader(file)?;

// We need at least one customer prefix
if data.customer_pool.is_empty() {
log::error!("No customer prefixes specified. At least one prefix must be specified in the `customer_pool` property of the config file");
std::process::exit(1);
}

Ok(data)
}
None => match &self.config_data {
Some(data) => Ok(data.clone()),
None => {
log::error!("No configuration provided. Either use --config to specify a file or set the configuration via CLI args (see --help)");
std::process::exit(1)
}
},
}
}
}

/// Program configuration. Specifiable via either CLI args or a config file
#[derive(Debug, clap::Args, serde::Deserialize, Clone)]
#[group()]
pub struct Config {
/// One or more customer-side IPv4 prefixes to allow through CLAT
#[clap(long = "customer-prefix")]
#[serde(rename = "customer_pool")]
pub customer_pool: Vec<Ipv4Net>,

/// Enable prometheus metrics on a given address
#[clap(long = "prometheus")]
#[serde(rename = "prometheus_bind_addr")]
pub prom_bind_addr: Option<SocketAddr>,

/// RFC6052 IPv6 prefix to encapsulate IPv4 packets within
#[clap(long="via", default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
#[serde(
rename = "via",
serialize_with = "crate::common::rfc6052::serialize_network_specific_prefix"
)]
pub embed_prefix: Ipv6Net,
}
1 change: 1 addition & 0 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
pub mod logging;
pub mod packet_handler;
pub mod rfc6052;
pub mod permissions;
9 changes: 9 additions & 0 deletions src/common/permissions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use nix::unistd::Uid;

/// Ensures the binary is being exxecuted as root
pub fn ensure_root() {
if !Uid::effective().is_root() {
log::error!("This program must be run as root");
std::process::exit(1);
}
}
65 changes: 19 additions & 46 deletions src/protomask-clat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,19 @@
//! This binary is a Customer-side transLATor (CLAT) that translates all native
//! IPv4 traffic to IPv6 traffic for transmission over an IPv6-only ISP network.

use crate::common::packet_handler::handle_packet;
use crate::{args::protomask_clat::Args, common::permissions::ensure_root};
use clap::Parser;
use common::{logging::enable_logger, rfc6052::parse_network_specific_prefix};
use common::logging::enable_logger;
use easy_tun::Tun;
use interproto::protocols::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4};
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
use nix::unistd::Uid;
use rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
use std::{
io::{Read, Write},
net::SocketAddr,
};

use crate::common::packet_handler::handle_packet;
use std::io::{Read, Write};

mod args;
mod common;

#[derive(Debug, Parser)]
#[clap(author, version, about="IPv4 to IPv6 Customer-side transLATor (CLAT)", long_about = None)]
struct Args {
/// One or more customer-side IPv4 prefixes to allow through CLAT
#[clap(short = 'c', long = "customer-prefix", required = true)]
customer_pool: Vec<Ipv4Net>,

/// Enable prometheus metrics on a given address
#[clap(long = "prometheus")]
prom_bind_addr: Option<SocketAddr>,

/// RFC6052 IPv6 prefix to encapsulate IPv4 packets within
#[clap(long="via", default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
embed_prefix: Ipv6Net,

/// Explicitly set the interface name to use
#[clap(short, long, default_value_t = ("clat%d").to_string())]
interface: String,

/// Enable verbose logging
#[clap(short, long)]
verbose: bool,
}

#[tokio::main]
pub async fn main() {
// Parse CLI args
Expand All @@ -51,16 +24,14 @@ pub async fn main() {
// Initialize logging
enable_logger(args.verbose);

// Load config data
let config = args.data().unwrap();

// We must be root to continue program execution
if !Uid::effective().is_root() {
log::error!("This program must be run as root");
std::process::exit(1);
}
ensure_root();

// Bring up a TUN interface
log::debug!("Creating new TUN interface");
let mut tun = Tun::new(&args.interface).unwrap();
log::debug!("Created TUN interface: {}", tun.name());

// Get the interface index
let rt_handle = rtnl::new_handle().unwrap();
Expand All @@ -78,11 +49,11 @@ pub async fn main() {
.unwrap();

// Add an IPv6 route for each customer prefix
for customer_prefix in args.customer_pool {
for customer_prefix in config.customer_pool {
let embedded_customer_prefix = unsafe {
Ipv6Net::new(
embed_ipv4_addr_unchecked(customer_prefix.addr(), args.embed_prefix),
args.embed_prefix.prefix_len() + customer_prefix.prefix_len(),
embed_ipv4_addr_unchecked(customer_prefix.addr(), config.embed_prefix),
config.embed_prefix.prefix_len() + customer_prefix.prefix_len(),
)
.unwrap_unchecked()
};
Expand All @@ -101,7 +72,7 @@ pub async fn main() {
}

// If we are configured to serve prometheus metrics, start the server
if let Some(bind_addr) = args.prom_bind_addr {
if let Some(bind_addr) = config.prom_bind_addr {
log::info!("Starting prometheus server on {}", bind_addr);
tokio::spawn(protomask_metrics::http::serve_metrics(bind_addr));
}
Expand All @@ -120,17 +91,19 @@ pub async fn main() {
|packet, source, dest| {
Ok(translate_ipv4_to_ipv6(
packet,
unsafe { embed_ipv4_addr_unchecked(*source, args.embed_prefix) },
unsafe { embed_ipv4_addr_unchecked(*dest, args.embed_prefix) },
unsafe { embed_ipv4_addr_unchecked(*source, config.embed_prefix) },
unsafe { embed_ipv4_addr_unchecked(*dest, config.embed_prefix) },
)
.map(Some)?)
},
// IPv6 -> IPv4
|packet, source, dest| {
Ok(translate_ipv6_to_ipv4(
packet,
unsafe { extract_ipv4_addr_unchecked(*source, args.embed_prefix.prefix_len()) },
unsafe { extract_ipv4_addr_unchecked(*dest, args.embed_prefix.prefix_len()) },
unsafe {
extract_ipv4_addr_unchecked(*source, config.embed_prefix.prefix_len())
},
unsafe { extract_ipv4_addr_unchecked(*dest, config.embed_prefix.prefix_len()) },
)
.map(Some)?)
},
Expand Down
Loading

2 comments on commit 7d15bdc

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary sizes for x86_64-unknown-linux-musl

Channel: stable

9.3M	target/x86_64-unknown-linux-musl/release/protomask-clat
9.3M	target/x86_64-unknown-linux-musl/release/protomask
4.8M	target/x86_64-unknown-linux-musl/release/protomask-6over4

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary sizes for aarch64-unknown-linux-musl

Channel: stable

9.5M	target/aarch64-unknown-linux-musl/release/protomask-clat
9.5M	target/aarch64-unknown-linux-musl/release/protomask
5.0M	target/aarch64-unknown-linux-musl/release/protomask-6over4

Please sign in to comment.