Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d920d19
refactor(nat): Move static NAT network function to dedicated file
qmonnet May 15, 2026
180e9fc
refactor(nat): Move unicast check closer to source IP mapping lookup
qmonnet May 15, 2026
48e13b3
refactor(nat): Handle zero-port check at port mapping lookup
qmonnet May 15, 2026
3ca3481
feat(net): Add wrappers for transport with types (TCP/UDP)
qmonnet May 15, 2026
6471cdd
refactor(nat): Use TcpUdp view in static NAT to simplify port update
qmonnet May 15, 2026
eaac55a
refactor(net): Simplify metadata flag toggling
qmonnet May 19, 2026
d4e5c20
chore(flow-filter): Split requires_stateless_nat() into source/dest
qmonnet May 19, 2026
8deb16a
feat(flow-filter): Tag packets for src/dst static NAT
qmonnet May 19, 2026
44396ea
feat(nat): Mark packets as NAT-ed for source or destination
qmonnet May 19, 2026
8ec430d
feat(nat): For static NAT, only apply NAT for relevant direction(s)
qmonnet May 19, 2026
4e58d78
feat(dataplane): Move static NAT stage before port forwarding
qmonnet May 20, 2026
d723f41
feat(flow-filter,nat): Store static NAT requirements in flow info
qmonnet May 20, 2026
06a97cf
feat(net): Embed flow key in packet metadata
qmonnet May 20, 2026
20bd0e3
feat(nat): Use initial IP addresses for forward flow table entry
qmonnet May 20, 2026
0c4339c
fix(nat): Remove NoMappingFound error
qmonnet May 30, 2026
48361d8
refactor(nat): Move NAT tables reader entering to process_packet()
qmonnet May 30, 2026
ac612e0
feat(nat): Go through static NAT step after ICMP Error handler
qmonnet Jun 8, 2026
1e38b45
feat(config): Allow static + stateful NAT, on opposite ends
qmonnet May 20, 2026
4813fb0
test(nat): Add tests for static NAT + {masquerade, port forwarding}
qmonnet May 20, 2026
f42b3ba
refactor(nat): Clean a little the error translation code in NAT
qmonnet May 20, 2026
30bb017
refactor(net): Drop FlowKey's Unidirectional enum variant
qmonnet May 20, 2026
e025e05
refactor(net): Merge FlowKey and FlowKeyData
qmonnet May 20, 2026
f562d77
refactor(net): Rename FlowKey::uni() to FlowKey::new()
qmonnet May 20, 2026
1dd7e31
refactor(net): Drop the Uni<T> wrapper around packets
qmonnet May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions config/src/external/overlay/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,7 @@ pub mod test {

// Build overlay object and validate it
let overlay = Overlay::new(vpc_table, peering_table);
assert!(
overlay
.validate()
.is_err_and(|e| e == ConfigError::IncompatibleNatModes("Peering-1".to_owned()))
);
assert!(overlay.validate().is_ok());
Comment thread
qmonnet marked this conversation as resolved.
}

#[test]
Expand Down
18 changes: 4 additions & 14 deletions config/src/external/overlay/validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@ mod test {
assert!(validate_overlay_with_peering(peering).is_ok());
}

// Stateless + stateful rejected
// Stateless + stateful passes
#[test]
fn test_stateless_plus_stateful_rejected() {
let peering = VpcPeering::with_default_group(
Expand Down Expand Up @@ -1237,15 +1237,10 @@ mod test {
],
),
);
let result = validate_overlay_with_peering(peering);
assert_eq!(
result,
Err(ConfigError::IncompatibleNatModes("Peering-1".to_owned())),
"{result:?}",
);
assert!(validate_overlay_with_peering(peering).is_ok());
}

// Stateless + port forwarding rejected
// Stateless + port forwarding passes
#[test]
fn test_stateless_plus_port_forwarding_rejected() {
let peering = VpcPeering::with_default_group(
Expand Down Expand Up @@ -1273,12 +1268,7 @@ mod test {
],
),
);
let result = validate_overlay_with_peering(peering);
assert_eq!(
result,
Err(ConfigError::IncompatibleNatModes("Peering-1".to_owned())),
"{result:?}",
);
assert!(validate_overlay_with_peering(peering).is_ok());
}

