Much cleaner packet handling system in progress
This commit is contained in:
parent
240348303d
commit
b2b99a8e11
@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use colored::Colorize;
|
||||
@ -32,12 +33,20 @@ pub struct AddressMappingRule {
|
||||
pub v6: Ipv6Addr,
|
||||
}
|
||||
|
||||
/// Used to generate the default reservation duration
|
||||
fn default_reservation_duration() -> Duration {
|
||||
Duration::from_secs(7200)
|
||||
}
|
||||
|
||||
/// Rules config
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct RulesConfig {
|
||||
/// Static mapping rules
|
||||
#[serde(rename = "MapStatic")]
|
||||
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
|
||||
|
59
src/main.rs
59
src/main.rs
@ -1,6 +1,7 @@
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use config::Config;
|
||||
use nat::Nat64;
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
@ -12,18 +13,27 @@ pub async fn main() {
|
||||
// Parse CLI args
|
||||
let args = cli::Args::parse();
|
||||
|
||||
// Set up logging
|
||||
// Set up logging
|
||||
let log_verbose = args.verbose;
|
||||
fern::Dispatch::new()
|
||||
.format(|out, message, record| {
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}: {}",
|
||||
match record.level() {
|
||||
log::Level::Error => "ERROR".red().bold().to_string(),
|
||||
log::Level::Warn => "WARN".yellow().bold().to_string(),
|
||||
log::Level::Info => "INFO".green().bold().to_string(),
|
||||
log::Level::Debug => "DEBUG".bright_blue().bold().to_string(),
|
||||
log::Level::Trace => "TRACE".bright_white().bold().to_string(),
|
||||
},
|
||||
format!(
|
||||
"{}{}",
|
||||
match record.level() {
|
||||
log::Level::Error => "ERROR".red().bold().to_string(),
|
||||
log::Level::Warn => "WARN ".yellow().bold().to_string(),
|
||||
log::Level::Info => "INFO ".green().bold().to_string(),
|
||||
log::Level::Debug => "DEBUG".bright_blue().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
|
||||
))
|
||||
})
|
||||
@ -41,20 +51,21 @@ pub async fn main() {
|
||||
// Parse the config file
|
||||
let config = Config::load(args.config_file).unwrap();
|
||||
|
||||
// // Create the NAT64 instance
|
||||
// let mut nat64 = Nat64::new(
|
||||
// config.interface.pool,
|
||||
// config.interface.prefix,
|
||||
// config
|
||||
// .rules
|
||||
// .static_map
|
||||
// .iter()
|
||||
// .map(|rule| (rule.v4, rule.v6))
|
||||
// .collect(),
|
||||
// )
|
||||
// .await
|
||||
// .unwrap();
|
||||
// Create the NAT64 instance
|
||||
let mut nat64 = Nat64::new(
|
||||
config.interface.prefix,
|
||||
config.interface.pool,
|
||||
config
|
||||
.rules
|
||||
.static_map
|
||||
.iter()
|
||||
.map(|rule| (rule.v6, rule.v4))
|
||||
.collect(),
|
||||
config.rules.reservation_duration,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// // Handle packets
|
||||
// nat64.run().await.unwrap();
|
||||
// Handle packets
|
||||
nat64.run().await.unwrap();
|
||||
}
|
||||
|
@ -15,11 +15,13 @@ pub enum InterfaceError {
|
||||
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> {
|
||||
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);
|
||||
@ -52,8 +54,13 @@ impl Nat64Interface {
|
||||
.add()
|
||||
.v6()
|
||||
.destination_prefix(v6_prefix.addr(), v6_prefix.prefix_len())
|
||||
.output_interface(interface_link.header.index)
|
||||
.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());
|
||||
|
||||
// Add every prefix in the v4 pool as a route
|
||||
@ -63,12 +70,26 @@ impl Nat64Interface {
|
||||
.add()
|
||||
.v4()
|
||||
.destination_prefix(prefix.addr(), prefix.prefix_len())
|
||||
.output_interface(interface_link.header.index)
|
||||
.execute()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|error| {
|
||||
log::error!("Failed to add route for {}: {}", prefix, error);
|
||||
error
|
||||
})?;
|
||||
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
|
||||
@ -81,6 +102,11 @@ impl Nat64Interface {
|
||||
self.interface.name()
|
||||
}
|
||||
|
||||
/// 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)
|
||||
|
@ -1,4 +1,92 @@
|
||||
pub mod xlat;
|
||||
pub mod packet;
|
||||
pub mod table;
|
||||
pub mod interface;
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,14 @@ use std::net::IpAddr;
|
||||
|
||||
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
|
||||
#[derive(Debug)]
|
||||
pub enum IpPacket<'a> {
|
||||
@ -15,11 +23,16 @@ pub enum IpPacket<'a> {
|
||||
|
||||
impl IpPacket<'_> {
|
||||
/// 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 {
|
||||
4 => Some(IpPacket::V4(Ipv4Packet::new(bytes)?)),
|
||||
6 => Some(IpPacket::V6(Ipv6Packet::new(bytes)?)),
|
||||
_ => None,
|
||||
4 => Ok(IpPacket::V4(
|
||||
Ipv4Packet::new(bytes).ok_or_else(|| PacketError::PacketTooSmall(bytes.len()))?,
|
||||
)),
|
||||
6 => Ok(IpPacket::V6(
|
||||
Ipv6Packet::new(bytes).ok_or_else(|| PacketError::PacketTooSmall(bytes.len()))?,
|
||||
)),
|
||||
n => Err(PacketError::UnknownVersion(n)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ use ipnet::Ipv4Net;
|
||||
|
||||
/// Possible errors thrown in the address reservation process
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
pub enum TableError {
|
||||
#[error("Address already reserved: {0}")]
|
||||
AddressAlreadyReserved(IpAddr),
|
||||
#[error("Address pool depleted")]
|
||||
@ -49,13 +49,13 @@ impl Nat64Table {
|
||||
&mut self,
|
||||
ipv6: Ipv6Addr,
|
||||
ipv4: Ipv4Addr,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<(), TableError> {
|
||||
// Check if either address is already reserved
|
||||
self.prune();
|
||||
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) {
|
||||
return Err(Error::AddressAlreadyReserved(ipv4.into()));
|
||||
return Err(TableError::AddressAlreadyReserved(ipv4.into()));
|
||||
}
|
||||
|
||||
// Add the reservation
|
||||
@ -65,7 +65,7 @@ impl Nat64Table {
|
||||
}
|
||||
|
||||
/// 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
|
||||
self.prune();
|
||||
|
||||
@ -94,7 +94,7 @@ impl Nat64Table {
|
||||
}
|
||||
|
||||
// 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user