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"
bimap = "0.6.3"
pnet_packet = "0.33.0"
# etherparse = "0.13.0"
rtnetlink = "0.13.0"
futures = "0.3.28"
[[bin]]
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 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
ip netns exec protomask ./target/debug/protomask protomask.toml -v

View File

@ -1,7 +1,6 @@
use clap::Parser;
use colored::Colorize;
use config::Config;
use nat::Nat64;
mod cli;
mod config;
@ -13,7 +12,7 @@ pub async fn main() {
// Parse CLI args
let args = cli::Args::parse();
// Set up logging
// Set up logging
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
@ -42,22 +41,20 @@ 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.address_v4,
config.interface.address_v6,
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.pool,
// config.interface.prefix,
// config
// .rules
// .static_map
// .iter()
// .map(|rule| (rule.v4, rule.v6))
// .collect(),
// )
// .await
// .unwrap();
// Handle packets
nat64.run().await.unwrap();
// // Handle packets
// 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};
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::{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));
}
}
pub mod xlat;
pub mod packet;
pub mod table;
pub mod interface;

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::{
ip::IpNextHeaderProtocol,
ipv4::{checksum, Ipv4, Ipv4Packet, MutableIpv4Packet},
ipv6::{Ipv6, Ipv6Packet, MutableIpv6Packet},
Packet,
};
use std::net::IpAddr;
use pnet_packet::{ip::IpNextHeaderProtocol, ipv4::Ipv4Packet, ipv6::Ipv6Packet, Packet};
/// A protocol-agnostic packet type
#[derive(Debug)]
@ -91,68 +88,55 @@ impl IpPacket<'_> {
}
}
pub fn xlat_v6_to_v4(
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(checksum(&packet.to_immutable()));
#[cfg(test)]
mod tests {
use pnet_packet::{ipv4::MutableIpv4Packet, ipv6::MutableIpv6Packet};
// Decrement the TTL if needed
if decr_ttl {
packet.set_ttl(packet.get_ttl() - 1);
use super::*;
#[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();
// TODO: There is a bug here.. for now, force write header size
output[0] = 0x45;
output
}
#[test]
fn test_ipv6_packet() {
// Build packet to test
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(
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);
// Parse
let header = packet.packet()[..40].to_vec();
let packet = IpPacket::new(packet.packet()).unwrap();
// Decrement the TTL if needed
if decr_ttl {
packet.set_hop_limit(packet.get_hop_limit() - 1);
// Test
assert_eq!(
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;

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};