commit 85c6f26136e026a191f24b9d12de058a2fa7e566 Author: mamoniot Date: Thu May 11 13:23:17 2023 -0400 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7589a3e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "nat" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1f6137d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nat" +version = "0.1.0" +edition = "2021" + +[lib] +name = "nat" +path = "src/lib.rs" +doc = true + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4a84262 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod rng; +pub mod simple; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 0000000..76f904b --- /dev/null +++ b/src/rng.rs @@ -0,0 +1,6 @@ +pub fn xorshift64star(state: &mut u64) -> u64 { + *state ^= *state >> 12; + *state ^= *state << 25; + *state ^= *state >> 27; + return state.wrapping_mul(0x2545F4914F6CDD1Du64); +} diff --git a/src/simple.rs b/src/simple.rs new file mode 100644 index 0000000..e53d160 --- /dev/null +++ b/src/simple.rs @@ -0,0 +1,339 @@ +use std::ops::Range; + +use crate::rng::xorshift64star; + + +pub const IP_POOLING_MAXIMUM: usize = 64; +pub const IP_POOLING_BEHAVIOR_ARBITRARY: u32 = 1 << 3; + +pub const ADDRESS_DEPENDENT_MAPPING: u32 = 1 << 4; +pub const PORT_DEPENDENT_MAPPING: u32 = 1 << 5; +pub const ADDRESS_AND_PORT_DEPENDENT_MAPPING: u32 = + ADDRESS_DEPENDENT_MAPPING | PORT_DEPENDENT_MAPPING; + +pub const ADDRESS_DEPENDENT_FILTERING: u32 = 1 << 6; +pub const PORT_DEPENDENT_FILTERING: u32 = 1 << 7; +pub const ADDRESS_AND_PORT_DEPENDENT_FILTERING: u32 = + ADDRESS_DEPENDENT_FILTERING | PORT_DEPENDENT_FILTERING; + +pub const INTERNAL_ADDRESS_AND_PORT_HAIRPINNING: u32 = 1 << 8; +pub const NO_HAIRPINNING: u32 = 1 << 9; + +pub const OUTBOUND_REFRESH_BEHAVIOR_FALSE: u32 = 1 << 10; +pub const INBOUND_REFRESH_BEHAVIOR_FALSE: u32 = 1 << 11; +pub const FILTERED_INBOUND_DESTROYS_MAPPING: u32 = 1 << 12; + +pub const NO_PORT_PRESERVATION: u32 = 1 << 1; +pub const NO_PORT_PARITY: u32 = 1 << 2; +pub const PORT_PRESERVATION_OVERRIDE: u32 = 1 << 0; +pub const PORT_PRESERVATION_OVERLOAD: u32 = 1 << 0; +//const NON_DETERMINISTIC_PORT_PRESERVATION: u32 = 1<<0; + +pub enum DestType { + Internet { + src_address: u32, + src_port: u16, + dest_address: u32, + dest_port: u16, + }, + Intranet { + src_address: u32, + src_port: u16, + dest_address: u32, + dest_port: u16, + }, + Drop +} +impl DestType { + pub fn unwrap(&self) -> Option<(u32, u16, u32, u16)> { + match self { + DestType::Internet { + src_address, + src_port, + dest_address, + dest_port, + } + | DestType::Intranet { + src_address, + src_port, + dest_address, + dest_port, + } => Some((*src_address, *src_port, *dest_address, *dest_port)), + DestType::Drop => None + } + } +} + +struct SymmetricEntry { + intranet_address: u32, + intranet_port: u16, + internet_port: u16, + /// When `ADDRESS_DEPENDENT_MAPPING` is true this will be set to the very last `dest_address` + /// sent through this mapping. + endpoint_address: u32, + /// When `PORT_DEPENDENT_MAPPING` is true this will be set to the very last `dest_port` + /// sent through this mapping. + endpoint_port: u16, + last_used_time: i64, +} +pub struct SymmetricNAT { + addresses_len: usize, + addresses: [u32; IP_POOLING_MAXIMUM], + map: [Vec; IP_POOLING_MAXIMUM], + mapping_timeout: i64, + max_routing_table_len: usize, + rng: u64, + valid_internet_ports: Range, + valid_intranet_addresses: Range, + flags: u32, +} +impl SymmetricNAT { + fn address_is_internal(&self, address: u32) -> bool { + return self.valid_intranet_addresses.contains(&address); + } + fn generate_port(&mut self) -> u16 { + let random_port = xorshift64star(&mut self.rng) as usize % self.valid_internet_ports.len(); + random_port as u16 + self.valid_internet_ports.start + } + fn remap( + &mut self, + intranet_address: u32, + intranet_port: u16, + internet_address: u32, + internet_port: u16, + dest_address: u32, + dest_port: u16, + current_time: i64, + ) -> DestType { + if let Some((_, _, dest_address, dest_port)) = self.from_internet(internet_address, internet_port, dest_address, dest_port, false, current_time) { + // Packet is for an internal recipient. We assume we are doing hairpinning to rewrite the packet for our intranet. + if self.flags & INTERNAL_ADDRESS_AND_PORT_HAIRPINNING > 0 { + DestType::Intranet { + src_address: intranet_address, + src_port: intranet_port, + dest_address, + dest_port, + } + } else { + DestType::Intranet { + src_address: internet_address, + src_port: internet_port, + dest_address, + dest_port, + } + } + } else if self.addresses.contains(&dest_address) { + // Packet was addressed to our intranet using their external address and was filtered. + DestType::Drop + } else { + DestType::Internet { + src_address: internet_address, + src_port: internet_port, + dest_address, + dest_port, + } + } + } + pub fn from_intranet( + &mut self, + src_address: u32, + src_port: u16, + dest_address: u32, + dest_port: u16, + current_time: i64, + ) -> DestType { + if self.address_is_internal(dest_address) { + return DestType::Intranet { + src_address, + src_port, + dest_address, + dest_port, + }; + } else if self.flags & NO_HAIRPINNING > 0 && self.addresses.contains(&dest_address) { + return DestType::Drop; + } + + let expiry = current_time - self.mapping_timeout; + let mut previously_assigned_address_idx = None; + for address_idx in 0..self.addresses_len { + let routing_table = &mut self.map[address_idx]; + let mut oldest_time = i64::MAX; + let mut oldest_idx = 0; + let mut i = 0; + while i < routing_table.len() { + let route = &mut routing_table[i]; + if route.last_used_time < expiry { + routing_table.swap_remove(i); + continue; + } else if route.intranet_address == src_address { + if self.flags & IP_POOLING_BEHAVIOR_ARBITRARY == 0 { + previously_assigned_address_idx = Some(address_idx); + } + if route.intranet_port == src_port { + if (self.flags & ADDRESS_DEPENDENT_MAPPING == 0 + || route.endpoint_address == dest_address) + && (self.flags & PORT_DEPENDENT_MAPPING == 0 + || route.endpoint_port == dest_port) + { + route.endpoint_address = dest_address; + route.endpoint_port = dest_port; + if self.flags & OUTBOUND_REFRESH_BEHAVIOR_FALSE == 0 { + route.last_used_time = current_time; + } + let internet_address = self.addresses[i]; + let internet_port = route.internet_port; + return self.remap(src_address, src_port, internet_address, internet_port, dest_address, dest_port, current_time); + } + } + } + if oldest_time >= route.last_used_time { + oldest_time = route.last_used_time; + oldest_idx = i; + } + i += 1; + } + if routing_table.len() >= self.max_routing_table_len { + routing_table.swap_remove(oldest_idx); + } + } + + let (internet_address_idx, internet_port) = { + let mut selected_inet = None; + if self.flags & NO_PORT_PRESERVATION == 0 { + let mut addr_perm: [usize; IP_POOLING_MAXIMUM] = std::array::from_fn(|i| i); + let mut addr_perm_len = self.addresses_len; + if let Some(idx) = previously_assigned_address_idx { + // If this NAT has the behavior of "Paired" then we may only consider addresses + // that match with the previously assigned address. + addr_perm[0] = idx; + addr_perm_len = 1; + } else { + for i in (1..addr_perm_len).rev() { + addr_perm.swap(i, xorshift64star(&mut self.rng) as usize % (i + 1)) + } + } + 'next_addr :for internet_address_idx in &addr_perm[..addr_perm_len] { + for route in &self.map[*internet_address_idx] { + if route.internet_port == src_port { + // This address and port combination collides so consider something else. + continue 'next_addr; + } + } + selected_inet = Some((*internet_address_idx, src_port)); + break; + } + if self.flags & PORT_PRESERVATION_OVERRIDE > 0 { + let routing_table = &mut self.map[addr_perm[0]]; + for i in 0..routing_table.len() { + if routing_table[i].internet_port == src_port { + // In port preservation override mode we remove everyone else who is + // using the chosen src_port. + routing_table.swap_remove(i); + } + } + selected_inet = Some((addr_perm[0], src_port)); + } else if self.flags & PORT_PRESERVATION_OVERLOAD > 0 { + // src_port is currently used by all of our IP addresses, so overload that port. + selected_inet = Some((addr_perm[0], src_port)); + } + } + if let Some((a, p)) = selected_inet { + (a, p) + } else { + let mut random_address; + let mut random_port; + 'regen: loop { + random_address = previously_assigned_address_idx.unwrap_or_else(|| xorshift64star(&mut self.rng) as usize % self.addresses_len); + random_port = self.generate_port(); + if self.flags & NO_PORT_PARITY == 0 { + // Force the port to have the same parity as the src_port. + random_port = (random_port & !1u16) | (src_port & 1u16); + } + for route in &self.map[random_address] { + if route.internet_port == random_port + { + continue 'regen; + } + } + break; + } + (random_address, random_port) + } + }; + let internet_address = self.addresses[internet_address_idx]; + self.map[internet_address_idx].push(SymmetricEntry { + intranet_address: src_address, + intranet_port: src_port, + internet_port, + endpoint_address: dest_address, + endpoint_port: dest_port, + last_used_time: current_time, + }); + return self.remap(src_address, src_port, internet_address, internet_port, dest_address, dest_port, current_time); + } + pub fn from_internet( + &mut self, + src_address: u32, + src_port: u16, + dest_address: u32, + dest_port: u16, + disable_filtering: bool, + current_time: i64, + ) -> Option<(u32, u16, u32, u16)> { + let mut dest_address_idx = IP_POOLING_MAXIMUM; + for i in 0..self.addresses_len { + if self.addresses[i] == dest_address { + dest_address_idx = i; + break; + } + } + if dest_address_idx == IP_POOLING_MAXIMUM { + // This packet was not addressed to this router/NAT + return None; + } + + let expiry = current_time - self.mapping_timeout; + let mut needs_destruction = false; + let mut i = 0; + let routing_table = &mut self.map[i]; + while i < routing_table.len() { + let route = &mut routing_table[dest_address_idx]; + if route.last_used_time < expiry { + routing_table.swap_remove(i); + } else if route.internet_port == dest_port { + if disable_filtering + || ((self.flags & ADDRESS_DEPENDENT_FILTERING == 0 + || route.endpoint_address == src_address) + && (self.flags & PORT_DEPENDENT_FILTERING == 0 + || route.endpoint_port == src_port)) + { + if self.flags & INBOUND_REFRESH_BEHAVIOR_FALSE == 0 { + route.last_used_time = current_time; + } + return Some(( + src_address, + src_port, + route.intranet_address, + route.intranet_port, + )); + } else if self.flags & FILTERED_INBOUND_DESTROYS_MAPPING > 0 { + needs_destruction = true; + } + } else { + i += 1; + } + } + // We could not find a valid recipient or the packet was filtered. + if needs_destruction { + while i < routing_table.len() { + let route = &routing_table[i]; + if route.internet_port == dest_port { + routing_table.swap_remove(i); + } else { + i += 1; + } + } + } + return None; + } +}