1

Filter out invalid destinations

This commit is contained in:
Evan Pratten 2023-07-16 15:01:04 -04:00
parent b2b99a8e11
commit a1ba812a5c
2 changed files with 150 additions and 12 deletions

View File

@ -1,9 +1,10 @@
use std::{
net::{Ipv4Addr, Ipv6Addr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
time::Duration,
};
use ipnet::{Ipv4Net, Ipv6Net};
use pnet_packet::ip::IpNextHeaderProtocols;
use self::{interface::Nat64Interface, packet::IpPacket, table::Nat64Table};
@ -25,26 +26,31 @@ pub enum Nat64Error {
pub struct Nat64 {
table: Nat64Table,
interface: Nat64Interface,
ipv6_nat_prefix: Ipv6Net,
}
impl Nat64 {
/// Construct a new NAT64 instance
pub async fn new(
v6_prefix: Ipv6Net,
v4_pool: Vec<Ipv4Net>,
ipv6_nat_prefix: Ipv6Net,
ipv4_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?;
let interface = Nat64Interface::new(ipv6_nat_prefix, &ipv4_pool).await?;
// Build the table and insert any static reservations
let mut table = Nat64Table::new(v4_pool, reservation_duration);
let mut table = Nat64Table::new(ipv4_pool, reservation_duration);
for (v6, v4) in static_reservations {
table.add_infinite_reservation(v6, v4)?;
}
Ok(Self { table, interface })
Ok(Self {
table,
interface,
ipv6_nat_prefix,
})
}
/// Block and process all packets
@ -87,6 +93,55 @@ impl Nat64 {
&mut self,
packet: IpPacket<'_>,
) -> Result<Option<IpPacket>, Nat64Error> {
Ok(None)
// The destination of the packet must be within a prefix we care about
if match packet.get_destination() {
IpAddr::V4(ipv4_addr) => !self.table.is_address_within_pool(&ipv4_addr),
IpAddr::V6(ipv6_addr) => !self.ipv6_nat_prefix.contains(&ipv6_addr),
} {
log::debug!(
"Packet destination {} is not within the NAT64 prefix or IPv4 pool",
packet.get_destination(),
);
return Ok(None);
}
// Compute the translated source and dest addresses
let source = packet.get_source();
let new_source = self
.table
.calculate_xlat_addr(&source, &self.ipv6_nat_prefix)?;
let destination = packet.get_destination();
let new_destination = self
.table
.calculate_xlat_addr(&destination, &self.ipv6_nat_prefix)?;
// Log information about the packet
log::debug!(
"Received packet traveling from {} to {}",
source,
destination
);
log::debug!(
"New path shall become: {} -> {}",
new_source,
new_destination
);
// Different logic is required for ICMP, UDP, and TCP
let next_header_protocol = packet.get_next_header();
log::debug!(
"Incoming packet has next header protocol: {}",
next_header_protocol
);
match next_header_protocol {
IpNextHeaderProtocols::Icmp => unimplemented!(),
IpNextHeaderProtocols::Icmpv6 => unimplemented!(),
IpNextHeaderProtocols::Udp => unimplemented!(),
IpNextHeaderProtocols::Tcp => unimplemented!(),
next_header_protocol => {
log::warn!("Unsupported next header protocol: {}", next_header_protocol);
Ok(None)
}
}
}
}

View File

@ -5,13 +5,15 @@ use std::{
};
use bimap::BiHashMap;
use ipnet::Ipv4Net;
use ipnet::{Ipv4Net, Ipv6Net};
/// Possible errors thrown in the address reservation process
#[derive(Debug, thiserror::Error)]
pub enum TableError {
#[error("Address already reserved: {0}")]
AddressAlreadyReserved(IpAddr),
#[error("IPv4 address has no IPv6 mapping: {0}")]
NoIpv6Mapping(Ipv4Addr),
#[error("Address pool depleted")]
AddressPoolDepleted,
}
@ -81,7 +83,7 @@ impl Nat64Table {
// Otherwise, try to assign a new IPv4 address
for ipv4_net in &self.ipv4_pool {
for ipv4 in ipv4_net.hosts(){
for ipv4 in ipv4_net.hosts() {
// Check if this address is available for use
if !self.reservations.contains_right(&ipv4) {
// Add the reservation
@ -98,7 +100,7 @@ impl Nat64Table {
}
/// Try to find an IPv6 address for the given IPv4 address
pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Option<Ipv6Addr> {
pub fn get_reverse(&mut self, ipv4: Ipv4Addr) -> Result<Ipv6Addr, TableError> {
// Prune old reservations
self.prune();
@ -109,11 +111,66 @@ impl Nat64Table {
.insert((*ipv6, ipv4), Some(Instant::now()));
// Return the v6 address
return Some(*ipv6);
return Ok(*ipv6);
}
// Otherwise, there is no matching reservation
None
Err(TableError::NoIpv6Mapping(ipv4))
}
/// Check if an address is within the IPv4 pool
pub fn is_address_within_pool(&self, address: &Ipv4Addr) -> bool {
self.ipv4_pool.iter().any(|net| net.contains(address))
}
/// Calculate the translated version of any address
pub fn calculate_xlat_addr(
&mut self,
input: &IpAddr,
ipv6_nat64_prefix: &Ipv6Net,
) -> Result<IpAddr, TableError> {
// Handle the incoming address type
match input {
// Handle IPv4
IpAddr::V4(ipv4_addr) => {
// If the address is in the IPv4 pool, it is a regular IPv4 address
if self.is_address_within_pool(ipv4_addr) {
// This means we need to pass through to `get_reverse`
return Ok(IpAddr::V6(self.get_reverse(*ipv4_addr)?));
}
// Otherwise, it shall be embedded inside the ipv6 prefix
let prefix_octets = ipv6_nat64_prefix.addr().octets();
let address_octets = ipv4_addr.octets();
return Ok(IpAddr::V6(Ipv6Addr::new(
u16::from_be_bytes([prefix_octets[0], prefix_octets[1]]),
u16::from_be_bytes([prefix_octets[2], prefix_octets[3]]),
u16::from_be_bytes([prefix_octets[4], prefix_octets[5]]),
u16::from_be_bytes([prefix_octets[6], prefix_octets[7]]),
u16::from_be_bytes([prefix_octets[8], prefix_octets[9]]),
u16::from_be_bytes([prefix_octets[10], prefix_octets[11]]),
u16::from_be_bytes([address_octets[0], address_octets[1]]),
u16::from_be_bytes([address_octets[2], address_octets[3]]),
)));
}
// Handle IPv6
IpAddr::V6(ipv6_addr) => {
// If the address is in the IPv6 prefix, it is an embedded IPv4 address
if ipv6_nat64_prefix.contains(ipv6_addr) {
let address_bytes = ipv6_addr.octets();
return Ok(IpAddr::V4(Ipv4Addr::new(
address_bytes[12],
address_bytes[13],
address_bytes[14],
address_bytes[15],
)));
}
// Otherwise, it is a regular IPv6 address and we can pass through to `get_or_assign_ipv4`
return Ok(IpAddr::V4(self.get_or_assign_ipv4(*ipv6_addr)?));
}
}
}
}
@ -141,3 +198,29 @@ impl Nat64Table {
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_infinite_reservation() {
let mut table = Nat64Table::new(
vec![Ipv4Net::new(Ipv4Addr::new(192, 0, 2, 0), 24).unwrap()],
Duration::from_secs(60),
);
// Add a reservation
table
.add_infinite_reservation("2001:db8::1".parse().unwrap(), "192.0.2.1".parse().unwrap())
.unwrap();
// Check that it worked
assert_eq!(
table
.reservations
.get_by_left(&"2001:db8::1".parse().unwrap()),
Some(&"192.0.2.1".parse().unwrap())
);
}
}