1

Much cleaner packet handling system in progress

This commit is contained in:
Evan Pratten 2023-07-16 14:11:56 -04:00
parent 240348303d
commit b2b99a8e11
6 changed files with 189 additions and 42 deletions

View File

@ -1,6 +1,7 @@
use std::{ use std::{
net::{Ipv4Addr, Ipv6Addr}, net::{Ipv4Addr, Ipv6Addr},
path::Path, path::Path,
time::Duration,
}; };
use colored::Colorize; use colored::Colorize;
@ -32,12 +33,20 @@ pub struct AddressMappingRule {
pub v6: Ipv6Addr, pub v6: Ipv6Addr,
} }
/// Used to generate the default reservation duration
fn default_reservation_duration() -> Duration {
Duration::from_secs(7200)
}
/// Rules config /// Rules config
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct RulesConfig { pub struct RulesConfig {
/// Static mapping rules /// Static mapping rules
#[serde(rename = "MapStatic")] #[serde(rename = "MapStatic")]
pub static_map: Vec<AddressMappingRule>, pub static_map: Vec<AddressMappingRule>,
/// How long to hold a dynamic mapping for
#[serde(rename = "ReservationDuration", default="default_reservation_duration")]
pub reservation_duration: Duration,
} }
/// Representation of the `protomask.toml` config file /// Representation of the `protomask.toml` config file

View File

