1

working on rewrite

This commit is contained in:
Evan Pratten 2023-07-16 12:45:43 -04:00
parent 397943bbc4
commit 240348303d
12 changed files with 772 additions and 473 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"rtnetlink"
]
}

View File

@ -26,7 +26,8 @@ colored = "2.0.4"
tun-tap = "0.1.3" tun-tap = "0.1.3"
bimap = "0.6.3" bimap = "0.6.3"
pnet_packet = "0.33.0" pnet_packet = "0.33.0"
# etherparse = "0.13.0" rtnetlink = "0.13.0"
futures = "0.3.28"
[[bin]] [[bin]]
name = "protomask" name = "protomask"

View File

@ -13,6 +13,11 @@ ip netns exec protomask ip link add test2 type dummy
ip netns exec protomask ip link set test2 up ip netns exec protomask ip link set test2 up
ip netns exec protomask ip addr add 172.16.10.2 dev test2 ip netns exec protomask ip addr add 172.16.10.2 dev test2
# Turn off the firewall for the test interfaces
ip netns exec protomask firewall-cmd --zone=trusted --add-interface=nat64i0
ip netns exec protomask firewall-cmd --zone=trusted --add-interface=test1
ip netns exec protomask firewall-cmd --zone=trusted --add-interface=test2
# Run protomask # Run protomask
ip netns exec protomask ./target/debug/protomask protomask.toml -v ip netns exec protomask ./target/debug/protomask protomask.toml -v

View File

@ -1,7 +1,6 @@
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;
@ -42,22 +41,20 @@ 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.address_v4, // config.interface.pool,
config.interface.address_v6, // config.interface.prefix,
config.interface.pool, // config
config.interface.prefix, // .rules
config // .static_map
.rules // .iter()
.static_map // .map(|rule| (rule.v4, rule.v6))
.iter() // .collect(),
.map(|rule| (rule.v4, rule.v6)) // )
.collect(), // .await
) // .unwrap();
.await
.unwrap();
// Handle packets // // Handle packets
nat64.run().await.unwrap(); // nat64.run().await.unwrap();
} }

93
src/nat/interface.rs Normal file
View File

@ -0,0 +1,93 @@
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),
}
/// Wrapper around a TUN interface that automatically configures itself
#[derive(Debug)]
pub struct Nat64Interface {
/// Underlying TUN interface
interface: Iface,
}
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)?;
// 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());
// Add the v6 prefix as a route
rt_handle
.route()
.add()
.v6()
.destination_prefix(v6_prefix.addr(), v6_prefix.prefix_len())
.execute()
.await?;
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())
.execute()
.await?;
log::info!("Added route: {} via {}", prefix, interface.name());
}
Ok(Self { interface })
}
/// Get the interface mode
pub fn mode(&self) -> Mode {
self.interface.mode()
}
/// Get the interface name
pub fn name(&self) -> &str {
self.interface.name()
}
/// 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)
}
}

View File

