diff --git a/dpd/src/nat.rs b/dpd/src/nat.rs index 8c887a5..b222772 100644 --- a/dpd/src/nat.rs +++ b/dpd/src/nat.rs @@ -16,52 +16,64 @@ use crate::types::{DpdError, DpdResult}; use common::nat::{Ipv4Nat, Ipv6Nat}; use common::network::NatTarget; -trait PortRange { - fn low(&self) -> u16; - fn high(&self) -> u16; +/// An inclusive range of ports, guaranteed by construction to have +/// `low <= high`. +#[derive(Clone, Copy, PartialEq)] +pub(crate) struct PortRange { + low: u16, + high: u16, } -#[derive(PartialEq)] -pub(crate) struct Ipv6NatEntry { - pub low: u16, - pub high: u16, - pub tgt: NatTarget, +#[derive(Debug)] +pub(crate) struct InvalidPortRange; + +impl From for DpdError { + fn from(_: InvalidPortRange) -> Self { + DpdError::Invalid("invalid port range".into()) + } } -impl PortRange for Ipv6NatEntry { - fn low(&self) -> u16 { - self.low +impl PortRange { + fn new(low: u16, high: u16) -> Result { + if low <= high { + Ok(PortRange { low, high }) + } else { + Err(InvalidPortRange) + } } - fn high(&self) -> u16 { - self.high + + fn overlaps(self, other: PortRange) -> bool { + self.low <= other.high && self.high >= other.low } } -impl fmt::Display for Ipv6NatEntry { +impl fmt::Display for PortRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{}-{}] -> {}", self.low, self.high, self.tgt) + write!(f, "[{}-{}]", self.low, self.high) } } -#[derive(Clone, PartialEq)] -pub(crate) struct Ipv4NatEntry { - pub low: u16, - pub high: u16, +#[derive(PartialEq)] +pub(crate) struct Ipv6NatEntry { + pub l4_ports: PortRange, pub tgt: NatTarget, } -impl PortRange for Ipv4NatEntry { - fn low(&self) -> u16 { - self.low - } - fn high(&self) -> u16 { - self.high +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 l4_ports: PortRange, + pub tgt: NatTarget, +} + impl fmt::Display for Ipv4NatEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{}-{}] -> {}", self.low, self.high, self.tgt) + write!(f, "{} -> {}", self.l4_ports, self.tgt) } } pub struct NatData { @@ -78,51 +90,38 @@ fn ipv4_entry(ipv4: Ipv4Addr, e: &Ipv4NatEntry) -> String { format!("{ipv4}/{e}") } -fn overlaps(e: &T, low: u16, high: u16) -> bool { - let elow = e.low(); - let ehigh = e.high(); - - (elow >= low && elow <= high) - || (ehigh >= low && ehigh <= high) - || (elow <= low && ehigh >= high) -} - /// find index of first mapping that overlaps with supplied port range -fn find_first_mapping( - entries: &[T], - low: u16, - high: u16, +fn find_first_mapping( + mut ranges: impl Iterator, + range_to_find: PortRange, ) -> Option { - entries.iter().position(|e| overlaps(e, low, high)) + ranges.position(|e| e.overlaps(range_to_find)) } /// find indices of all mappings that overlap with supplied port range -fn find_mappings( - entries: &[T], - low: u16, - high: u16, +fn find_mappings( + ranges: impl Iterator, + range_to_find: PortRange, ) -> Vec { - entries - .iter() + ranges .enumerate() - .filter(|(_, e)| overlaps(*e, low, high)) + .filter(|(_, e)| e.overlaps(range_to_find)) .map(|(i, _)| i) .collect() } -fn find_space( - entries: &[T], - low: u16, - high: u16, +fn find_space( + ranges: impl ExactSizeIterator, + candidate_range: PortRange, ) -> Option { - let len = entries.len(); + let len = ranges.len(); + let iter = ranges.enumerate(); - for (idx, e) in entries.iter().enumerate() { - if overlaps(e, low, high) { + for (idx, e) in iter { + if e.overlaps(candidate_range) { return None; } - if e.low() >= high && (idx == len - 1 || entries[idx + 1].low() >= high) - { + if e.low >= candidate_range.high { return Some(idx); } } @@ -131,41 +130,43 @@ fn find_space( #[test] fn test_mapping() { - use super::MacAddr; - use common::network::Vni; + let entries = [ + PortRange::new(1, 4).unwrap(), + PortRange::new(7, 10).unwrap(), + PortRange::new(12, 18).unwrap(), + ]; - let dummy_target = NatTarget { - internal_ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), - inner_mac: MacAddr::new(0, 0, 0, 0, 0, 0), - vni: Vni::new(0).unwrap(), + let first_mapping = |low, high| { + find_first_mapping( + entries.iter().copied(), + PortRange::new(low, high).unwrap(), + ) + }; + let space = |low, high| { + find_space(entries.iter().copied(), PortRange::new(low, high).unwrap()) }; - let entries = vec![ - Ipv4NatEntry { low: 1, high: 4, tgt: dummy_target }, - Ipv4NatEntry { low: 7, high: 10, tgt: dummy_target }, - Ipv4NatEntry { low: 12, high: 18, tgt: dummy_target }, - ]; - - assert_eq!(find_first_mapping(&entries, 2, 2), Some(0)); - assert_eq!(find_first_mapping(&entries, 4, 5), Some(0)); - assert_eq!(find_first_mapping(&entries, 5, 6), None); - assert_eq!(find_first_mapping(&entries, 5, 7), Some(1)); - assert_eq!(find_first_mapping(&entries, 2, 6), Some(0)); - assert_eq!(find_first_mapping(&entries, 5, 5), None); - assert_eq!(find_first_mapping(&entries, 5, 20), Some(1)); - assert_eq!(find_first_mapping(&entries, 12, 12), Some(2)); - assert_eq!(find_first_mapping(&entries, 18, 18), Some(2)); - assert_eq!(find_first_mapping(&entries, 19, 19), None); - assert_eq!(find_first_mapping(&entries, 19, 40), None); - assert_eq!(find_first_mapping(&entries, 0, 0), None); - assert_eq!(find_first_mapping(&entries, 0, 2), Some(0)); - assert_eq!(find_space(&entries, 0, 0), Some(0)); - assert_eq!(find_space(&entries, 0, 1), None); - assert_eq!(find_space(&entries, 11, 11), Some(2)); - assert_eq!(find_space(&entries, 19, 32), Some(3)); - assert_eq!(find_space(&entries, 0, 2), None); - assert_eq!(find_space(&entries, 3, 5), None); - assert_eq!(find_space(&entries, 3, 8), None); + assert_eq!(first_mapping(2, 2), Some(0)); + assert_eq!(first_mapping(4, 5), Some(0)); + assert_eq!(first_mapping(5, 6), None); + assert_eq!(first_mapping(5, 7), Some(1)); + assert_eq!(first_mapping(2, 6), Some(0)); + assert_eq!(first_mapping(5, 5), None); + assert_eq!(first_mapping(5, 20), Some(1)); + assert_eq!(first_mapping(12, 12), Some(2)); + assert_eq!(first_mapping(18, 18), Some(2)); + assert_eq!(first_mapping(19, 19), None); + assert_eq!(first_mapping(19, 40), None); + assert_eq!(first_mapping(0, 0), None); + assert_eq!(first_mapping(0, 2), Some(0)); + assert_eq!(space(0, 0), Some(0)); + assert_eq!(space(0, 1), None); + assert_eq!(space(5, 8), None); + assert_eq!(space(11, 11), Some(2)); + assert_eq!(space(19, 32), Some(3)); + assert_eq!(space(0, 2), None); + assert_eq!(space(3, 5), None); + assert_eq!(space(3, 8), None); } pub fn get_ipv6_addrs_range( @@ -206,11 +207,11 @@ pub fn get_ipv6_mappings_range( let mut entries = Vec::new(); for m in mappings { - if m.low >= port { + if m.l4_ports.low >= port { entries.push(Ipv6Nat { external, - low: m.low, - high: m.high, + low: m.l4_ports.low, + high: m.l4_ports.high, target: m.tgt, }); if entries.len() >= max { @@ -229,9 +230,11 @@ pub fn get_ipv6_mapping( 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, low, high) + && let Some(idx) = + find_first_mapping(v.iter().map(|e| e.l4_ports), range) { return Ok(v[idx].tgt); } @@ -245,14 +248,11 @@ pub fn set_ipv6_mapping( high: u16, tgt: NatTarget, ) -> DpdResult<()> { - let new_entry = Ipv6NatEntry { low, high, tgt }; + 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); - if high < low { - return Err(DpdError::Invalid("invalid port range".into())); - } - let mut nat = switch.nat.lock().unwrap(); let (entries, idx) = match nat.ipv6_mappings.get_mut(&nat_ip) { Some(e) => { @@ -260,7 +260,7 @@ pub fn set_ipv6_mapping( // entry already exists return Ok(()); } - match find_space(e, low, high) { + match find_space(e.iter().map(|x| x.l4_ports), l4_ports) { Some(i) => (e, i), None => { trace!( @@ -298,18 +298,25 @@ pub fn clear_ipv6_mapping( 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, low, high) + && 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 nat::delete_ipv6_entry(switch, nat_ip, ent.low, ent.high) { + return match nat::delete_ipv6_entry( + switch, + nat_ip, + ent.l4_ports.low, + ent.l4_ports.high, + ) { Err(e) => { error!(switch.log, "failed to clear {}: {:?}", full, e); Err(e) @@ -362,11 +369,11 @@ pub fn get_ipv4_mappings_range( let mut entries = Vec::new(); for m in mappings { - if m.low >= port { + if m.l4_ports.low >= port { entries.push(Ipv4Nat { external, - low: m.low, - high: m.high, + low: m.l4_ports.low, + high: m.l4_ports.high, target: m.tgt, }); if entries.len() >= max { @@ -385,9 +392,11 @@ pub fn get_ipv4_mapping( 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 Some(idx) = find_first_mapping(v, low, high) + && let Some(idx) = + find_first_mapping(v.iter().map(|e| e.l4_ports), range) { return Ok(v[idx].tgt); } @@ -414,14 +423,11 @@ pub fn set_ipv4_mapping( high: u16, tgt: NatTarget, ) -> DpdResult<()> { - let new_entry = Ipv4NatEntry { low, high, tgt }; + let l4_ports = PortRange::new(low, high)?; + let new_entry = Ipv4NatEntry { l4_ports, tgt }; let full = ipv4_entry(nat_ip, &new_entry); trace!(switch.log, "adding nat entry {}", full); - if high < low { - return Err(DpdError::Invalid("invalid port range".into())); - } - let mut nat = switch.nat.lock().unwrap(); let (entries, idx) = match nat.ipv4_mappings.get_mut(&nat_ip) { Some(e) => { @@ -429,7 +435,7 @@ pub fn set_ipv4_mapping( // entry already exists return Ok(()); } - match find_space(e, low, high) { + match find_space(e.iter().map(|x| x.l4_ports), l4_ports) { Some(i) => (e, i), None => { error!( @@ -479,6 +485,7 @@ pub fn clear_ipv4_mapping( low: u16, high: u16, ) -> DpdResult<()> { + let range = PortRange::new(low, high)?; let mut nat = switch.nat.lock().unwrap(); trace!( switch.log, @@ -486,14 +493,20 @@ pub fn clear_ipv4_mapping( ); if let Some(mappings) = nat.ipv4_mappings.get_mut(&nat_ip) - && let Some(idx) = find_first_mapping(mappings, low, high) + && let Some(idx) = + find_first_mapping(mappings.iter().map(|e| e.l4_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::delete_ipv4_entry(switch, nat_ip, ent.low, ent.high) { + return match nat::delete_ipv4_entry( + switch, + nat_ip, + ent.l4_ports.low, + ent.l4_ports.high, + ) { Err(e) => { error!(switch.log, "failed to clear {}: {:?}", full, e); Err(e) @@ -532,6 +545,7 @@ pub fn clear_overlapping_mappings_v4( low: u16, high: u16, ) -> DpdResult<()> { + let range = PortRange::new(low, high)?; let mut nat = switch.nat.lock().unwrap(); trace!( switch.log, @@ -539,14 +553,20 @@ pub fn clear_overlapping_mappings_v4( ); if let Some(mappings) = nat.ipv4_mappings.get_mut(&nat_ip) { - let mut mappings_to_delete = find_mappings(mappings, low, high); + 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 nat::delete_ipv4_entry(switch, nat_ip, ent.low, ent.high) { + match nat::delete_ipv4_entry( + switch, + nat_ip, + ent.l4_ports.low, + ent.l4_ports.high, + ) { Err(e) => { error!(switch.log, "failed to clear {}: {:?}", full, e); return Err(e); @@ -570,6 +590,7 @@ pub fn clear_overlapping_mappings_v6( low: u16, high: u16, ) -> DpdResult<()> { + let range = PortRange::new(low, high)?; let mut nat = switch.nat.lock().unwrap(); trace!( switch.log, @@ -577,14 +598,20 @@ pub fn clear_overlapping_mappings_v6( ); if let Some(mappings) = nat.ipv6_mappings.get_mut(&nat_ip) { - let mut mappings_to_delete = find_mappings(mappings, low, high); + 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 = ipv6_entry(nat_ip, &ent); - match nat::delete_ipv6_entry(switch, nat_ip, ent.low, ent.high) { + match nat::delete_ipv6_entry( + switch, + nat_ip, + ent.l4_ports.low, + ent.l4_ports.high, + ) { Err(e) => { error!(switch.log, "failed to clear {}: {:?}", full, e); return Err(e);