Working on tun rewrite
This commit is contained in:
parent
3d80c1fc0e
commit
7b88a1cad9
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/protomask-*/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -12,22 +12,19 @@ license = "GPL-3.0"
|
||||
keywords = []
|
||||
categories = []
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enable-profiling = ["profiling/profile-with-puffin", "puffin_http"]
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
tokio = { version = "1.29.1", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"process"
|
||||
"process",
|
||||
"sync"
|
||||
] }
|
||||
clap = { version = "4.3.11", features = ["derive"] }
|
||||
serde = { version = "1.0.171", features = ["derive"] }
|
||||
ipnet = { version = "2.8.0", features = ["serde"] }
|
||||
puffin_http = { version = "0.13.0", optional = true }
|
||||
profiling = "1.0.8"
|
||||
toml = "0.7.6"
|
||||
log = "0.4.19"
|
||||
fern = "0.6.2"
|
||||
@ -39,10 +36,11 @@ bimap = "0.6.3"
|
||||
pnet_packet = "0.33.0"
|
||||
rtnetlink = "0.13.0"
|
||||
futures = "0.3.28"
|
||||
protomask-tun = { path = "protomask-tun", version = "0.1.0" }
|
||||
|
||||
[[bin]]
|
||||
name = "protomask"
|
||||
path = "src/main.rs"
|
||||
path = "src/cli/main.rs"
|
||||
|
||||
[package.metadata.rpm]
|
||||
package = "protomask"
|
||||
|
23
protomask-tun/Cargo.toml
Normal file
23
protomask-tun/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "protomask-tun"
|
||||
version = "0.1.0"
|
||||
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "An async interface to Linux TUN devices"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/ewpratten/protomask/protomask-tun"
|
||||
documentation = "https://docs.rs/protomask-tun"
|
||||
repository = "https://github.com/ewpratten/protomask"
|
||||
license = "GPL-3.0"
|
||||
keywords = []
|
||||
categories = []
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
tokio = { version = "1.29.1", features = ["sync", "rt"] }
|
||||
log = "0.4.19"
|
||||
thiserror = "1.0.43"
|
||||
tun-tap = "0.1.3"
|
||||
rtnetlink = "0.13.0"
|
||||
futures = "0.3.28"
|
||||
ipnet = "^2.8.0"
|
10
protomask-tun/src/error.rs
Normal file
10
protomask-tun/src/error.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
NetlinkError(#[from] rtnetlink::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
5
protomask-tun/src/lib.rs
Normal file
5
protomask-tun/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod error;
|
||||
mod tun;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
pub use tun::TunDevice;
|
190
protomask-tun/src/tun.rs
Normal file
190
protomask-tun/src/tun.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::IpAddr,
|
||||
os::fd::{AsRawFd, FromRawFd},
|
||||
};
|
||||
|
||||
use futures::TryStreamExt;
|
||||
use ipnet::IpNet;
|
||||
use tokio::{
|
||||
sync::{broadcast, mpsc},
|
||||
task,
|
||||
};
|
||||
use tun_tap::Mode;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TunDevice {
|
||||
device: tun_tap::Iface,
|
||||
rt_handle: rtnetlink::Handle,
|
||||
link_index: u32,
|
||||
mtu: usize,
|
||||
}
|
||||
|
||||
impl TunDevice {
|
||||
/// Create and bring up a new TUN device
|
||||
///
|
||||
/// ## Name format
|
||||
///
|
||||
/// The name field can be any string. If `%d` is present in the string,
|
||||
/// it will be replaced with a unique number.
|
||||
pub async fn new(name: &str) -> Result<Self> {
|
||||
// Bring up an rtnetlink connection
|
||||
let (rt_connection, rt_handle, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(rt_connection);
|
||||
|
||||
// Create the TUN device
|
||||
let tun_device = tun_tap::Iface::without_packet_info(name, Mode::Tun)?;
|
||||
log::debug!("Created new TUN device: {}", tun_device.name());
|
||||
|
||||
// Get access to the link through rtnetlink
|
||||
// NOTE: I don't think there is any way this can fail, so `except` is probably OK
|
||||
let tun_link = rt_handle
|
||||
.link()
|
||||
.get()
|
||||
.match_name(tun_device.name().to_owned())
|
||||
.execute()
|
||||
.try_next()
|
||||
.await?
|
||||
.expect("Failed to access newly created TUN device");
|
||||
|
||||
// Bring the link up
|
||||
rt_handle
|
||||
.link()
|
||||
.set(tun_link.header.index)
|
||||
.up()
|
||||
.execute()
|
||||
.await?;
|
||||
log::debug!("Brought {} up", tun_device.name());
|
||||
|
||||
// Read the link MTU
|
||||
let mtu: usize =
|
||||
std::fs::read_to_string(format!("/sys/class/net/{}/mtu", tun_device.name()))
|
||||
.expect("Failed to read link MTU")
|
||||
.strip_suffix("\n")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
Ok(Self {
|
||||
device: tun_device,
|
||||
rt_handle,
|
||||
link_index: tun_link.header.index,
|
||||
mtu,
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an IP address to this device
|
||||
pub async fn add_address(&mut self, ip_address: IpAddr, prefix_len: u8) -> Result<()> {
|
||||
self.rt_handle
|
||||
.address()
|
||||
.add(self.link_index, ip_address, prefix_len)
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove an IP address from this device
|
||||
pub async fn remove_address(&mut self, ip_address: IpAddr, prefix_len: u8) -> Result<()> {
|
||||
// Find the address message that matches the given address
|
||||
if let Some(address_message) = self
|
||||
.rt_handle
|
||||
.address()
|
||||
.get()
|
||||
.set_link_index_filter(self.link_index)
|
||||
.set_address_filter(ip_address)
|
||||
.set_prefix_length_filter(prefix_len)
|
||||
.execute()
|
||||
.try_next()
|
||||
.await?
|
||||
{
|
||||
// Delete the address
|
||||
self.rt_handle
|
||||
.address()
|
||||
.del(address_message)
|
||||
.execute()
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a route to this device
|
||||
pub async fn add_route(&mut self, destination: IpNet) -> Result<()> {
|
||||
match destination {
|
||||
IpNet::V4(destination) => {
|
||||
self.rt_handle
|
||||
.route()
|
||||
.add()
|
||||
.v4()
|
||||
.destination_prefix(destination.addr(), destination.prefix_len())
|
||||
.execute()
|
||||
.await?;
|
||||
}
|
||||
IpNet::V6(destination) => {
|
||||
self.rt_handle
|
||||
.route()
|
||||
.add()
|
||||
.v6()
|
||||
.destination_prefix(destination.addr(), destination.prefix_len())
|
||||
.execute()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Spawns worker threads, and returns a tx/rx pair for the caller to interact with them
|
||||
pub async fn spawn_worker(&self) -> (mpsc::Sender<Vec<u8>>, broadcast::Receiver<Vec<u8>>) {
|
||||
// Create a channel for packets to be sent to the caller
|
||||
let (tx_to_caller, rx_from_worker) = broadcast::channel(32);
|
||||
|
||||
// Create a channel for packets being received from the caller
|
||||
let (tx_to_worker, mut rx_from_caller) = mpsc::channel(32);
|
||||
|
||||
// Clone some values for use in worker threads
|
||||
let mtu = self.mtu;
|
||||
let device_fd = self.device.as_raw_fd();
|
||||
|
||||
// Create a task that broadcasts all incoming packets
|
||||
let _rx_task = task::spawn_blocking(move || {
|
||||
// Build a buffer to read packets into
|
||||
let mut buffer = vec![0u8; mtu];
|
||||
|
||||
// Create a file to access the TUN device
|
||||
let mut device = unsafe { std::fs::File::from_raw_fd(device_fd) };
|
||||
|
||||
loop {
|
||||
// Read a packet from the TUN device
|
||||
let packet_len = device.read(&mut buffer[..]).unwrap();
|
||||
let packet = buffer[..packet_len].to_vec();
|
||||
|
||||
// Broadcast the packet to all listeners
|
||||
tx_to_caller.send(packet).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Create a task that sends all outgoing packets
|
||||
let _tx_task = task::spawn(async move {
|
||||
// Create a file to access the TUN device
|
||||
let mut device = unsafe { std::fs::File::from_raw_fd(device_fd) };
|
||||
|
||||
loop {
|
||||
// Wait for a packet to be sent
|
||||
let packet: Vec<u8> = rx_from_caller.recv().await.unwrap();
|
||||
|
||||
// Write the packet to the TUN device
|
||||
device.write_all(&packet[..]).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
// Create a task that sends all outgoing packets
|
||||
let _tx_task = task::spawn_blocking(|| {});
|
||||
|
||||
// Return an rx/tx pair for the caller to interact with the workers
|
||||
(tx_to_worker, rx_from_worker)
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//! Command line argument definitions
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
@ -11,9 +13,4 @@ pub struct Args {
|
||||
/// Enable verbose logging
|
||||
#[clap(short, long)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Enable the puffin profiling server for debugging
|
||||
#[cfg(feature = "enable-profiling")]
|
||||
#[clap(long)]
|
||||
pub enable_profiling: bool,
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
//! Serde definitions for the config file
|
||||
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
path::Path,
|
||||
@ -65,6 +67,7 @@ impl Config {
|
||||
// Parse
|
||||
match serde_path_to_error::deserialize(deserializer) {
|
||||
Ok(config) => Ok(config),
|
||||
// If there is a parsing error, display a reasonable error message
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Failed to parse config file due to:\n {}\n at {}",
|
@ -1,20 +1,5 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
/// A global variable that is used to early-kill attempts to write debug logs if debug logging is disabled
|
||||
pub static DEBUG_ENABLED: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
/// A macro that can completely skip the debug step if debug logging is disabled
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)*) => {
|
||||
if *$crate::logging::DEBUG_ENABLED.get().unwrap_or(&false) {
|
||||
log::debug!($($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Enable the logger
|
||||
#[allow(dead_code)]
|
||||
pub fn enable_logger(verbose: bool) {
|
||||
@ -42,17 +27,13 @@ pub fn enable_logger(verbose: bool) {
|
||||
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();
|
||||
if verbose {
|
||||
log::debug!("Verbose logging enabled");
|
||||
}
|
||||
|
||||
// Set the global debug enabled variable
|
||||
DEBUG_ENABLED.set(verbose).unwrap();
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
//! This is the entrypoint for `protomask` from the command line.
|
||||
|
||||
use clap::Parser;
|
||||
use config::Config;
|
||||
use logging::enable_logger;
|
||||
use nat::Nat64;
|
||||
use protomask::nat::Nat64;
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
mod nat;
|
||||
mod logging;
|
||||
|
||||
#[tokio::main]
|
||||
@ -16,16 +17,6 @@ pub async fn main() {
|
||||
// Set up logging
|
||||
enable_logger(args.verbose);
|
||||
|
||||
// If the binary was built with profiling support, enable it
|
||||
#[cfg(feature = "enable-profiling")]
|
||||
let _puffin_server: puffin_http::Server;
|
||||
#[cfg(feature = "enable-profiling")]
|
||||
if args.enable_profiling {
|
||||
_puffin_server =
|
||||
puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap();
|
||||
log::info!("Puffin profiling server started");
|
||||
}
|
||||
|
||||
// Parse the config file
|
||||
let config = Config::load(args.config_file).unwrap();
|
||||
|
@ -1,4 +1,6 @@
|
||||
#![deny(unsafe_code)]
|
||||
//! # Protomask library
|
||||
//!
|
||||
//! *Note: There is a fair chance you are looking for `src/cli/main.rs` instead of this file.*
|
||||
|
||||
pub mod nat;
|
||||
mod logging;
|
||||
mod packet;
|
||||
|
@ -1,121 +1,121 @@
|
||||
use futures::stream::TryStreamExt;
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use tun_tap::{Iface, Mode};
|
||||
// use futures::stream::TryStreamExt;
|
||||
// use ipnet::{Ipv4Net, Ipv6Net};
|
||||
// use tun_tap::{Iface, Mode};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum InterfaceError {
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
NetlinkError(#[from] rtnetlink::Error),
|
||||
}
|
||||
// #[derive(Debug, thiserror::Error)]
|
||||
// pub enum InterfaceError {
|
||||
// #[error(transparent)]
|
||||
// IoError(#[from] std::io::Error),
|
||||
// #[error(transparent)]
|
||||
// NetlinkError(#[from] rtnetlink::Error),
|
||||
// }
|
||||
|
||||
/// Wrapper around a TUN interface that automatically configures itself
|
||||
#[derive(Debug)]
|
||||
pub struct Nat64Interface {
|
||||
/// Underlying TUN interface
|
||||
interface: Iface,
|
||||
/// Interface MTU
|
||||
mtu: usize,
|
||||
}
|
||||
// /// Wrapper around a TUN interface that automatically configures itself
|
||||
// #[derive(Debug)]
|
||||
// pub struct Nat64Interface {
|
||||
// /// Underlying TUN interface
|
||||
// interface: Iface,
|
||||
// /// Interface MTU
|
||||
// mtu: usize,
|
||||
// }
|
||||
|
||||
impl Nat64Interface {
|
||||
/// Create a new NAT64 interface
|
||||
pub async fn new(v6_prefix: Ipv6Net, v4_pool: &Vec<Ipv4Net>) -> Result<Self, InterfaceError> {
|
||||
// Bring up an rtnetlink connection
|
||||
let (rt_connection, rt_handle, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(rt_connection);
|
||||
// impl Nat64Interface {
|
||||
// /// Create a new NAT64 interface
|
||||
// pub async fn new(v6_prefix: Ipv6Net, v4_pool: &Vec<Ipv4Net>) -> Result<Self, InterfaceError> {
|
||||
// // Bring up an rtnetlink connection
|
||||
// let (rt_connection, rt_handle, _) = rtnetlink::new_connection()?;
|
||||
// tokio::spawn(rt_connection);
|
||||
|
||||
// Set up the TUN interface
|
||||
let interface = Iface::without_packet_info("nat64i%d", Mode::Tun)?;
|
||||
// // Set up the TUN interface
|
||||
// let interface = Iface::without_packet_info("nat64i%d", Mode::Tun)?;
|
||||
|
||||
// Get access to the new interface through rtnetlink
|
||||
let interface_link = rt_handle
|
||||
.link()
|
||||
.get()
|
||||
.match_name(interface.name().to_owned())
|
||||
.execute()
|
||||
.try_next()
|
||||
.await?
|
||||
.expect("Interface not found even though it was just created");
|
||||
// // Get access to the new interface through rtnetlink
|
||||
// let interface_link = rt_handle
|
||||
// .link()
|
||||
// .get()
|
||||
// .match_name(interface.name().to_owned())
|
||||
// .execute()
|
||||
// .try_next()
|
||||
// .await?
|
||||
// .expect("Interface not found even though it was just created");
|
||||
|
||||
// Bring up the interface
|
||||
rt_handle
|
||||
.link()
|
||||
.set(interface_link.header.index)
|
||||
.up()
|
||||
.execute()
|
||||
.await?;
|
||||
log::info!("Created interface: {}", interface.name());
|
||||
// // Bring up the interface
|
||||
// rt_handle
|
||||
// .link()
|
||||
// .set(interface_link.header.index)
|
||||
// .up()
|
||||
// .execute()
|
||||
// .await?;
|
||||
// log::info!("Created interface: {}", interface.name());
|
||||
|
||||
// Add the v6 prefix as a route
|
||||
rt_handle
|
||||
.route()
|
||||
.add()
|
||||
.v6()
|
||||
.destination_prefix(v6_prefix.addr(), v6_prefix.prefix_len())
|
||||
.output_interface(interface_link.header.index)
|
||||
.execute()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
log::error!("Failed to add route for {}: {}", v6_prefix, error);
|
||||
error
|
||||
})?;
|
||||
log::info!("Added route: {} via {}", v6_prefix, interface.name());
|
||||
// // Add the v6 prefix as a route
|
||||
// rt_handle
|
||||
// .route()
|
||||
// .add()
|
||||
// .v6()
|
||||
// .destination_prefix(v6_prefix.addr(), v6_prefix.prefix_len())
|
||||
// .output_interface(interface_link.header.index)
|
||||
// .execute()
|
||||
// .await
|
||||
// .map_err(|error| {
|
||||
// log::error!("Failed to add route for {}: {}", v6_prefix, error);
|
||||
// error
|
||||
// })?;
|
||||
// log::info!("Added route: {} via {}", v6_prefix, interface.name());
|
||||
|
||||
// Add every prefix in the v4 pool as a route
|
||||
for prefix in v4_pool {
|
||||
rt_handle
|
||||
.route()
|
||||
.add()
|
||||
.v4()
|
||||
.destination_prefix(prefix.addr(), prefix.prefix_len())
|
||||
.output_interface(interface_link.header.index)
|
||||
.execute()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
log::error!("Failed to add route for {}: {}", prefix, error);
|
||||
error
|
||||
})?;
|
||||
log::info!("Added route: {} via {}", prefix, interface.name());
|
||||
}
|
||||
// // Add every prefix in the v4 pool as a route
|
||||
// for prefix in v4_pool {
|
||||
// rt_handle
|
||||
// .route()
|
||||
// .add()
|
||||
// .v4()
|
||||
// .destination_prefix(prefix.addr(), prefix.prefix_len())
|
||||
// .output_interface(interface_link.header.index)
|
||||
// .execute()
|
||||
// .await
|
||||
// .map_err(|error| {
|
||||
// log::error!("Failed to add route for {}: {}", prefix, error);
|
||||
// error
|
||||
// })?;
|
||||
// log::info!("Added route: {} via {}", prefix, interface.name());
|
||||
// }
|
||||
|
||||
// Read the interface MTU
|
||||
let mtu: usize =
|
||||
std::fs::read_to_string(format!("/sys/class/net/{}/mtu", interface.name()))
|
||||
.expect("Failed to read interface MTU")
|
||||
.strip_suffix("\n")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
// // Read the interface MTU
|
||||
// let mtu: usize =
|
||||
// std::fs::read_to_string(format!("/sys/class/net/{}/mtu", interface.name()))
|
||||
// .expect("Failed to read interface MTU")
|
||||
// .strip_suffix("\n")
|
||||
// .unwrap()
|
||||
// .parse()
|
||||
// .unwrap();
|
||||
|
||||
Ok(Self { interface, mtu })
|
||||
}
|
||||
// Ok(Self { interface, mtu })
|
||||
// }
|
||||
|
||||
/// Get the interface mode
|
||||
#[allow(dead_code)]
|
||||
pub fn mode(&self) -> Mode {
|
||||
self.interface.mode()
|
||||
}
|
||||
// /// Get the interface mode
|
||||
// #[allow(dead_code)]
|
||||
// pub fn mode(&self) -> Mode {
|
||||
// self.interface.mode()
|
||||
// }
|
||||
|
||||
/// Get the interface nam
|
||||
#[allow(dead_code)]
|
||||
pub fn name(&self) -> &str {
|
||||
self.interface.name()
|
||||
}
|
||||
// /// Get the interface nam
|
||||
// #[allow(dead_code)]
|
||||
// pub fn name(&self) -> &str {
|
||||
// self.interface.name()
|
||||
// }
|
||||
|
||||
/// Get the interface MTU
|
||||
pub fn mtu(&self) -> usize {
|
||||
self.mtu
|
||||
}
|
||||
// /// Get the interface MTU
|
||||
// pub fn mtu(&self) -> usize {
|
||||
// self.mtu
|
||||
// }
|
||||
|
||||
/// Receive a packet from the interface
|
||||
pub fn recv(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
||||
self.interface.recv(buf)
|
||||
}
|
||||
// /// Receive a packet from the interface
|
||||
// pub fn recv(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
||||
// self.interface.recv(buf)
|
||||
// }
|
||||
|
||||
/// Send a packet to the interface
|
||||
pub fn send(&self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||
self.interface.send(buf)
|
||||
}
|
||||
}
|
||||
// /// Send a packet to the interface
|
||||
// pub fn send(&self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||
// self.interface.send(buf)
|
||||
// }
|
||||
// }
|
||||
|
153
src/nat/mod.rs
153
src/nat/mod.rs
@ -1,21 +1,20 @@
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
time::Duration,
|
||||
use self::{
|
||||
// interface::Nat64Interface,
|
||||
packet::IpPacket,
|
||||
table::{Nat64Table, TableError},
|
||||
};
|
||||
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use pnet_packet::{ip::IpNextHeaderProtocols, Packet};
|
||||
|
||||
use crate::{
|
||||
into_icmp, into_icmpv6, into_tcp, into_udp, ipv4_packet, ipv6_packet,
|
||||
nat::xlat::translate_udp_4_to_6,
|
||||
};
|
||||
|
||||
use self::{
|
||||
interface::Nat64Interface,
|
||||
packet::IpPacket,
|
||||
table::{Nat64Table, TableError},
|
||||
use ipnet::{Ipv4Net, Ipv6Net};
|
||||
use pnet_packet::{ip::IpNextHeaderProtocols, Packet};
|
||||
use protomask_tun::TunDevice;
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
|
||||
mod interface;
|
||||
mod macros;
|
||||
@ -28,16 +27,18 @@ pub enum Nat64Error {
|
||||
#[error(transparent)]
|
||||
TableError(#[from] table::TableError),
|
||||
#[error(transparent)]
|
||||
InterfaceError(#[from] interface::InterfaceError),
|
||||
TunError(#[from] protomask_tun::Error),
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
XlatError(#[from] xlat::PacketTranslationError),
|
||||
#[error(transparent)]
|
||||
PacketReceiveError(#[from] broadcast::error::RecvError),
|
||||
}
|
||||
|
||||
pub struct Nat64 {
|
||||
table: Nat64Table,
|
||||
interface: Nat64Interface,
|
||||
interface: TunDevice,
|
||||
ipv6_nat_prefix: Ipv6Net,
|
||||
}
|
||||
|
||||
@ -50,7 +51,15 @@ impl Nat64 {
|
||||
reservation_duration: Duration,
|
||||
) -> Result<Self, Nat64Error> {
|
||||
// Bring up the interface
|
||||
let interface = Nat64Interface::new(ipv6_nat_prefix, &ipv4_pool).await?;
|
||||
let mut interface = TunDevice::new("nat64i%d").await?;
|
||||
|
||||
// Add the NAT64 prefix as a route
|
||||
interface.add_route(ipv6_nat_prefix.into()).await?;
|
||||
|
||||
// Add the IPv4 pool prefixes as routes
|
||||
for ipv4_prefix in ipv4_pool.iter() {
|
||||
interface.add_route((*ipv4_prefix).into()).await?;
|
||||
}
|
||||
|
||||
// Build the table and insert any static reservations
|
||||
let mut table = Nat64Table::new(ipv4_pool, reservation_duration);
|
||||
@ -67,60 +76,78 @@ impl Nat64 {
|
||||
|
||||
/// Block and process all packets
|
||||
pub async fn run(&mut self) -> Result<(), Nat64Error> {
|
||||
// Allocate a buffer for incoming packets
|
||||
let mut buffer = vec![0u8; self.interface.mtu()];
|
||||
// Get an rx/tx pair for the interface
|
||||
let (tx, mut rx) = self.interface.spawn_worker().await;
|
||||
|
||||
// Loop forever
|
||||
// Process packets in a loop
|
||||
loop {
|
||||
// Read a packet from the interface
|
||||
match self.interface.recv(&mut buffer) {
|
||||
Ok(packet_len) => {
|
||||
// Parse in to a more friendly format
|
||||
crate::debug!("--- NEW PACKET ---");
|
||||
match IpPacket::new(&buffer[..packet_len]) {
|
||||
// Try to process the packet
|
||||
Ok(inbound_packet) => match self.process_packet(inbound_packet).await {
|
||||
Ok(outbound_packet) => match outbound_packet {
|
||||
// If data is returned, send it back out the interface
|
||||
Some(outbound_packet) => {
|
||||
let packet_bytes = outbound_packet.to_bytes();
|
||||
crate::debug!(
|
||||
"Outbound packet next header: {}",
|
||||
outbound_packet.get_next_header().0
|
||||
);
|
||||
crate::debug!("Sending packet: {:?}", packet_bytes);
|
||||
self.interface.send(&packet_bytes).unwrap();
|
||||
}
|
||||
// Otherwise, we can assume that the packet was dealt with, and can move on
|
||||
None => {}
|
||||
},
|
||||
// Try to read a packet
|
||||
let packet = rx.recv().await?;
|
||||
|
||||
// Some errors are non-critical as far as this loop is concerned
|
||||
Err(error) => match error {
|
||||
Nat64Error::TableError(TableError::NoIpv6Mapping(address)) => {
|
||||
crate::debug!("No IPv6 mapping for {}", address);
|
||||
}
|
||||
error => {
|
||||
return Err(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(error) => {
|
||||
log::error!("Failed to parse packet: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
log::error!("Failed to read packet: {}", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Spawn a task to process the packet
|
||||
let mut tx = tx.clone();
|
||||
tokio::spawn(async move{
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// // Allocate a buffer for incoming packets
|
||||
// let mut buffer = vec![0u8; self.interface.mtu()];
|
||||
|
||||
// // Loop forever
|
||||
// loop {
|
||||
// // Read a packet from the interface
|
||||
// match self.interface.recv(&mut buffer) {
|
||||
// Ok(packet_len) => {
|
||||
// // Parse in to a more friendly format
|
||||
// log::debug!("--- NEW PACKET ---");
|
||||
// match IpPacket::new(&buffer[..packet_len]) {
|
||||
// // Try to process the packet
|
||||
// Ok(inbound_packet) => match self.process_packet(inbound_packet).await {
|
||||
// Ok(outbound_packet) => match outbound_packet {
|
||||
// // If data is returned, send it back out the interface
|
||||
// Some(outbound_packet) => {
|
||||
// let packet_bytes = outbound_packet.to_bytes();
|
||||
// log::debug!(
|
||||
// "Outbound packet next header: {}",
|
||||
// outbound_packet.get_next_header().0
|
||||
// );
|
||||
// log::debug!("Sending packet: {:?}", packet_bytes);
|
||||
// self.interface.send(&packet_bytes).unwrap();
|
||||
// }
|
||||
// // Otherwise, we can assume that the packet was dealt with, and can move on
|
||||
// None => {}
|
||||
// },
|
||||
|
||||
// // Some errors are non-critical as far as this loop is concerned
|
||||
// Err(error) => match error {
|
||||
// Nat64Error::TableError(TableError::NoIpv6Mapping(address)) => {
|
||||
// log::debug!("No IPv6 mapping for {}", address);
|
||||
// }
|
||||
// error => {
|
||||
// return Err(error);
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// Err(error) => {
|
||||
// log::error!("Failed to parse packet: {}", error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Err(error) => {
|
||||
// log::error!("Failed to read packet: {}", error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
impl Nat64 {
|
||||
|
||||
#[profiling::function]
|
||||
async fn process_packet<'a>(
|
||||
&mut self,
|
||||
packet: IpPacket<'a>,
|
||||
@ -130,7 +157,7 @@ impl Nat64 {
|
||||
IpAddr::V4(ipv4_addr) => !self.table.is_address_within_pool(&ipv4_addr),
|
||||
IpAddr::V6(ipv6_addr) => !self.ipv6_nat_prefix.contains(&ipv6_addr),
|
||||
} {
|
||||
crate::debug!(
|
||||
log::debug!(
|
||||
"Packet destination {} is not within the NAT64 prefix or IPv4 pool",
|
||||
packet.get_destination(),
|
||||
);
|
||||
@ -148,12 +175,12 @@ impl Nat64 {
|
||||
.calculate_xlat_addr(&destination, &self.ipv6_nat_prefix)?;
|
||||
|
||||
// Log information about the packet
|
||||
crate::debug!(
|
||||
log::debug!(
|
||||
"Received packet traveling from {} to {}",
|
||||
source,
|
||||
destination
|
||||
);
|
||||
crate::debug!(
|
||||
log::debug!(
|
||||
"New path shall become: {} -> {}",
|
||||
new_source,
|
||||
new_destination
|
||||
|
@ -68,7 +68,6 @@ impl Nat64Table {
|
||||
}
|
||||
|
||||
/// Get or assign an IPv4 address for the given IPv6 address
|
||||
#[profiling::function]
|
||||
pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result<Ipv4Addr, TableError> {
|
||||
// Prune old reservations
|
||||
self.prune();
|
||||
@ -103,7 +102,6 @@ impl Nat64Table {
|
||||
}
|
||||
|
||||
/// Try to find an IPv6 address for the given IPv4 address
|
||||
#[profiling::function]
|
||||
pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Result<Ipv6Addr, TableError> {
|
||||
// Prune old reservations
|
||||
self.prune();
|
||||
@ -128,7 +126,6 @@ impl Nat64Table {
|
||||
}
|
||||
|
||||
/// Calculate the translated version of any address
|
||||
#[profiling::function]
|
||||
pub fn calculate_xlat_addr(
|
||||
&mut self,
|
||||
input: &IpAddr,
|
||||
@ -181,7 +178,6 @@ impl Nat64Table {
|
||||
|
||||
impl Nat64Table {
|
||||
/// Prune old reservations
|
||||
#[profiling::function]
|
||||
pub fn prune(&mut self) {
|
||||
let now = Instant::now();
|
||||
|
||||
|
@ -144,7 +144,7 @@ pub fn translate_icmp_4_to_6(
|
||||
|
||||
// if the original payload's next header is ICMP, we need to translated the inner payload's ICMP type
|
||||
if original_payload.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
|
||||
crate::debug!("Time Exceeded packet contains another ICMP packet.. Translating");
|
||||
log::debug!("Time Exceeded packet contains another ICMP packet.. Translating");
|
||||
if let Some((icmpv6_type, icmpv6_code)) = translate_type_and_code_4_to_6(
|
||||
IcmpType(original_payload_inner[0]),
|
||||
IcmpCode(original_payload_inner[1]),
|
||||
@ -157,7 +157,7 @@ pub fn translate_icmp_4_to_6(
|
||||
&original_payload_inner[4..]
|
||||
);
|
||||
original_payload_inner = inner_icmpv6.packet().to_vec();
|
||||
crate::debug!(
|
||||
log::debug!(
|
||||
"Translated inner ICMPv6 packet: {:?}",
|
||||
original_payload_inner
|
||||
);
|
||||
@ -194,7 +194,7 @@ pub fn translate_icmp_4_to_6(
|
||||
output.set_icmpv6_code(icmpv6_code);
|
||||
|
||||
// Set the payload
|
||||
crate::debug!("Setting ICMPv6 payload: {:?}", output_payload);
|
||||
log::debug!("Setting ICMPv6 payload: {:?}", output_payload);
|
||||
output.set_payload(&output_payload);
|
||||
|
||||
// Calculate the checksum
|
||||
@ -256,7 +256,7 @@ pub fn translate_icmp_6_to_4(
|
||||
|
||||
// if the original payload's next header is ICMPv6, we need to translated the inner payload's ICMPv6 type
|
||||
if original_payload.get_next_header() == IpNextHeaderProtocols::Icmpv6 {
|
||||
crate::debug!("Time Exceeded packet contains another ICMPv6 packet.. Translating");
|
||||
log::debug!("Time Exceeded packet contains another ICMPv6 packet.. Translating");
|
||||
if let Some((icmp_type, icmp_code)) = translate_type_and_code_6_to_4(
|
||||
Icmpv6Type(original_payload_inner[0]),
|
||||
Icmpv6Code(original_payload_inner[1]),
|
||||
@ -264,7 +264,7 @@ pub fn translate_icmp_6_to_4(
|
||||
let inner_icmp =
|
||||
icmp_packet!(icmp_type, icmp_code, &original_payload_inner[8..]);
|
||||
original_payload_inner = inner_icmp.packet().to_vec();
|
||||
crate::debug!("Translated inner ICMP packet: {:?}", original_payload_inner);
|
||||
log::debug!("Translated inner ICMP packet: {:?}", original_payload_inner);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user