Skip to content

Commit 15e98e7

Browse files
committed
feat(nat): Improve target prefix lookup for stateless NAT mapping
The idea behind the stateless NAT lookup is that, for a given IP and port, we find the matching covering prefix (and associated port range) in a LPM trie, and get the list of corresponding target ranges from the LPM value. Then we retrieve the offset of the IP and port within the prefix (LPM key), and look for the corresponding IP and port within the target prefixes (LPM value). To find the IP and port at the right offset, we need to iterate over all target prefixes in the list and to compare the accumulated size with the offset. This is not efficient; instead, we can use another data structure to perform a faster lookup. To that end, replace the list of prefixes with a DisjointRangesBTreeMap that associates a target prefix to each portion of the original prefix. Suggested-by: Manish Vachharajani <[email protected]> Signed-off-by: Quentin Monnet <[email protected]>
1 parent 1b53fb7 commit 15e98e7

File tree

3 files changed

+350
-88
lines changed

3 files changed

+350
-88
lines changed

lpm/src/prefix/range_map.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,32 @@ where
6969
pub fn iter(&self) -> impl Iterator<Item = (&R, &V)> {
7070
self.0.iter()
7171
}
72+
73+
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&R, &mut V)> {
74+
self.0.iter_mut()
75+
}
76+
77+
pub fn range(&self, range: impl RangeBounds<R>) -> impl Iterator<Item = (&R, &V)> {
78+
self.0.range(range)
79+
}
80+
81+
pub fn range_mut(&mut self, range: impl RangeBounds<R>) -> impl Iterator<Item = (&R, &mut V)> {
82+
self.0.range_mut(range)
83+
}
7284
}
7385

7486
impl<R, V> Default for DisjointRangesBTreeMap<R, V> {
7587
fn default() -> Self {
7688
Self(BTreeMap::default())
7789
}
7890
}
91+
92+
// Permits to .collect() as a DisjointRangesBTreeMap
93+
impl<R, V> FromIterator<(R, V)> for DisjointRangesBTreeMap<R, V>
94+
where
95+
R: Ord,
96+
{
97+
fn from_iter<T: IntoIterator<Item = (R, V)>>(iter: T) -> Self {
98+
Self(BTreeMap::from_iter(iter))
99+
}
100+
}

nat/src/stateless/setup/range_builder.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Copyright Open Network Fabric Authors
33

44
use super::NatPeeringError;
5-
use super::tables::{IpPortRange, IpRange, NatTableValue, PortAddrTranslationValue};
5+
use super::tables::{
6+
IpPort, IpPortRange, IpPortRangeBounds, IpRange, NatTableValue, PortAddrTranslationValue,
7+
};
68
use bnum::cast::CastFrom;
79
use lpm::prefix::{
810
IpRangeWithPorts, PortRange, Prefix, PrefixSize, PrefixWithOptionalPorts, PrefixWithPorts,
@@ -191,6 +193,10 @@ impl Iterator for RangeBuilder<'_> {
191193
);
192194

193195
let orig_prefix_size = orig_prefix.size();
196+
let mut orig_prefix_cursor = (
197+
orig_prefix.prefix().as_address(),
198+
orig_prefix.ports().map_or(0, |ports| ports.start()),
199+
);
194200
let mut orig_offset_cursor = PrefixWithPortsSize::from(0u8);
195201
let mut processed_ranges_size = PrefixWithPortsSize::from(0u8);
196202

@@ -231,15 +237,33 @@ impl Iterator for RangeBuilder<'_> {
231237
return Some(Err(NatPeeringError::MalformedPeering));
232238
};
233239
let ranges = create_new_ranges(addr_port_cursor, range_end, target_prefix.ports());
234-
value.add_ranges(ranges);
240+
if let Err(e) = add_new_ranges(
241+
&mut value,
242+
&orig_prefix_cursor,
243+
&orig_offset_cursor,
244+
orig_prefix.ports().unwrap_or(PortRange::new_max_range()),
245+
&ranges,
246+
) {
247+
return Some(Err(e));
248+
}
235249

236250
// Update state for next loop iteration (if original prefix is not fully covered), or
237251
// next iterator call
238252

239253
processed_ranges_size += range_size;
240-
// Do not update orig_offset_cursor if we're done processing the current prefix
241-
// (we'd risk an overflow if we reached the end of the IP space)
254+
// Do not update orig_prefix_cursor and orig_offset_cursor if we're done processing the
255+
// current prefix (we'd risk an overflow if we reached the end of the IP space)
242256
if processed_ranges_size < orig_prefix_size {
257+
let new_cursor = match add_offset_to_address_and_port(
258+
&orig_prefix_cursor.0,
259+
orig_prefix_cursor.1,
260+
orig_prefix.ports().unwrap_or(PortRange::new_max_range()),
261+
range_size,
262+
) {
263+
Ok(cursor) => cursor,
264+
Err(e) => return Some(Err(e)),
265+
};
266+
orig_prefix_cursor = new_cursor;
243267
orig_offset_cursor += range_size;
244268
}
245269

@@ -399,6 +423,47 @@ fn create_new_ranges(
399423
ranges
400424
}
401425

426+
// Add new ranges to a PortAddrTranslationValue. The struct contains a BTreeMap associating portions
427+
// of the PrefixWithOptionalPorts that we're processing to target IP and port ranges. We need to
428+
// compute these prefix portions to use them as keys when inserting the target ranges into the map.
429+
fn add_new_ranges(
430+
value: &mut PortAddrTranslationValue,
431+
orig_prefix_cursor: &(IpAddr, u16),
432+
orig_offset_cursor: &PrefixWithPortsSize,
433+
orig_port_range: PortRange,
434+
ranges: &[IpPortRange],
435+
) -> Result<(), NatPeeringError> {
436+
let mut cursor = *orig_prefix_cursor;
437+
let mut offset = *orig_offset_cursor;
438+
439+
for (i, range) in ranges.iter().enumerate() {
440+
// Compute a "prefix portion", a range of addresses within a prefix, with the same size as
441+
// the range we want to insert
442+
let end_prefix_portion = add_offset_to_address_and_port(
443+
&cursor.0,
444+
cursor.1,
445+
orig_port_range,
446+
range.size().saturating_sub(PrefixWithPortsSize::from(1u8)),
447+
)?;
448+
let prefix_portion = IpPortRangeBounds::new(
449+
IpPort::new(cursor.0, cursor.1),
450+
IpPort::new(end_prefix_portion.0, end_prefix_portion.1),
451+
);
452+
453+
value.insert_and_merge(prefix_portion, (*range, offset));
454+
455+
offset += range.size();
456+
if i == ranges.len() - 1 {
457+
// Skip updating the cursor on the last iteration, it's useless and we'd risk
458+
// overflowing the IP space.
459+
} else {
460+
cursor =
461+
add_offset_to_address_and_port(&cursor.0, cursor.1, orig_port_range, range.size())?;
462+
}
463+
}
464+
Ok(())
465+
}
466+
402467
fn ip_addr_diff(addr1: &IpAddr, addr2: &IpAddr) -> u128 {
403468
match (addr1, addr2) {
404469
(IpAddr::V4(a), IpAddr::V4(b)) => u128::from(a.to_bits() - b.to_bits()),

0 commit comments

Comments
 (0)