Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions config/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ pub enum ConfigError {
// NAT-specific
#[error("Mismatched prefixes sizes for static NAT: {0:?} and {1:?}")]
MismatchedPrefixSizes(PrefixWithPortsSize, PrefixWithPortsSize),
#[error(
"Stateful NAT is only supported on one side of a peering, but peering {0} has stateful NAT enabled on both sides"
)]
StatefulNatOnBothSides(String),
#[error(
"Stateful NAT is not compatible with stateless NAT at the other side of a peering, but peering {0} has stateful and stateless NAT enabled on different sides"
)]
StatefulPlusStatelessNat(String),

// Interface addresses
#[error("Invalid interface address format: {0}")]
Expand Down
13 changes: 10 additions & 3 deletions config/src/external/overlay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,22 @@ impl Overlay {
debug!("Validating overlay configuration...");

self.validate_peerings()?;
let id_map = self.vpcid_map();

// collect peerings for every vpc.
self.vpc_table
.collect_peerings(&self.peering_table, &id_map);
self.collect_peerings();

self.vpc_table.validate()?;

debug!("Overlay configuration is VALID:\n{self}");
Ok(())
}

/// Collect peerings from the peering table for every VPC.
///
/// Should only be called in `validate`, or in tests.
pub fn collect_peerings(&mut self) {
let id_map = self.vpcid_map();
self.vpc_table
.collect_peerings(&self.peering_table, &id_map);
}
}
96 changes: 96 additions & 0 deletions config/src/external/overlay/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,102 @@ pub mod test {
);
}

#[test]
fn test_peering_nat_both_sides() {
// Build VPCs and VPC table
let vpc1 = Vpc::new("VPC-1", "VPC01", 1).unwrap();
let vpc2 = Vpc::new("VPC-2", "VPC02", 2).unwrap();
let mut vpc_table = VpcTable::new();
vpc_table.add(vpc1).unwrap();
vpc_table.add(vpc2).unwrap();

// Build peering with stateful NAT on both sides
let peering = VpcPeering::new(
"Peering-1",
VpcManifest {
name: "VPC-1".to_owned(),
exposes: vec![
VpcExpose::empty()
.make_stateful_nat(None)
.unwrap()
.ip("1.0.0.0/8".into())
.as_range("2.0.0.0/8".into()),
],
},
VpcManifest {
name: "VPC-2".to_owned(),
exposes: vec![
VpcExpose::empty()
.make_stateful_nat(None)
.unwrap()
.ip("3.0.0.0/8".into())
.as_range("4.0.0.0/8".into()),
],
},
None,
);

// Build VPC pering table and add peering
let mut peering_table = VpcPeeringTable::new();
peering_table.add(peering).unwrap();

// Build overlay object and validate it
let mut overlay = Overlay::new(vpc_table, peering_table);
assert_eq!(
overlay.validate(),
Err(ConfigError::StatefulNatOnBothSides("Peering-1".to_owned()))
);
}

#[test]
fn test_peering_nat_stateful_plus_stateless() {
// Build VPCs and VPC table
let vpc1 = Vpc::new("VPC-1", "VPC01", 1).unwrap();
let vpc2 = Vpc::new("VPC-2", "VPC02", 2).unwrap();
let mut vpc_table = VpcTable::new();
vpc_table.add(vpc1).unwrap();
vpc_table.add(vpc2).unwrap();

// Build peering with stateful NAT on one side and stateless NAT on the other side
let peering = VpcPeering::new(
"Peering-1",
VpcManifest {
name: "VPC-1".to_owned(),
exposes: vec![
VpcExpose::empty()
.make_stateful_nat(None)
.unwrap()
.ip("1.0.0.0/8".into())
.as_range("2.0.0.0/8".into()),
],
},
VpcManifest {
name: "VPC-2".to_owned(),
exposes: vec![
VpcExpose::empty()
.make_stateless_nat()
.unwrap()
.ip("3.0.0.0/8".into())
.as_range("4.0.0.0/8".into()),
],
},
None,
);

// Build VPC pering table and add peering
let mut peering_table = VpcPeeringTable::new();
peering_table.add(peering).unwrap();

// Build overlay object and validate it
let mut overlay = Overlay::new(vpc_table, peering_table);
assert_eq!(
overlay.validate(),
Err(ConfigError::StatefulPlusStatelessNat(
"Peering-1".to_owned()
))
);
}

#[test]
fn test_overlay_missing_vpc() {
/* build VPCs */
Expand Down
31 changes: 31 additions & 0 deletions config/src/external/overlay/vpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,37 @@ impl Peering {
// not needed will be validated when validating the remote vpc
self.remote.validate()?;
}

self.validate_nat_combinations()
}

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_nat = false;
let mut local_has_stateful_nat = false;
for expose in &self.local.exposes {
if expose.has_stateful_nat() {
local_has_stateful_nat = true;
local_has_nat = true;
break;
} else if expose.has_stateless_nat() {
local_has_nat = true;
}
}

if !local_has_nat {
return Ok(());
}

for expose in &self.remote.exposes {
if expose.has_stateful_nat() {
return Err(ConfigError::StatefulNatOnBothSides(self.name.clone()));
}
if expose.has_stateless_nat() && local_has_stateful_nat {
return Err(ConfigError::StatefulPlusStatelessNat(self.name.clone()));
}
}
Ok(())
}
}
Expand Down
4 changes: 2 additions & 2 deletions flow-filter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,8 +820,8 @@ mod tests {
.unwrap();

let mut overlay = Overlay::new(vpc_table, peering_table);
// Validation is necessary to build overlay.vpc_table's peerings from peering_table
overlay.validate().unwrap();
// Build overlay.vpc_table's peerings from peering_table, with no validation
overlay.collect_peerings();

let table = FlowFilterTable::build_from_overlay(&overlay).unwrap();

Expand Down
Loading
Loading