Skip to content

Commit 7ddd0b8

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 532d480 commit 7ddd0b8

File tree

3 files changed

+347
-88
lines changed

3 files changed

+347
-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: 71 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,
@@ -181,6 +183,10 @@ impl Iterator for RangeBuilder<'_> {
181183
);
182184

183185
let orig_prefix_size = orig_prefix.size();
186+
let mut orig_prefix_cursor = (
187+
orig_prefix.prefix().as_address(),
188+
orig_prefix.ports().map_or(0, |ports| ports.start()),
189+
);
184190
let mut orig_offset_cursor = PrefixWithPortsSize::from(0u8);
185191
let mut processed_ranges_size = PrefixWithPortsSize::from(0u8);
186192

@@ -221,15 +227,32 @@ impl Iterator for RangeBuilder<'_> {
221227
return Some(Err(NatPeeringError::MalformedPeering));
222228
};
223229
let ranges = create_new_ranges(addr_port_cursor, range_end, target_prefix.ports());
224-
value.add_ranges(ranges);
230+
if let Err(e) = add_new_ranges(
231+
&mut value,
232+
&orig_prefix_cursor,
233+
&orig_offset_cursor,
234+
&ranges,
235+
) {
236+
return Some(Err(e));
237+
}
225238

226239
// Update state for next loop iteration (if original prefix is not fully covered), or
227240
// next iterator call
228241

229242
processed_ranges_size += range_size;
230-
// Do not update orig_offset_cursor if we're done processing the current prefix
231-
// (we'd risk an overflow if we reached the end of the IP space)
243+
// Do not update orig_prefix_cursor and orig_offset_cursor if we're done processing the
244+
// current prefix (we'd risk an overflow if we reached the end of the IP space)
232245
if processed_ranges_size < orig_prefix_size {
246+
let new_cursor = match add_offset_to_address_and_port(
247+
&orig_prefix_cursor.0,
248+
orig_prefix_cursor.1,
249+
orig_prefix.ports().unwrap_or(PortRange::new_max_range()),
250+
range_size,
251+
) {
252+
Ok(cursor) => cursor,
253+
Err(e) => return Some(Err(e)),
254+
};
255+
orig_prefix_cursor = new_cursor;
233256
orig_offset_cursor += range_size;
234257
}
235258

@@ -390,6 +413,50 @@ fn create_new_ranges(
390413
ranges
391414
}
392415

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

0 commit comments

Comments
 (0)