diff --git a/dpd/src/nat.rs b/dpd/src/nat.rs index 06ada29..1f48b66 100644 --- a/dpd/src/nat.rs +++ b/dpd/src/nat.rs @@ -53,41 +53,201 @@ impl fmt::Display for PortRange { } } -#[derive(PartialEq)] -pub(crate) struct Ipv6NatEntry { +#[derive(Clone, PartialEq)] +pub(crate) struct NatEntry { pub ports: PortRange, pub tgt: NatTarget, } -impl fmt::Display for Ipv6NatEntry { +impl fmt::Display for NatEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} -> {}", self.ports, self.tgt) } } -#[derive(Clone, PartialEq)] -pub(crate) struct Ipv4NatEntry { - pub ports: PortRange, - pub tgt: NatTarget, +struct NatMap { + mappings: BTreeMap>, } -impl fmt::Display for Ipv4NatEntry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} -> {}", self.ports, self.tgt) +impl NatMap { + fn new() -> Self { + NatMap { mappings: BTreeMap::new() } } -} -pub struct NatData { - ipv6_mappings: BTreeMap>, - ipv4_mappings: BTreeMap>, - ipv4_generation: i64, -} -fn ipv6_entry(ipv6: Ipv6Addr, e: &Ipv6NatEntry) -> String { - format!("{ipv6}/{e}") + fn get_addrs_range(&self, 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), + }; + + self.mappings.range(range).take(max).map(|(ip, _)| *ip).collect() + } + + /// Paginates through the mappings for one address, using `last_port` as + /// the starting offset + fn get_mappings_range( + &self, + external: A, + last_port: Option, + max: usize, + ) -> Vec { + let max = max.min(64); + + let port = match last_port { + None => 0, + Some(l) => l + 1, + }; + + self.mappings + .get(&external) + .map(|entries| { + entries + .iter() + .filter(|e| e.ports.low >= port) + .take(max) + .cloned() + .collect() + }) + .unwrap_or_default() + } + + /// Find the first `NatTarget` where its `NatEntry` overlaps with the + /// provided port range + fn get_mapping(&self, nat_ip: A, range: PortRange) -> DpdResult { + if let Some(v) = self.mappings.get(&nat_ip) + && let Some(idx) = + find_first_mapping(v.iter().map(|e| e.ports), range) + { + return Ok(v[idx].tgt); + } + Err(DpdError::Missing("no mapping".into())) + } + + fn add_mapping( + &mut self, + switch: &Switch, + nat_ip: A, + ports: PortRange, + tgt: NatTarget, + ) -> DpdResult<()> { + let new_entry = NatEntry { ports, tgt }; + let full = format!("{nat_ip}/{new_entry}"); + trace!(switch.log, "adding nat entry {}", full); + + let entries = self.mappings.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.ports), ports) + else { + error!(switch.log, "unable to add {}: conflicting mapping", full); + return Err(DpdError::Exists("conflicting mapping".into())); + }; + + match nat_ip.add_entry(switch, ports.low, ports.high, 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(()) + } + } + } + + /// Find the first `NatEntry` that overlaps with the provided port range, + /// then remove it. + fn remove_mapping( + &mut self, + switch: &Switch, + nat_ip: A, + range: PortRange, + ) -> DpdResult<()> { + trace!(switch.log, "clearing nat entry covering {}/{}", nat_ip, range); + + if let Some(entries) = self.mappings.get_mut(&nat_ip) + && let Some(idx) = + find_first_mapping(entries.iter().map(|e| e.ports), range) + { + let ent = entries.remove(idx); + if entries.is_empty() { + self.mappings.remove(&nat_ip); + } + let full = format!("{nat_ip}/{ent}"); + return match nat_ip.delete_entry( + switch, + ent.ports.low, + ent.ports.high, + ) { + Err(e) => { + error!(switch.log, "failed to clear {}: {:?}", full, e); + Err(e) + } + _ => { + debug!(switch.log, "cleared nat entry {}", full); + Ok(()) + } + }; + } + + Ok(()) + } + + /// Deletes any `NatEntry` that overlaps with the provided port range + fn remove_overlapping_mappings( + &mut self, + switch: &Switch, + nat_ip: A, + range: PortRange, + ) -> DpdResult<()> { + trace!( + switch.log, + "clearing all nat entries overlapping with {}/{}", nat_ip, range + ); + + if let Some(entries) = self.mappings.get_mut(&nat_ip) { + let mut mappings_to_delete = + find_mappings(entries.iter().map(|e| e.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 = entries.remove(idx); + let full = format!("{nat_ip}/{ent}"); + match nat_ip.delete_entry(switch, ent.ports.low, ent.ports.high) + { + Err(e) => { + error!(switch.log, "failed to clear {}: {:?}", full, e); + return Err(e); + } + _ => { + debug!(switch.log, "cleared nat entry {}", full); + } + }; + } + if entries.is_empty() { + self.mappings.remove(&nat_ip); + } + } + + Ok(()) + } + + fn reset(&mut self, switch: &Switch) -> DpdResult<()> { + self.mappings.clear(); + A::reset(switch) + } } -fn ipv4_entry(ipv4: Ipv4Addr, e: &Ipv4NatEntry) -> String { - format!("{ipv4}/{e}") +pub struct NatData { + ipv6: NatMap, + ipv4: NatMap, + ipv4_generation: i64, } /// find index of first mapping that overlaps with supplied port range @@ -173,17 +333,9 @@ fn test_mapping() { pub fn get_ipv6_addrs_range( switch: &Switch, last_addr: Option, - mut max: usize, + 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), - }; - - nat.ipv6_mappings.range(range).take(max).map(|(ip, _)| *ip).collect() + switch.nat.lock().unwrap().ipv6.get_addrs_range(last_addr, max) } /// Paginates through `Ipv6Nat` using `last_port` as the starting offset @@ -191,39 +343,25 @@ pub fn get_ipv6_mappings_range( switch: &Switch, external: Ipv6Addr, last_port: Option, - mut max: usize, + 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, - }; - - let mut entries = Vec::new(); - - for m in mappings { - if m.ports.low >= port { - entries.push(Ipv6Nat { - external, - low: m.ports.low, - high: m.ports.high, - target: m.tgt, - }); - if entries.len() >= max { - break; - } - } - } - entries + switch + .nat + .lock() + .unwrap() + .ipv6 + .get_mappings_range(external, last_port, max) + .into_iter() + .map(|m| Ipv6Nat { + external, + low: m.ports.low, + high: m.ports.high, + target: m.tgt, + }) + .collect() } -/// Find the first `NatTarget` where its `Ipv6NatEntry` matches the provided +/// Find the first `NatTarget` where its `NatEntry` matches the provided /// `Ipv6Addr` and overlaps with the provided port range pub fn get_ipv6_mapping( switch: &Switch, @@ -232,13 +370,7 @@ pub fn get_ipv6_mapping( 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.ports), range) - { - return Ok(v[idx].tgt); - } - Err(DpdError::Missing("no mapping".into())) + switch.nat.lock().unwrap().ipv6.get_mapping(nat_ip, range) } pub fn set_ipv6_mapping( @@ -249,48 +381,10 @@ pub fn set_ipv6_mapping( tgt: NatTarget, ) -> DpdResult<()> { let ports = PortRange::new(low, high)?; - let new_entry = Ipv6NatEntry { ports, tgt }; - let full = ipv6_entry(nat_ip, &new_entry); - trace!(switch.log, "adding nat entry {}", full); - - 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.ports), 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) - } - }; - - match nat_ip.add_entry(switch, low, high, 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(()) - } - } + switch.nat.lock().unwrap().ipv6.add_mapping(switch, nat_ip, ports, tgt) } -/// Find the first `NatTarget` where its `Ipv6NatEntry` matches the provided +/// Find the first `NatTarget` where its `NatEntry` matches the provided /// `Ipv6Addr` and overlaps with the provided port range, then remove it. pub fn clear_ipv6_mapping( switch: &Switch, @@ -299,48 +393,15 @@ pub fn clear_ipv6_mapping( 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.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 nat_ip.delete_entry(switch, ent.ports.low, ent.ports.high) - { - Err(e) => { - error!(switch.log, "failed to clear {}: {:?}", full, e); - Err(e) - } - _ => { - debug!(switch.log, "cleared nat entry {}", full); - Ok(()) - } - }; - } - - Ok(()) + switch.nat.lock().unwrap().ipv6.remove_mapping(switch, nat_ip, range) } pub fn get_ipv4_addrs_range( switch: &Switch, last_addr: Option, - mut max: usize, + 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), - }; - - nat.ipv4_mappings.range(range).take(max).map(|(ip, _)| *ip).collect() + switch.nat.lock().unwrap().ipv4.get_addrs_range(last_addr, max) } /// Paginates through `Ipv4Nat` using `last_port` as the starting offset @@ -348,39 +409,25 @@ pub fn get_ipv4_mappings_range( switch: &Switch, external: Ipv4Addr, last_port: Option, - mut max: usize, + 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(), - }; - - let port = match last_port { - None => 0, - Some(l) => l + 1, - }; - - let mut entries = Vec::new(); - - for m in mappings { - if m.ports.low >= port { - entries.push(Ipv4Nat { - external, - low: m.ports.low, - high: m.ports.high, - target: m.tgt, - }); - if entries.len() >= max { - break; - } - } - } - entries + switch + .nat + .lock() + .unwrap() + .ipv4 + .get_mappings_range(external, last_port, max) + .into_iter() + .map(|m| Ipv4Nat { + external, + low: m.ports.low, + high: m.ports.high, + target: m.tgt, + }) + .collect() } -/// Find the first `NatTarget` where its `Ipv4NatEntry` matches the provided +/// Find the first `NatTarget` where its `NatEntry` matches the provided /// `Ipv4Addr` and overlaps with the provided port range pub fn get_ipv4_mapping( switch: &Switch, @@ -389,13 +436,7 @@ pub fn get_ipv4_mapping( 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 Some(idx) = find_first_mapping(v.iter().map(|e| e.ports), range) - { - return Ok(v[idx].tgt); - } - Err(DpdError::Missing("no mapping".into())) + switch.nat.lock().unwrap().ipv4.get_mapping(nat_ip, range) } pub fn set_mapping( @@ -419,45 +460,7 @@ pub fn set_ipv4_mapping( tgt: NatTarget, ) -> DpdResult<()> { let ports = PortRange::new(low, high)?; - let new_entry = Ipv4NatEntry { ports, tgt }; - let full = ipv4_entry(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.ports), 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) - } - }; - - match nat_ip.add_entry(switch, low, high, tgt) { - Err(e) => { - error!(switch.log, "failed to add nat entry {}: {:?}", full, e); - Err(e) - } - _ => { - debug!(switch.log, "added nat entry {}", full); - entries.insert(idx, new_entry); - Ok(()) - } - } + switch.nat.lock().unwrap().ipv4.add_mapping(switch, nat_ip, ports, tgt) } pub fn clear_mapping( @@ -472,7 +475,7 @@ pub fn clear_mapping( } } -/// Find the first `NatTarget` where its `Ipv4NatEntry` matches the provided +/// Find the first `NatTarget` where its `NatEntry` matches the provided /// `Ipv4Addr` and overlaps with the provided port range, then remove it. pub fn clear_ipv4_mapping( switch: &Switch, @@ -481,35 +484,7 @@ pub fn clear_ipv4_mapping( 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 - ); - - if let Some(mappings) = nat.ipv4_mappings.get_mut(&nat_ip) - && let Some(idx) = - find_first_mapping(mappings.iter().map(|e| e.ports), range) - { - let ent = mappings.remove(idx); - if mappings.is_empty() { - nat.ipv4_mappings.remove(&nat_ip); - } - let full = ipv4_entry(nat_ip, &ent); - return match nat_ip.delete_entry(switch, ent.ports.low, ent.ports.high) - { - Err(e) => { - error!(switch.log, "failed to clear {}: {:?}", full, e); - Err(e) - } - _ => { - debug!(switch.log, "cleared nat entry {}", full); - Ok(()) - } - }; - } - - Ok(()) + switch.nat.lock().unwrap().ipv4.remove_mapping(switch, nat_ip, range) } pub fn clear_overlapping_mappings( @@ -518,110 +493,24 @@ pub fn clear_overlapping_mappings( low: u16, high: u16, ) -> DpdResult<()> { + let range = PortRange::new(low, high)?; + let mut nat = switch.nat.lock().unwrap(); match nat_ip { IpAddr::V4(nat_ip) => { - clear_overlapping_mappings_v4(switch, nat_ip, low, high) + nat.ipv4.remove_overlapping_mappings(switch, nat_ip, range) } IpAddr::V6(nat_ip) => { - clear_overlapping_mappings_v6(switch, nat_ip, low, high) + nat.ipv6.remove_overlapping_mappings(switch, nat_ip, range) } } } -/// 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.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 nat_ip.delete_entry(switch, ent.ports.low, ent.ports.high) { - 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); - } - } - - Ok(()) -} - -pub fn clear_overlapping_mappings_v6( - 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 all nat entries overlapping with {}/{}-{}", nat_ip, low, high - ); - - if let Some(mappings) = nat.ipv6_mappings.get_mut(&nat_ip) { - let mut mappings_to_delete = - find_mappings(mappings.iter().map(|e| e.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 = ipv6_entry(nat_ip, &ent); - match nat_ip.delete_entry(switch, ent.ports.low, ent.ports.high) { - 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.ipv6_mappings.remove(&nat_ip); - } - } - - Ok(()) -} - pub fn reset_ipv6(switch: &Switch) -> DpdResult<()> { - let mut nat = switch.nat.lock().unwrap(); - - nat.ipv6_mappings.clear(); - Ipv6Addr::reset(switch) + switch.nat.lock().unwrap().ipv6.reset(switch) } pub fn reset_ipv4(switch: &Switch) -> DpdResult<()> { - let mut nat = switch.nat.lock().unwrap(); - - nat.ipv4_mappings.clear(); - Ipv4Addr::reset(switch) + switch.nat.lock().unwrap().ipv4.reset(switch) } pub fn set_nat_generation(switch: &Switch, generation: i64) { @@ -639,9 +528,5 @@ pub fn get_nat_generation(switch: &Switch) -> i64 { } pub fn init() -> NatData { - NatData { - ipv6_mappings: BTreeMap::new(), - ipv4_mappings: BTreeMap::new(), - ipv4_generation: 0, - } + NatData { ipv6: NatMap::new(), ipv4: NatMap::new(), ipv4_generation: 0 } }