use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
use std::time::{Duration, Instant};
use log::info;
use crate::pool::LeasePool;
pub struct Nat {
pool: LeasePool,
by_peer: HashMap<SocketAddr, Session>,
by_internal: HashMap<Ipv4Addr, SocketAddr>,
}
#[derive(Clone, Copy)]
struct Session {
internal: Ipv4Addr,
client_ip: Ipv4Addr,
}
pub enum Ingress {
Rewritten(Ipv4Addr),
Exhausted,
Invalid,
}
impl Nat {
pub fn new(server_ip: Ipv4Addr, netmask: Ipv4Addr, ttl: Duration) -> Self {
Self {
pool: LeasePool::new(server_ip, netmask, ttl),
by_peer: HashMap::new(),
by_internal: HashMap::new(),
}
}
pub fn capacity(&self) -> usize {
self.pool.capacity()
}
pub fn ingress(&mut self, peer: SocketAddr, pkt: &mut [u8], now: Instant) -> Ingress {
let src = match parse_src(pkt) {
Some(ip) => ip,
None => return Ingress::Invalid,
};
let internal = match self.by_peer.get(&peer) {
Some(session) => {
self.pool.refresh(session.internal, now);
session.internal
}
None => {
let internal = match self.pool.allocate(now) {
Some(ip) => ip,
None => return Ingress::Exhausted,
};
self.by_peer.insert(
peer,
Session {
internal,
client_ip: src,
},
);
self.by_internal.insert(internal, peer);
info!("nat: {peer} (as {src}) -> internal {internal}");
internal
}
};
rewrite_addr(pkt, SRC_OFFSET, internal);
Ingress::Rewritten(internal)
}
pub fn touch(&mut self, peer: SocketAddr, now: Instant) {
if let Some(session) = self.by_peer.get(&peer) {
self.pool.refresh(session.internal, now);
}
}
pub fn egress(&mut self, pkt: &mut [u8], now: Instant) -> Option<SocketAddr> {
let dst = parse_dst(pkt)?;
let peer = *self.by_internal.get(&dst)?;
let session = *self.by_peer.get(&peer)?;
self.pool.refresh(dst, now);
rewrite_addr(pkt, DST_OFFSET, session.client_ip);
Some(peer)
}
pub fn reap(&mut self, now: Instant) -> usize {
let freed = self.pool.reap(now);
for ip in &freed {
if let Some(peer) = self.by_internal.remove(ip) {
self.by_peer.remove(&peer);
info!("nat: reclaimed idle mapping {ip} ({peer})");
}
}
freed.len()
}
}
const SRC_OFFSET: usize = 12;
const DST_OFFSET: usize = 16;
fn parse_src(pkt: &[u8]) -> Option<Ipv4Addr> {
addr_at(pkt, SRC_OFFSET)
}
fn parse_dst(pkt: &[u8]) -> Option<Ipv4Addr> {
addr_at(pkt, DST_OFFSET)
}
fn addr_at(pkt: &[u8], off: usize) -> Option<Ipv4Addr> {
if pkt.len() < 20 || (pkt[0] >> 4) != 4 {
return None;
}
Some(Ipv4Addr::new(
pkt[off],
pkt[off + 1],
pkt[off + 2],
pkt[off + 3],
))
}
fn rewrite_addr(pkt: &mut [u8], off: usize, new: Ipv4Addr) {
let old: [u8; 4] = [pkt[off], pkt[off + 1], pkt[off + 2], pkt[off + 3]];
let new = new.octets();
let ip_ck = u16::from_be_bytes([pkt[10], pkt[11]]);
let ip_ck = adjust_checksum(ip_ck, &old, &new);
pkt[10..12].copy_from_slice(&ip_ck.to_be_bytes());
let ihl = (pkt[0] & 0x0f) as usize * 4;
let frag_off = (u16::from_be_bytes([pkt[6], pkt[7]]) & 0x1fff) != 0;
if !frag_off {
match pkt[9] {
6 if pkt.len() >= ihl + 18 => {
let pos = ihl + 16;
let ck = u16::from_be_bytes([pkt[pos], pkt[pos + 1]]);
let ck = adjust_checksum(ck, &old, &new);
pkt[pos..pos + 2].copy_from_slice(&ck.to_be_bytes());
}
17 if pkt.len() >= ihl + 8 => {
let pos = ihl + 6;
let ck = u16::from_be_bytes([pkt[pos], pkt[pos + 1]]);
if ck != 0 {
let ck = adjust_checksum(ck, &old, &new);
pkt[pos..pos + 2].copy_from_slice(&ck.to_be_bytes());
}
}
_ => {}
}
}
pkt[off..off + 4].copy_from_slice(&new);
}
fn adjust_checksum(check: u16, old: &[u8; 4], new: &[u8; 4]) -> u16 {
let mut sum: u32 = (!check) as u32;
for i in (0..4).step_by(2) {
let m = u16::from_be_bytes([old[i], old[i + 1]]);
let mp = u16::from_be_bytes([new[i], new[i + 1]]);
sum += (!m) as u32 & 0xffff;
sum += mp as u32;
}
while (sum >> 16) != 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!(sum as u16)
}
#[cfg(test)]
mod tests {
use super::*;
fn ones_complement(bytes: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < bytes.len() {
sum += u16::from_be_bytes([bytes[i], bytes[i + 1]]) as u32;
i += 2;
}
if i < bytes.len() {
sum += (bytes[i] as u32) << 8;
}
while (sum >> 16) != 0 {
sum = (sum & 0xffff) + (sum >> 16);
}
!(sum as u16)
}
fn udp_packet(src: Ipv4Addr, dst: Ipv4Addr) -> Vec<u8> {
let payload = *b"ping";
let total = 20 + 8 + payload.len();
let mut p = vec![0u8; total];
p[0] = 0x45;
p[2..4].copy_from_slice(&(total as u16).to_be_bytes());
p[8] = 64; p[9] = 17; p[12..16].copy_from_slice(&src.octets());
p[16..20].copy_from_slice(&dst.octets());
let ck = ones_complement(&p[0..20]);
p[10..12].copy_from_slice(&ck.to_be_bytes());
p[20..22].copy_from_slice(&1111u16.to_be_bytes()); p[22..24].copy_from_slice(&53u16.to_be_bytes()); p[24..26].copy_from_slice(&((8 + payload.len()) as u16).to_be_bytes());
p[28..32].copy_from_slice(&payload);
let mut pseudo = Vec::new();
pseudo.extend_from_slice(&src.octets());
pseudo.extend_from_slice(&dst.octets());
pseudo.push(0);
pseudo.push(17);
pseudo.extend_from_slice(&((8 + payload.len()) as u16).to_be_bytes());
pseudo.extend_from_slice(&p[20..]);
let ck = ones_complement(&pseudo);
p[26..28].copy_from_slice(&ck.to_be_bytes());
p
}
fn ip_checksum_ok(p: &[u8]) -> bool {
ones_complement(&p[0..20]) == 0
}
fn udp_checksum_ok(p: &[u8]) -> bool {
let mut pseudo = Vec::new();
pseudo.extend_from_slice(&p[12..16]); pseudo.extend_from_slice(&p[16..20]); pseudo.push(0);
pseudo.push(17);
pseudo.extend_from_slice(&((p.len() - 20) as u16).to_be_bytes());
pseudo.extend_from_slice(&p[20..]);
ones_complement(&pseudo) == 0
}
#[test]
fn rewrite_keeps_checksums_valid() {
let mut p = udp_packet(Ipv4Addr::new(10, 9, 0, 2), Ipv4Addr::new(8, 8, 8, 8));
assert!(ip_checksum_ok(&p) && udp_checksum_ok(&p));
rewrite_addr(&mut p, SRC_OFFSET, Ipv4Addr::new(10, 9, 0, 37));
assert_eq!(parse_src(&p), Some(Ipv4Addr::new(10, 9, 0, 37)));
assert!(ip_checksum_ok(&p), "IP checksum after src rewrite");
assert!(udp_checksum_ok(&p), "UDP checksum after src rewrite");
rewrite_addr(&mut p, DST_OFFSET, Ipv4Addr::new(10, 9, 0, 2));
assert_eq!(parse_dst(&p), Some(Ipv4Addr::new(10, 9, 0, 2)));
assert!(ip_checksum_ok(&p) && udp_checksum_ok(&p));
}
#[test]
fn ingress_allocates_distinct_internals_per_peer() {
let mut nat = Nat::new(
Ipv4Addr::new(10, 9, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
Duration::from_secs(120),
);
let now = Instant::now();
let p1: SocketAddr = "203.0.113.1:5000".parse().unwrap();
let p2: SocketAddr = "203.0.113.2:5000".parse().unwrap();
let mut a = udp_packet(Ipv4Addr::new(10, 9, 0, 2), Ipv4Addr::new(8, 8, 8, 8));
let mut b = udp_packet(Ipv4Addr::new(10, 9, 0, 2), Ipv4Addr::new(8, 8, 8, 8));
let ia = match nat.ingress(p1, &mut a, now) {
Ingress::Rewritten(ip) => ip,
_ => panic!("ingress p1"),
};
let ib = match nat.ingress(p2, &mut b, now) {
Ingress::Rewritten(ip) => ip,
_ => panic!("ingress p2"),
};
assert_ne!(ia, ib);
let mut a2 = udp_packet(Ipv4Addr::new(10, 9, 0, 2), Ipv4Addr::new(8, 8, 8, 8));
assert!(matches!(nat.ingress(p1, &mut a2, now), Ingress::Rewritten(ip) if ip == ia));
}
#[test]
fn egress_maps_back_to_the_right_peer_and_placeholder() {
let mut nat = Nat::new(
Ipv4Addr::new(10, 9, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
Duration::from_secs(120),
);
let now = Instant::now();
let peer: SocketAddr = "203.0.113.9:5000".parse().unwrap();
let mut up = udp_packet(Ipv4Addr::new(10, 9, 0, 2), Ipv4Addr::new(8, 8, 8, 8));
let internal = match nat.ingress(peer, &mut up, now) {
Ingress::Rewritten(ip) => ip,
_ => panic!("ingress"),
};
let mut down = udp_packet(Ipv4Addr::new(8, 8, 8, 8), internal);
let to = nat.egress(&mut down, now);
assert_eq!(to, Some(peer));
assert_eq!(parse_dst(&down), Some(Ipv4Addr::new(10, 9, 0, 2)));
assert!(ip_checksum_ok(&down) && udp_checksum_ok(&down));
let mut stray = udp_packet(Ipv4Addr::new(8, 8, 8, 8), Ipv4Addr::new(10, 9, 0, 200));
assert_eq!(nat.egress(&mut stray, now), None);
}
#[test]
fn reap_frees_idle_mappings() {
let mut nat = Nat::new(
Ipv4Addr::new(10, 9, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
Duration::from_secs(10),
);
let t0 = Instant::now();
let peer: SocketAddr = "203.0.113.5:5000".parse().unwrap();
let mut up = udp_packet(Ipv4Addr::new(10, 9, 0, 2), Ipv4Addr::new(8, 8, 8, 8));
let internal = match nat.ingress(peer, &mut up, t0) {
Ingress::Rewritten(ip) => ip,
_ => panic!("ingress"),
};
assert_eq!(nat.reap(t0 + Duration::from_secs(11)), 1);
let mut down = udp_packet(Ipv4Addr::new(8, 8, 8, 8), internal);
assert_eq!(nat.egress(&mut down, t0 + Duration::from_secs(11)), None);
}
}