Filter out invalid destinations
This commit is contained in:
parent
b2b99a8e11
commit
a1ba812a5c
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user