1
protomask/src/protomask.rs
2023-08-03 20:07:13 -04:00

193 lines
6.1 KiB
Rust

use clap::Parser;
use common::{logging::enable_logger, rfc6052::parse_network_specific_prefix};
use easy_tun::Tun;
use fast_nat::CrossProtocolNetworkAddressTableWithIpv4Pool;
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::{
cell::RefCell,
io::{BufRead, Read, Write},
net::{Ipv4Addr, Ipv6Addr},
path::PathBuf,
time::Duration,
};
use crate::common::packet_handler::handle_packet;
mod common;
#[derive(Parser)]
#[clap(author, version, about="Fast and simple NAT64", long_about = None)]
struct Args {
/// RFC6052 IPv6 translation prefix
#[clap(long, default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
translation_prefix: Ipv6Net,
#[command(flatten)]
pool: PoolArgs,
/// A CSV file containing static address mappings from IPv6 to IPv4
#[clap(long = "static-file")]
static_file: Option<PathBuf>,
/// NAT reservation timeout in seconds
#[clap(long, default_value = "7200")]
reservation_timeout: u64,
/// Explicitly set the interface name to use
#[clap(short, long, default_value_t = ("nat%d").to_string())]
interface: String,
/// Enable verbose logging
#[clap(short, long)]
verbose: bool,
}
impl Args {
pub fn get_static_reservations(
&self,
) -> Result<Vec<(Ipv6Addr, Ipv4Addr)>, Box<dyn std::error::Error>> {
log::warn!("Static reservations are not yet implemented");
Ok(Vec::new())
}
}
#[derive(clap::Args)]
#[group(required = true, multiple = false)]
struct PoolArgs {
/// IPv4 prefixes to use as NAT pool address space
#[clap(long = "pool-add")]
pool_prefixes: Vec<Ipv4Net>,
/// A file containing newline-delimited IPv4 prefixes to use as NAT pool address space
#[clap(long = "pool-file", conflicts_with = "pool_prefixes")]
pool_file: Option<PathBuf>,
}
impl PoolArgs {
/// Read all pool prefixes from the chosen source
pub fn prefixes(&self) -> Result<Vec<Ipv4Net>, Box<dyn std::error::Error>> {
match !self.pool_prefixes.is_empty() {
true => Ok(self.pool_prefixes.clone()),
false => {
let mut prefixes = Vec::new();
let file = std::fs::File::open(self.pool_file.as_ref().unwrap())?;
let reader = std::io::BufReader::new(file);
for line in reader.lines() {
let line = line?;
let prefix = line.parse::<Ipv4Net>()?;
prefixes.push(prefix);
}
Ok(prefixes)
}
}
}
}
#[tokio::main]
pub async fn main() {
// Parse CLI args
let args = Args::parse();
// Initialize logging
enable_logger(args.verbose);
// 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);
}
// 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();
let tun_link_idx = rtnl::link::get_link_index(&rt_handle, tun.name())
.await
.unwrap()
.unwrap();
// Bring the interface up
rtnl::link::link_up(&rt_handle, tun_link_idx).await.unwrap();
// Add a route for the translation prefix
log::debug!(
"Adding route for {} to {}",
args.translation_prefix,
tun.name()
);
rtnl::route::route_add(IpNet::V6(args.translation_prefix), &rt_handle, tun_link_idx)
.await
.unwrap();
// Add a route for each NAT pool prefix
let pool_prefixes = args.pool.prefixes().unwrap();
for pool_prefix in &pool_prefixes {
log::debug!("Adding route for {} to {}", pool_prefix, tun.name());
rtnl::route::route_add(IpNet::V4(*pool_prefix), &rt_handle, tun_link_idx)
.await
.unwrap();
}
// Set up the address table
let mut addr_table = RefCell::new(CrossProtocolNetworkAddressTableWithIpv4Pool::new(
pool_prefixes
.iter()
.map(|prefix| (u32::from(prefix.addr()), prefix.prefix_len() as u32))
.collect::<Vec<(u32, u32)>>()
.as_slice(),
Duration::from_secs(args.reservation_timeout),
));
for (v6_addr, v4_addr) in args.get_static_reservations().unwrap() {
addr_table
.get_mut()
.insert_static(v4_addr, v6_addr)
.unwrap();
}
// Translate all incoming packets
log::info!("Translating packets on {}", tun.name());
let mut buffer = vec![0u8; 1500];
loop {
// Read a packet
let len = tun.read(&mut buffer).unwrap();
// Translate it based on the Layer 3 protocol number
if let Some(output) = handle_packet(
&buffer[..len],
// IPv4 -> IPv6
|packet, source, dest| match addr_table.borrow().get_ipv6(*dest) {
Some(new_destination) => Ok(translate_ipv4_to_ipv6(
packet,
unsafe { embed_ipv4_addr_unchecked(*source, args.translation_prefix) },
new_destination.into(),
)
.map(Some)?),
None => {
protomask_metrics::metric!(PACKET_COUNTER, PROTOCOL_IPV4, STATUS_DROPPED);
Ok(None)
}
},
// IPv6 -> IPv4
|packet, source, dest| {
Ok(translate_ipv6_to_ipv4(
packet,
addr_table.borrow_mut().get_or_create_ipv4(*source)?.into(),
unsafe {
extract_ipv4_addr_unchecked(*dest, args.translation_prefix.prefix_len())
},
)
.map(Some)?)
},
) {
// Write the packet if we get one back from the handler functions
tun.write_all(&output).unwrap();
}
}
}