Skip to content
Draft
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
16 changes: 16 additions & 0 deletions config/src/converters/k8s/config/expose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ impl TryFrom<(&SubnetMap, &GatewayAgentPeeringsPeeringExpose)> for VpcExpose {
) -> Result<Self, Self::Error> {
let mut vpc_expose = VpcExpose::empty();

// check if it is a default expose
vpc_expose.default = expose.default.unwrap_or(false);
if vpc_expose.default {
if expose.ips.as_ref().is_some_and(|ips| !ips.is_empty()) {
return Err(FromK8sConversionError::Invalid(
"A Default expose can't contain prefixes".to_string(),
));
}
if expose.r#as.as_ref().is_some_and(|r#as| !r#as.is_empty()) {
return Err(FromK8sConversionError::Invalid(
"A Default expose can't contain 'as' prefixes".to_string(),
));
}
return Ok(vpc_expose);
}

// Process PeeringIP rules
if let Some(ips) = expose.ips.as_ref() {
if ips.is_empty() {
Expand Down
11 changes: 11 additions & 0 deletions config/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use crate::external::overlay::vpc::Vpc;
use std::fmt::Display;

use crate::external::overlay::Overlay;
use crate::external::overlay::vpc::{Peering, VpcId, VpcTable};
use crate::external::overlay::vpcpeering::VpcManifest;
use crate::external::overlay::vpcpeering::{VpcExpose, VpcPeering, VpcPeeringTable};
Expand All @@ -28,6 +29,9 @@ const SEP: &str = " ";
impl Display for VpcExpose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut carriage = false;
if self.default {
write!(f, "{SEP} prefixes: default")?;
}
if !self.ips.is_empty() {
write!(f, "{SEP} prefixes:")?;
self.ips.iter().for_each(|x| {
Expand Down Expand Up @@ -234,3 +238,10 @@ impl Display for VpcPeeringTable {
Ok(())
}
}

impl Display for Overlay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.vpc_table.fmt(f)?;
self.peering_table.fmt(f)
}
}
44 changes: 23 additions & 21 deletions config/src/external/overlay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,48 +28,50 @@ impl Overlay {
peering_table,
}
}
/// Check if a `Vpc` referred in a peering exists
fn check_peering_vpc(&self, peering: &str, manifest: &VpcManifest) -> ConfigResult {
if self.vpc_table.get_vpc(&manifest.name).is_none() {
self.vpc_table.get_vpc(&manifest.name).ok_or_else(|| {
error!("peering '{}': unknown VPC '{}'", peering, manifest.name);
return Err(ConfigError::NoSuchVpc(manifest.name.clone()));
}
ConfigError::NoSuchVpc(manifest.name.clone())
})?;
Ok(())
}
pub fn validate(&mut self) -> ConfigResult {
debug!("Validating overlay configuration...");

/* validate peerings and check if referred VPCs exist */
/// Validate all peerings, checking if the VPCs they refer to exist in vpc table
pub fn validate_peerings(&self) -> ConfigResult {
debug!("Validating VPC peerings...");
for peering in self.peering_table.values() {
peering.validate()?;
self.check_peering_vpc(&peering.name, &peering.left)?;
self.check_peering_vpc(&peering.name, &peering.right)?;
}
Ok(())
}

/* temporary map of vpc names and ids */
/// Build a `VpcIdMap`. We have already checked that all VPC Ids are distinct
#[must_use]
pub fn vpcid_map(&self) -> VpcIdMap {
let id_map: VpcIdMap = self
.vpc_table
.values()
.map(|vpc| (vpc.name.clone(), vpc.id.clone()))
.collect();
id_map
}

/* collect peerings of every VPC */
/// Top most validation function for `Overlay` configuration
pub fn validate(&mut self) -> ConfigResult {
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.vpc_table.validate()?;

debug!(
"Overlay configuration is VALID and looks as:\n{}\n{}",
self.vpc_table, self.peering_table
);

/* empty peering table: we no longer need it since we have collected
all of the peerings and added them to the corresponding VPCs */
self.peering_table.clear();

/* empty collections used for validation */
self.vpc_table.clear_vnis();

debug!("Overlay configuration is VALID:\n{self}");
Ok(())
}
}
73 changes: 55 additions & 18 deletions config/src/external/overlay/vpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ use lpm::prefix::Prefix;
use net::vxlan::Vni;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use tracing::{debug, warn};
use tracing::{debug, error, warn};

use crate::converters::k8s::config::peering;
use crate::external::overlay::VpcManifest;
use crate::external::overlay::VpcPeeringTable;
use crate::internal::interfaces::interface::{InterfaceConfig, InterfaceConfigTable};
Expand All @@ -33,6 +34,21 @@ pub struct Peering {
pub adv_communities: Vec<String>, /* communities with which to advertise prefixes in this peering */
}

impl Peering {
fn validate(&self) -> ConfigResult {
debug!(
"Validating manifest of VPC {} in peering {}",
self.local.name, self.name
);
self.local.validate()?;
if false {
// not needed will be validated when validating the remote vpc
self.remote.validate()?;
}
Ok(())
}
}

#[derive(Clone, Debug, PartialEq, Ord, PartialOrd, Eq)]
/// Type for a fixed-sized VPC unique id
pub struct VpcId(pub(crate) [char; 5]);
Expand Down Expand Up @@ -89,7 +105,7 @@ impl Vpc {
}

/// Collect all peerings from the [`VpcPeeringTable`] table this vpc participates in
pub fn collect_peerings(&mut self, peering_table: &VpcPeeringTable, idmap: &VpcIdMap) {
pub fn set_peerings(&mut self, peering_table: &VpcPeeringTable, idmap: &VpcIdMap) {
debug!("Collecting peerings for vpc '{}'...", self.name);
self.peerings = peering_table
.peerings_vpc(&self.name)
Expand All @@ -114,6 +130,40 @@ impl Vpc {
}
}

/// Check that a [`Vpc`] does not peer more than once with another.
fn check_peering_count(&self) -> ConfigResult {
debug!("Checking peering duplicates for for VPC {}...", self.name);
// We use the VPC Ids to identify peer VPCs.
let mut peers = BTreeSet::new();
for peering in &self.peerings {
if (!peers.insert(peering.remote_id.clone())) {
error!(
"VPC {} peers more than once with peer {}",
self.name, peering.remote.name
);
return Err(ConfigError::DuplicateVpcPeerings(peering.name.clone()));
}
}
Ok(())
}

/// Check the peerings that a VPC participates in
fn check_peerings(&self) -> ConfigResult {
debug!("Checking peerings of VPC {}...", self.name);
for peering in &self.peerings {
peering.validate()?;
}
Ok(())
}

/// Validate a [`Vpc`]
pub fn validate(&self) -> ConfigResult {
debug!("Validating config for VPC {}...", self.name);
self.check_peering_count()?;
self.check_peerings()?;
Ok(())
}

/// Tell how many peerings this VPC has
#[must_use]
pub fn num_peerings(&self) -> usize {
Expand Down Expand Up @@ -170,6 +220,7 @@ impl VpcTable {
self.vpcs.insert(vpc.name.clone(), vpc);
Ok(())
}

/// Get a [`Vpc`] from the vpc table by name
#[must_use]
pub fn get_vpc(&self, vpc_name: &str) -> Option<&Vpc> {
Expand Down Expand Up @@ -205,27 +256,13 @@ impl VpcTable {
pub fn collect_peerings(&mut self, peering_table: &VpcPeeringTable, idmap: &VpcIdMap) {
debug!("Collecting peerings for all VPCs..");
self.values_mut()
.for_each(|vpc| vpc.collect_peerings(peering_table, idmap));
}
/// Clear set of vnis
pub fn clear_vnis(&mut self) {
self.vnis.clear();
.for_each(|vpc| vpc.set_peerings(peering_table, idmap));
}

/// Validate the [`VpcTable`]
pub fn validate(&self) -> ConfigResult {
for vpc in self.values() {
let mut peers = BTreeSet::new();
// For each VPC, loop over all peerings
for peering in &vpc.peerings {
// Check whether we have duplicate remote VPCs between peerings.
// If we fail to insert, this means the remote VPC ID is already in our set,
// and we have a duplicate peering: this is a configuration error.
if (!peers.insert(peering.remote_id.clone())) {
return Err(ConfigError::DuplicateVpcPeerings(peering.name.clone()));
}
}
peers.clear();
vpc.validate()?;
}
Ok(())
}
Expand Down
64 changes: 57 additions & 7 deletions config/src/external/overlay/vpcpeering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use lpm::prefix::{IpRangeWithPorts, Prefix, PrefixWithOptionalPorts, PrefixWithP
use std::collections::{BTreeMap, BTreeSet};
use std::ops::Bound::{Excluded, Unbounded};
use std::time::Duration;
use tracing::debug;

#[derive(Clone, Debug, Default, PartialEq)]
pub struct VpcExposeStatelessNat;
Expand Down Expand Up @@ -66,6 +65,7 @@ fn empty_btreeset() -> &'static BTreeSet<PrefixWithOptionalPorts> {
use crate::{ConfigError, ConfigResult};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct VpcExpose {
pub default: bool,
pub ips: BTreeSet<PrefixWithOptionalPorts>,
pub nots: BTreeSet<PrefixWithOptionalPorts>,
pub nat: Option<VpcExposeNat>,
Expand Down Expand Up @@ -193,6 +193,20 @@ impl VpcExpose {
pub fn has_host_prefixes(&self) -> bool {
self.ips.iter().filter(|p| p.prefix().is_host()).count() > 0
}

/// The prefixes of an expose to be advertised to a remote peer
#[must_use]
pub fn adv_prefixes(&self) -> Vec<Prefix> {
if self.default {
// only V4 atm
vec![Prefix::root_v4()]
} else if let Some(nat) = self.nat.as_ref() {
nat.as_range.iter().map(|p| p.prefix()).collect::<Vec<_>>()
} else {
self.ips.iter().map(|p| p.prefix()).collect::<Vec<_>>()
}
}

// If the as_range list is empty, then there's no NAT required for the expose, meaning that the
// public IPs are those from the "ips" list. This method returns the current list of public IPs
// for the VpcExpose.
Expand All @@ -207,6 +221,7 @@ impl VpcExpose {
&nat.as_range
}
}

// Same as public_ips, but returns the list of excluded prefixes
#[must_use]
pub fn public_excludes(&self) -> &BTreeSet<PrefixWithOptionalPorts> {
Expand Down Expand Up @@ -278,6 +293,40 @@ impl VpcExpose {
self.nat.as_ref().is_some_and(VpcExposeNat::is_stateless)
}

fn validate_default_expose(&self) -> ConfigResult {
if self.default {
if !self.ips.is_empty() || !self.nots.is_empty() || self.nat.is_some() {
return Err(ConfigError::Invalid(
"Default expose cannot have ips/nots or nat configuration".to_string(),
));
}
} else {
if self.ips.iter().any(|p| p.prefix().is_root()) {
return Err(ConfigError::Forbidden(
"Expose: root prefix as 'ip' forbidden",
));
}
if self.nots.iter().any(|p| p.prefix().is_root()) {
return Err(ConfigError::Forbidden(
"Expose: root prefix as 'not' is forbidden",
));
}
if let Some(nat) = &self.nat {
if nat.as_range.iter().any(|p| p.prefix().is_root()) {
return Err(ConfigError::Forbidden(
"Expose: root prefix as NAT 'as' is forbidden",
));
}
if nat.not_as.iter().any(|p| p.prefix().is_root()) {
return Err(ConfigError::Forbidden(
"Expose: root prefix as NAT 'as-not' is forbidden",
));
}
}
}
Ok(())
}

/// Validate the [`VpcExpose`]:
///
/// 1. Make sure that all prefixes and exclusion prefixes for this [`VpcExpose`] are of the same
Expand All @@ -292,6 +341,9 @@ impl VpcExpose {
/// 5. Make sure we have the same number of addresses available on each side (public/private),
/// taking exclusion prefixes into account.
pub fn validate(&self) -> ConfigResult {
// 0. Check default exposes and prefixes
self.validate_default_expose()?;

// 1. Static NAT: Check that all prefixes in a list are of the same IP version, as we don't
// support NAT46 or NAT64 at the moment.
//
Expand Down Expand Up @@ -526,8 +578,9 @@ impl VpcPeering {
gw_group,
}
}
#[cfg(test)]
/// Validate A VpcPeering. Only used in tests. Dataplane validates `Peerings`
pub fn validate(&self) -> ConfigResult {
debug!("Validating VPC peering '{}'...", &self.name);
self.left.validate()?;
self.right.validate()?;
Ok(())
Expand Down Expand Up @@ -561,11 +614,7 @@ impl VpcPeeringTable {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Empty a [`VpcPeeringTable`]
pub fn clear(&mut self) {
debug!("Emptying peering table...");
self.0.clear();
}

/// Add a [`VpcPeering`] to a [`VpcPeeringTable`]
pub fn add(&mut self, peering: VpcPeering) -> ConfigResult {
if peering.name.is_empty() {
Expand All @@ -587,6 +636,7 @@ impl VpcPeeringTable {
Ok(())
}
}

/// Iterate over all [`VpcPeering`]s in a [`VpcPeeringTable`]
pub fn values(&self) -> impl Iterator<Item = &VpcPeering> {
self.0.values()
Expand Down
1 change: 1 addition & 0 deletions k8s-intf/src/bolero/expose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl ValueGenerator for LegalValueExposeGenerator<'_> {
Some(GatewayAgentPeeringsPeeringExpose {
r#as: Some(final_as).filter(|f| !f.is_empty()),
ips: Some(final_ips).filter(|f| !f.is_empty()),
default: None,
nat: if has_as {
Some(
d.produce::<LegalValue<GatewayAgentPeeringsPeeringExposeNat>>()?
Expand Down
4 changes: 3 additions & 1 deletion k8s-intf/src/generated/gateway_agent_crd.rs

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

Loading
Loading