From f4154154ae6fa9b08435c67f81d88b23175a0e00 Mon Sep 17 00:00:00 2001 From: Nicolas Kagami Date: Thu, 6 Aug 2026 10:46:25 -0300 Subject: [PATCH] unify the per-family NAT mapping code Replace the duplicated v4/v6 mapping storage and logic with a single NatEntry type and a NatMap generic over NatAddress; the public API keeps its per-family signatures as thin wrappers. --- dpd/src/api_server.rs | 35 ++- dpd/src/main.rs | 4 +- dpd/src/nat.rs | 496 ++++++++++++------------------------------ dpd/src/rpw/mod.rs | 4 +- dpd/src/table/nat.rs | 24 ++ 5 files changed, 180 insertions(+), 383 deletions(-) diff --git a/dpd/src/api_server.rs b/dpd/src/api_server.rs index e65d6ddb..ea111235 100644 --- a/dpd/src/api_server.rs +++ b/dpd/src/api_server.rs @@ -105,13 +105,14 @@ use crate::attached_subnet; use crate::counters; #[cfg(feature = "multicast")] use crate::mcast; +use crate::nat; use crate::oxstats; use crate::rpw::Task; use crate::switch_port::FixedSideDevice; use crate::switch_port::LedState; use crate::transceivers::PowerState; use crate::types::DpdError; -use crate::{Switch, arp, loopback, nat, ports, route}; +use crate::{Switch, arp, loopback, ports, route}; use common::attached_subnet::AttachedSubnetEntry; use common::nat::{Ipv4Nat, Ipv6Nat}; use common::network::{InstanceTarget, MacAddr, NatTarget}; @@ -1422,7 +1423,7 @@ impl DpdApi for DpdApiImpl { WhichPage::Next(Ipv6Token { ip }) => Some(*ip), }; - let entries = nat::get_ipv6_addrs_range( + let entries = nat::get_addrs_range( switch, last_addr, usize::try_from(max).expect("invalid usize"), @@ -1449,7 +1450,7 @@ impl DpdApi for DpdApiImpl { WhichPage::Next(NatToken { port }) => Some(*port), }; - let entries = nat::get_ipv6_mappings_range( + let entries = nat::get_mappings_range( switch, params.ipv6, port, @@ -1469,8 +1470,7 @@ impl DpdApi for DpdApiImpl { ) -> Result, HttpError> { let switch: &Switch = rqctx.context(); let params = path.into_inner(); - match nat::get_ipv6_mapping(switch, params.ipv6, params.low, params.low) - { + match nat::get_mapping(switch, params.ipv6, params.low, params.low) { Ok(tgt) => Ok(HttpResponseOk(tgt)), Err(e) => Err(e.into()), } @@ -1483,7 +1483,7 @@ impl DpdApi for DpdApiImpl { ) -> Result { let switch: &Switch = rqctx.context(); let params = path.into_inner(); - match nat::set_ipv6_mapping( + match nat::add_mapping( switch, params.ipv6, params.low, @@ -1501,7 +1501,7 @@ impl DpdApi for DpdApiImpl { ) -> Result { let switch: &Switch = rqctx.context(); let params = path.into_inner(); - nat::clear_ipv6_mapping(switch, params.ipv6, params.low, params.low) + nat::remove_mapping(switch, params.ipv6, params.low, params.low) .map(|_| HttpResponseDeleted()) .map_err(HttpError::from) } @@ -1511,7 +1511,7 @@ impl DpdApi for DpdApiImpl { ) -> Result { let switch: &Switch = rqctx.context(); - match nat::reset_ipv6(switch) { + match nat::reset::(switch) { Ok(_) => Ok(HttpResponseUpdatedNoContent()), Err(e) => Err(e.into()), } @@ -1530,7 +1530,7 @@ impl DpdApi for DpdApiImpl { WhichPage::Next(Ipv4Token { ip }) => Some(*ip), }; - let entries = nat::get_ipv4_addrs_range( + let entries = nat::get_addrs_range( switch, last_addr, usize::try_from(max).expect("invalid usize"), @@ -1558,7 +1558,7 @@ impl DpdApi for DpdApiImpl { WhichPage::Next(NatToken { port }) => Some(*port), }; - let entries = nat::get_ipv4_mappings_range( + let entries = nat::get_mappings_range( switch, params.ipv4, port, @@ -1578,8 +1578,7 @@ impl DpdApi for DpdApiImpl { ) -> Result, HttpError> { let switch: &Switch = rqctx.context(); let params = path.into_inner(); - match nat::get_ipv4_mapping(switch, params.ipv4, params.low, params.low) - { + match nat::get_mapping(switch, params.ipv4, params.low, params.low) { Ok(tgt) => Ok(HttpResponseOk(tgt)), Err(e) => Err(e.into()), } @@ -1592,7 +1591,7 @@ impl DpdApi for DpdApiImpl { ) -> Result { let switch: &Switch = rqctx.context(); let params = path.into_inner(); - match nat::set_ipv4_mapping( + match nat::add_mapping( switch, params.ipv4, params.low, @@ -1610,7 +1609,7 @@ impl DpdApi for DpdApiImpl { ) -> Result { let switch: &Switch = rqctx.context(); let params = path.into_inner(); - nat::clear_ipv4_mapping(switch, params.ipv4, params.low, params.low) + nat::remove_mapping(switch, params.ipv4, params.low, params.low) .map(|_| HttpResponseDeleted()) .map_err(HttpError::from) } @@ -1620,7 +1619,7 @@ impl DpdApi for DpdApiImpl { ) -> Result { let switch: &Switch = rqctx.context(); - match nat::reset_ipv4(switch) { + match nat::reset::(switch) { Ok(_) => Ok(HttpResponseUpdatedNoContent()), Err(e) => Err(e.into()), } @@ -1744,11 +1743,11 @@ impl DpdApi for DpdApiImpl { error!(switch.log, "failed to clear all link state: {:?}", e); err = Some(e); } - if let Err(e) = nat::reset_ipv4(switch) { + if let Err(e) = nat::reset::(switch) { error!(switch.log, "failed to reset ipv4 nat table: {:?}", e); err = Some(e); } - if let Err(e) = nat::reset_ipv6(switch) { + if let Err(e) = nat::reset::(switch) { error!(switch.log, "failed to reset ipv6 nat table: {:?}", e); err = Some(e); } @@ -1913,7 +1912,7 @@ impl DpdApi for DpdApiImpl { ) -> Result, HttpError> { let switch = rqctx.context(); - Ok(HttpResponseOk(nat::get_nat_generation(switch))) + Ok(HttpResponseOk(nat::generation(switch))) } async fn nat_trigger_update( diff --git a/dpd/src/main.rs b/dpd/src/main.rs index e978abbd..1e2d91c4 100644 --- a/dpd/src/main.rs +++ b/dpd/src/main.rs @@ -193,7 +193,7 @@ pub struct Switch { pub links: Mutex, pub routes: TokioMutex, pub arp: Mutex, - pub nat: Mutex, + pub nat: nat::Nat, pub attached_subnet: Mutex, pub loopback: Mutex, pub identifiers: Mutex>, @@ -308,7 +308,7 @@ impl Switch { counters, routes: TokioMutex::new(route_data), arp: Mutex::new(arp::init()), - nat: Mutex::new(nat::init()), + nat: nat::Nat::new(), attached_subnet: Mutex::new(attached_subnet::init()), loopback: Mutex::new(loopback::init()), switch_ports, diff --git a/dpd/src/nat.rs b/dpd/src/nat.rs index 5234938d..c6031132 100644 --- a/dpd/src/nat.rs +++ b/dpd/src/nat.rs @@ -9,12 +9,12 @@ use std::collections::BTreeMap; use std::fmt; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::ops::Bound; +use std::sync::{Mutex, MutexGuard}; use crate::Switch; use crate::table; -use crate::table::nat::{add_entry, delete_entry}; +use crate::table::nat::{NatAddress, add_entry, delete_entry}; use crate::types::{DpdError, DpdResult}; -use common::nat::{Ipv4Nat, Ipv6Nat}; use common::network::NatTarget; /// An inclusive range of ports, guaranteed by construction to have @@ -62,42 +62,17 @@ impl fmt::Display for PortRange { } } -#[derive(PartialEq)] -pub(crate) struct Ipv6NatEntry { - pub l4_ports: PortRange, - pub tgt: NatTarget, -} - -impl fmt::Display for Ipv6NatEntry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} -> {}", self.l4_ports, self.tgt) - } -} - #[derive(Clone, PartialEq)] -pub(crate) struct Ipv4NatEntry { +pub(crate) struct NatEntry { pub l4_ports: PortRange, pub tgt: NatTarget, } -impl fmt::Display for Ipv4NatEntry { +impl fmt::Display for NatEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} -> {}", self.l4_ports, self.tgt) } } -pub struct NatData { - ipv6_mappings: BTreeMap>, - ipv4_mappings: BTreeMap>, - ipv4_generation: i64, -} - -fn ipv6_entry(ipv6: Ipv6Addr, e: &Ipv6NatEntry) -> String { - format!("{ipv6}/{e}") -} - -fn ipv4_entry(ipv4: Ipv4Addr, e: &Ipv4NatEntry) -> String { - format!("{ipv4}/{e}") -} /// find index of first mapping that overlaps with supplied port range fn find_first_mapping( @@ -178,227 +153,115 @@ fn test_mapping() { assert_eq!(space(3, 8), None); } -pub fn get_ipv6_addrs_range( - switch: &Switch, - last_addr: Option, - mut max: usize, -) -> Vec { - max = std::cmp::min(max, 64); - let nat = switch.nat.lock().unwrap(); - - let range = match last_addr { - Some(a) => (Bound::Excluded(a), Bound::Unbounded), - None => (Bound::Unbounded, Bound::Unbounded), - }; +type NatMappings = BTreeMap>; - nat.ipv6_mappings.range(range).take(max).map(|(ip, _)| *ip).collect() +pub struct NatData { + ipv4: NatMappings, + ipv6: NatMappings, + generation: i64, } -/// Paginates through `Ipv6Nat` using `last_port` as the starting offset -pub fn get_ipv6_mappings_range( - switch: &Switch, - external: Ipv6Addr, - last_port: Option, - mut max: usize, -) -> Vec { - max = std::cmp::min(max, 64); - let nat = switch.nat.lock().unwrap(); - let mappings = match nat.ipv6_mappings.get(&external) { - Some(m) => m, - None => return Vec::new(), - }; - - let port = match last_port { - None => 0, - Some(l) => l + 1, - }; +/// Ties an address family to its NAT table inside `NatData`. +pub(crate) trait NatFamily: NatAddress { + fn mappings(data: &mut NatData) -> &mut NatMappings; +} - let mut entries = Vec::new(); - - for m in mappings { - if m.l4_ports.low >= port { - entries.push(Ipv6Nat { - external, - low: m.l4_ports.low, - high: m.l4_ports.high, - target: m.tgt, - }); - if entries.len() >= max { - break; - } - } +impl NatFamily for Ipv4Addr { + fn mappings(data: &mut NatData) -> &mut NatMappings { + &mut data.ipv4 } - entries } -/// Find the first `NatTarget` where its `Ipv6NatEntry` matches the provided -/// `Ipv6Addr` and overlaps with the provided port range -pub fn get_ipv6_mapping( - switch: &Switch, - nat_ip: Ipv6Addr, - low: u16, - high: u16, -) -> DpdResult { - let range = PortRange::new(low, high)?; - let nat = switch.nat.lock().unwrap(); - if let Some(v) = nat.ipv6_mappings.get(&nat_ip) - && let Some(idx) = - find_first_mapping(v.iter().map(|e| e.l4_ports), range) - { - return Ok(v[idx].tgt); +impl NatFamily for Ipv6Addr { + fn mappings(data: &mut NatData) -> &mut NatMappings { + &mut data.ipv6 } - Err(DpdError::Missing("no mapping".into())) } -pub fn set_ipv6_mapping( - switch: &Switch, - nat_ip: Ipv6Addr, - low: u16, - high: u16, - tgt: NatTarget, -) -> DpdResult<()> { - let l4_ports = PortRange::new(low, high)?; - let new_entry = Ipv6NatEntry { l4_ports, tgt }; - let full = ipv6_entry(nat_ip, &new_entry); - trace!(switch.log, "adding nat entry {}", full); +pub struct Nat(Mutex); - let mut nat = switch.nat.lock().unwrap(); - let (entries, idx) = match nat.ipv6_mappings.get_mut(&nat_ip) { - Some(e) => { - if e.contains(&new_entry) { - // entry already exists - return Ok(()); - } - match find_space(e.iter().map(|x| x.l4_ports), l4_ports) { - Some(i) => (e, i), - None => { - trace!( - switch.log, - "unable to add nat entry {}: conflicting mapping", full - ); - return Err(DpdError::Exists("conflicting mapping".into())); - } - } - } - None => { - nat.ipv6_mappings.insert(nat_ip, Vec::new()); - (nat.ipv6_mappings.get_mut(&nat_ip).unwrap(), 0) - } - }; +impl Nat { + pub(crate) fn new() -> Self { + Nat(Mutex::new(NatData { + ipv4: BTreeMap::new(), + ipv6: BTreeMap::new(), + generation: 0, + })) + } - match add_entry(switch, nat_ip, l4_ports, tgt) { - Err(e) => { - error!(switch.log, "failed to add {}: {:?}", full, e); - Err(e) - } - _ => { - debug!(switch.log, "added nat entry {}", full); - entries.insert(idx, new_entry); - Ok(()) - } + fn lock(&self) -> MutexGuard<'_, NatData> { + self.0.lock().unwrap() } } -/// Find the first `NatTarget` where its `Ipv6NatEntry` matches the provided -/// `Ipv6Addr` and overlaps with the provided port range, then remove it. -pub fn clear_ipv6_mapping( - switch: &Switch, - nat_ip: Ipv6Addr, - low: u16, - high: u16, -) -> DpdResult<()> { - let range = PortRange::new(low, high)?; - let mut nat = switch.nat.lock().unwrap(); - trace!(switch.log, "clearing nat entry {}/{}-{}", nat_ip, low, high); - - if let Some(mappings) = nat.ipv6_mappings.get_mut(&nat_ip) - && let Some(idx) = - find_first_mapping(mappings.iter().map(|e| e.l4_ports), range) - { - let ent = mappings.remove(idx); - if mappings.is_empty() { - nat.ipv6_mappings.remove(&nat_ip); - } - let full = ipv6_entry(nat_ip, &ent); - return match delete_entry(switch, nat_ip, ent.l4_ports) { - Err(e) => { - error!(switch.log, "failed to clear {}: {:?}", full, e); - Err(e) - } - _ => { - debug!(switch.log, "cleared nat entry {}", full); - Ok(()) - } - }; - } +pub(crate) fn generation(switch: &Switch) -> i64 { + let data = switch.nat.lock(); + trace!(switch.log, "fetching nat generation"); + data.generation +} - Ok(()) +pub(crate) fn set_generation(switch: &Switch, generation: i64) { + let mut data = switch.nat.lock(); + trace!(switch.log, "setting nat generation {generation}"); + data.generation = generation; } -pub fn get_ipv4_addrs_range( +pub(crate) fn get_addrs_range( switch: &Switch, - last_addr: Option, - mut max: usize, -) -> Vec { - max = std::cmp::min(max, 64); - let nat = switch.nat.lock().unwrap(); + last_addr: Option, + max: usize, +) -> Vec { + let max = max.min(64); let range = match last_addr { Some(a) => (Bound::Excluded(a), Bound::Unbounded), None => (Bound::Unbounded, Bound::Unbounded), }; - nat.ipv4_mappings.range(range).take(max).map(|(ip, _)| *ip).collect() + let mut data = switch.nat.lock(); + A::mappings(&mut data).range(range).take(max).map(|(ip, _)| *ip).collect() } -/// Paginates through `Ipv4Nat` using `last_port` as the starting offset -pub fn get_ipv4_mappings_range( +/// Paginates through the mappings for one address, using `last_port` as +/// the starting offset +pub(crate) fn get_mappings_range( switch: &Switch, - external: Ipv4Addr, + external: A, last_port: Option, - mut max: usize, -) -> Vec { - max = std::cmp::min(max, 64); - let nat = switch.nat.lock().unwrap(); - let mappings = match nat.ipv4_mappings.get(&external) { - Some(m) => m, - None => return Vec::new(), - }; + max: usize, +) -> Vec { + let max = max.min(64); let port = match last_port { None => 0, Some(l) => l + 1, }; - let mut entries = Vec::new(); - - for m in mappings { - if m.l4_ports.low >= port { - entries.push(Ipv4Nat { - external, - low: m.l4_ports.low, - high: m.l4_ports.high, - target: m.tgt, - }); - if entries.len() >= max { - break; - } - } - } - entries -} - -/// Find the first `NatTarget` where its `Ipv4NatEntry` matches the provided -/// `Ipv4Addr` and overlaps with the provided port range -pub fn get_ipv4_mapping( + let mut data = switch.nat.lock(); + A::mappings(&mut data) + .get(&external) + .map(|entries| { + entries + .iter() + .filter(|e| e.l4_ports.low >= port) + .take(max) + .map(|e| external.reservation(e.l4_ports, e.tgt)) + .collect() + }) + .unwrap_or_default() +} + +/// Find the first `NatTarget` where its `NatEntry` overlaps with the +/// provided port range +pub(crate) fn get_mapping( switch: &Switch, - nat_ip: Ipv4Addr, + nat_ip: A, low: u16, high: u16, ) -> DpdResult { let range = PortRange::new(low, high)?; - let nat = switch.nat.lock().unwrap(); - if let Some(v) = nat.ipv4_mappings.get(&nat_ip) + let mut data = switch.nat.lock(); + if let Some(v) = A::mappings(&mut data).get(&nat_ip) && let Some(idx) = find_first_mapping(v.iter().map(|e| e.l4_ports), range) { @@ -407,58 +270,33 @@ pub fn get_ipv4_mapping( Err(DpdError::Missing("no mapping".into())) } -pub fn set_mapping( - switch: &Switch, - nat_ip: IpAddr, - low: u16, - high: u16, - tgt: NatTarget, -) -> DpdResult<()> { - match nat_ip { - IpAddr::V4(nat_ip) => set_ipv4_mapping(switch, nat_ip, low, high, tgt), - IpAddr::V6(nat_ip) => set_ipv6_mapping(switch, nat_ip, low, high, tgt), - } -} - -pub fn set_ipv4_mapping( +pub(crate) fn add_mapping( switch: &Switch, - nat_ip: Ipv4Addr, + nat_ip: A, low: u16, high: u16, tgt: NatTarget, ) -> DpdResult<()> { let l4_ports = PortRange::new(low, high)?; - let new_entry = Ipv4NatEntry { l4_ports, tgt }; - let full = ipv4_entry(nat_ip, &new_entry); + let new_entry = NatEntry { l4_ports, tgt }; + let full = format!("{nat_ip}/{new_entry}"); trace!(switch.log, "adding nat entry {}", full); - let mut nat = switch.nat.lock().unwrap(); - let (entries, idx) = match nat.ipv4_mappings.get_mut(&nat_ip) { - Some(e) => { - if e.contains(&new_entry) { - // entry already exists - return Ok(()); - } - match find_space(e.iter().map(|x| x.l4_ports), l4_ports) { - Some(i) => (e, i), - None => { - error!( - switch.log, - "unable to add {}: conflicting mapping", full - ); - return Err(DpdError::Exists("conflicting mapping".into())); - } - } - } - None => { - nat.ipv4_mappings.insert(nat_ip, Vec::new()); - (nat.ipv4_mappings.get_mut(&nat_ip).unwrap(), 0) - } + let mut data = switch.nat.lock(); + let entries = A::mappings(&mut data).entry(nat_ip).or_default(); + if entries.contains(&new_entry) { + // entry already exists + return Ok(()); + } + let Some(idx) = find_space(entries.iter().map(|e| e.l4_ports), l4_ports) + else { + error!(switch.log, "unable to add {}: conflicting mapping", full); + return Err(DpdError::Exists("conflicting mapping".into())); }; match add_entry(switch, nat_ip, l4_ports, tgt) { Err(e) => { - error!(switch.log, "failed to add nat entry {}: {:?}", full, e); + error!(switch.log, "failed to add {}: {:?}", full, e); Err(e) } _ => { @@ -469,42 +307,41 @@ pub fn set_ipv4_mapping( } } -pub fn clear_mapping( +pub(crate) fn set_mapping( switch: &Switch, nat_ip: IpAddr, low: u16, high: u16, + tgt: NatTarget, ) -> DpdResult<()> { match nat_ip { - IpAddr::V4(nat_ip) => clear_ipv4_mapping(switch, nat_ip, low, high), - IpAddr::V6(nat_ip) => clear_ipv6_mapping(switch, nat_ip, low, high), + IpAddr::V4(ip) => add_mapping(switch, ip, low, high, tgt), + IpAddr::V6(ip) => add_mapping(switch, ip, low, high, tgt), } } -/// Find the first `NatTarget` where its `Ipv4NatEntry` matches the provided -/// `Ipv4Addr` and overlaps with the provided port range, then remove it. -pub fn clear_ipv4_mapping( +/// Find the first `NatEntry` that overlaps with the provided port range, +/// then remove it. +pub(crate) fn remove_mapping( switch: &Switch, - nat_ip: Ipv4Addr, + nat_ip: A, low: u16, high: u16, ) -> DpdResult<()> { let range = PortRange::new(low, high)?; - let mut nat = switch.nat.lock().unwrap(); - trace!( - switch.log, - "clearing nat entry covering {}/{}-{}", nat_ip, low, high - ); + trace!(switch.log, "clearing nat entry covering {}/{}", nat_ip, range); - if let Some(mappings) = nat.ipv4_mappings.get_mut(&nat_ip) + let mut data = switch.nat.lock(); + let mappings = A::mappings(&mut data); + if let Some(entries) = mappings.get_mut(&nat_ip) && let Some(idx) = - find_first_mapping(mappings.iter().map(|e| e.l4_ports), range) + find_first_mapping(entries.iter().map(|e| e.l4_ports), range) { - let ent = mappings.remove(idx); - if mappings.is_empty() { - nat.ipv4_mappings.remove(&nat_ip); + let ent = entries.remove(idx); + if entries.is_empty() { + mappings.remove(&nat_ip); } - let full = ipv4_entry(nat_ip, &ent); + let full = format!("{nat_ip}/{ent}"); return match delete_entry(switch, nat_ip, ent.l4_ports) { Err(e) => { error!(switch.log, "failed to clear {}: {:?}", full, e); @@ -520,86 +357,48 @@ pub fn clear_ipv4_mapping( Ok(()) } -pub fn clear_overlapping_mappings( +pub(crate) fn clear_mapping( switch: &Switch, nat_ip: IpAddr, low: u16, high: u16, ) -> DpdResult<()> { match nat_ip { - IpAddr::V4(nat_ip) => { - clear_overlapping_mappings_v4(switch, nat_ip, low, high) - } - IpAddr::V6(nat_ip) => { - clear_overlapping_mappings_v6(switch, nat_ip, low, high) - } + IpAddr::V4(ip) => remove_mapping(switch, ip, low, high), + IpAddr::V6(ip) => remove_mapping(switch, ip, low, high), } } -/// Deletes any `Ipv4NatEntry` where each entry matches the provided -/// `Ipv4Addr` and overlaps with the provided port range -pub fn clear_overlapping_mappings_v4( - switch: &Switch, - nat_ip: Ipv4Addr, - low: u16, - high: u16, -) -> DpdResult<()> { - let range = PortRange::new(low, high)?; - let mut nat = switch.nat.lock().unwrap(); - trace!( - switch.log, - "clearing all nat entries overlapping with {}/{}-{}", nat_ip, low, high - ); - - if let Some(mappings) = nat.ipv4_mappings.get_mut(&nat_ip) { - let mut mappings_to_delete = - find_mappings(mappings.iter().map(|e| e.l4_ports), range); - // delete starting with the last index first, or you'll end up shifting the - // collection underneath you - mappings_to_delete.reverse(); - for idx in mappings_to_delete { - let ent = mappings.remove(idx); - let full = ipv4_entry(nat_ip, &ent); - match delete_entry(switch, nat_ip, ent.l4_ports) { - Err(e) => { - error!(switch.log, "failed to clear {}: {:?}", full, e); - return Err(e); - } - _ => { - debug!(switch.log, "cleared nat entry {}", full); - } - }; - } - if mappings.is_empty() { - nat.ipv4_mappings.remove(&nat_ip); - } - } +pub(crate) fn reset(switch: &Switch) -> DpdResult<()> { + let mut data = switch.nat.lock(); + table::nat::reset::(switch)?; + A::mappings(&mut data).clear(); Ok(()) } -pub fn clear_overlapping_mappings_v6( +/// Deletes any `NatEntry` that overlaps with the provided port range +pub(crate) fn remove_overlapping_mappings( switch: &Switch, - nat_ip: Ipv6Addr, - low: u16, - high: u16, + nat_ip: A, + l4_ports: PortRange, ) -> DpdResult<()> { - let range = PortRange::new(low, high)?; - let mut nat = switch.nat.lock().unwrap(); trace!( switch.log, - "clearing all nat entries overlapping with {}/{}-{}", nat_ip, low, high + "clearing all nat entries overlapping with {}/{}", nat_ip, l4_ports ); - if let Some(mappings) = nat.ipv6_mappings.get_mut(&nat_ip) { + let mut data = switch.nat.lock(); + let mappings = A::mappings(&mut data); + if let Some(entries) = mappings.get_mut(&nat_ip) { let mut mappings_to_delete = - find_mappings(mappings.iter().map(|e| e.l4_ports), range); + find_mappings(entries.iter().map(|e| e.l4_ports), l4_ports); // delete starting with the last index first, or you'll end up shifting the // collection underneath you mappings_to_delete.reverse(); for idx in mappings_to_delete { - let ent = mappings.remove(idx); - let full = ipv6_entry(nat_ip, &ent); + let ent = entries.remove(idx); + let full = format!("{nat_ip}/{ent}"); match delete_entry(switch, nat_ip, ent.l4_ports) { Err(e) => { error!(switch.log, "failed to clear {}: {:?}", full, e); @@ -610,48 +409,23 @@ pub fn clear_overlapping_mappings_v6( } }; } - if mappings.is_empty() { - nat.ipv6_mappings.remove(&nat_ip); + if entries.is_empty() { + mappings.remove(&nat_ip); } } Ok(()) } -pub fn reset_ipv6(switch: &Switch) -> DpdResult<()> { - let mut nat = switch.nat.lock().unwrap(); - - table::nat::reset::(switch)?; - nat.ipv6_mappings.clear(); - Ok(()) -} - -pub fn reset_ipv4(switch: &Switch) -> DpdResult<()> { - let mut nat = switch.nat.lock().unwrap(); - - table::nat::reset::(switch)?; - nat.ipv4_mappings.clear(); - Ok(()) -} - -pub fn set_nat_generation(switch: &Switch, generation: i64) { - let mut nat = switch.nat.lock().unwrap(); - - debug!(switch.log, "setting nat generation"); - nat.ipv4_generation = generation; -} - -pub fn get_nat_generation(switch: &Switch) -> i64 { - let nat = switch.nat.lock().unwrap(); - - debug!(switch.log, "fetching nat generation"); - nat.ipv4_generation -} - -pub fn init() -> NatData { - NatData { - ipv6_mappings: BTreeMap::new(), - ipv4_mappings: BTreeMap::new(), - ipv4_generation: 0, +pub(crate) fn clear_overlapping_mappings( + switch: &Switch, + nat_ip: IpAddr, + low: u16, + high: u16, +) -> DpdResult<()> { + let l4_ports = PortRange::new(low, high)?; + match nat_ip { + IpAddr::V4(ip) => remove_overlapping_mappings(switch, ip, l4_ports), + IpAddr::V6(ip) => remove_overlapping_mappings(switch, ip, l4_ports), } } diff --git a/dpd/src/rpw/mod.rs b/dpd/src/rpw/mod.rs index 5b3ef261..99772309 100644 --- a/dpd/src/rpw/mod.rs +++ b/dpd/src/rpw/mod.rs @@ -101,7 +101,7 @@ pub async fn nat_workflow( wait(timer.clone()).await; debug!(log, "starting nat reconciliation"); - let generation = nat::get_nat_generation(&switch); + let generation = nat::generation(&switch); debug!(log, "we are currently at nat generation: {}", generation); let mut updates = @@ -216,7 +216,7 @@ fn apply_updates( } // update gen if nat entry update was successful generation = entry.r#gen; - nat::set_nat_generation(switch, generation); + nat::set_generation(switch, generation); } generation } diff --git a/dpd/src/table/nat.rs b/dpd/src/table/nat.rs index edba403e..c27e13c7 100644 --- a/dpd/src/table/nat.rs +++ b/dpd/src/table/nat.rs @@ -18,6 +18,7 @@ use aal_macros::*; use crate::Switch; use crate::nat::PortRange; use crate::table::*; +use common::nat::{Ipv4Nat, Ipv6Nat}; use common::network::{MacAddr, NatTarget}; pub(crate) trait NatAddress: Copy + Ord + fmt::Display { @@ -26,9 +27,12 @@ pub(crate) trait NatAddress: Copy + Ord + fmt::Display { type MatchKey: MatchParse + Hash + fmt::Display; type Action: ActionParse; + type Reservation; fn match_key(self, ports: PortRange) -> Self::MatchKey; fn action(tgt: NatTarget) -> Self::Action; + fn reservation(self, ports: PortRange, tgt: NatTarget) + -> Self::Reservation; } pub(crate) fn add_entry( @@ -79,6 +83,7 @@ impl NatAddress for Ipv4Addr { type MatchKey = Ipv4MatchKey; type Action = Ipv4Action; + type Reservation = Ipv4Nat; fn match_key(self, ports: PortRange) -> Ipv4MatchKey { Ipv4MatchKey::new(self, ports.low(), ports.high()) @@ -91,6 +96,15 @@ impl NatAddress for Ipv4Addr { vni: tgt.vni.as_u32(), } } + + fn reservation(self, ports: PortRange, tgt: NatTarget) -> Ipv4Nat { + Ipv4Nat { + external: self, + low: ports.low(), + high: ports.high(), + target: tgt, + } + } } impl NatAddress for Ipv6Addr { @@ -99,6 +113,7 @@ impl NatAddress for Ipv6Addr { type MatchKey = Ipv6MatchKey; type Action = Ipv6Action; + type Reservation = Ipv6Nat; fn match_key(self, ports: PortRange) -> Ipv6MatchKey { Ipv6MatchKey::new(self, ports.low(), ports.high()) @@ -111,6 +126,15 @@ impl NatAddress for Ipv6Addr { vni: tgt.vni.as_u32(), } } + + fn reservation(self, ports: PortRange, tgt: NatTarget) -> Ipv6Nat { + Ipv6Nat { + external: self, + low: ports.low(), + high: ports.high(), + target: tgt, + } + } } #[derive(MatchParse, Hash)]