-
Notifications
You must be signed in to change notification settings - Fork 7
Add more validation for peerings with regards to "default", overlap #1207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more validation for peerings with regards to "default", overlap #1207
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds validation to prevent overlapping prefixes in VPC peerings, building on PR #1205 which introduced support for "default" destination in flow-filter stage. The validation ensures that prefixes exposed to a VPC do not overlap, with the exception that overlaps are allowed when one of the exposes is a "default" expose destination.
Changes:
- Added
check_overlap()method to validate non-overlapping prefixes across VPC peerings - Updated import from
PrefixtoIpRangeWithPortstrait to use theoverlaps()method - Added comprehensive test coverage for overlapping prefix detection and default expose behavior
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| config/src/external/overlay/vpc.rs | Implements overlap validation logic in new check_overlap() method and integrates it into the VPC validation flow |
| config/src/external/overlay/tests.rs | Adds tests for overlapping prefix detection and validation of default expose allowing overlaps |
bf1f81b to
82efafa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| // FIXME: Find a less expensive approach to find overlapping prefixes | ||
| for (i, current_peering) in self.peerings.iter().enumerate() { | ||
| for other_peering in &self.peerings[i + 1..] { | ||
| for current_expose in ¤t_peering.remote.exposes { | ||
| for other_expose in &other_peering.remote.exposes { | ||
| match (current_expose.default, other_expose.default) { | ||
| (true, true) => { | ||
| // We support at most one default destination exposed to any VPC | ||
| error!( | ||
| "Multiple 'default' destinations exposed to VPC {}", | ||
| self.name | ||
| ); | ||
| return Err(ConfigError::Forbidden( | ||
| "Multiple 'default' destinations exposed to VPC", | ||
| )); | ||
| } | ||
| (true, false) | (false, true) => { | ||
| // Overlap is allowed between a prefix and a default expose | ||
| continue; | ||
| } | ||
| (false, false) => { /* keep processing */ } | ||
| } | ||
| for current_prefix in current_expose.public_ips() { | ||
| for other_prefix in other_expose.public_ips() { | ||
| if current_prefix.overlaps(other_prefix) { | ||
| error!( | ||
| "Prefixes exposed to VPC {} overlap: {} and {}", | ||
| self.name, current_prefix, other_prefix | ||
| ); | ||
| return Err(ConfigError::OverlappingPrefixes( | ||
| *current_prefix, | ||
| *other_prefix, | ||
| )); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nested loop structure has O(n²×m²) complexity where n is the number of peerings and m is the number of exposes per peering. While the FIXME comment acknowledges this, consider whether this validation should be performed at a different stage or if there's a more efficient approach using a data structure like a prefix tree or interval tree to detect overlaps in O(n×m×log(n×m)) time.
| // FIXME: Find a less expensive approach to find overlapping prefixes | |
| for (i, current_peering) in self.peerings.iter().enumerate() { | |
| for other_peering in &self.peerings[i + 1..] { | |
| for current_expose in ¤t_peering.remote.exposes { | |
| for other_expose in &other_peering.remote.exposes { | |
| match (current_expose.default, other_expose.default) { | |
| (true, true) => { | |
| // We support at most one default destination exposed to any VPC | |
| error!( | |
| "Multiple 'default' destinations exposed to VPC {}", | |
| self.name | |
| ); | |
| return Err(ConfigError::Forbidden( | |
| "Multiple 'default' destinations exposed to VPC", | |
| )); | |
| } | |
| (true, false) | (false, true) => { | |
| // Overlap is allowed between a prefix and a default expose | |
| continue; | |
| } | |
| (false, false) => { /* keep processing */ } | |
| } | |
| for current_prefix in current_expose.public_ips() { | |
| for other_prefix in other_expose.public_ips() { | |
| if current_prefix.overlaps(other_prefix) { | |
| error!( | |
| "Prefixes exposed to VPC {} overlap: {} and {}", | |
| self.name, current_prefix, other_prefix | |
| ); | |
| return Err(ConfigError::OverlappingPrefixes( | |
| *current_prefix, | |
| *other_prefix, | |
| )); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Enforce "at most one default destination exposed to any VPC" and | |
| // efficiently detect overlapping prefixes between non-default exposes | |
| // across different peerings. | |
| // Track how many *peerings* have at least one default expose. | |
| // The previous implementation only rejected multiple defaults across | |
| // different peerings (it never compared exposes within the same peering), | |
| // so we preserve that behavior here. | |
| let mut peerings_with_default = 0usize; | |
| // Collect all prefixes from non-default exposes, annotated with the | |
| // peering index. Overlap is allowed between a prefix and a default | |
| // expose, so defaults are excluded from overlap detection. | |
| let mut prefixes: Vec<(IpRangeWithPorts, usize)> = Vec::new(); | |
| for (peering_idx, peering) in self.peerings.iter().enumerate() { | |
| let mut has_default_in_peering = false; | |
| for expose in &peering.remote.exposes { | |
| if expose.default { | |
| has_default_in_peering = true; | |
| // Skip prefixes for default exposes: overlap with defaults is allowed. | |
| continue; | |
| } | |
| for prefix in expose.public_ips() { | |
| prefixes.push((*prefix, peering_idx)); | |
| } | |
| } | |
| if has_default_in_peering { | |
| peerings_with_default += 1; | |
| if peerings_with_default > 1 { | |
| // We support at most one default destination exposed to any VPC | |
| error!( | |
| "Multiple 'default' destinations exposed to VPC {}", | |
| self.name | |
| ); | |
| return Err(ConfigError::Forbidden( | |
| "Multiple 'default' destinations exposed to VPC", | |
| )); | |
| } | |
| } | |
| } | |
| // Sort prefixes by their natural ordering so that any overlapping ranges | |
| // from different peerings will be adjacent or near-adjacent. | |
| prefixes.sort_by(|(a, _), (b, _)| a.cmp(b)); | |
| // Scan adjacent prefixes to detect overlaps between different peerings. | |
| for window in prefixes.windows(2) { | |
| let ((prefix_a, peering_a), (prefix_b, peering_b)) = (&window[0], &window[1]); | |
| // Only enforce no-overlap across *different* peerings, matching the | |
| // previous implementation's semantics. | |
| if peering_a != peering_b && prefix_a.overlaps(prefix_b) { | |
| error!( | |
| "Prefixes exposed to VPC {} overlap: {} and {}", | |
| self.name, prefix_a, prefix_b | |
| ); | |
| return Err(ConfigError::OverlappingPrefixes(*prefix_a, *prefix_b)); | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shhhhh don't tell Manish, he'll make nightmares about it!
a976833 to
6832a8c
Compare
Make sure that we do not expose overlapping prefixes to a given VPC. The only supported exception is when one of the expose is a `default` expose destination. Note: The implementation is currently rather inefficient as we have seven nested loops (counting the loop on VPCs for validation). We should look into some structure where to insert prefixes as we encounter them, like for check_peering_count(), but it's not trivial to identify overlapping prefixes (accounting for both IP prefixes and port ranges) in a structure without looping on them anyway, so we stick to the naive implementation for now. Signed-off-by: Quentin Monnet <qmo@qmon.net>
6832a8c to
3f933d2
Compare
We decided to reject, for now at least, configurations where multiple "default" VpcExpose objects are exposed to a single VPC. This commit adjusts validation accordingly, and add the related unit tests. Also reject the configuration if a peering (not a VPC) contains multiple "default" expose blocks. Signed-off-by: Quentin Monnet <qmo@qmon.net>
3f933d2 to
8275ebc
Compare
Based on #1205
Make sure that we do not expose overlapping prefixes to a given VPC. The only supported exception is when one of the expose is a
defaultexpose destination.WIP: Ensure we have at most one
defaultexpose object exposed to any given VPC.