@ -1,387 +1,4 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; pub mod xlat;
pub mod packet;
use bimap::BiMap; pub mod table;
use colored::Colorize; pub mod interface;
use ipnet::{Ipv4Net, Ipv6Net};
use pnet_packet::{
icmp::IcmpPacket,
icmpv6::Icmpv6Packet,
ip::IpNextHeaderProtocols,
ipv4::{self, Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
Packet,
};
use tokio::process::Command;
use tun_tap::{Iface, Mode};
use crate::nat::packet::{xlat_v6_to_v4, IpPacket};
use self::packet::xlat_v4_to_v6;
mod icmp;
mod packet;
/// A cleaner way to execute a CLI command
macro_rules! command {
($cmd:expr, $($arg:expr),*) => {{
Command::new($cmd)
$(.arg($arg))*
.status()
}}
}
/// Converts bytes to a hex string for debugging
fn bytes_to_hex_str(bytes: &[u8]) -> String {
bytes
.iter()
.map(|val| format!("{:02x}", val))
.collect::<Vec<String>>()
.join(" ")
}
pub struct Nat64 {
/// Handle for the Tun interface
interface: Iface,
/// Instance IPv4 address
instance_v4: Ipv4Addr,
/// Instance IPv6 address
instance_v6: Ipv6Addr,
/// IPv4 pool
ipv4_pool: Vec<Ipv4Net>,
/// IPv6 prefix
ipv6_prefix: Ipv6Net,
/// A mapping of currently allocated pool reservations
pool_reservations: BiMap<Ipv4Addr, Ipv6Addr>,
}
impl Nat64 {
/// Bring up a new NAT64 interface
///
/// **Arguments:**
/// - `nat_v4`: An IPv4 address to assign to this NAT instance for ICMP and other purposes
/// - `nat_v6`: An IPv6 address to assign to this NAT instance for ICMP and other purposes
/// - `ipv4_pool`: A list of IPv4 prefixes to communicate from
/// - `ipv6_prefix`: The IPv6 prefix to listen on (should generally be `64:ff9b::/96`)
pub async fn new(
nat_v4: Ipv4Addr,
nat_v6: Ipv6Addr,
ipv4_pool: Vec<Ipv4Net>,
ipv6_prefix: Ipv6Net,
static_mappings: Vec<(Ipv4Addr, Ipv6Addr)>,
) -> Result<Self, std::io::Error> {
// Bring up tun interface
let interface = Iface::without_packet_info("nat64i%d", Mode::Tun)?;
// Configure the interface
let interface_name = interface.name();
log::info!("Configuring interface {}", interface_name);
#[cfg_attr(rustfmt, rustfmt_skip)]
{
// Add the nat addresses
log::debug!("Assigning {} to {}", nat_v4, interface_name);
command!("ip", "address", "add", format!("{}/32", nat_v4), "dev", interface_name).await?;
log::debug!("Assigning {} to {}", nat_v6, interface_name);
command!("ip", "address", "add", format!("{}/128", nat_v6), "dev", interface_name ).await?;
// Bring up the interface
log::debug!("Bringing up {}", interface_name);
command!("ip", "link", "set", "dev", interface_name, "up").await?;
// Add route for IPv6 prefix
log::debug!("Adding route {} via {}", ipv6_prefix, interface_name);
command!("ip", "route", "add", ipv6_prefix.to_string(), "dev", interface_name).await?;
// Configure iptables
log::debug!("Configuring iptables");
command!("iptables", "-A", "FORWARD", "-i", interface_name, "-j", "ACCEPT").await?;
command!("iptables", "-A", "FORWARD", "-o", interface_name, "-j", "ACCEPT").await?;
command!("ip6tables", "-A", "FORWARD", "-i", interface_name, "-j", "ACCEPT").await?;
command!("ip6tables", "-A", "FORWARD", "-o", interface_name, "-j", "ACCEPT").await?;
}
// Add every IPv4 prefix to the routing table
for prefix in ipv4_pool.iter() {
log::debug!("Adding route {} via {}", prefix, interface_name);
command!(
"ip",
"route",
"add",
prefix.to_string(),
"dev",
interface_name
)
.await?;
}
// Build a reservation list
let mut pool_reservations = BiMap::new();
for (v4, v6) in static_mappings {
pool_reservations.insert(v4, v6);
}
pool_reservations.insert(nat_v4, nat_v6);
Ok(Self {
interface,
instance_v4: nat_v4,
instance_v6: nat_v6,
ipv4_pool,
ipv6_prefix,
pool_reservations,
})
}
/// Block and run the NAT instance. This will handle all packets
pub async fn run(&mut self) -> Result<(), std::io::Error> {
// Read the interface MTU
let mtu: u16 =
std::fs::read_to_string(format!("/sys/class/net/{}/mtu", self.interface.name()))
.expect("Failed to read interface MTU")
.strip_suffix("\n")
.unwrap()
.parse()
.unwrap();
// Allocate a buffer for incoming packets
let mut buffer = vec![0; mtu as usize];
log::info!("Translating packets");
loop {
// Read incoming packet
let len = self.interface.recv(&mut buffer)?;
// Process the packet
let response = self.process(&buffer[..len]).await?;
// If there is a response, send it
if let Some(response) = response {
self.interface.send(&response)?;
}
}
}
/// Internal function that checks if a destination address is allowed to be processed
fn is_dest_allowed(&self, dest: IpAddr) -> bool {
return dest == self.instance_v4
|| dest == self.instance_v6
|| match dest {
IpAddr::V4(addr) => self.ipv4_pool.iter().any(|prefix| prefix.contains(&addr)),
IpAddr::V6(addr) => self.ipv6_prefix.contains(&addr),
};
}
/// Calculate a unique IPv4 address inside the pool for a given IPv6 address
fn calculate_ipv4(&self, _addr: Ipv6Addr) -> Option<Ipv4Addr> {
// Search the list of possible IPv4 addresses
for prefix in self.ipv4_pool.iter() {
for addr in prefix.hosts() {
// If this address is available, use it
if !self.pool_reservations.contains_left(&addr) {
return Some(addr);
}
}
}
None
}
/// Embeds an IPv4 address into an IPv6 address
fn embed_v4_into_v6(&self, addr: Ipv4Addr) -> Ipv6Addr {
let mut octets = [0u8; 16];
octets[..12].copy_from_slice(&self.ipv6_prefix.network().octets()[..12]);
octets[12..].copy_from_slice(&addr.octets());
Ipv6Addr::from(octets)
}
/// Extracts an IPv4 address from an IPv6 address
fn extract_v4_from_v6(&self, addr: Ipv6Addr) -> Ipv4Addr {
let mut octets = [0u8; 4];
octets.copy_from_slice(&addr.octets()[12..]);
Ipv4Addr::from(octets)
}
/// Gets or creates a reservation for a given address
fn get_or_create_reservation(&mut self, addr: IpAddr) -> Option<IpAddr> {
match addr {
IpAddr::V4(addr) => {
if self.pool_reservations.contains_left(&addr) {
return Some(IpAddr::V6(
*self.pool_reservations.get_by_left(&addr).unwrap(),
));
} else {
return None;
}
}
IpAddr::V6(addr) => {
// If the address is already reserved, return it
if self.pool_reservations.contains_right(&addr) {
return Some(IpAddr::V4(
*self.pool_reservations.get_by_right(&addr).unwrap(),
));
}
// Otherwise, calculate a new address
let new_addr = self.calculate_ipv4(addr)?;
self.pool_reservations.insert(new_addr, addr);
return Some(IpAddr::V4(new_addr));
}
}
}
/// Internal function to process an incoming packet.
/// If `Some` is returned, the result is sent back out the interface
async fn process(&mut self, packet: &[u8]) -> Result<Option<Vec<u8>>, std::io::Error> {
// Parse the packet
let input_packet = IpPacket::new(&packet);
if let None = input_packet {
log::warn!(
"{}",
format!(
"Malformed packet received: version: {}, len: {}",
packet[0] >> 4,
packet.len()
)
.yellow()
);
return Ok(None);
}
let input_packet = input_packet.unwrap();
// Log some info about the packet
#[cfg_attr(rustfmt, rustfmt_skip)]
{
log::debug!("Processing packet with length: {}", input_packet.len().to_string().bright_cyan());
log::debug!("> IP Header: {}", bytes_to_hex_str(input_packet.get_header()).bright_cyan());
log::debug!("> Source: {}", input_packet.get_source().to_string().bright_cyan());
log::debug!("> Destination: {}", input_packet.get_destination().to_string().bright_cyan());
log::debug!("> Next Header: {}", input_packet.get_next_header().to_string().bright_cyan());
}
// Ignore packets that aren't destined for the NAT instance
if !self.is_dest_allowed(input_packet.get_destination()) {
log::debug!("{}", "Ignoring packet. Invalid destination".yellow());
return Ok(None);
}
// Drop packets with 0 TTL
if input_packet.get_ttl() == 0 {
log::debug!("{}", "Ignoring packet. TTL is 0".yellow());
return Ok(None);
}
// Handle packet translation
let output_packet = match input_packet {
IpPacket::V4(packet) => {
let new_source = self.embed_v4_into_v6(packet.get_source());
let new_dest =
self.get_or_create_reservation(std::net::IpAddr::V4(packet.get_destination()));
if let Some(IpAddr::V6(new_dest)) = new_dest {
// Log the new addresses
#[cfg_attr(rustfmt, rustfmt_skip)]
{
log::debug!("> Mapped IPv6 Source: {}", new_source.to_string().bright_cyan());
log::debug!("> Mapped IPv6 Destination: {}", new_dest.to_string().bright_cyan());
}
// Handle inner packet conversion for protocols that don't support both v4 and v6
if let Some(packet) =
Ipv4Packet::owned(match packet.get_next_level_protocol() {
// ICMP must be translated to ICMPv6
IpNextHeaderProtocols::Icmp => {
if let Some(new_payload) = icmp::icmp_to_icmpv6(
&IcmpPacket::new(packet.payload()).unwrap(),
&new_source,
&new_dest,
) {
// Mutate the input packet
let mut packet =
MutableIpv4Packet::owned(packet.packet().to_vec()).unwrap();
packet.set_next_level_protocol(IpNextHeaderProtocols::Icmpv6);
packet.set_payload(&new_payload.packet().to_vec());
packet.set_checksum(ipv4::checksum(&packet.to_immutable()));
packet.packet().to_vec()
} else {
return Ok(None);
}
}
// By default, packets can be directly fed to the next function
_ => packet.packet().to_vec(),
})
{
// Translate the packet
let translated = xlat_v4_to_v6(&packet, new_source, new_dest, true);
// Log the translated packet header
log::debug!(
"> Translated Header: {}",
bytes_to_hex_str(&translated[0..40]).bright_cyan()
);
// Return the translated packet
translated
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
IpPacket::V6(packet) => {
let new_source =
self.get_or_create_reservation(std::net::IpAddr::V6(packet.get_source()));
let new_dest = self.extract_v4_from_v6(packet.get_destination());
if let Some(IpAddr::V4(new_source)) = new_source {
// Log the new addresses
#[cfg_attr(rustfmt, rustfmt_skip)]
{
log::debug!("> Mapped IPv4 Source: {}", new_source.to_string().bright_cyan());
log::debug!("> Mapped IPv4 Destination: {}", new_dest.to_string().bright_cyan());
}
// Handle inner packet conversion for protocols that don't support both v4 and v6
if let Some(packet) = Ipv6Packet::owned(match packet.get_next_header() {
// ICMPv6 must be translated to ICMP
IpNextHeaderProtocols::Icmpv6 => {
if let Some(new_payload) =
icmp::icmpv6_to_icmp(&Icmpv6Packet::new(packet.payload()).unwrap())
{
// Mutate the input packet
let mut packet =
MutableIpv6Packet::owned(packet.packet().to_vec()).unwrap();
packet.set_next_header(IpNextHeaderProtocols::Icmp);
packet.set_payload(&new_payload.packet().to_vec());
packet.packet().to_vec()
} else {
return Ok(None);
}
}
// By default, packets can be directly fed to the next function
_ => packet.packet().to_vec(),
}) {
// Translate the packet
let translated = xlat_v6_to_v4(&packet, new_source, new_dest, true);
// Log the translated packet header
log::debug!(
"> Translated Header: {}",
bytes_to_hex_str(&translated[0..20]).bright_cyan()
);
// Return the translated packet
translated
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
};
// Build the response
log::debug!("{}", "Sending translated packet".bright_green());
return Ok(Some(output_packet));
}
}

376
src/nat/mod.rs.old Normal file
View File

@ -0,0 +1,376 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use bimap::BiMap;
use colored::Colorize;
use ipnet::{Ipv4Net, Ipv6Net};
use pnet_packet::{
icmp::IcmpPacket,
icmpv6::Icmpv6Packet,
ip::IpNextHeaderProtocols,
ipv4::{self, Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6Packet, MutableIpv6Packet},
Packet,
};
use tokio::process::Command;
use tun_tap::{Iface, Mode};
use crate::nat::packet::IpPacket;
mod xlat;
mod packet;
mod table;
/// A cleaner way to execute a CLI command
macro_rules! command {
($cmd:expr, $($arg:expr),*) => {{
Command::new($cmd)
$(.arg($arg))*
.status()
}}
}
/// Converts bytes to a hex string for debugging
fn bytes_to_hex_str(bytes: &[u8]) -> String {
bytes
.iter()
.map(|val| format!("{:02x}", val))
.collect::<Vec<String>>()
.join(" ")
}
pub struct Nat64 {
/// Handle for the Tun interface
interface: Iface,
/// IPv4 pool
ipv4_pool: Vec<Ipv4Net>,
/// IPv6 prefix
ipv6_prefix: Ipv6Net,
/// A mapping of currently allocated pool reservations
pool_reservations: BiMap<Ipv4Addr, Ipv6Addr>,
}
impl Nat64 {
/// Bring up a new NAT64 interface
///
/// **Arguments:**
/// - `ipv4_pool`: A list of IPv4 prefixes to communicate from
/// - `ipv6_prefix`: The IPv6 prefix to listen on (should generally be `64:ff9b::/96`)
pub async fn new(
ipv4_pool: Vec<Ipv4Net>,
ipv6_prefix: Ipv6Net,
static_mappings: Vec<(Ipv4Addr, Ipv6Addr)>,
) -> Result<Self, std::io::Error> {
// Bring up tun interface
let interface = Iface::without_packet_info("nat64i%d", Mode::Tun)?;
// Configure the interface
let interface_name = interface.name();
log::info!("Configuring interface {}", interface_name);
#[cfg_attr(rustfmt, rustfmt_skip)]
{
// Add the nat addresses
log::debug!("Assigning {} to {}", nat_v4, interface_name);
command!("ip", "address", "add", format!("{}/32", nat_v4), "dev", interface_name).await?;
log::debug!("Assigning {} to {}", nat_v6, interface_name);
command!("ip", "address", "add", format!("{}/128", nat_v6), "dev", interface_name ).await?;
// Bring up the interface
log::debug!("Bringing up {}", interface_name);
command!("ip", "link", "set", "dev", interface_name, "up").await?;
// Add route for IPv6 prefix
log::debug!("Adding route {} via {}", ipv6_prefix, interface_name);
command!("ip", "route", "add", ipv6_prefix.to_string(), "dev", interface_name).await?;
// Configure iptables
log::debug!("Configuring iptables");
command!("iptables", "-A", "FORWARD", "-i", interface_name, "-j", "ACCEPT").await?;
command!("iptables", "-A", "FORWARD", "-o", interface_name, "-j", "ACCEPT").await?;
command!("ip6tables", "-A", "FORWARD", "-i", interface_name, "-j", "ACCEPT").await?;
command!("ip6tables", "-A", "FORWARD", "-o", interface_name, "-j", "ACCEPT").await?;
}
// Add every IPv4 prefix to the routing table
for prefix in ipv4_pool.iter() {
log::debug!("Adding route {} via {}", prefix, interface_name);
command!(
"ip",
"route",
"add",
prefix.to_string(),
"dev",
interface_name
)
.await?;
}
// Build a reservation list
let mut pool_reservations = BiMap::new();
for (v4, v6) in static_mappings {
pool_reservations.insert(v4, v6);
}
pool_reservations.insert(nat_v4, nat_v6);
Ok(Self {
interface,
ipv4_pool,
ipv6_prefix,
pool_reservations,
})
}
/// Block and run the NAT instance. This will handle all packets
pub async fn run(&mut self) -> Result<(), std::io::Error> {
// Read the interface MTU
let mtu: u16 =
std::fs::read_to_string(format!("/sys/class/net/{}/mtu", self.interface.name()))
.expect("Failed to read interface MTU")
.strip_suffix("\n")
.unwrap()
.parse()
.unwrap();
// Allocate a buffer for incoming packets
let mut buffer = vec![0; mtu as usize];
log::info!("Translating packets");
loop {
// Read incoming packet
let len = self.interface.recv(&mut buffer)?;
// Process the packet
let response = self.process(&buffer[..len]).await?;
// If there is a response, send it
if let Some(response) = response {
self.interface.send(&response)?;
}
}
}
/// Internal function that checks if a destination address is allowed to be processed
fn is_dest_allowed(&self, dest: IpAddr) -> bool {
return dest == self.instance_v4
|| dest == self.instance_v6
|| match dest {
IpAddr::V4(addr) => self.ipv4_pool.iter().any(|prefix| prefix.contains(&addr)),
IpAddr::V6(addr) => self.ipv6_prefix.contains(&addr),
};
}
/// Calculate a unique IPv4 address inside the pool for a given IPv6 address
fn calculate_ipv4(&self, _addr: Ipv6Addr) -> Option<Ipv4Addr> {
// Search the list of possible IPv4 addresses
for prefix in self.ipv4_pool.iter() {
for addr in prefix.hosts() {
// If this address is available, use it
if !self.pool_reservations.contains_left(&addr) {
return Some(addr);
}
}
}
None
}
/// Embeds an IPv4 address into an IPv6 address
fn embed_v4_into_v6(&self, addr: Ipv4Addr) -> Ipv6Addr {
let mut octets = [0u8; 16];
octets[..12].copy_from_slice(&self.ipv6_prefix.network().octets()[..12]);
octets[12..].copy_from_slice(&addr.octets());
Ipv6Addr::from(octets)
}
/// Extracts an IPv4 address from an IPv6 address
fn extract_v4_from_v6(&self, addr: Ipv6Addr) -> Ipv4Addr {
let mut octets = [0u8; 4];
octets.copy_from_slice(&addr.octets()[12..]);
Ipv4Addr::from(octets)
}
/// Gets or creates a reservation for a given address
fn get_or_create_reservation(&mut self, addr: IpAddr) -> Option<IpAddr> {
match addr {
IpAddr::V4(addr) => {
if self.pool_reservations.contains_left(&addr) {
return Some(IpAddr::V6(
*self.pool_reservations.get_by_left(&addr).unwrap(),
));
} else {
return None;
}
}
IpAddr::V6(addr) => {
// If the address is already reserved, return it
if self.pool_reservations.contains_right(&addr) {
return Some(IpAddr::V4(
*self.pool_reservations.get_by_right(&addr).unwrap(),
));
}
// Otherwise, calculate a new address
let new_addr = self.calculate_ipv4(addr)?;
self.pool_reservations.insert(new_addr, addr);
return Some(IpAddr::V4(new_addr));
}
}
}
/// Internal function to process an incoming packet.
/// If `Some` is returned, the result is sent back out the interface
async fn process(&mut self, packet: &[u8]) -> Result<Option<Vec<u8>>, std::io::Error> {
// Parse the packet
let input_packet = IpPacket::new(&packet);
if let None = input_packet {
log::warn!(
"{}",
format!(
"Malformed packet received: version: {}, len: {}",
packet[0] >> 4,
packet.len()
)
.yellow()
);
return Ok(None);
}
let input_packet = input_packet.unwrap();
// Log some info about the packet
#[cfg_attr(rustfmt, rustfmt_skip)]
{
log::debug!("Processing packet with length: {}", input_packet.len().to_string().bright_cyan());
log::debug!("> IP Header: {}", bytes_to_hex_str(input_packet.get_header()).bright_cyan());
log::debug!("> Source: {}", input_packet.get_source().to_string().bright_cyan());
log::debug!("> Destination: {}", input_packet.get_destination().to_string().bright_cyan());
log::debug!("> Next Header: {}", input_packet.get_next_header().to_string().bright_cyan());
}
// Ignore packets that aren't destined for the NAT instance
if !self.is_dest_allowed(input_packet.get_destination()) {
log::debug!("{}", "Ignoring packet. Invalid destination".yellow());
return Ok(None);
}
// Drop packets with 0 TTL
if input_packet.get_ttl() == 0 {
log::debug!("{}", "Ignoring packet. TTL is 0".yellow());
return Ok(None);
}
// Handle packet translation
let output_packet = match input_packet {
IpPacket::V4(packet) => {
let new_source = self.embed_v4_into_v6(packet.get_source());
let new_dest =
self.get_or_create_reservation(std::net::IpAddr::V4(packet.get_destination()));
if let Some(IpAddr::V6(new_dest)) = new_dest {
// Log the new addresses
#[cfg_attr(rustfmt, rustfmt_skip)]
{
log::debug!("> Mapped IPv6 Source: {}", new_source.to_string().bright_cyan());
log::debug!("> Mapped IPv6 Destination: {}", new_dest.to_string().bright_cyan());
}
// Handle inner packet conversion for protocols that don't support both v4 and v6
if let Some(packet) =
Ipv4Packet::owned(match packet.get_next_level_protocol() {
// ICMP must be translated to ICMPv6
IpNextHeaderProtocols::Icmp => {
if let Some(new_payload) = xlat::icmp_to_icmpv6(
&IcmpPacket::new(packet.payload()).unwrap(),
&new_source,
&new_dest,
) {
// Mutate the input packet
let mut packet =
MutableIpv4Packet::owned(packet.packet().to_vec()).unwrap();
packet.set_next_level_protocol(IpNextHeaderProtocols::Icmpv6);
packet.set_payload(&new_payload.packet().to_vec());
packet.set_checksum(ipv4::checksum(&packet.to_immutable()));
packet.packet().to_vec()
} else {
return Ok(None);
}
}
// By default, packets can be directly fed to the next function
_ => packet.packet().to_vec(),
})
{
// Translate the packet
let translated = xlat::ipv4_to_ipv6(&packet, new_source, new_dest, true);
// Log the translated packet header
log::debug!(
"> Translated Header: {}",
bytes_to_hex_str(&translated[0..40]).bright_cyan()
);
// Return the translated packet
translated
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
IpPacket::V6(packet) => {
let new_source =
self.get_or_create_reservation(std::net::IpAddr::V6(packet.get_source()));
let new_dest = self.extract_v4_from_v6(packet.get_destination());
if let Some(IpAddr::V4(new_source)) = new_source {
// Log the new addresses
#[cfg_attr(rustfmt, rustfmt_skip)]
{
log::debug!("> Mapped IPv4 Source: {}", new_source.to_string().bright_cyan());
log::debug!("> Mapped IPv4 Destination: {}", new_dest.to_string().bright_cyan());
}
// Handle inner packet conversion for protocols that don't support both v4 and v6
if let Some(packet) = Ipv6Packet::owned(match packet.get_next_header() {
// ICMPv6 must be translated to ICMP
IpNextHeaderProtocols::Icmpv6 => {
if let Some(new_payload) =
xlat::icmpv6_to_icmp(&Icmpv6Packet::new(packet.payload()).unwrap())
{
// Mutate the input packet
let mut packet =
MutableIpv6Packet::owned(packet.packet().to_vec()).unwrap();
packet.set_next_header(IpNextHeaderProtocols::Icmp);
packet.set_payload(&new_payload.packet().to_vec());
packet.packet().to_vec()
} else {
return Ok(None);
}
}
// By default, packets can be directly fed to the next function
_ => packet.packet().to_vec(),
}) {
// Translate the packet
let translated = xlat::ipv6_to_ipv4(&packet, new_source, new_dest, true);
// Log the translated packet header
log::debug!(
"> Translated Header: {}",
bytes_to_hex_str(&translated[0..20]).bright_cyan()
);
// Return the translated packet
translated
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
};
// Build the response
log::debug!("{}", "Sending translated packet".bright_green());
return Ok(Some(output_packet));
}
}

View File

@ -1,11 +1,8 @@
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; //! A generic internet protocol packet type
use pnet_packet::{ use std::net::IpAddr;
ip::IpNextHeaderProtocol,
ipv4::{checksum, Ipv4, Ipv4Packet, MutableIpv4Packet}, use pnet_packet::{ip::IpNextHeaderProtocol, ipv4::Ipv4Packet, ipv6::Ipv6Packet, Packet};
ipv6::{Ipv6, Ipv6Packet, MutableIpv6Packet},
Packet,
};
/// A protocol-agnostic packet type /// A protocol-agnostic packet type
#[derive(Debug)] #[derive(Debug)]
@ -91,68 +88,55 @@ impl IpPacket<'_> {
} }
} }
pub fn xlat_v6_to_v4( #[cfg(test)]
ipv6_packet: &Ipv6Packet, mod tests {
new_source: Ipv4Addr, use pnet_packet::{ipv4::MutableIpv4Packet, ipv6::MutableIpv6Packet};
new_dest: Ipv4Addr,
decr_ttl: bool,
) -> Vec<u8> {
let data = Ipv4 {
version: 4,
header_length: 20,
dscp: 0,
ecn: 0,
total_length: 20 + ipv6_packet.payload().len() as u16,
identification: 0,
flags: 0,
fragment_offset: 0,
ttl: ipv6_packet.get_hop_limit(),
next_level_protocol: ipv6_packet.get_next_header(),
checksum: 0,
source: new_source,
destination: new_dest,
options: vec![],
payload: ipv6_packet.payload().to_vec(),
};
let mut packet = MutableIpv4Packet::owned(vec![0; 20 + ipv6_packet.payload().len()]).unwrap();
packet.populate(&data);
packet.set_checksum(checksum(&packet.to_immutable()));
// Decrement the TTL if needed use super::*;
if decr_ttl {
packet.set_ttl(packet.get_ttl() - 1); #[test]
fn test_ipv4_packet() {
// Build packet to test
let mut packet = MutableIpv4Packet::owned(vec![0; 20]).unwrap();
packet.set_version(4);
packet.set_source("192.0.2.1".parse().unwrap());
packet.set_destination("192.0.2.2".parse().unwrap());
// Parse
let header = packet.packet()[..20].to_vec();
let packet = IpPacket::new(packet.packet()).unwrap();
assert_eq!(
packet.get_source(),
IpAddr::V4("192.0.2.1".parse().unwrap())
);
assert_eq!(
packet.get_destination(),
IpAddr::V4("192.0.2.2".parse().unwrap())
);
assert_eq!(packet.get_header(), header);
} }
let mut output = packet.to_immutable().packet().to_vec(); #[test]
// TODO: There is a bug here.. for now, force write header size fn test_ipv6_packet() {
output[0] = 0x45; // Build packet to test
output let mut packet = MutableIpv6Packet::owned(vec![0; 40]).unwrap();
} packet.set_version(6);
packet.set_source("2001:db8::c0a8:1".parse().unwrap());
packet.set_destination("2001:db8::c0a8:2".parse().unwrap());
pub fn xlat_v4_to_v6( // Parse
ipv4_packet: &Ipv4Packet, let header = packet.packet()[..40].to_vec();
new_source: Ipv6Addr, let packet = IpPacket::new(packet.packet()).unwrap();
new_dest: Ipv6Addr,
decr_ttl: bool,
) -> Vec<u8> {
let data = Ipv6 {
version: 6,
traffic_class: 0,
flow_label: 0,
payload_length: ipv4_packet.payload().len() as u16,
next_header: ipv4_packet.get_next_level_protocol(),
hop_limit: ipv4_packet.get_ttl(),
source: new_source,
destination: new_dest,
payload: ipv4_packet.payload().to_vec(),
};
let mut packet = MutableIpv6Packet::owned(vec![0; 40 + ipv4_packet.payload().len()]).unwrap();
packet.populate(&data);
// Decrement the TTL if needed // Test
if decr_ttl { assert_eq!(
packet.set_hop_limit(packet.get_hop_limit() - 1); packet.get_source(),
IpAddr::V6("2001:db8::c0a8:1".parse().unwrap())
);
assert_eq!(
packet.get_destination(),
IpAddr::V6("2001:db8::c0a8:2".parse().unwrap())
);
assert_eq!(packet.get_header(), header);
} }
packet.to_immutable().packet().to_vec()
} }

143
src/nat/table.rs Normal file
View File

@ -0,0 +1,143 @@
use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
time::{Duration, Instant},
};
use bimap::BiHashMap;
use ipnet::Ipv4Net;
/// Possible errors thrown in the address reservation process
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Address already reserved: {0}")]
AddressAlreadyReserved(IpAddr),
#[error("Address pool depleted")]
AddressPoolDepleted,
}
/// A NAT address table
#[derive(Debug)]
pub struct Nat64Table {
/// All possible IPv4 addresses that can be used
ipv4_pool: Vec<Ipv4Net>,
/// Current reservations
reservations: BiHashMap<Ipv6Addr, Ipv4Addr>,
/// The timestamp of each reservation (used for pruning)
reservation_times: HashMap<(Ipv6Addr, Ipv4Addr), Option<Instant>>,
/// The maximum amount of time to reserve an address pair for
reservation_timeout: Duration,
}
impl Nat64Table {
/// Construct a new NAT64 table
///
/// **Arguments:**
/// - `ipv4_pool`: The pool of IPv4 addresses to use in the mapping process
/// - `reservation_timeout`: The amount of time to reserve an address pair for
pub fn new(ipv4_pool: Vec<Ipv4Net>, reservation_timeout: Duration) -> Self {
Self {
ipv4_pool,
reservations: BiHashMap::new(),
reservation_times: HashMap::new(),
reservation_timeout,
}
}
/// Make a reservation for an IP address pair for eternity
pub fn add_infinite_reservation(
&mut self,
ipv6: Ipv6Addr,
ipv4: Ipv4Addr,
) -> Result<(), Error> {
// Check if either address is already reserved
self.prune();
if self.reservations.contains_left(&ipv6) {
return Err(Error::AddressAlreadyReserved(ipv6.into()));
} else if self.reservations.contains_right(&ipv4) {
return Err(Error::AddressAlreadyReserved(ipv4.into()));
}
// Add the reservation
self.reservations.insert(ipv6, ipv4);
self.reservation_times.insert((ipv6, ipv4), None);
Ok(())
}
/// Get or assign an IPv4 address for the given IPv6 address
pub fn get_or_assign_ipv4(&mut self, ipv6: Ipv6Addr) -> Result<Ipv4Addr, Error> {
// Prune old reservations
self.prune();
// If the IPv6 address is already reserved, return the IPv4 address
if let Some(ipv4) = self.reservations.get_by_left(&ipv6) {
// Update the reservation time
self.reservation_times
.insert((ipv6, *ipv4), Some(Instant::now()));
// Return the v4 address
return Ok(*ipv4);
}
// Otherwise, try to assign a new IPv4 address
for ipv4_net in &self.ipv4_pool {
for ipv4 in ipv4_net.hosts(){
// Check if this address is available for use
if !self.reservations.contains_right(&ipv4) {
// Add the reservation
self.reservations.insert(ipv6, ipv4);
self.reservation_times
.insert((ipv6, ipv4), Some(Instant::now()));
return Ok(ipv4);
}
}
}
// If we get here, we failed to find an available address
Err(Error::AddressPoolDepleted)
}
/// Try to find an IPv6 address for the given IPv4 address
pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Option<Ipv6Addr> {
// Prune old reservations
self.prune();
// If the IPv4 address is already reserved, return the IPv6 address
if let Some(ipv6) = self.reservations.get_by_right(&ipv4) {
// Update the reservation time
self.reservation_times
.insert((*ipv6, ipv4), Some(Instant::now()));
// Return the v6 address
return Some(*ipv6);
}
// Otherwise, there is no matching reservation
None
}
}
impl Nat64Table {
/// Prune old reservations
pub fn prune(&mut self) {
let now = Instant::now();
// Prune from the reservation map
self.reservations.retain(|v6, v4| {
if let Some(time) = self.reservation_times.get(&(*v6, *v4)) {
if let Some(time) = time {
now - *time < self.reservation_timeout
} else {
true
}
} else {
true
}
});
// Remove all times assigned to reservations that no longer exist
self.reservation_times.retain(|(v6, v4), _| {
self.reservations.contains_left(v6) && self.reservations.contains_right(v4)
});
}
}

View File

@ -1,4 +1,4 @@
//! ICMP packets require their own translation system //! Translation logic for ICMP and ICMPv6
use std::net::Ipv6Addr; use std::net::Ipv6Addr;

71
src/nat/xlat/ip.rs Normal file
View File

@ -0,0 +1,71 @@
//! Translation logic for IPv4 and IPv6
use std::net::{Ipv4Addr, Ipv6Addr};
use pnet_packet::{ipv6::{Ipv6Packet, Ipv6, MutableIpv6Packet}, ipv4::{Ipv4, MutableIpv4Packet, self, Ipv4Packet}, Packet};
pub fn ipv6_to_ipv4(
ipv6_packet: &Ipv6Packet,
new_source: Ipv4Addr,
new_dest: Ipv4Addr,
decr_ttl: bool,
) -> Vec<u8> {
let data = Ipv4 {
version: 4,
header_length: 20,
dscp: 0,
ecn: 0,
total_length: 20 + ipv6_packet.payload().len() as u16,
identification: 0,
flags: 0,
fragment_offset: 0,
ttl: ipv6_packet.get_hop_limit(),
next_level_protocol: ipv6_packet.get_next_header(),
checksum: 0,
source: new_source,
destination: new_dest,
options: vec![],
payload: ipv6_packet.payload().to_vec(),
};
let mut packet = MutableIpv4Packet::owned(vec![0; 20 + ipv6_packet.payload().len()]).unwrap();
packet.populate(&data);
packet.set_checksum(ipv4::checksum(&packet.to_immutable()));
// Decrement the TTL if needed
if decr_ttl {
packet.set_ttl(packet.get_ttl() - 1);
}
let mut output = packet.to_immutable().packet().to_vec();
// TODO: There is a bug here.. for now, force write header size
output[0] = 0x45;
output
}
pub fn ipv4_to_ipv6(
ipv4_packet: &Ipv4Packet,
new_source: Ipv6Addr,
new_dest: Ipv6Addr,
decr_ttl: bool,
) -> Vec<u8> {
let data = Ipv6 {
version: 6,
traffic_class: 0,
flow_label: 0,
payload_length: ipv4_packet.payload().len() as u16,
next_header: ipv4_packet.get_next_level_protocol(),
hop_limit: ipv4_packet.get_ttl(),
source: new_source,
destination: new_dest,
payload: ipv4_packet.payload().to_vec(),
};
let mut packet = MutableIpv6Packet::owned(vec![0; 40 + ipv4_packet.payload().len()]).unwrap();
packet.populate(&data);
// Decrement the TTL if needed
if decr_ttl {
packet.set_hop_limit(packet.get_hop_limit() - 1);
}
packet.to_immutable().packet().to_vec()
}

7
src/nat/xlat/mod.rs Normal file
View File

@ -0,0 +1,7 @@
//! Packet type translation functionality
mod icmp;
mod ip;
pub use icmp::{icmpv6_to_icmp, icmp_to_icmpv6};
pub use ip::{ipv4_to_ipv6, ipv6_to_ipv4};