// Stateful + stateful rejected (across peering sides)
Expand Down
36 changes: 10 additions & 26 deletions config/src/external/overlay/vpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,55 +122,39 @@ impl ValidatedPeering {
fn validate_nat_combinations(&self) -> ConfigResult {
// If stateful NAT is set up on one side of the peering, we don't support NAT (stateless or
// stateful) on the other side.
let mut local_has_stateless_nat = false;
let mut local_has_stateful_nat = false;
let mut local_has_masquerading = false;
let mut local_has_port_forwarding = false;
for expose in self.local.valexp() {
match expose.nat_config() {
Some(VpcExposeNatConfig::Stateful { .. }) => {
local_has_stateful_nat = true;
}
Some(VpcExposeNatConfig::Stateless { .. }) => {
local_has_stateless_nat = true;
local_has_masquerading = true;
}
Some(VpcExposeNatConfig::PortForwarding { .. }) => {
local_has_port_forwarding = true;
}
None => {}
Some(VpcExposeNatConfig::Stateless { .. }) | None => {}
}
}
let local_has_nat =
local_has_stateless_nat || local_has_stateful_nat || local_has_port_forwarding;

if !local_has_nat {
// No NAT or static NAT only is compatible with all other modes on the other side
if !(local_has_masquerading || local_has_port_forwarding) {
return Ok(());
}

let local_has_stateless_nat_only =
local_has_stateless_nat && !local_has_stateful_nat && !local_has_port_forwarding;

// Allowed:
//
// - no NAT ------------ *
// - stateless NAT ----- stateless NAT
// - stateless NAT ----- *
//
// Disallowed (some of them may be supported in the future):
//
// - stateful NAT ------ stateless NAT
// - stateful NAT ------ stateful NAT
// - stateful NAT ------ port forwarding
// - masquerading ------ masquerading
// - masquerading ------ port forwarding
// - port forwarding --- port forwarding
// - port forwarding --- stateless NAT

for remote_expose in self.remote.valexp() {
if !remote_expose.has_nat() {
continue;
}
if local_has_stateless_nat_only && remote_expose.has_stateless_nat() {
continue;
if remote_expose.has_stateful_nat() || remote_expose.has_port_forwarding() {
return Err(ConfigError::IncompatibleNatModes(self.name.clone()));
}
// Other combinations are rejected
return Err(ConfigError::IncompatibleNatModes(self.name.clone()));
}
Ok(())
}
Expand Down
7 changes: 0 additions & 7 deletions config/src/external/overlay/vpcpeering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,13 +535,6 @@ impl ValidatedExpose {
)
}

#[must_use]
pub(crate) fn has_nat(&self) -> bool {
self.nat
.as_ref()
.is_some_and(|nat| !nat.as_range.is_empty())
}

#[must_use]
pub fn has_stateful_nat(&self) -> bool {
self.nat.as_ref().is_some_and(VpcExposeNat::is_stateful)
Expand Down
4 changes: 2 additions & 2 deletions dataplane/src/packet_processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub(crate) fn start_router<Buf: PacketBufferMut>(
let stage_egress = Egress::new("Egress", iftr_factory.handle(), atabler_factory.handle());
let iprouter1 = IpForwarder::new("IP-Forward-1", fibtr_factory.handle());
let iprouter2 = IpForwarder::new("IP-Forward-2", fibtr_factory.handle());
let stateless_nat = StatelessNat::with_reader("stateless-NAT", nattabler_factory.handle());
let static_nat = StatelessNat::with_reader("static-NAT-1", nattabler_factory.handle());
let stateful_nat = StatefulNat::new(
"stateful-NAT",
flow_table_clone.clone(),
Expand All @@ -128,8 +128,8 @@ pub(crate) fn start_router<Buf: PacketBufferMut>(
.add_stage(icmp_error_handler)
.add_stage(flow_lookup)
.add_stage(flow_filter)
.add_stage(static_nat)
.add_stage(portfw)
.add_stage(stateless_nat)
.add_stage(stateful_nat)
.add_stage(iprouter2)
.add_stage(stage_egress)
Expand Down
25 changes: 15 additions & 10 deletions flow-entry/src/flow_table/nf_lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use pipeline::NetworkFunction;

use crate::flow_table::FlowTable;
use net::FlowKey;
use net::flow_key;

use tracectl::trace_target;
trace_target!("flow-lookup", LevelFilter::INFO, &["pipeline"]);
Expand Down Expand Up @@ -40,7 +39,7 @@ impl<Buf: PacketBufferMut> NetworkFunction<Buf> for FlowLookup {
input.filter_map(move |mut packet| {
let nfi = &self.name;
if !packet.is_done() && packet.meta().is_overlay() && packet.meta().dst_vpcd.is_none() {
if let Ok(flow_key) = FlowKey::try_from(flow_key::Uni(&packet)) {
if let Ok(flow_key) = FlowKey::try_from(&packet) {
if let Some(flow_info) = self.flow_table.lookup(&flow_key) {
debug!("{nfi}: Tagging packet with flow info for flow key {flow_key}",);
packet.meta_mut().flow_info = Some(flow_info);
Expand All @@ -62,7 +61,7 @@ mod test {
use net::FlowKey;
use net::buffer::PacketBufferMut;
use net::buffer::TestBuffer;
use net::flows::FlowInfo;
use net::flows::{FlowInfo, FlowInfoFlags};
use net::ip::NextHeader;
use net::ip::UnicastIpAddr;
use net::packet::Packet;
Expand Down Expand Up @@ -101,7 +100,7 @@ mod test {
packet.meta_mut().set_overlay(true);

// Insert matching flow entry
let flow_key = FlowKey::try_from(net::flow_key::Uni(&packet)).unwrap();
let flow_key = FlowKey::try_from(&packet).unwrap();
let flow_info = FlowInfo::new(flow_key, Instant::now() + Duration::from_secs(10));
flow_table.insert(flow_info).unwrap();

Expand Down Expand Up @@ -130,7 +129,7 @@ mod test {
input: Input,
) -> impl Iterator<Item = Packet<Buf>> + 'a {
input.filter_map(move |packet| {
let flow_key = FlowKey::try_from(net::flow_key::Uni(&packet)).unwrap();
let flow_key = FlowKey::try_from(&packet).unwrap();
let flow_info = FlowInfo::new(flow_key, Instant::now() + self.timeout);
self.flow_table
.insert(flow_info)
Expand Down Expand Up @@ -193,12 +192,18 @@ mod test {
packet_2.meta_mut().set_overlay(true);

// build keys for the packets
let key_1 = FlowKey::try_from(net::flow_key::Uni(&packet_1)).unwrap();
let key_2 = FlowKey::try_from(net::flow_key::Uni(&packet_2)).unwrap();
let key_1 = FlowKey::try_from(&packet_1).unwrap();
let key_2 = FlowKey::try_from(&packet_2).unwrap();

// create a pair of related flow entries; flow_2 will get a longer timeout
let expires_at = tokio::time::Instant::now().into_std() + Duration::from_secs(2);
let (flow_1, flow_2) = FlowInfo::related_pair(expires_at, key_1, key_2);
let (flow_1, flow_2) = FlowInfo::related_pair(
expires_at,
key_1,
FlowInfoFlags::default(),
key_2,
FlowInfoFlags::default(),
);
assert_eq!(Arc::weak_count(&flow_1), 1);
assert_eq!(Arc::weak_count(&flow_2), 1);
assert_eq!(Arc::strong_count(&flow_1), 1);
Expand Down Expand Up @@ -245,8 +250,8 @@ mod test {
let mut packet_2 = build_test_udp_ipv4_packet("192.168.1.1", "20.0.0.1", 500, 80);
packet_1.meta_mut().set_overlay(true);
packet_2.meta_mut().set_overlay(true);
let key_1 = FlowKey::try_from(net::flow_key::Uni(&packet_1)).unwrap();
let key_2 = FlowKey::try_from(net::flow_key::Uni(&packet_2)).unwrap();
let key_1 = FlowKey::try_from(&packet_1).unwrap();
let key_2 = FlowKey::try_from(&packet_2).unwrap();
let input = vec![packet_1, packet_2];
let out: Vec<_> = pipeline.process(input.into_iter()).collect();
let packet_1 = &out[0];
Expand Down
Loading
Loading