big WIP
This commit is contained in:
parent
66552aeccc
commit
19c8499861
62
Cargo.toml
62
Cargo.toml
@ -16,61 +16,44 @@ exclude = ["/.github/", "/.vscode/"]
|
||||
[workspace]
|
||||
members = [
|
||||
"libs/easy-tun",
|
||||
"libs/fast-nat",
|
||||
# "libs/fast-nat",
|
||||
"libs/interproto",
|
||||
"libs/rfc6052",
|
||||
"libs/rtnl",
|
||||
"libs/protomask-metrics",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
profiler = [
|
||||
"puffin",
|
||||
"puffin_http",
|
||||
"easy-tun/profile-puffin",
|
||||
"fast-nat/profile-puffin",
|
||||
"interproto/profile-puffin",
|
||||
]
|
||||
|
||||
[[bin]]
|
||||
name = "protomask"
|
||||
path = "src/protomask.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "protomask-clat"
|
||||
path = "src/protomask-clat.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "protomask-6over4"
|
||||
path = "src/protomask-6over4.rs"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Internal dependencies
|
||||
easy-tun = { version = "^2.0.0", path = "libs/easy-tun" }
|
||||
fast-nat = { version = "^1.0.0", path = "libs/fast-nat" }
|
||||
interproto = { version = "^1.0.0", path = "libs/interproto", features = [
|
||||
"metrics",
|
||||
] }
|
||||
interproto = { version = "^1.0.0", path = "libs/interproto" }
|
||||
rfc6052 = { version = "^1.0.0", path = "libs/rfc6052" }
|
||||
rtnl = { version = "^1.0.0", path = "libs/rtnl", features = ["tokio"] }
|
||||
protomask-metrics = { version = "^0.1.0", path = "libs/protomask-metrics" }
|
||||
|
||||
# External Dependencies
|
||||
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"] }
|
||||
puffin_http = { version = "0.13.0", optional = true }
|
||||
puffin = { version = "0.16.0", optional = true }
|
||||
# puffin_http = { version = "0.13.0", optional = true }
|
||||
# puffin = { version = "0.16.0", optional = true }
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = "^1.0"
|
||||
log = "0.4.19"
|
||||
fern = "0.6.2"
|
||||
nix = "0.26.2"
|
||||
# nix = "0.26.2"
|
||||
thiserror = "1.0.44"
|
||||
cfg-if = "1.0.0"
|
||||
profiling = "1.0.9"
|
||||
# cfg-if = "1.0.0"
|
||||
# profiling = "1.0.9"
|
||||
caps = "0.5.5"
|
||||
bimap = "0.6.3"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
@ -84,21 +67,11 @@ assets = [
|
||||
"/usr/local/bin/protomask",
|
||||
"755",
|
||||
],
|
||||
[
|
||||
"target/release/protomask-clat",
|
||||
"/usr/local/bin/protomask-clat",
|
||||
"755",
|
||||
],
|
||||
[
|
||||
"config/protomask.json",
|
||||
"/etc/protomask/protomask.json",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"config/protomask-clat.json",
|
||||
"/etc/protomask/protomask-clat.json",
|
||||
"644",
|
||||
],
|
||||
[
|
||||
"README.md",
|
||||
"/usr/share/doc/protomask/README.md",
|
||||
@ -108,16 +81,11 @@ assets = [
|
||||
conf-files = []
|
||||
depends = []
|
||||
maintainer-scripts = "./debian/"
|
||||
systemd-units = [
|
||||
{ unit-name = "protomask-service", enable = false },
|
||||
{ unit-name = "protomask-clat-service", enable = false },
|
||||
]
|
||||
systemd-units = [{ unit-name = "protomask-service", enable = false }]
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
assets = [
|
||||
{ source = "target/release/protomask", dest = "/usr/local/bin/protomask", mode = "755"},
|
||||
{ source = "target/release/protomask-clat", dest = "/usr/local/bin/protomask-clat", mode = "755"},
|
||||
{ source = "config/protomask.json", dest = "/etc/protomask/protomask.json", mode = "644"},
|
||||
{ source = "config/protomask-clat.json", dest = "/etc/protomask/protomask-clat.json", mode = "644"},
|
||||
{ source = "README.md", dest = "/usr/share/doc/protomask/README.md", mode = "644"},
|
||||
]
|
||||
{ source = "target/release/protomask", dest = "/usr/local/bin/protomask", mode = "755" },
|
||||
{ source = "config/protomask.json", dest = "/etc/protomask/protomask.json", mode = "644" },
|
||||
{ source = "README.md", dest = "/usr/share/doc/protomask/README.md", mode = "644" },
|
||||
]
|
||||
|
9
debian/protomask-clat-service
vendored
9
debian/protomask-clat-service
vendored
@ -1,9 +0,0 @@
|
||||
[Unit]
|
||||
Description = Protomask CLAT
|
||||
After = network.target
|
||||
|
||||
[Service]
|
||||
ExecStart = /usr/local/bin/protomask-clat --config /etc/protomask/protomask-clat.json
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -85,8 +85,9 @@ impl Tun {
|
||||
|
||||
// Check for errors
|
||||
if err < 0 {
|
||||
log::error!("ioctl failed: {}", err);
|
||||
return Err(std::io::Error::last_os_error());
|
||||
let last_error = std::io::Error::last_os_error();
|
||||
log::error!("ioctl failed: {:?}", last_error);
|
||||
return Err(last_error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,9 @@ categories = []
|
||||
|
||||
[features]
|
||||
default = []
|
||||
metrics = ["protomask-metrics"]
|
||||
profile-puffin = ["profiling/profile-with-puffin"]
|
||||
|
||||
[dependencies]
|
||||
protomask-metrics = { version = "^0.1.0", path = "../protomask-metrics", optional = true }
|
||||
log = "^0.4"
|
||||
pnet = "0.34.0"
|
||||
thiserror = "^1.0.44"
|
||||
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "protomask-metrics"
|
||||
version = "0.1.0"
|
||||
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "Internal metrics library used by protomask"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/ewpratten/protomask/tree/master/libs/protomask-metrics"
|
||||
documentation = "https://docs.rs/protomask-metrics"
|
||||
repository = "https://github.com/ewpratten/protomask"
|
||||
license = "GPL-3.0"
|
||||
keywords = []
|
||||
categories = []
|
||||
|
||||
[dependencies]
|
||||
hyper = { version = "0.14.27", features = ["server", "http1", "tcp"] }
|
||||
log = "^0.4"
|
||||
prometheus = "0.13.3"
|
||||
lazy_static = "1.4.0"
|
@ -1 +0,0 @@
|
||||
**`protomask-metrics` is exclusively for use in `protomask` and is not intended to be used on its own.**
|
@ -1,44 +0,0 @@
|
||||
use hyper::{
|
||||
service::{make_service_fn, service_fn},
|
||||
Body, Method, Request, Response, Server,
|
||||
};
|
||||
use prometheus::{Encoder, TextEncoder};
|
||||
use std::{convert::Infallible, net::SocketAddr};
|
||||
|
||||
/// Handle an HTTP request
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn handle_request(request: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
// If the request is targeting the metrics endpoint
|
||||
if request.method() == Method::GET && request.uri().path() == "/metrics" {
|
||||
// Gather metrics
|
||||
let metric_families = prometheus::gather();
|
||||
let body = {
|
||||
let mut buffer = Vec::new();
|
||||
let encoder = TextEncoder::new();
|
||||
encoder.encode(&metric_families, &mut buffer).unwrap();
|
||||
String::from_utf8(buffer).unwrap()
|
||||
};
|
||||
|
||||
// Return the response
|
||||
return Ok(Response::new(Body::from(body)));
|
||||
}
|
||||
|
||||
// Otherwise, just return a 404
|
||||
Ok(Response::builder()
|
||||
.status(404)
|
||||
.body(Body::from("Not found"))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
/// Bring up an HTTP server that listens for metrics requests
|
||||
pub async fn serve_metrics(bind_addr: SocketAddr) {
|
||||
// Set up the server
|
||||
let make_service =
|
||||
make_service_fn(|_| async { Ok::<_, Infallible>(service_fn(handle_request)) });
|
||||
let server = Server::bind(&bind_addr).serve(make_service);
|
||||
|
||||
// Run the server
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("Metrics server error: {e}");
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::missing_panics_doc)]
|
||||
#![allow(clippy::doc_markdown)]
|
||||
|
||||
pub mod http;
|
||||
pub mod metrics;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
@ -1,9 +0,0 @@
|
||||
/// A short-hand way to access one of the metrics in `protomask_metrics::metrics`
|
||||
#[macro_export]
|
||||
macro_rules! metric {
|
||||
// Accept and name and multiple labels
|
||||
($metric_name: ident, $($label_name: ident),+) => {
|
||||
protomask_metrics::metrics::$metric_name.with_label_values(&[$(protomask_metrics::metrics::label_values::$label_name),+])
|
||||
};
|
||||
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
pub mod label_values {
|
||||
/// IPv4 protocol
|
||||
pub const PROTOCOL_IPV4: &str = "ipv4";
|
||||
/// IPv6 protocol
|
||||
pub const PROTOCOL_IPV6: &str = "ipv6";
|
||||
/// ICMP protocol
|
||||
pub const PROTOCOL_ICMP: &str = "icmp";
|
||||
/// ICMPv6 protocol
|
||||
pub const PROTOCOL_ICMPV6: &str = "icmpv6";
|
||||
/// TCP protocol
|
||||
pub const PROTOCOL_TCP: &str = "tcp";
|
||||
/// UDP protocol
|
||||
pub const PROTOCOL_UDP: &str = "udp";
|
||||
|
||||
/// Dropped status
|
||||
pub const STATUS_DROPPED: &str = "dropped";
|
||||
/// Translated status
|
||||
pub const STATUS_TRANSLATED: &str = "translated";
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Counter for the number of packets processed
|
||||
pub static ref PACKET_COUNTER: prometheus::IntCounterVec = prometheus::register_int_counter_vec!(
|
||||
"protomask_packets",
|
||||
"Number of packets processed",
|
||||
&["protocol", "status"]
|
||||
).unwrap();
|
||||
|
||||
/// Counter for the number of different types of ICMP packets received
|
||||
pub static ref ICMP_COUNTER: prometheus::IntCounterVec = prometheus::register_int_counter_vec!(
|
||||
"protomask_icmp_packets_recv",
|
||||
"Number of ICMP packets received",
|
||||
&["protocol", "icmp_type", "icmp_code"]
|
||||
).unwrap();
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
//! This module contains the definitions for each binary's CLI arguments and config file structure for the sake of readability.
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
pub mod protomask;
|
||||
pub mod protomask_clat;
|
||||
|
||||
// Used to trick the build process into including a CLI argument based on a feature flag
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "profiler")] {
|
||||
#[derive(Debug, clap::Args)]
|
||||
pub struct ProfilerArgs {
|
||||
/// Expose the puffin HTTP server on this endpoint
|
||||
#[clap(long)]
|
||||
pub puffin_endpoint: Option<std::net::SocketAddr>,
|
||||
}
|
||||
} else {
|
||||
#[derive(Debug, clap::Args)]
|
||||
pub struct ProfilerArgs;
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
|
||||
use crate::common::rfc6052::parse_network_specific_prefix;
|
||||
|
||||
use super::ProfilerArgs;
|
||||
|
||||
#[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,
|
||||
|
||||
#[command(flatten)]
|
||||
pub profiler_args: ProfilerArgs,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Number of queues to create on the TUN device
|
||||
#[clap(long, default_value = "10")]
|
||||
#[serde(rename = "queues")]
|
||||
pub num_queues: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, Clone)]
|
||||
pub struct StaticMap {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl From<StaticMap> for (Ipv4Addr, Ipv6Addr) {
|
||||
fn from(val: StaticMap) -> Self {
|
||||
(val.ipv4, val.ipv6)
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
//! Commandline arguments and config file definitions for `protomask-clat`
|
||||
|
||||
use super::ProfilerArgs;
|
||||
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,
|
||||
|
||||
#[command(flatten)]
|
||||
pub profiler_args: ProfilerArgs,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// Number of queues to create on the TUN device
|
||||
#[clap(long, default_value = "10")]
|
||||
#[serde(rename = "queues")]
|
||||
pub num_queues: usize,
|
||||
}
|
102
src/cli.rs
Normal file
102
src/cli.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
|
||||
/// Fast & reliable user space NAT64
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// Translation engine
|
||||
#[command(subcommand)]
|
||||
pub engine: Modes,
|
||||
|
||||
/// Enable verbose logs
|
||||
#[clap(short, long)]
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum Modes {
|
||||
/// Run as a NAT64 translator
|
||||
Nat64 {
|
||||
/// Explicitly set the interface name to use
|
||||
#[clap(short, long, default_value_t = ("nat%d").to_string())]
|
||||
interface: String,
|
||||
|
||||
/// IPv4 prefixes to use as NAT pool address space
|
||||
#[clap(short, long = "pool-prefix", required = true)]
|
||||
pool_prefixes: Vec<Ipv4Net>,
|
||||
|
||||
/// Statically map an IPv4 and IPv6 address to each other
|
||||
#[clap(short, long, value_parser = parse_static_map)]
|
||||
static_map: Vec<(Ipv4Addr, Ipv6Addr)>,
|
||||
|
||||
/// RFC6052 IPv6 translation prefix
|
||||
#[clap(short, long, default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
|
||||
translation_prefix: Ipv6Net,
|
||||
|
||||
/// NAT lease duration in seconds
|
||||
#[clap(short, long, default_value = "7200")]
|
||||
lease_duration: u64,
|
||||
|
||||
/// Number of queues to create on the TUN device
|
||||
#[clap(short = 'q', long, default_value = "10")]
|
||||
num_queues: usize,
|
||||
},
|
||||
|
||||
/// Run as a Customer-side transLATor
|
||||
Clat {
|
||||
/// Explicitly set the interface name to use
|
||||
#[clap(short, long, default_value_t = ("clat%d").to_string())]
|
||||
interface: String,
|
||||
|
||||
/// One or more customer-side IPv4 prefixes to allow through CLAT
|
||||
#[clap(short, long = "customer-prefix", required = true)]
|
||||
customer_pool: Vec<Ipv4Net>,
|
||||
|
||||
/// RFC6052 IPv6 prefix to encapsulate IPv4 packets within
|
||||
#[clap(short, long="via", default_value_t = ("64:ff9b::/96").parse().unwrap(), value_parser = parse_network_specific_prefix)]
|
||||
embed_prefix: Ipv6Net,
|
||||
|
||||
/// Number of queues to create on the TUN device
|
||||
#[clap(short = 'q', long, default_value = "10")]
|
||||
num_queues: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Parses an [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)-compliant IPv6 prefix from a string
|
||||
fn parse_network_specific_prefix(string: &str) -> Result<Ipv6Net, String> {
|
||||
// First, parse to an IPv6Net struct
|
||||
let net = Ipv6Net::from_str(string).map_err(|err| err.to_string())?;
|
||||
|
||||
// Ensure the prefix length is one of the allowed lengths according to RFC6052 Section 2.2
|
||||
if !rfc6052::ALLOWED_PREFIX_LENS.contains(&net.prefix_len()) {
|
||||
return Err(format!(
|
||||
"Prefix length must be one of {:?}",
|
||||
rfc6052::ALLOWED_PREFIX_LENS
|
||||
));
|
||||
}
|
||||
|
||||
// Return the parsed network struct
|
||||
Ok(net)
|
||||
}
|
||||
|
||||
/// Parses a mapping of an IPv4 address to an IPv6 address
|
||||
fn parse_static_map(string: &str) -> Result<(Ipv4Addr, Ipv6Addr), String> {
|
||||
// Split the string into two parts
|
||||
let parts: Vec<&str> = string.split('=').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Static map must be in the form 'IPv4=IPv6'".to_string());
|
||||
}
|
||||
|
||||
// Parse the IPv4 and IPv6 addresses
|
||||
let v4 = Ipv4Addr::from_str(parts[0]).map_err(|err| err.to_string())?;
|
||||
let v6 = Ipv6Addr::from_str(parts[1]).map_err(|err| err.to_string())?;
|
||||
|
||||
// Return the parsed mapping
|
||||
Ok((v4, v6))
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||
|
||||
/// Enable the logger
|
||||
#[allow(dead_code)]
|
||||
pub fn enable_logger(verbose: bool) {
|
||||
fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}{}: {}",
|
||||
// Level messages are padded to keep the output looking somewhat sane
|
||||
match record.level() {
|
||||
log::Level::Error => "ERROR"
|
||||
.if_supports_color(Stdout, |text| text.red())
|
||||
.if_supports_color(Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Warn => "WARN "
|
||||
.if_supports_color(Stdout, |text| text.yellow())
|
||||
.if_supports_color(Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Info => "INFO "
|
||||
.if_supports_color(Stdout, |text| text.green())
|
||||
.if_supports_color(Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Debug => "DEBUG"
|
||||
.if_supports_color(Stdout, |text| text.bright_blue())
|
||||
.if_supports_color(Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Trace => "TRACE"
|
||||
.if_supports_color(Stdout, |text| text.bright_white())
|
||||
.if_supports_color(Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
},
|
||||
// Only show the outer package name if verbose logging is enabled (otherwise nothing)
|
||||
match verbose {
|
||||
true => format!(" [{}]", record.target().split("::").next().unwrap()),
|
||||
false => String::new(),
|
||||
}
|
||||
.if_supports_color(Stdout, |text| text.bright_black()),
|
||||
message
|
||||
))
|
||||
})
|
||||
// Set the correct log level based on CLI flags
|
||||
.level(match verbose {
|
||||
true => log::LevelFilter::Debug,
|
||||
false => log::LevelFilter::Info,
|
||||
})
|
||||
// Output to STDOUT
|
||||
.chain(std::io::stdout())
|
||||
.apply()
|
||||
.unwrap();
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
//! Common code used across all protomask binaries
|
||||
|
||||
pub mod logging;
|
||||
pub mod packet_handler;
|
||||
pub mod permissions;
|
||||
pub mod profiler;
|
||||
pub mod rfc6052;
|
@ -1,180 +0,0 @@
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PacketHandlingError {
|
||||
#[error(transparent)]
|
||||
InterprotoError(#[from] interproto::error::Error),
|
||||
#[error(transparent)]
|
||||
FastNatError(#[from] fast_nat::error::Error),
|
||||
}
|
||||
|
||||
/// Get the layer 3 protocol of a packet
|
||||
pub fn get_layer_3_proto(packet: &[u8]) -> Option<u8> {
|
||||
// If the packet is empty, return nothing
|
||||
if packet.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Switch on the layer 3 protocol number to call the correct handler
|
||||
let layer_3_proto = packet[0] >> 4;
|
||||
log::trace!("New packet with layer 3 protocol: {}", layer_3_proto);
|
||||
Some(layer_3_proto)
|
||||
}
|
||||
|
||||
/// Get the source and destination addresses of an IPv4 packet
|
||||
pub fn get_ipv4_src_dst(packet: &[u8]) -> (Ipv4Addr, Ipv4Addr) {
|
||||
let source_addr = Ipv4Addr::from(u32::from_be_bytes(packet[12..16].try_into().unwrap()));
|
||||
let destination_addr = Ipv4Addr::from(u32::from_be_bytes(packet[16..20].try_into().unwrap()));
|
||||
|
||||
(source_addr, destination_addr)
|
||||
}
|
||||
|
||||
/// Get the source and destination addresses of an IPv6 packet
|
||||
pub fn get_ipv6_src_dst(packet: &[u8]) -> (Ipv6Addr, Ipv6Addr) {
|
||||
let source_addr = Ipv6Addr::from(u128::from_be_bytes(packet[8..24].try_into().unwrap()));
|
||||
let destination_addr = Ipv6Addr::from(u128::from_be_bytes(packet[24..40].try_into().unwrap()));
|
||||
|
||||
(source_addr, destination_addr)
|
||||
}
|
||||
|
||||
/// Appropriately handle a translation error
|
||||
pub fn handle_translation_error(
|
||||
result: Result<Option<Vec<u8>>, PacketHandlingError>,
|
||||
) -> Option<Vec<u8>> {
|
||||
// We may or may not have a warn-able error
|
||||
match result {
|
||||
// If we get data, return it
|
||||
Ok(data) => data,
|
||||
// If we get an error, handle it and return None
|
||||
Err(error) => match error {
|
||||
PacketHandlingError::InterprotoError(interproto::error::Error::PacketTooShort {
|
||||
expected,
|
||||
actual,
|
||||
}) => {
|
||||
log::warn!(
|
||||
"Got packet with length {} when expecting at least {} bytes",
|
||||
actual,
|
||||
expected
|
||||
);
|
||||
None
|
||||
}
|
||||
PacketHandlingError::InterprotoError(
|
||||
interproto::error::Error::UnsupportedIcmpType(icmp_type),
|
||||
) => {
|
||||
log::warn!("Got a packet with an unsupported ICMP type: {}", icmp_type);
|
||||
None
|
||||
}
|
||||
PacketHandlingError::InterprotoError(
|
||||
interproto::error::Error::UnsupportedIcmpv6Type(icmpv6_type),
|
||||
) => {
|
||||
log::warn!(
|
||||
"Got a packet with an unsupported ICMPv6 type: {}",
|
||||
icmpv6_type
|
||||
);
|
||||
None
|
||||
}
|
||||
PacketHandlingError::FastNatError(fast_nat::error::Error::Ipv4PoolExhausted) => {
|
||||
log::warn!("IPv4 pool exhausted. Dropping packet.");
|
||||
None
|
||||
}
|
||||
PacketHandlingError::FastNatError(fast_nat::error::Error::InvalidIpv4Address(addr)) => {
|
||||
log::warn!("Invalid IPv4 address: {}", addr);
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// /// Handles checking the version number of an IP packet and calling the correct handler with needed data
|
||||
// pub fn handle_packet<Ipv4Handler, Ipv6Handler>(
|
||||
// packet: &[u8],
|
||||
// mut ipv4_handler: Ipv4Handler,
|
||||
// mut ipv6_handler: Ipv6Handler,
|
||||
// ) -> Option<Vec<u8>>
|
||||
// where
|
||||
// Ipv4Handler: FnMut(&[u8], &Ipv4Addr, &Ipv4Addr) -> Result<Option<Vec<u8>>, PacketHandlingError>,
|
||||
// Ipv6Handler: FnMut(&[u8], &Ipv6Addr, &Ipv6Addr) -> Result<Option<Vec<u8>>, PacketHandlingError>,
|
||||
// {
|
||||
// // If the packet is empty, return nothing
|
||||
// if packet.is_empty() {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// // Switch on the layer 3 protocol number to call the correct handler
|
||||
// let layer_3_proto = packet[0] >> 4;
|
||||
// log::trace!("New packet with layer 3 protocol: {}", layer_3_proto);
|
||||
// let handler_response = match layer_3_proto {
|
||||
// // IPv4
|
||||
// 4 => {
|
||||
// // Extract the source and destination addresses
|
||||
// let source_addr =
|
||||
// Ipv4Addr::from(u32::from_be_bytes(packet[12..16].try_into().unwrap()));
|
||||
// let destination_addr =
|
||||
// Ipv4Addr::from(u32::from_be_bytes(packet[16..20].try_into().unwrap()));
|
||||
|
||||
// // Call the handler
|
||||
// ipv4_handler(packet, &source_addr, &destination_addr)
|
||||
// }
|
||||
|
||||
// // IPv6
|
||||
// 6 => {
|
||||
// // Extract the source and destination addresses
|
||||
// let source_addr =
|
||||
// Ipv6Addr::from(u128::from_be_bytes(packet[8..24].try_into().unwrap()));
|
||||
// let destination_addr =
|
||||
// Ipv6Addr::from(u128::from_be_bytes(packet[24..40].try_into().unwrap()));
|
||||
|
||||
// // Call the handler
|
||||
// ipv6_handler(packet, &source_addr, &destination_addr)
|
||||
// }
|
||||
|
||||
// // Unknown protocol numbers can't be handled
|
||||
// proto => {
|
||||
// log::warn!("Unknown Layer 3 protocol: {}", proto);
|
||||
// return None;
|
||||
// }
|
||||
// };
|
||||
|
||||
// // The response from the handler may or may not be a warn-able error
|
||||
// match handler_response {
|
||||
// // If we get data, return it
|
||||
// Ok(data) => data,
|
||||
// // If we get an error, handle it and return None
|
||||
// Err(error) => match error {
|
||||
// PacketHandlingError::InterprotoError(interproto::error::Error::PacketTooShort {
|
||||
// expected,
|
||||
// actual,
|
||||
// }) => {
|
||||
// log::warn!(
|
||||
// "Got packet with length {} when expecting at least {} bytes",
|
||||
// actual,
|
||||
// expected
|
||||
// );
|
||||
// None
|
||||
// }
|
||||
// PacketHandlingError::InterprotoError(
|
||||
// interproto::error::Error::UnsupportedIcmpType(icmp_type),
|
||||
// ) => {
|
||||
// log::warn!("Got a packet with an unsupported ICMP type: {}", icmp_type);
|
||||
// None
|
||||
// }
|
||||
// PacketHandlingError::InterprotoError(
|
||||
// interproto::error::Error::UnsupportedIcmpv6Type(icmpv6_type),
|
||||
// ) => {
|
||||
// log::warn!(
|
||||
// "Got a packet with an unsupported ICMPv6 type: {}",
|
||||
// icmpv6_type
|
||||
// );
|
||||
// None
|
||||
// }
|
||||
// PacketHandlingError::FastNatError(fast_nat::error::Error::Ipv4PoolExhausted) => {
|
||||
// log::warn!("IPv4 pool exhausted. Dropping packet.");
|
||||
// None
|
||||
// }
|
||||
// PacketHandlingError::FastNatError(fast_nat::error::Error::InvalidIpv4Address(addr)) => {
|
||||
// log::warn!("Invalid IPv4 address: {}", addr);
|
||||
// None
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
// }
|
@ -1,9 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
use crate::args::ProfilerArgs;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "profiler")] {
|
||||
pub fn start_puffin_server(args: &ProfilerArgs) -> Option<puffin_http::Server> {
|
||||
if let Some(endpoint) = args.puffin_endpoint {
|
||||
log::info!("Starting puffin server on {}", endpoint);
|
||||
puffin::set_scopes_on(true);
|
||||
Some(puffin_http::Server::new(&endpoint.to_string()).unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#[allow(dead_code)]
|
||||
pub fn start_puffin_server(_args: &ProfilerArgs){}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//! Utilities for interacting with [RFC6052](https://datatracker.ietf.org/doc/html/rfc6052) "IPv4-Embedded IPv6 Addresses"
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use ipnet::Ipv6Net;
|
||||
|
||||
/// Parses an [RFC6052 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6052#section-2.2)-compliant IPv6 prefix from a string
|
||||
pub fn parse_network_specific_prefix(string: &str) -> Result<Ipv6Net, String> {
|
||||
// First, parse to an IPv6Net struct
|
||||
let net = Ipv6Net::from_str(string).map_err(|err| err.to_string())?;
|
||||
|
||||
// Ensure the prefix length is one of the allowed lengths according to RFC6052 Section 2.2
|
||||
if !rfc6052::ALLOWED_PREFIX_LENS.contains(&net.prefix_len()) {
|
||||
return Err(format!(
|
||||
"Prefix length must be one of {:?}",
|
||||
rfc6052::ALLOWED_PREFIX_LENS
|
||||
));
|
||||
}
|
||||
|
||||
// Return the parsed network struct
|
||||
Ok(net)
|
||||
}
|
9
src/engines/clat.rs
Normal file
9
src/engines/clat.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
|
||||
pub async fn do_clat(
|
||||
interface: String,
|
||||
customer_pool: Vec<Ipv4Net>,
|
||||
embed_prefix: Ipv6Net,
|
||||
num_queues: usize,
|
||||
) {
|
||||
}
|
3
src/engines/mod.rs
Normal file
3
src/engines/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! This module contains each of the "translation engines" that power protomask
|
||||
pub mod nat64;
|
||||
pub mod clat;
|
246
src/engines/nat64.rs
Normal file
246
src/engines/nat64.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use easy_tun::Tun;
|
||||
use interproto::protocols::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4};
|
||||
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
||||
use rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::nat::NetworkAddressTranslationTable;
|
||||
|
||||
/// Run NAT64 logic
|
||||
pub async fn do_nat64(
|
||||
interface: String,
|
||||
pool_prefixes: Vec<Ipv4Net>,
|
||||
static_map: Vec<(Ipv4Addr, Ipv6Addr)>,
|
||||
translation_prefix: Ipv6Net,
|
||||
lease_duration: u64,
|
||||
num_queues: usize,
|
||||
) {
|
||||
// Create a TUN interface
|
||||
let tun = Arc::new(
|
||||
Tun::new(&interface, num_queues)
|
||||
.map_err(|err| {
|
||||
log::error!("Failed to create TUN interface");
|
||||
err
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
// Get the "interface index" of our new interface
|
||||
let rt_netlink_handle = rtnl::new_handle().unwrap();
|
||||
let tun_link_index = rtnl::link::get_link_index(&rt_netlink_handle, tun.name())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
log::debug!("TUN interface index: {}", tun_link_index);
|
||||
|
||||
// Bring the interface up
|
||||
log::info!("Bringing up TUN interface: {}", tun.name());
|
||||
rtnl::link::link_up(&rt_netlink_handle, tun_link_index)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add a route for the translation prefix
|
||||
log::debug!("Adding route for {} to {}", translation_prefix, tun.name());
|
||||
rtnl::route::route_add(
|
||||
IpNet::V6(translation_prefix),
|
||||
&rt_netlink_handle,
|
||||
tun_link_index,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add a route for each pool prefix
|
||||
for pool_prefix in &pool_prefixes {
|
||||
log::debug!("Adding route for {} to {}", pool_prefix, tun.name());
|
||||
rtnl::route::route_add(
|
||||
IpNet::V4(pool_prefix.clone()),
|
||||
&rt_netlink_handle,
|
||||
tun_link_index,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Set up the address table
|
||||
let address_table = Arc::new(Mutex::new(NetworkAddressTranslationTable::new(
|
||||
Duration::from_secs(lease_duration),
|
||||
)));
|
||||
for (ipv4, ipv6) in static_map {
|
||||
log::info!("Adding static mapping: {} <--> {}", ipv4, ipv6);
|
||||
address_table
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_pair(ipv4, ipv6, false)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Perform translation
|
||||
log::info!("Starting {} worker threads...", num_queues);
|
||||
let mut worker_threads = Vec::new();
|
||||
for queue_id in 0..num_queues {
|
||||
let tun = tun.clone();
|
||||
let address_table = address_table.clone();
|
||||
let pool_prefixes = pool_prefixes.clone();
|
||||
worker_threads.push(thread::spawn(move || {
|
||||
log::debug!("Starting worker thread for queue ID: {}", queue_id);
|
||||
|
||||
// Allocate a buffer for the packet
|
||||
// TODO: Add custom MTU support
|
||||
let mut buffer = vec![0u8; 1500];
|
||||
|
||||
// Process forever
|
||||
loop {
|
||||
// Read a packet
|
||||
let len = tun.fd(queue_id).unwrap().read(&mut buffer).unwrap();
|
||||
let packet = &buffer[..len];
|
||||
|
||||
// If we have an empty packet, skip
|
||||
if len == 0 {
|
||||
log::debug!("Skipping empty packet");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine the layer 3 protocol
|
||||
let layer_3_proto = packet[0] >> 4;
|
||||
log::trace!("Handling packet with layer 3 protocol: {}", layer_3_proto);
|
||||
|
||||
// Try to translate the packet
|
||||
match layer_3_proto {
|
||||
// Ipv4 -> IPv6
|
||||
4 => {
|
||||
// Figure out the original (untranslated) source and destination addresses
|
||||
let ipv4_source_addr =
|
||||
Ipv4Addr::from(u32::from_be_bytes(packet[12..16].try_into().unwrap()));
|
||||
let ipv4_destination_addr =
|
||||
Ipv4Addr::from(u32::from_be_bytes(packet[16..20].try_into().unwrap()));
|
||||
|
||||
// Look up the appropriate IPv6 address for the destination
|
||||
let ipv6_destination_addr = address_table
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_ipv6(&ipv4_destination_addr);
|
||||
|
||||
// If we have a mapping for the destination address, we can translate the packet
|
||||
if let Some(ipv6_destination_addr) = ipv6_destination_addr {
|
||||
// Construct a new IPv6 source addr
|
||||
let ipv6_source_addr = unsafe {
|
||||
embed_ipv4_addr_unchecked(ipv4_source_addr, translation_prefix)
|
||||
};
|
||||
|
||||
if let Ok(translated_packet) = translate_ipv4_to_ipv6(
|
||||
packet,
|
||||
ipv6_source_addr,
|
||||
ipv6_destination_addr,
|
||||
) {
|
||||
// Update the lease
|
||||
address_table
|
||||
.lock()
|
||||
.unwrap()
|
||||
.tick_flow(&IpAddr::V4(ipv4_destination_addr));
|
||||
|
||||
// Write the translated packet to the TUN interface
|
||||
tun.fd(queue_id)
|
||||
.unwrap()
|
||||
.write_all(&translated_packet)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6 -> IPv4
|
||||
6 => {
|
||||
// Figure out the original (untranslated) source and destination addresses
|
||||
let ipv6_source_addr =
|
||||
Ipv6Addr::from(u128::from_be_bytes(packet[8..24].try_into().unwrap()));
|
||||
let ipv6_destination_addr =
|
||||
Ipv6Addr::from(u128::from_be_bytes(packet[24..40].try_into().unwrap()));
|
||||
|
||||
// Check if we have a source IPv4 address already leased. If not, attempt to allocate one
|
||||
let mut ipv4_source_addr =
|
||||
address_table.lock().unwrap().get_ipv4(&ipv6_source_addr);
|
||||
if ipv4_source_addr.is_none() {
|
||||
// Find the next available IPv4 address in the pool
|
||||
ipv4_source_addr =
|
||||
address_table.lock().unwrap().find_free_ipv4(&pool_prefixes);
|
||||
|
||||
// Try to insert a new mapping for this address
|
||||
if let Some(ipv4_source_addr) = ipv4_source_addr {
|
||||
address_table
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_pair(ipv4_source_addr, ipv6_source_addr, true)
|
||||
.unwrap();
|
||||
log::debug!(
|
||||
"Created mapping for {} <--> {}",
|
||||
ipv6_source_addr,
|
||||
ipv4_source_addr
|
||||
);
|
||||
} else {
|
||||
// If we are here, we have run out of IPv4 addresses
|
||||
log::warn!(
|
||||
"IPv4 pool exhausted. {} did not get a lease",
|
||||
ipv6_source_addr
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap the source address
|
||||
let ipv4_source_addr = ipv4_source_addr.unwrap();
|
||||
|
||||
// Figure out the correct IPv4 destination address
|
||||
let ipv4_destination_addr = unsafe {
|
||||
extract_ipv4_addr_unchecked(
|
||||
ipv6_destination_addr,
|
||||
translation_prefix.prefix_len(),
|
||||
)
|
||||
};
|
||||
|
||||
// Translate the packet
|
||||
if let Ok(translated_packet) =
|
||||
translate_ipv6_to_ipv4(packet, ipv4_source_addr, ipv4_destination_addr)
|
||||
{
|
||||
// Update the lease
|
||||
address_table
|
||||
.lock()
|
||||
.unwrap()
|
||||
.tick_flow(&IpAddr::V6(ipv6_source_addr));
|
||||
|
||||
// Write the translated packet to the TUN interface
|
||||
tun.fd(queue_id)
|
||||
.unwrap()
|
||||
.write_all(&translated_packet)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown protocol
|
||||
_ => {
|
||||
log::warn!("Unsupported layer 3 protocol: {}", layer_3_proto);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Spawn a helper thread that periodically cleans up expired leases
|
||||
let address_table = address_table.clone();
|
||||
worker_threads.push(thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_secs(30));
|
||||
log::trace!("Pruning expired leases");
|
||||
address_table.lock().unwrap().prune();
|
||||
}));
|
||||
|
||||
// Wait for all workers to finish
|
||||
log::info!("Processing packets");
|
||||
for worker in worker_threads {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
}
|
101
src/main.rs
Normal file
101
src/main.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use caps::{CapSet, Capability};
|
||||
use clap::Parser;
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
mod cli;
|
||||
mod engines;
|
||||
mod nat;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
// Parse CLI args
|
||||
let args = cli::Args::parse();
|
||||
|
||||
// Set up the logger
|
||||
fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}{}: {}",
|
||||
// Level messages are padded to keep the output looking somewhat sane
|
||||
match record.level() {
|
||||
log::Level::Error => "ERROR"
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.red())
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Warn => "WARN "
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.yellow())
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Info => "INFO "
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.green())
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Debug => "DEBUG"
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bright_blue())
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
log::Level::Trace => "TRACE"
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bright_white())
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bold())
|
||||
.to_string(),
|
||||
},
|
||||
// Only show the outer package name if verbose logging is enabled (otherwise nothing)
|
||||
match args.verbose {
|
||||
true => format!(" [{}]", record.target().split("::").next().unwrap()),
|
||||
false => String::new(),
|
||||
}
|
||||
.if_supports_color(owo_colors::Stream::Stdout, |text| text.bright_black()),
|
||||
message
|
||||
))
|
||||
})
|
||||
// Set the correct log level based on CLI flags
|
||||
.level(match args.verbose {
|
||||
true => log::LevelFilter::Debug,
|
||||
false => log::LevelFilter::Info,
|
||||
})
|
||||
// Output to STDOUT
|
||||
.chain(std::io::stdout())
|
||||
.apply()
|
||||
.unwrap();
|
||||
|
||||
// We require NET_ADMIN capabilities to run
|
||||
if !caps::has_cap(None, CapSet::Permitted, Capability::CAP_NET_ADMIN).unwrap() {
|
||||
log::error!("This program must be run with the NET_ADMIN capability");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Start up the correct translation engine
|
||||
log::info!(
|
||||
"Starting translation engine: {}",
|
||||
format!("{:?}", args.engine).split(' ').next().unwrap()
|
||||
);
|
||||
match args.engine {
|
||||
cli::Modes::Nat64 {
|
||||
interface,
|
||||
pool_prefixes,
|
||||
static_map,
|
||||
translation_prefix,
|
||||
lease_duration,
|
||||
num_queues,
|
||||
} => {
|
||||
engines::nat64::do_nat64(
|
||||
interface,
|
||||
pool_prefixes,
|
||||
static_map,
|
||||
translation_prefix,
|
||||
lease_duration,
|
||||
num_queues,
|
||||
)
|
||||
.await
|
||||
}
|
||||
cli::Modes::Clat {
|
||||
interface,
|
||||
customer_pool,
|
||||
embed_prefix,
|
||||
num_queues,
|
||||
} => engines::clat::do_clat(interface, customer_pool, embed_prefix, num_queues).await,
|
||||
}
|
||||
|
||||
// We are done at this point
|
||||
log::info!("Protomask has finished running. Cleaning up and exiting.");
|
||||
}
|
153
src/nat.rs
Normal file
153
src/nat.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use bimap::BiHashMap;
|
||||
use ipnet::Ipv4Net;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NatTableError {
|
||||
#[error("IPv4 address {0} is already in the table")]
|
||||
Ipv4AlreadyInTable(Ipv4Addr),
|
||||
#[error("IPv6 address {0} is already in the table")]
|
||||
Ipv6AlreadyInTable(Ipv6Addr),
|
||||
}
|
||||
|
||||
struct IpMappingLease {
|
||||
/// Total number of times this lease has been "ticked"
|
||||
ticks: u128,
|
||||
|
||||
/// Time that this lease will expire
|
||||
expiry: Option<SystemTime>,
|
||||
}
|
||||
|
||||
pub struct NetworkAddressTranslationTable {
|
||||
/// Mapping between addresses
|
||||
addr_pairs: BiHashMap<Ipv4Addr, Ipv6Addr>,
|
||||
|
||||
/// Lease duration
|
||||
lease_duration: Duration,
|
||||
|
||||
/// Information about leases
|
||||
leases: HashMap<(Ipv4Addr, Ipv6Addr), IpMappingLease>,
|
||||
}
|
||||
|
||||
impl NetworkAddressTranslationTable {
|
||||
/// Construct a new Cross-protocol network address table
|
||||
pub fn new(lease_duration: Duration) -> Self {
|
||||
Self {
|
||||
addr_pairs: BiHashMap::new(),
|
||||
lease_duration,
|
||||
leases: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a pair of addresses to the pool
|
||||
pub fn add_pair(
|
||||
&mut self,
|
||||
ipv4: Ipv4Addr,
|
||||
ipv6: Ipv6Addr,
|
||||
expires: bool,
|
||||
) -> Result<(), NatTableError> {
|
||||
// If either address is already in the table, throw an error
|
||||
if self.addr_pairs.contains_left(&ipv4) {
|
||||
return Err(NatTableError::Ipv4AlreadyInTable(ipv4));
|
||||
}
|
||||
if self.addr_pairs.contains_right(&ipv6) {
|
||||
return Err(NatTableError::Ipv6AlreadyInTable(ipv6));
|
||||
}
|
||||
|
||||
// Insert the pair into the table
|
||||
self.addr_pairs.insert(ipv4, ipv6);
|
||||
|
||||
// Add a lease to the lease table
|
||||
self.leases.insert(
|
||||
(ipv4, ipv6),
|
||||
IpMappingLease {
|
||||
ticks: 0,
|
||||
expiry: if expires {
|
||||
Some(SystemTime::now() + self.lease_duration)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the corresponding IPv6 address for a given IPv4 address
|
||||
pub fn get_ipv6(&self, ipv4: &Ipv4Addr) -> Option<Ipv6Addr> {
|
||||
self.addr_pairs.get_by_left(ipv4).map(|addr| *addr)
|
||||
}
|
||||
|
||||
/// Get the corresponding IPv4 address for a given IPv6 address
|
||||
pub fn get_ipv4(&self, ipv6: &Ipv6Addr) -> Option<Ipv4Addr> {
|
||||
self.addr_pairs.get_by_right(ipv6).map(|addr| *addr)
|
||||
}
|
||||
|
||||
/// "tick" a flow. Allowing it to continue living
|
||||
pub fn tick_flow(&mut self, ip: &IpAddr) {
|
||||
// Figure out the whole address pair
|
||||
let (ipv4, ipv6) = match ip {
|
||||
IpAddr::V4(ipv4) => {
|
||||
let ipv6 = self.get_ipv6(ipv4).unwrap();
|
||||
(ipv4.clone(), ipv6)
|
||||
}
|
||||
IpAddr::V6(ipv6) => {
|
||||
let ipv4 = self.get_ipv4(ipv6).unwrap();
|
||||
(ipv4, ipv6.clone())
|
||||
}
|
||||
};
|
||||
|
||||
// Get the lease
|
||||
let lease = self.leases.get_mut(&(ipv4, ipv6)).unwrap();
|
||||
|
||||
// Increment the tick count
|
||||
lease.ticks += 1;
|
||||
|
||||
// If the lease has an expiry, update it
|
||||
if let Some(_) = lease.expiry {
|
||||
lease.expiry = Some(SystemTime::now() + self.lease_duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prune any expired leases
|
||||
pub fn prune(&mut self) {
|
||||
// Get the current time
|
||||
let now = SystemTime::now();
|
||||
|
||||
// Filter out any leases that have expired
|
||||
self.leases.retain(|_, lease| {
|
||||
// If the lease has no expiry, keep it
|
||||
if lease.expiry.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the lease has expired, remove it
|
||||
lease.expiry.unwrap() > now
|
||||
});
|
||||
|
||||
// Remove any address pairs that no longer have a lease
|
||||
self.addr_pairs
|
||||
.retain(|ipv4, ipv6| self.leases.contains_key(&(*ipv4, *ipv6)));
|
||||
}
|
||||
|
||||
/// Finds a free IPv4 address within a set of prefixes
|
||||
pub fn find_free_ipv4(&self, prefixes: &Vec<Ipv4Net>) -> Option<Ipv4Addr> {
|
||||
// Iterate over the prefixes
|
||||
for prefix in prefixes {
|
||||
// Iterate over all addresses in the prefix
|
||||
for addr in prefix.hosts() {
|
||||
// If the address is not in the table, return it
|
||||
if !self.addr_pairs.contains_left(&addr) {
|
||||
return Some(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
//! This is a placeholder for a future 6over4 implementation
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
println!("You did it! You found the incomplete binary :)");
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
//! Entrypoint for the `protomask-clat` binary.
|
||||
//!
|
||||
//! 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::{
|
||||
get_ipv4_src_dst, get_ipv6_src_dst, get_layer_3_proto, handle_translation_error,
|
||||
PacketHandlingError,
|
||||
};
|
||||
use crate::common::profiler::start_puffin_server;
|
||||
use crate::{args::protomask_clat::Args, common::permissions::ensure_root};
|
||||
use clap::Parser;
|
||||
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 rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod args;
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
// Parse CLI args
|
||||
let args = Args::parse();
|
||||
|
||||
// Initialize logging
|
||||
enable_logger(args.verbose);
|
||||
|
||||
// Load config data
|
||||
let config = args.data().unwrap();
|
||||
|
||||
// We must be root to continue program execution
|
||||
ensure_root();
|
||||
|
||||
// Start profiling
|
||||
#[allow(clippy::let_unit_value)]
|
||||
let _server = start_puffin_server(&args.profiler_args);
|
||||
|
||||
// Bring up a TUN interface
|
||||
let tun = Arc::new(Tun::new(&args.interface, config.num_queues).unwrap());
|
||||
|
||||
// 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 an IPv4 default route towards the interface
|
||||
rtnl::route::route_add(IpNet::V4(Ipv4Net::default()), &rt_handle, tun_link_idx)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add an IPv6 route for each customer prefix
|
||||
for customer_prefix in config.customer_pool {
|
||||
let embedded_customer_prefix = unsafe {
|
||||
Ipv6Net::new(
|
||||
embed_ipv4_addr_unchecked(customer_prefix.addr(), config.embed_prefix),
|
||||
config.embed_prefix.prefix_len() + customer_prefix.prefix_len(),
|
||||
)
|
||||
.unwrap_unchecked()
|
||||
};
|
||||
log::debug!(
|
||||
"Adding route for {} to {}",
|
||||
embedded_customer_prefix,
|
||||
tun.name()
|
||||
);
|
||||
rtnl::route::route_add(
|
||||
IpNet::V6(embedded_customer_prefix),
|
||||
&rt_handle,
|
||||
tun_link_idx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// If we are configured to serve prometheus metrics, start the server
|
||||
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));
|
||||
}
|
||||
|
||||
// Translate all incoming packets
|
||||
log::info!("Translating packets on {}", tun.name());
|
||||
let mut worker_threads = Vec::new();
|
||||
for queue_id in 0..config.num_queues {
|
||||
let tun = Arc::clone(&tun);
|
||||
worker_threads.push(std::thread::spawn(move || {
|
||||
log::debug!("Starting worker thread for queue {}", queue_id);
|
||||
let mut buffer = vec![0u8; 1500];
|
||||
loop {
|
||||
// Indicate to the profiler that we are starting a new packet
|
||||
profiling::finish_frame!();
|
||||
profiling::scope!("packet");
|
||||
|
||||
// Read a packet
|
||||
let len = tun.fd(queue_id).unwrap().read(&mut buffer).unwrap();
|
||||
|
||||
// Translate it based on the Layer 3 protocol number
|
||||
let translation_result: Result<Option<Vec<u8>>, PacketHandlingError> =
|
||||
match get_layer_3_proto(&buffer[..len]) {
|
||||
Some(4) => {
|
||||
let (source, dest) = get_ipv4_src_dst(&buffer[..len]);
|
||||
translate_ipv4_to_ipv6(
|
||||
&buffer[..len],
|
||||
unsafe { embed_ipv4_addr_unchecked(source, config.embed_prefix) },
|
||||
unsafe { embed_ipv4_addr_unchecked(dest, config.embed_prefix) },
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(PacketHandlingError::from)
|
||||
}
|
||||
Some(6) => {
|
||||
let (source, dest) = get_ipv6_src_dst(&buffer[..len]);
|
||||
translate_ipv6_to_ipv4(
|
||||
&buffer[..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)
|
||||
.map_err(PacketHandlingError::from)
|
||||
}
|
||||
Some(proto) => {
|
||||
log::warn!("Unknown Layer 3 protocol: {}", proto);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle any errors and write
|
||||
if let Some(output) = handle_translation_error(translation_result) {
|
||||
tun.fd(queue_id).unwrap().write_all(&output).unwrap();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
for worker in worker_threads {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
}
|
187
src/protomask.rs
187
src/protomask.rs
@ -1,187 +0,0 @@
|
||||
use crate::common::{
|
||||
packet_handler::{
|
||||
get_ipv4_src_dst, get_ipv6_src_dst, get_layer_3_proto, handle_translation_error,
|
||||
PacketHandlingError,
|
||||
},
|
||||
permissions::ensure_root,
|
||||
profiler::start_puffin_server,
|
||||
};
|
||||
use clap::Parser;
|
||||
use common::logging::enable_logger;
|
||||
use easy_tun::Tun;
|
||||
use fast_nat::CrossProtocolNetworkAddressTableWithIpv4Pool;
|
||||
use interproto::protocols::ip::{translate_ipv4_to_ipv6, translate_ipv6_to_ipv4};
|
||||
use ipnet::IpNet;
|
||||
use rfc6052::{embed_ipv4_addr_unchecked, extract_ipv4_addr_unchecked};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
mod args;
|
||||
mod common;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
// Parse CLI args
|
||||
let args = args::protomask::Args::parse();
|
||||
|
||||
// Initialize logging
|
||||
enable_logger(args.verbose);
|
||||
|
||||
// Load config data
|
||||
let config = args.data().unwrap();
|
||||
|
||||
// We must be root to continue program execution
|
||||
ensure_root();
|
||||
|
||||
// Start profiling
|
||||
#[allow(clippy::let_unit_value)]
|
||||
let _server = start_puffin_server(&args.profiler_args);
|
||||
|
||||
// Bring up a TUN interface
|
||||
log::debug!("Creating new TUN interface");
|
||||
let tun = Arc::new(Tun::new(&args.interface, config.num_queues).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 {}",
|
||||
config.translation_prefix,
|
||||
tun.name()
|
||||
);
|
||||
rtnl::route::route_add(
|
||||
IpNet::V6(config.translation_prefix),
|
||||
&rt_handle,
|
||||
tun_link_idx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add a route for each NAT pool prefix
|
||||
for pool_prefix in &config.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 addr_table = Arc::new(Mutex::new(
|
||||
CrossProtocolNetworkAddressTableWithIpv4Pool::new(
|
||||
&config.pool_prefixes,
|
||||
Duration::from_secs(config.reservation_timeout),
|
||||
),
|
||||
));
|
||||
for (v4_addr, v6_addr) in &config.static_map {
|
||||
addr_table
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert_static(*v4_addr, *v6_addr)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// If we are configured to serve prometheus metrics, start the server
|
||||
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));
|
||||
}
|
||||
|
||||
// Translate all incoming packets
|
||||
log::info!("Translating packets on {}", tun.name());
|
||||
let mut worker_threads = Vec::new();
|
||||
for queue_id in 0..config.num_queues {
|
||||
let tun = Arc::clone(&tun);
|
||||
let addr_table = Arc::clone(&addr_table);
|
||||
worker_threads.push(std::thread::spawn(move || {
|
||||
log::debug!("Starting worker thread for queue {}", queue_id);
|
||||
|
||||
let mut buffer = vec![0u8; 1500];
|
||||
loop {
|
||||
// Indicate to the profiler that we are starting a new packet
|
||||
profiling::finish_frame!();
|
||||
profiling::scope!("packet");
|
||||
|
||||
// Read a packet
|
||||
let len = tun.fd(queue_id).unwrap().read(&mut buffer).unwrap();
|
||||
|
||||
// Translate it based on the Layer 3 protocol number
|
||||
let translation_result: Result<Option<Vec<u8>>, PacketHandlingError> =
|
||||
match get_layer_3_proto(&buffer[..len]) {
|
||||
Some(4) => {
|
||||
let (source, dest) = get_ipv4_src_dst(&buffer[..len]);
|
||||
match addr_table.lock().unwrap().get_ipv6(&dest) {
|
||||
Some(new_destination) => translate_ipv4_to_ipv6(
|
||||
&buffer[..len],
|
||||
unsafe {
|
||||
embed_ipv4_addr_unchecked(source, config.translation_prefix)
|
||||
},
|
||||
new_destination,
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(PacketHandlingError::from),
|
||||
None => {
|
||||
protomask_metrics::metric!(
|
||||
PACKET_COUNTER,
|
||||
PROTOCOL_IPV4,
|
||||
STATUS_DROPPED
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(6) => {
|
||||
let (source, dest) = get_ipv6_src_dst(&buffer[..len]);
|
||||
match addr_table.lock().unwrap().get_or_create_ipv4(&source) {
|
||||
Ok(new_source) => {
|
||||
translate_ipv6_to_ipv4(&buffer[..len], new_source, unsafe {
|
||||
extract_ipv4_addr_unchecked(
|
||||
dest,
|
||||
config.translation_prefix.prefix_len(),
|
||||
)
|
||||
})
|
||||
.map(Some)
|
||||
.map_err(PacketHandlingError::from)
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("Error getting IPv4 address: {}", error);
|
||||
protomask_metrics::metric!(
|
||||
PACKET_COUNTER,
|
||||
PROTOCOL_IPV6,
|
||||
STATUS_DROPPED
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(proto) => {
|
||||
log::warn!("Unknown Layer 3 protocol: {}", proto);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle any errors and write
|
||||
if let Some(output) = handle_translation_error(translation_result) {
|
||||
tun.fd(queue_id).unwrap().write_all(&output).unwrap();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
for worker in worker_threads {
|
||||
worker.join().unwrap();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user