Implement fast NAT
This commit is contained in:
parent
f4dc096d83
commit
198b7bcd92
@ -56,4 +56,4 @@
|
|||||||
# systemd-units = { enable = false }
|
# systemd-units = { enable = false }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members=["libs/easy-tun"]
|
members = ["libs/easy-tun", "libs/fast-nat"]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
#![deny(clippy::pedantic)]
|
#![deny(clippy::pedantic)]
|
||||||
#![allow(clippy::module_name_repetitions)]
|
#![allow(clippy::module_name_repetitions)]
|
||||||
#![allow(clippy::missing_errors_doc)]
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
17
libs/fast-nat/Cargo.toml
Normal file
17
libs/fast-nat/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "fast-nat"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
description = "An OSI layer 3 Network Address Table built for speed"
|
||||||
|
readme = "README.md"
|
||||||
|
homepage = "https://github.com/ewpratten/protomask/tree/master/libs/fast-nat"
|
||||||
|
documentation = "https://docs.rs/fast-nat"
|
||||||
|
repository = "https://github.com/ewpratten/protomask"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
keywords = []
|
||||||
|
categories = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "^0.4"
|
||||||
|
rustc-hash = "1.1.0"
|
0
libs/fast-nat/README.md
Normal file
0
libs/fast-nat/README.md
Normal file
111
libs/fast-nat/src/bimap.rs
Normal file
111
libs/fast-nat/src/bimap.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
/// A bi-directional hash map
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BiHashMap<Left, Right> {
|
||||||
|
/// Mapping from a left value to a right value
|
||||||
|
left_to_right: FxHashMap<Left, Right>,
|
||||||
|
/// Mapping from a right value to a left value
|
||||||
|
right_to_left: FxHashMap<Right, Left>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> BiHashMap<Left, Right>
|
||||||
|
where
|
||||||
|
Left: Eq + Hash + Clone,
|
||||||
|
Right: Eq + Hash + Clone,
|
||||||
|
{
|
||||||
|
/// Construct a new empty `BiHashMap`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a new mapping into the `BiHashMap`
|
||||||
|
pub fn insert(&mut self, left: Left, right: Right) {
|
||||||
|
self.left_to_right.insert(left.clone(), right.clone());
|
||||||
|
self.right_to_left.insert(right, left);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the right value for a given left value
|
||||||
|
pub fn get_right(&self, left: &Left) -> Option<&Right> {
|
||||||
|
self.left_to_right.get(left)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the left value for a given right value
|
||||||
|
pub fn get_left(&self, right: &Right) -> Option<&Left> {
|
||||||
|
self.right_to_left.get(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a mapping from the `BiHashMap`
|
||||||
|
pub fn remove(&mut self, left: &Left, right: &Right) {
|
||||||
|
self.left_to_right.remove(left);
|
||||||
|
self.right_to_left.remove(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a mapping from the `BiHashMap` by left value
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn remove_left(&mut self, left: &Left) {
|
||||||
|
if let Some(right) = self.left_to_right.remove(left) {
|
||||||
|
self.right_to_left.remove(&right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a mapping from the `BiHashMap` by right value
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn remove_right(&mut self, right: &Right) {
|
||||||
|
if let Some(left) = self.right_to_left.remove(right) {
|
||||||
|
self.left_to_right.remove(&left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Left, Right> Default for BiHashMap<Left, Right> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
left_to_right: FxHashMap::default(),
|
||||||
|
right_to_left: FxHashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_insert() {
|
||||||
|
let mut bimap = BiHashMap::new();
|
||||||
|
bimap.insert(1, "one");
|
||||||
|
bimap.insert(2, "two");
|
||||||
|
bimap.insert(3, "three");
|
||||||
|
|
||||||
|
assert_eq!(bimap.get_right(&1), Some(&"one"));
|
||||||
|
assert_eq!(bimap.get_right(&2), Some(&"two"));
|
||||||
|
assert_eq!(bimap.get_right(&3), Some(&"three"));
|
||||||
|
|
||||||
|
assert_eq!(bimap.get_left(&"one"), Some(&1));
|
||||||
|
assert_eq!(bimap.get_left(&"two"), Some(&2));
|
||||||
|
assert_eq!(bimap.get_left(&"three"), Some(&3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove() {
|
||||||
|
let mut bimap = BiHashMap::new();
|
||||||
|
bimap.insert(1, "one");
|
||||||
|
bimap.insert(2, "two");
|
||||||
|
bimap.insert(3, "three");
|
||||||
|
|
||||||
|
bimap.remove(&1, &"one");
|
||||||
|
assert_eq!(bimap.get_right(&1), None);
|
||||||
|
assert_eq!(bimap.get_left(&"one"), None);
|
||||||
|
|
||||||
|
bimap.remove_left(&2);
|
||||||
|
assert_eq!(bimap.get_right(&2), None);
|
||||||
|
assert_eq!(bimap.get_left(&"two"), None);
|
||||||
|
|
||||||
|
bimap.remove_right(&"three");
|
||||||
|
assert_eq!(bimap.get_right(&3), None);
|
||||||
|
assert_eq!(bimap.get_left(&"three"), None);
|
||||||
|
}
|
||||||
|
}
|
92
libs/fast-nat/src/cpnat.rs
Normal file
92
libs/fast-nat/src/cpnat.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::{bimap::BiHashMap, timeout::MaybeTimeout};
|
||||||
|
|
||||||
|
/// A table of network address mappings across IPv4 and IPv6
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CrossProtocolNetworkAddressTable {
|
||||||
|
/// Internal address map
|
||||||
|
addr_map: BiHashMap<u32, u128>,
|
||||||
|
/// Secondary map used to keep track of timeouts
|
||||||
|
timeouts: FxHashMap<(u32, u128), MaybeTimeout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CrossProtocolNetworkAddressTable {
|
||||||
|
/// Construct a new empty `CrossProtocolNetworkAddressTable`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
addr_map: BiHashMap::new(),
|
||||||
|
timeouts: FxHashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prune all old mappings
|
||||||
|
pub fn prune(&mut self) {
|
||||||
|
log::trace!("Pruning old network address mappings");
|
||||||
|
|
||||||
|
// Compare all mappings against a common timestamp
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
|
// Remove all old mappings from both the bimap and the timeouts map
|
||||||
|
self.timeouts.retain(|(left, right), timeout| {
|
||||||
|
match timeout {
|
||||||
|
// Retain all indefinite mappings
|
||||||
|
MaybeTimeout::Never => true,
|
||||||
|
// Only retain mappings that haven't timed out yet
|
||||||
|
MaybeTimeout::After { duration, start } => {
|
||||||
|
let should_retain = now.duration_since(*start) < *duration;
|
||||||
|
if !should_retain {
|
||||||
|
log::trace!(
|
||||||
|
"Mapping {:?} -> {:?} has timed out and will be removed",
|
||||||
|
left,
|
||||||
|
right
|
||||||
|
);
|
||||||
|
self.addr_map.remove(left, right);
|
||||||
|
}
|
||||||
|
should_retain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a new indefinite mapping
|
||||||
|
pub fn insert_indefinite<T4: Into<u32>, T6: Into<u128>>(&mut self, ipv4: T4, ipv6: T6) {
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
self.prune();
|
||||||
|
let (ipv4, ipv6) = (ipv4.into(), ipv6.into());
|
||||||
|
self.addr_map.insert(ipv4, ipv6);
|
||||||
|
self.timeouts.insert(
|
||||||
|
(ipv4, ipv6),
|
||||||
|
MaybeTimeout::After {
|
||||||
|
duration,
|
||||||
|
start: std::time::Instant::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
}
|
9
libs/fast-nat/src/lib.rs
Normal file
9
libs/fast-nat/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
mod bimap;
|
||||||
|
mod cpnat;
|
||||||
|
mod nat;
|
||||||
|
mod timeout;
|
||||||
|
|
||||||
|
pub use cpnat::CrossProtocolNetworkAddressTable;
|
||||||
|
pub use nat::NetworkAddressTable;
|
87
libs/fast-nat/src/nat.rs
Normal file
87
libs/fast-nat/src/nat.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::{bimap::BiHashMap, timeout::MaybeTimeout};
|
||||||
|
|
||||||
|
/// A table of network address mappings
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NetworkAddressTable {
|
||||||
|
/// Internal address map
|
||||||
|
addr_map: BiHashMap<u32, u32>,
|
||||||
|
/// Secondary map used to keep track of timeouts
|
||||||
|
timeouts: FxHashMap<(u32, u32), MaybeTimeout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkAddressTable {
|
||||||
|
/// Construct a new empty `NetworkAddressTable`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
addr_map: BiHashMap::new(),
|
||||||
|
timeouts: FxHashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prune all old mappings
|
||||||
|
pub fn prune(&mut self) {
|
||||||
|
log::trace!("Pruning old network address mappings");
|
||||||
|
|
||||||
|
// Compare all mappings against a common timestamp
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
|
// Remove all old mappings from both the bimap and the timeouts map
|
||||||
|
self.timeouts.retain(|(left, right), timeout| {
|
||||||
|
match timeout {
|
||||||
|
// Retain all indefinite mappings
|
||||||
|
MaybeTimeout::Never => true,
|
||||||
|
// Only retain mappings that haven't timed out yet
|
||||||
|
MaybeTimeout::After { duration, start } => {
|
||||||
|
let should_retain = now.duration_since(*start) < *duration;
|
||||||
|
if !should_retain {
|
||||||
|
log::trace!(
|
||||||
|
"Mapping {:?} -> {:?} has timed out and will be removed",
|
||||||
|
left,
|
||||||
|
right
|
||||||
|
);
|
||||||
|
self.addr_map.remove(left, right);
|
||||||
|
}
|
||||||
|
should_retain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a new indefinite mapping
|
||||||
|
pub fn insert_indefinite<T: Into<u32>>(&mut self, left: T, right: T) {
|
||||||
|
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) {
|
||||||
|
self.prune();
|
||||||
|
let (left, right) = (left.into(), right.into());
|
||||||
|
self.addr_map.insert(left, right);
|
||||||
|
self.timeouts.insert(
|
||||||
|
(left, right),
|
||||||
|
MaybeTimeout::After {
|
||||||
|
duration,
|
||||||
|
start: std::time::Instant::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
|
}
|
13
libs/fast-nat/src/timeout.rs
Normal file
13
libs/fast-nat/src/timeout.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Describes a possible timeout for a mapping
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum MaybeTimeout {
|
||||||
|
/// Indicates that a mapping should never time out
|
||||||
|
Never,
|
||||||
|
/// Indicates that a mapping should time out after a given duration
|
||||||
|
After {
|
||||||
|
duration: Duration,
|
||||||
|
start: std::time::Instant,
|
||||||
|
},
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user