@ -1,6 +1,7 @@
use clap::Parser; use clap::Parser;
use colored::Colorize; use colored::Colorize;
use config::Config; use config::Config;
use nat::Nat64;
mod cli; mod cli;
mod config; mod config;
@ -13,10 +14,13 @@ pub async fn main() {
let args = cli::Args::parse(); let args = cli::Args::parse();
// Set up logging // Set up logging
let log_verbose = args.verbose;
fern::Dispatch::new() fern::Dispatch::new()
.format(|out, message, record| { .format(move |out, message, record| {
out.finish(format_args!( out.finish(format_args!(
"{}: {}", "{}: {}",
format!(
"{}{}",
match record.level() { match record.level() {
log::Level::Error => "ERROR".red().bold().to_string(), log::Level::Error => "ERROR".red().bold().to_string(),
log::Level::Warn => "WARN ".yellow().bold().to_string(), log::Level::Warn => "WARN ".yellow().bold().to_string(),
@ -24,6 +28,12 @@ pub async fn main() {
log::Level::Debug => "DEBUG".bright_blue().bold().to_string(), log::Level::Debug => "DEBUG".bright_blue().bold().to_string(),
log::Level::Trace => "TRACE".bright_white().bold().to_string(), log::Level::Trace => "TRACE".bright_white().bold().to_string(),
}, },
match log_verbose {
true => format!(" [{:13}]", record.target().split("::").nth(0).unwrap()),
false => String::new(),
}
.bright_black()
),
message message
)) ))
}) })
@ -41,20 +51,21 @@ pub async fn main() {
// Parse the config file // Parse the config file
let config = Config::load(args.config_file).unwrap(); let config = Config::load(args.config_file).unwrap();
// // Create the NAT64 instance // Create the NAT64 instance
// let mut nat64 = Nat64::new( let mut nat64 = Nat64::new(
// config.interface.pool, config.interface.prefix,
// config.interface.prefix, config.interface.pool,
// config config
// .rules .rules
// .static_map .static_map
// .iter() .iter()
// .map(|rule| (rule.v4, rule.v6)) .map(|rule| (rule.v6, rule.v4))
// .collect(), .collect(),
// ) config.rules.reservation_duration,
// .await )
// .unwrap(); .await
.unwrap();
// // Handle packets // Handle packets
// nat64.run().await.unwrap(); nat64.run().await.unwrap();
} }

View File

@ -15,11 +15,13 @@ pub enum InterfaceError {
pub struct Nat64Interface { pub struct Nat64Interface {
/// Underlying TUN interface /// Underlying TUN interface
interface: Iface, interface: Iface,
/// Interface MTU
mtu: usize,
} }
impl Nat64Interface { impl Nat64Interface {
/// Create a new NAT64 interface /// Create a new NAT64 interface
pub async fn new(v6_prefix: Ipv6Net, v4_pool: Vec<Ipv4Net>) -> Result<Self, InterfaceError> { pub async fn new(v6_prefix: Ipv6Net, v4_pool: &Vec<Ipv4Net>) -> Result<Self, InterfaceError> {
// Bring up an rtnetlink connection // Bring up an rtnetlink connection
let (rt_connection, rt_handle, _) = rtnetlink::new_connection()?; let (rt_connection, rt_handle, _) = rtnetlink::new_connection()?;
tokio::spawn(rt_connection); tokio::spawn(rt_connection);
@ -52,8 +54,13 @@ impl Nat64Interface {
.add() .add()
.v6() .v6()
.destination_prefix(v6_prefix.addr(), v6_prefix.prefix_len()) .destination_prefix(v6_prefix.addr(), v6_prefix.prefix_len())
.output_interface(interface_link.header.index)
.execute() .execute()
.await?; .await
.map_err(|error| {
log::error!("Failed to add route for {}: {}", v6_prefix, error);
error
})?;
log::info!("Added route: {} via {}", v6_prefix, interface.name()); log::info!("Added route: {} via {}", v6_prefix, interface.name());
// Add every prefix in the v4 pool as a route // Add every prefix in the v4 pool as a route
@ -63,12 +70,26 @@ impl Nat64Interface {
.add() .add()
.v4() .v4()
.destination_prefix(prefix.addr(), prefix.prefix_len()) .destination_prefix(prefix.addr(), prefix.prefix_len())
.output_interface(interface_link.header.index)
.execute() .execute()
.await?; .await
.map_err(|error| {
log::error!("Failed to add route for {}: {}", prefix, error);
error
})?;
log::info!("Added route: {} via {}", prefix, interface.name()); log::info!("Added route: {} via {}", prefix, interface.name());
} }
Ok(Self { interface }) // 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 })
} }
/// Get the interface mode /// Get the interface mode
@ -81,6 +102,11 @@ impl Nat64Interface {
self.interface.name() self.interface.name()
} }
/// Get the interface MTU
pub fn mtu(&self) -> usize {
self.mtu
}
/// Receive a packet from the interface /// Receive a packet from the interface
pub fn recv(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> { pub fn recv(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
self.interface.recv(buf) self.interface.recv(buf)

View File

@ -1,4 +1,92 @@
pub mod xlat; use std::{
pub mod packet; net::{Ipv4Addr, Ipv6Addr},
pub mod table; time::Duration,
pub mod interface; };
use ipnet::{Ipv4Net, Ipv6Net};
use self::{interface::Nat64Interface, packet::IpPacket, table::Nat64Table};
mod interface;
mod packet;
mod table;
mod xlat;
#[derive(Debug, thiserror::Error)]
pub enum Nat64Error {
#[error(transparent)]
TableError(#[from] table::TableError),
#[error(transparent)]
InterfaceError(#[from] interface::InterfaceError),
#[error(transparent)]
IoError(#[from] std::io::Error),
}
pub struct Nat64 {
table: Nat64Table,
interface: Nat64Interface,
}
impl Nat64 {
/// Construct a new NAT64 instance
pub async fn new(
v6_prefix: Ipv6Net,
v4_pool: Vec<Ipv4Net>,
static_reservations: Vec<(Ipv6Addr, Ipv4Addr)>,
reservation_duration: Duration,
) -> Result<Self, Nat64Error> {
// Bring up the interface
let interface = Nat64Interface::new(v6_prefix, &v4_pool).await?;
// Build the table and insert any static reservations
let mut table = Nat64Table::new(v4_pool, reservation_duration);
for (v6, v4) in static_reservations {
table.add_infinite_reservation(v6, v4)?;
}
Ok(Self { table, interface })
}
/// 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()];
// 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
match IpPacket::new(&buffer[..packet_len]) {
// Try to process the packet
Ok(inbound_packet) => match self.process_packet(inbound_packet).await? {
// If data is returned, send it back out the interface
Some(outbound_packet) => {
let packet_bytes = outbound_packet.to_bytes();
self.interface.send(&packet_bytes)?;
}
// Otherwise, we can assume that the packet was dealt with, and can move on
None => {}
},
Err(error) => {
log::error!("Failed to parse packet: {}", error);
}
}
}
Err(error) => {
log::error!("Failed to read packet: {}", error);
}
}
}
}
}
impl Nat64 {
async fn process_packet(
&mut self,
packet: IpPacket<'_>,
) -> Result<Option<IpPacket>, Nat64Error> {
Ok(None)
}
}

View File

@ -4,6 +4,14 @@ use std::net::IpAddr;
use pnet_packet::{ip::IpNextHeaderProtocol, ipv4::Ipv4Packet, ipv6::Ipv6Packet, Packet}; use pnet_packet::{ip::IpNextHeaderProtocol, ipv4::Ipv4Packet, ipv6::Ipv6Packet, Packet};
#[derive(Debug, thiserror::Error)]
pub enum PacketError {
#[error("Packed too small (len: {0})")]
PacketTooSmall(usize),
#[error("Unknown Internet Protocol version: {0}")]
UnknownVersion(u8),
}
/// A protocol-agnostic packet type /// A protocol-agnostic packet type
#[derive(Debug)] #[derive(Debug)]
pub enum IpPacket<'a> { pub enum IpPacket<'a> {
@ -15,11 +23,16 @@ pub enum IpPacket<'a> {
impl IpPacket<'_> { impl IpPacket<'_> {
/// Creates a new packet from a byte slice /// Creates a new packet from a byte slice
pub fn new<'a>(bytes: &'a [u8]) -> Option<IpPacket<'a>> { pub fn new<'a>(bytes: &'a [u8]) -> Result<IpPacket<'a>, PacketError> {
// Parse the packet. If there is an error, cast None to the error type
match bytes[0] >> 4 { match bytes[0] >> 4 {
4 => Some(IpPacket::V4(Ipv4Packet::new(bytes)?)), 4 => Ok(IpPacket::V4(
6 => Some(IpPacket::V6(Ipv6Packet::new(bytes)?)), Ipv4Packet::new(bytes).ok_or_else(|| PacketError::PacketTooSmall(bytes.len()))?,
_ => None, )),
6 => Ok(IpPacket::V6(
Ipv6Packet::new(bytes).ok_or_else(|| PacketError::PacketTooSmall(bytes.len()))?,
)),
n => Err(PacketError::UnknownVersion(n)),
} }
} }

View File

@ -9,7 +9,7 @@ use ipnet::Ipv4Net;
/// Possible errors thrown in the address reservation process /// Possible errors thrown in the address reservation process
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum TableError {
#[error("Address already reserved: {0}")] #[error("Address already reserved: {0}")]
AddressAlreadyReserved(IpAddr), AddressAlreadyReserved(IpAddr),
#[error("Address pool depleted")] #[error("Address pool depleted")]
@ -49,13 +49,13 @@ impl Nat64Table {
&mut self, &mut self,
ipv6: Ipv6Addr, ipv6: Ipv6Addr,
ipv4: Ipv4Addr, ipv4: Ipv4Addr,
) -> Result<(), Error> { ) -> Result<(), TableError> {
// Check if either address is already reserved // Check if either address is already reserved
self.prune(); self.prune();
if self.reservations.contains_left(&ipv6) { if self.reservations.contains_left(&ipv6) {
return Err(Error::AddressAlreadyReserved(ipv6.into())); return Err(TableError::AddressAlreadyReserved(ipv6.into()));
} else if self.reservations.contains_right(&ipv4) { } else if self.reservations.contains_right(&ipv4) {
return Err(Error::AddressAlreadyReserved(ipv4.into())); return Err(TableError::AddressAlreadyReserved(ipv4.into()));
} }
// Add the reservation // Add the reservation
@ -65,7 +65,7 @@ impl Nat64Table {
} }
/// Get or assign an IPv4 address for the given IPv6 address /// Get or assign an IPv4 address for the given IPv6 address
pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result<Ipv4Addr, Error> { pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result<Ipv4Addr, TableError> {
// Prune old reservations // Prune old reservations
self.prune(); self.prune();
@ -94,7 +94,7 @@ impl Nat64Table {
} }
// If we get here, we failed to find an available address // If we get here, we failed to find an available address
Err(Error::AddressPoolDepleted) Err(TableError::AddressPoolDepleted)
} }
/// Try to find an IPv6 address for the given IPv4 address /// Try to find an IPv6 address for the given IPv4 address