Skip to content

Commit

Permalink
Fix addr format ambiguity
Browse files Browse the repository at this point in the history
  • Loading branch information
ewpratten committed Aug 4, 2023
1 parent 79a861a commit 23fd3f2
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 79 deletions.
3 changes: 2 additions & 1 deletion libs/fast-nat/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ categories = []
[dependencies]
log = "^0.4"
rustc-hash = "1.1.0"
thiserror = "^1.0.44"
thiserror = "^1.0.44"
ipnet = "^2.8.0"
83 changes: 28 additions & 55 deletions libs/fast-nat/src/cpnat.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::time::Duration;
use std::{
net::{Ipv4Addr, Ipv6Addr},
time::Duration,
};

use ipnet::Ipv4Net;
use rustc_hash::FxHashMap;

use crate::{bimap::BiHashMap, error::Error, timeout::MaybeTimeout};
Expand Down Expand Up @@ -50,20 +54,15 @@ impl CrossProtocolNetworkAddressTable {
}

/// Insert a new indefinite mapping
pub fn insert_indefinite<T4: Into<u32>, T6: Into<u128>>(&mut self, ipv4: T4, ipv6: T6) {
pub fn insert_indefinite(&mut self, ipv4: Ipv4Addr, ipv6: Ipv6Addr) {
self.prune();
let (ipv4, ipv6) = (ipv4.into(), ipv6.into());
self.addr_map.insert(ipv4, ipv6);
self.timeouts.insert((ipv4, ipv6), MaybeTimeout::Never);
}

/// Insert a new mapping with a finite time-to-live
pub fn insert<T4: Into<u32>, T6: Into<u128>>(
&mut self,
ipv4: T4,
ipv6: T6,
duration: Duration,
) {
pub fn insert(&mut self, ipv4: Ipv4Addr, ipv6: Ipv6Addr, duration: Duration) {
self.prune();
let (ipv4, ipv6) = (ipv4.into(), ipv6.into());
self.addr_map.insert(ipv4, ipv6);
Expand All @@ -78,14 +77,18 @@ impl CrossProtocolNetworkAddressTable {

/// Get the IPv6 address for a given IPv4 address
#[must_use]
pub fn get_ipv6<T: Into<u32>>(&self, ipv4: T) -> Option<u128> {
self.addr_map.get_right(&ipv4.into()).copied()
pub fn get_ipv6(&self, ipv4: &Ipv4Addr) -> Option<Ipv6Addr> {
self.addr_map
.get_right(&(*ipv4).into())
.map(|addr| (*addr).into())
}

/// Get the IPv4 address for a given IPv6 address
#[must_use]
pub fn get_ipv4<T: Into<u128>>(&self, ipv6: T) -> Option<u32> {
self.addr_map.get_left(&ipv6.into()).copied()
pub fn get_ipv4(&self, ipv6: &Ipv6Addr) -> Option<Ipv4Addr> {
self.addr_map
.get_left(&(*ipv6).into())
.map(|addr| (*addr).into())
}

/// Get the number of mappings in the table
Expand Down Expand Up @@ -115,81 +118,51 @@ pub struct CrossProtocolNetworkAddressTableWithIpv4Pool {
/// Internal table
table: CrossProtocolNetworkAddressTable,
/// Internal pool of IPv4 prefixes to assign new mappings from
pool: Vec<(u32, u32)>,
pool: Vec<Ipv4Net>,
/// The timeout to use for new entries
timeout: Duration,
/// The pre-calculated maximum number of mappings that can be created
max_mappings: usize,
}

impl CrossProtocolNetworkAddressTableWithIpv4Pool {
/// Construct a new Cross-protocol network address table with a given IPv4 pool
#[must_use]
pub fn new<T: Into<u32> + Clone>(pool: &[(T, T)], timeout: Duration) -> Self {
pub fn new(pool: &[Ipv4Net], timeout: Duration) -> Self {
Self {
table: CrossProtocolNetworkAddressTable::default(),
pool: pool
.iter()
.map(|(a, b)| (a.clone().into(), b.clone().into()))
.collect(),
pool: pool.to_vec(),
timeout,
max_mappings: pool
.iter()
.map(|(_, netmask)| (*netmask).clone().into() as usize)
.map(|netmask| !netmask)
.sum(),
}
}

/// Check if the pool contains an address
#[must_use]
pub fn contains<T: Into<u32>>(&self, addr: T) -> bool {
let addr = addr.into();
self.pool
.iter()
.any(|(network_addr, netmask)| (addr & netmask) == *network_addr)
}

/// Insert a new static mapping
pub fn insert_static<T4: Into<u32>, T6: Into<u128>>(
&mut self,
ipv4: T4,
ipv6: T6,
) -> Result<(), Error> {
let (ipv4, ipv6) = (ipv4.into(), ipv6.into());
if !self.contains(ipv4) {
pub fn insert_static(&mut self, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Result<(), Error> {
if !self.pool.iter().any(|prefix| prefix.contains(&ipv4)) {
return Err(Error::InvalidIpv4Address(ipv4));
}
self.table.insert_indefinite(ipv4, ipv6);
Ok(())
}

/// Gets the IPv4 address for a given IPv6 address or inserts a new mapping if one does not exist (if possible)
pub fn get_or_create_ipv4<T: Into<u128>>(&mut self, ipv6: T) -> Result<u32, Error> {
let ipv6 = ipv6.into();

pub fn get_or_create_ipv4(&mut self, ipv6: &Ipv6Addr) -> Result<Ipv4Addr, Error> {
// Return the known mapping if it exists
if let Some(ipv4) = self.table.get_ipv4(ipv6) {
return Ok(ipv4);
}

// Otherwise, we first need to make sure there is actually room for a new mapping
if self.table.len() >= self.max_mappings {
return Err(Error::Ipv4PoolExhausted(self.max_mappings));
}

// Find the next available IPv4 address in the pool
let new_address = self
.pool
.iter()
.map(|(network_address, netmask)| (*network_address)..(*network_address | !netmask))
.find_map(|mut addr_range| addr_range.find(|addr| !self.table.get_ipv6(*addr).is_some()))
.ok_or(Error::Ipv4PoolExhausted(self.max_mappings))?;
.map(|prefix| prefix.hosts())

Check failure on line 157 in libs/fast-nat/src/cpnat.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

error: redundant closure --> libs/fast-nat/src/cpnat.rs:157:18 | 157 | .map(|prefix| prefix.hosts()) | ^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `ipnet::Ipv4Net::hosts` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls note: the lint level is defined here --> libs/fast-nat/src/lib.rs:2:9 | 2 | #![deny(clippy::pedantic)] | ^^^^^^^^^^^^^^^^ = note: `#[deny(clippy::redundant_closure_for_method_calls)]` implied by `#[deny(clippy::pedantic)]`
.flatten()

Check warning on line 158 in libs/fast-nat/src/cpnat.rs

View workflow job for this annotation

GitHub Actions / clippy

called `map(..).flatten()` on `Iterator`

warning: called `map(..).flatten()` on `Iterator` --> libs/fast-nat/src/cpnat.rs:157:14 | 157 | .map(|prefix| prefix.hosts()) | ______________^ 158 | | .flatten() | |______________________^ help: try replacing `map` with `flat_map` and remove the `.flatten()`: `flat_map(|prefix| prefix.hosts())` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten = note: `#[warn(clippy::map_flatten)]` on by default
.find(|addr| !self.table.get_ipv6(addr).is_some())

Check warning on line 159 in libs/fast-nat/src/cpnat.rs

View workflow job for this annotation

GitHub Actions / clippy

this boolean expression can be simplified

warning: this boolean expression can be simplified --> libs/fast-nat/src/cpnat.rs:159:26 | 159 | .find(|addr| !self.table.get_ipv6(addr).is_some()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `self.table.get_ipv6(addr).is_none()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool = note: `#[warn(clippy::nonminimal_bool)]` on by default
.ok_or(Error::Ipv4PoolExhausted)?;

// Insert the new mapping
self.table.insert(new_address, ipv6, self.timeout);
self.table.insert(new_address, *ipv6, self.timeout);
log::info!(
"New cross-protocol address mapping: {:02x} -> {:02x}",
"New cross-protocol address mapping: {} -> {}",
ipv6,
new_address
);
Expand All @@ -200,7 +173,7 @@ impl CrossProtocolNetworkAddressTableWithIpv4Pool {

/// Gets the IPv6 address for a given IPv4 address if it exists
#[must_use]
pub fn get_ipv6<T: Into<u32>>(&self, ipv4: T) -> Option<u128> {
pub fn get_ipv6(&self, ipv4: &Ipv4Addr) -> Option<Ipv6Addr> {
self.table.get_ipv6(ipv4)
}
}
10 changes: 6 additions & 4 deletions libs/fast-nat/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::net::Ipv4Addr;

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Ipv4 address does not belong to the NAT pool: {0:02x}")]
InvalidIpv4Address(u32),
#[error("IPv4 pool exhausted. All {0} spots filled")]
Ipv4PoolExhausted(usize),
#[error("Ipv4 address does not belong to the NAT pool: {0}")]
InvalidIpv4Address(Ipv4Addr),
#[error("IPv4 pool exhausted")]
Ipv4PoolExhausted,
}
22 changes: 12 additions & 10 deletions libs/fast-nat/src/nat.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::time::Duration;

use rustc_hash::FxHashMap;

use crate::{bimap::BiHashMap, timeout::MaybeTimeout};
use rustc_hash::FxHashMap;
use std::{net::Ipv4Addr, time::Duration};

/// A table of network address mappings
#[derive(Debug)]
Expand Down Expand Up @@ -50,15 +48,15 @@ impl NetworkAddressTable {
}

/// Insert a new indefinite mapping
pub fn insert_indefinite<T: Into<u32>>(&mut self, left: T, right: T) {
pub fn insert_indefinite(&mut self, left: Ipv4Addr, right: Ipv4Addr) {
self.prune();
let (left, right) = (left.into(), right.into());
self.addr_map.insert(left, right);
self.timeouts.insert((left, right), MaybeTimeout::Never);
}

/// Insert a new mapping with a finite time-to-live
pub fn insert<T: Into<u32>>(&mut self, left: T, right: T, duration: Duration) {
pub fn insert(&mut self, left: Ipv4Addr, right: Ipv4Addr, duration: Duration) {
self.prune();
let (left, right) = (left.into(), right.into());
self.addr_map.insert(left, right);
Expand All @@ -73,14 +71,18 @@ impl NetworkAddressTable {

/// Get the right value for a given left value
#[must_use]
pub fn get_right<T: Into<u32>>(&self, left: T) -> Option<u32> {
self.addr_map.get_right(&left.into()).copied()
pub fn get_right(&self, left: &Ipv4Addr) -> Option<Ipv4Addr> {
self.addr_map
.get_right(&(*left).into())
.map(|addr| (*addr).into())
}

/// Get the left value for a given right value
#[must_use]
pub fn get_left<T: Into<u32>>(&self, right: T) -> Option<u32> {
self.addr_map.get_left(&right.into()).copied()
pub fn get_left(&self, right: &Ipv4Addr) -> Option<Ipv4Addr> {
self.addr_map
.get_left(&(*right).into())
.map(|addr| (*addr).into())
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/common/packet_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ where
);
None
}
PacketHandlingError::FastNatError(fast_nat::error::Error::Ipv4PoolExhausted(size)) => {
log::warn!("IPv4 pool exhausted with {} mappings", size);
PacketHandlingError::FastNatError(fast_nat::error::Error::Ipv4PoolExhausted) => {
log::warn!("IPv4 pool exhausted. Dropping packet.");
None
}
PacketHandlingError::FastNatError(fast_nat::error::Error::InvalidIpv4Address(addr)) => {
Expand Down
10 changes: 3 additions & 7 deletions src/protomask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,7 @@ pub async fn main() {

// Set up the address table
let mut addr_table = RefCell::new(CrossProtocolNetworkAddressTableWithIpv4Pool::new(
pool_prefixes
.iter()
.map(|prefix| (u32::from(prefix.addr()), u32::from(prefix.netmask())))
.collect::<Vec<(u32, u32)>>()
.as_slice(),
&pool_prefixes,
Duration::from_secs(args.reservation_timeout),
));
for (v6_addr, v4_addr) in args.get_static_reservations().unwrap() {
Expand All @@ -171,7 +167,7 @@ pub async fn main() {
if let Some(output) = handle_packet(
&buffer[..len],
// IPv4 -> IPv6
|packet, source, dest| match addr_table.borrow().get_ipv6(*dest) {
|packet, source, dest| match addr_table.borrow().get_ipv6(dest) {
Some(new_destination) => Ok(translate_ipv4_to_ipv6(
packet,
unsafe { embed_ipv4_addr_unchecked(*source, args.translation_prefix) },
Expand All @@ -187,7 +183,7 @@ pub async fn main() {
|packet, source, dest| {
Ok(translate_ipv6_to_ipv4(
packet,
addr_table.borrow_mut().get_or_create_ipv4(*source)?.into(),
addr_table.borrow_mut().get_or_create_ipv4(source)?.into(),
unsafe {
extract_ipv4_addr_unchecked(*dest, args.translation_prefix.prefix_len())
},
Expand Down

2 comments on commit 23fd3f2

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary sizes for x86_64-unknown-linux-musl

Channel: stable

9.2M	target/x86_64-unknown-linux-musl/release/protomask-clat
9.2M	target/x86_64-unknown-linux-musl/release/protomask
4.8M	target/x86_64-unknown-linux-musl/release/protomask-6over4

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binary sizes for aarch64-unknown-linux-musl

Channel: stable

9.5M	target/aarch64-unknown-linux-musl/release/protomask
9.4M	target/aarch64-unknown-linux-musl/release/protomask-clat
5.0M	target/aarch64-unknown-linux-musl/release/protomask-6over4

Please sign in to comment.