Keep NAT tuples exclusive across overlapping pools - #1707
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe PR refactors NAT allocation around disjoint address regions and VPC-aware pool keys. It adds shared port-forward leases, flow reconciliation, concurrency-model tests, Bolero fuzzing recipes, sanitizer guidance, and direct nextest coverage reporting. ChangesNAT allocator and flow integration
Concurrency and test tooling
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
24e46e5 to
ef6ba3b
Compare
ef6ba3b to
8fae1a9
Compare
There was a problem hiding this comment.
Pull request overview
This PR reworks NAT masquerade/port-forwarding interactions to correctly manage overlapping public tuple capacity at runtime (via leases) while significantly expanding allocator correctness, concurrency, and fuzz/test coverage.
Changes:
- Integrate port-forwarding with the masquerade allocator by leasing overlapping public tuples only while port-forward flows are live (and carrying leases across allocator replacement).
- Rebuild masquerade pool construction to safely handle overlapping public ranges and shared private address space across VPCs (region decomposition + per-(src,dst) pool keys).
- Add extensive unit, property, and concurrency tests (including Shuttle/Miri-oriented fixes and new
justhelpers for fuzzing/coverage).
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| nat/src/test.rs | Updates pipeline setup for the new port-forwarder allocator handle and adds overlap regression tests. |
| nat/src/portfw/test.rs | Adjusts port-forward tests for the new allocator dependency. |
| nat/src/portfw/nf.rs | Reserves/maintains masquerade tuple leases for port-forwarded flows and updates flow state accordingly. |
| nat/src/portfw/mod.rs | Re-exports lease update helper for cross-module reconciliation. |
| nat/src/portfw/flow_state.rs | Introduces PublicTuple + lease tracking and provides lease migration helper for live flows. |
| nat/src/masquerade/test.rs | Adds broader coverage: graceful close, allocator replacement carry-over, overlapping prefix scenarios, etc. |
| nat/src/masquerade/state.rs | Removes Clone from masquerade state to prevent accidental allocation duplication in tests. |
| nat/src/masquerade/nf.rs | Adds discriminant validation, adjusts allocation call signature, and scales timeouts for emulated tests. |
| nat/src/masquerade/natip.rs | Simplifies NatIp trait and adds explicit to_addr_bits to avoid method-resolution pitfalls. |
| nat/src/masquerade/mod.rs | Exposes allocator reader internally for port-forward integration. |
| nat/src/masquerade/icmp_handling.rs | Hardens ICMP error handling against missing state/discriminants. |
| nat/src/masquerade/flows.rs | Reconciles both masquerade allocations and port-forward leases across allocator changes/removal. |
| nat/src/masquerade/apalloc/test_alloc.rs | Adds/extends allocator tests for overlap, IPv6, port-forward leasing, and region behavior. |
| nat/src/masquerade/apalloc/setup.rs | Refactors pool construction: gather exposes, decompose overlaps into shared regions, build pool sets. |
| nat/src/masquerade/apalloc/region.rs | New region decomposition logic + property tests to guarantee disjoint/maximal regions and ordering. |
| nat/src/masquerade/apalloc/port_alloc.rs | Improves reservation/lookup race handling and makes accounting errors observable. |
| nat/src/masquerade/apalloc/pool_fuzz.rs | New property fuzz tests for uniqueness, bounds, reuse, carry-over, and IPv6 edge cases. |
| nat/src/masquerade/apalloc/natip_with_bitmap.rs | Updates IPv6 reverse mapping to return errors instead of panicking. |
| nat/src/masquerade/apalloc/display.rs | Adds PoolSet display and avoids allocator deadlocks while formatting. |
| nat/src/masquerade/apalloc/concurrent_fuzz.rs | New concurrent fuzz suite for allocator publication/replacement and race regressions. |
| nat/src/masquerade/apalloc/alloc.rs | Adds PoolSet abstraction, region-based pools, better error handling, and deadlock avoidance. |
| nat/src/masquerade/allocator_writer.rs | Switches to unified reconciliation flow and ensures allocator removal detaches state appropriately. |
| nat/src/masquerade/allocation.rs | Adds is_exhaustion() classification with tests ensuring consistent error→DoneReason mapping. |
| miri.just | Ensures Miri runs with consistent flags and avoids incremental compilation sharing across workers. |
| justfile | Adds fuzz/fuzz-list helpers and simplifies coverage workflow. |
| flow-entry/src/flow_table/concurrent_fuzz.rs | Marks stress test as #[concurrency::model_test] for backend-specific nextest filtering. |
| development/code/running-tests.md | Documents running a real libfuzzer campaign via just fuzz. |
| dataplane/src/packet_processor/mod.rs | Wires allocator handle into the port-forwarder stage construction. |
| concurrency/src/macros.rs | Re-exports the new model_test macro. |
| concurrency-macros/src/lib.rs | Implements #[model_test] and clarifies backend leaf naming for nextest filters. |
| acl-filter/src/tests.rs | Updates end-to-end tests for the port-forwarder allocator handle. |
| .gitignore | Ignores libfuzzer worker logs (fuzz-*.log). |
Suppressed comments (1)
nat/src/portfw/flow_state.rs:84
confidence: 8
tags: [other]
Same concern as `new_snat`: `PortFwState` is publicly re-exported, so making `new_dnat` `pub(crate)` breaks external callers. Keep it `pub` (or, if construction must be internal, consider also making the type non-public/re-exported to keep the API consistent).
pub(crate) fn new_dnat(
</details>
---
💡 <a href="/githedgehog/dataplane/new/main?filename=.github/skills/code-review/SKILL.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add a `code-review` agent skill</a> or configure MCP servers for context-aware, tailored reviews. <a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review#mcp-servers-and-agent-skills" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn more in the docs.</a>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (12)
nat/src/masquerade/test.rs (1)
332-471: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a parameterized overlay builder.
The four builders repeat the same VPC table, peering names, and VPC-3 expose. Only the VPC-1 and VPC-2 exposes differ. A single helper that takes the exposes per peering would remove about 100 lines and keep the scenarios readable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/test.rs` around lines 332 - 471, Replace the duplicated setup in build_overlay_shared_private_prefix, build_overlay_shared_private_prefix_extended, build_overlay_shared_private_prefix_narrowed, and build_overlay_without_masquerade with a parameterized helper accepting the VPC-1 and VPC-2 exposes (including optional VPC-2/peering configuration as needed). Keep the shared VPC table, peering names, VPC-3 expose, and Overlay construction in that helper, then have each scenario builder provide only its differing exposes.nat/src/masquerade/apalloc/concurrent_fuzz.rs (1)
303-303: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueGuard the modulo against an empty pool list.
step % published.pools.len()panics ifpoolsis empty. Line 340 already guards the same pattern withsurvivors.len().max(1).pool_sets_for_specscurrently returns one pool set per spec, andspecsis never empty, so this is not reachable today. Applying the same guard keeps the two sites consistent and protects against a future change inpool_sets_for_specs.♻️ Proposed change
PacketOp::Allocate => { - let owner = step % published.pools.len(); + let Some(owner) = published + .pools + .len() + .checked_rem(published.pools.len()) + .map(|_| step % published.pools.len()) + else { + continue; + }; match published.pools[owner].allocate(false) {A simpler form is also acceptable:
if published.pools.is_empty() { continue; } let owner = step % published.pools.len();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/apalloc/concurrent_fuzz.rs` at line 303, Guard the owner calculation around published.pools in the relevant loop so an empty pool list skips the iteration before modulo, or otherwise use a nonzero length consistent with the existing survivors.len().max(1) guard. Preserve the current owner selection for non-empty pools.nat/src/masquerade/allocator_writer.rs (1)
128-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUpdate the doc comment: the disabled-masquerade path no longer cancels every flow.
The doc comment on
update_nat_allocatorstates that flows using the allocator "will be cancelled".remove_allocator_from_flowsnow invalidates only flows carrying NAT state and clears the port-forward lease on the rest, so port-forward flows survive. Thedebug!message on line 130 says "will invalidate flows" and has the same problem.Align both texts with the new behaviour.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/allocator_writer.rs` around lines 128 - 135, Update the doc comment for update_nat_allocator and the debug! message in its disabled-masquerading branch to describe the actual remove_allocator_from_flows behavior: NAT-state flows are invalidated, while other flows have their port-forward lease cleared and survive.nat/src/masquerade/apalloc/setup.rs (1)
131-140: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueBuild
specsonce per destination VPC, not once per protocol.
specsdoes not depend onprotocol. The current code clones every expose'spublic_rangesthree times per destination VPC. Move the construction above the protocol loop.♻️ Proposed refactor
for (dst_vpc_id, exposes) in groups { + let specs: Vec<PoolSpec> = exposes + .iter() + .map(|expose| PoolSpec { + public_ranges: expose.public_ranges.clone(), + idle_timeout: expose.idle_timeout, + }) + .collect(); // Allocations for TCP, for example, do not affect allocations for UDP or for ICMP: the // space made of addresses and L4 ports or identifiers is distinct for each protocol. So // each region backs one allocator per protocol, over the same addresses. for protocol in [NextHeader::TCP, NextHeader::UDP, icmp_proto] { - let specs: Vec<PoolSpec> = exposes - .iter() - .map(|expose| PoolSpec { - public_ranges: expose.public_ranges.clone(), - idle_timeout: expose.idle_timeout, - }) - .collect(); - let pool_sets = pool_sets_for_specs::<J>(&specs, protocol, randomize);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/apalloc/setup.rs` around lines 131 - 140, Move the `specs` construction from inside the protocol loop to immediately before `for protocol in [NextHeader::TCP, NextHeader::UDP, icmp_proto]`, then reuse that single collection for each call to `pool_sets_for_specs`. Keep the existing `PoolSpec` mapping and protocol-specific processing unchanged.nat/src/masquerade/nf.rs (1)
86-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
discriminantsduplicates the checks inprocess_packet, and the two paths report different outcomes.
process_packet(lines 492-505) already rejects a packet without either discriminant and setsDoneReason::Unroutable.discriminantstherefore never returnsMissingDiscriminanton that path, andMissingDiscriminantmaps toDoneReason::NatFailure. The same malformed input would be reported two ways depending on which check runs.Remove the two pre-checks in
process_packetand letmasquerade_packetsurfaceMissingDiscriminant, or keep the pre-checks and align theDoneReason.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/nf.rs` around lines 86 - 95, Remove the duplicate source and destination VPC discriminant checks in process_packet and rely on masquerade_packet’s discriminants validation to return MissingDiscriminant consistently. Preserve the existing error propagation so malformed packets receive the corresponding single DoneReason rather than diverging based on which check runs.nat/src/masquerade/apalloc/port_alloc.rs (1)
316-330: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueThe retry loop spins without yielding.
find_block_for_portrepeats immediately when the free flag and the map entry disagree. The window it waits for is closed by another thread that runs between the CAS intry_to_reserve_blockand theinsertinallocate_block_for_reservation. Four tight iterations can complete before that thread is rescheduled, which turns a recoverable race intoPortReservationFailed.Add
std::hint::spin_loop()(or athread::yield_now()on the last attempts) between iterations to widen the window.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/apalloc/port_alloc.rs` around lines 316 - 330, Update the retry loop in find_block_for_port to yield between failed block lookups, after logging the detected free-flag/map-entry race and before the next iteration. Use std::hint::spin_loop() or thread::yield_now(), while preserving the existing retry limit and PortReservationFailed outcome.nat/src/masquerade/flows.rs (1)
176-184: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueSkip the lease update when
check_masquerading_flowhas just invalidated the flow.
for_each_flow_filteredtestsis_activebefore the closure runs, butcheck_masquerading_flowcan callinvalidate_pairinside it.update_port_forward_leasethen reserves a public tuple for a flow that is already dead. The lease is released when the flow state is dropped, so this is not a permanent reservation, but during reconciliation the tuple is unavailable to other flows.♻️ Proposed change
|flow_key, flow_info| { check_masquerading_flow(flow_key, flow_info, new_allocator); + if !flow_info.is_active() { + return; + } if let Err(error) = update_port_forward_lease(flow_info, Some(new_allocator)) { error!("Failed to reserve a live port-forward tuple: {error}"); flow_info.invalidate_pair(); } },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/flows.rs` around lines 176 - 184, In the closure passed to for_each_flow_filtered, check whether flow_info remains active after check_masquerading_flow and before calling update_port_forward_lease. Skip the lease update for flows invalidated by check_masquerading_flow, while preserving the existing error handling for still-active flows.nat/src/masquerade/apalloc/mod.rs (2)
337-347: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueStale lease entries are only pruned when a reservation happens.
leases.retainruns once per 256 calls toreserve_port_forward. If port-forward traffic stops, the expiredWeak<Allocation>entries stay in the map until the next reservation. The map is bounded by the number of distinct forwarded tuples, so this is not a leak of unbounded size, but the memory is held for an unbounded time.Consider also removing the entry when
Weak::upgradefails on the lookup path just below.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/apalloc/mod.rs` around lines 337 - 347, Update the lease lookup path around the existing leases.get(...).and_then(Weak::upgrade) check to remove the corresponding key from leases when Weak::upgrade fails. Preserve returning the upgraded lease when it exists, and ensure stale entries are deleted during lookup rather than relying solely on the periodic retain cleanup.
203-215: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winConsider indexing public regions instead of scanning the whole table.
public_allocatoriterates every entry of the pool table and every region of every matching pool set. The same region is visited once per private prefix that shares the pool set. This runs on the first packet of each port-forward flow and once per live port-forward flow during allocator replacement, so the cost grows with the number of exposes multiplied by the number of live port-forward flows.A per-destination-VPC map from public interval to allocator, built once in
build_pools, would make the lookup logarithmic and remove the duplicate visits.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/apalloc/mod.rs` around lines 203 - 215, Replace the full-table scan in public_allocator with a per-destination-VPC public-region index built once by build_pools. Index each public interval to its allocator, then perform a logarithmic interval lookup keyed by protocol and dst_vpcd while preserving the existing address-containment behavior.nat/src/masquerade/apalloc/test_alloc.rs (1)
469-495: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNegative
reserve_portassertions accept the wrong error. Both tests prove that a port-forward lease blocks a masquerade reservation, but they only checkis_err().reserve_portalso returnsNoPoolFoundwhen the pool lookup misses andDeniedfor a well-known port. A regression that breaks the pool lookup, or that stops carrying the lease into the replacement allocator, would leave both tests green.
nat/src/masquerade/apalloc/test_alloc.rs#L469-L495: replace bothis_err()assertions with a match on theAllocatorErrorvariant that a live lease produces.nat/src/test.rs#L221-L243: maketuple_is_reservedreturn the error variant comparison instead ofis_err(), so the post-replacement assertion proves lease carry-over rather than any failure.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/masquerade/apalloc/test_alloc.rs` around lines 469 - 495, The negative reserve_port assertions in nat/src/masquerade/apalloc/test_alloc.rs lines 469-495 must match the AllocatorError variant produced when a live lease blocks reservation, rather than accepting any error; update both assertions while preserving the successful post-drop reservation. In nat/src/test.rs lines 221-243, update tuple_is_reserved to compare the returned error variant so the replacement-allocator check specifically verifies lease carry-over.nat/src/portfw/test.rs (1)
207-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe allocator slot stays empty, so the lease path is never exercised.
NatAllocatorWriter::new()creates an empty slot, andsetup_pipelinenever callsupdate_nat_allocator. Thereforeself.allocator.get()returnsNoneindo_port_forwarding, and both the reservation atnat/src/portfw/nf.rsLines 128-143 andupdate_port_forward_leasetake the no-allocator branch in every test in this module.Add one test that installs a masquerade config whose public range covers
70.71.72.73, then assert that the forward and reverse flows hold the same lease and that masquerade cannot reserve the same tuple while the flows live.nat/src/masquerade/apalloc/test_alloc.rsLines 454-496 shows the expected sharing and release behaviour.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/portfw/test.rs` around lines 207 - 213, Add a port-forwarding test that updates the NatAllocatorWriter with a masquerade configuration whose public range includes 70.71.72.73 before running setup_pipeline. Assert the forward and reverse flows share the same lease, and verify masquerade cannot reserve that tuple while both flows remain active, following the sharing and release pattern in the allocator tests.nat/src/portfw/flow_state.rs (1)
145-164: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe
boolresult is not used by the only caller.
nat/src/portfw/nf.rsLines 205-211 match only onErr. TheOk(false)case, which means the flow carries no port-forwarding state, is silently ignored. Consider returningResult<(), AllocatorError>and logging the missing-state case here, or handlingOk(false)in the caller.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/portfw/flow_state.rs` around lines 145 - 164, Update update_port_forward_lease and its sole caller to remove the unused boolean result, either by returning Result<(), AllocatorError> and logging the missing port-forwarding state within the function, or by explicitly handling the no-state outcome in the caller’s match. Ensure allocator errors remain propagated and missing state is no longer silently ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@acl-filter/src/tests.rs`:
- Around line 938-939: Use a single NatAllocatorWriter in the test setup,
configure it with update_nat_allocator, and pass readers from that same writer
to both PortForwarder and Masquerade. Remove the second allocator and ensure the
shared writer is retained in PipelineHandles so both stages observe and reserve
the same allocation state.
---
Nitpick comments:
In `@nat/src/masquerade/allocator_writer.rs`:
- Around line 128-135: Update the doc comment for update_nat_allocator and the
debug! message in its disabled-masquerading branch to describe the actual
remove_allocator_from_flows behavior: NAT-state flows are invalidated, while
other flows have their port-forward lease cleared and survive.
In `@nat/src/masquerade/apalloc/concurrent_fuzz.rs`:
- Line 303: Guard the owner calculation around published.pools in the relevant
loop so an empty pool list skips the iteration before modulo, or otherwise use a
nonzero length consistent with the existing survivors.len().max(1) guard.
Preserve the current owner selection for non-empty pools.
In `@nat/src/masquerade/apalloc/mod.rs`:
- Around line 337-347: Update the lease lookup path around the existing
leases.get(...).and_then(Weak::upgrade) check to remove the corresponding key
from leases when Weak::upgrade fails. Preserve returning the upgraded lease when
it exists, and ensure stale entries are deleted during lookup rather than
relying solely on the periodic retain cleanup.
- Around line 203-215: Replace the full-table scan in public_allocator with a
per-destination-VPC public-region index built once by build_pools. Index each
public interval to its allocator, then perform a logarithmic interval lookup
keyed by protocol and dst_vpcd while preserving the existing address-containment
behavior.
In `@nat/src/masquerade/apalloc/port_alloc.rs`:
- Around line 316-330: Update the retry loop in find_block_for_port to yield
between failed block lookups, after logging the detected free-flag/map-entry
race and before the next iteration. Use std::hint::spin_loop() or
thread::yield_now(), while preserving the existing retry limit and
PortReservationFailed outcome.
In `@nat/src/masquerade/apalloc/setup.rs`:
- Around line 131-140: Move the `specs` construction from inside the protocol
loop to immediately before `for protocol in [NextHeader::TCP, NextHeader::UDP,
icmp_proto]`, then reuse that single collection for each call to
`pool_sets_for_specs`. Keep the existing `PoolSpec` mapping and
protocol-specific processing unchanged.
In `@nat/src/masquerade/apalloc/test_alloc.rs`:
- Around line 469-495: The negative reserve_port assertions in
nat/src/masquerade/apalloc/test_alloc.rs lines 469-495 must match the
AllocatorError variant produced when a live lease blocks reservation, rather
than accepting any error; update both assertions while preserving the successful
post-drop reservation. In nat/src/test.rs lines 221-243, update
tuple_is_reserved to compare the returned error variant so the
replacement-allocator check specifically verifies lease carry-over.
In `@nat/src/masquerade/flows.rs`:
- Around line 176-184: In the closure passed to for_each_flow_filtered, check
whether flow_info remains active after check_masquerading_flow and before
calling update_port_forward_lease. Skip the lease update for flows invalidated
by check_masquerading_flow, while preserving the existing error handling for
still-active flows.
In `@nat/src/masquerade/nf.rs`:
- Around line 86-95: Remove the duplicate source and destination VPC
discriminant checks in process_packet and rely on masquerade_packet’s
discriminants validation to return MissingDiscriminant consistently. Preserve
the existing error propagation so malformed packets receive the corresponding
single DoneReason rather than diverging based on which check runs.
In `@nat/src/masquerade/test.rs`:
- Around line 332-471: Replace the duplicated setup in
build_overlay_shared_private_prefix,
build_overlay_shared_private_prefix_extended,
build_overlay_shared_private_prefix_narrowed, and
build_overlay_without_masquerade with a parameterized helper accepting the VPC-1
and VPC-2 exposes (including optional VPC-2/peering configuration as needed).
Keep the shared VPC table, peering names, VPC-3 expose, and Overlay construction
in that helper, then have each scenario builder provide only its differing
exposes.
In `@nat/src/portfw/flow_state.rs`:
- Around line 145-164: Update update_port_forward_lease and its sole caller to
remove the unused boolean result, either by returning Result<(), AllocatorError>
and logging the missing port-forwarding state within the function, or by
explicitly handling the no-state outcome in the caller’s match. Ensure allocator
errors remain propagated and missing state is no longer silently ignored.
In `@nat/src/portfw/test.rs`:
- Around line 207-213: Add a port-forwarding test that updates the
NatAllocatorWriter with a masquerade configuration whose public range includes
70.71.72.73 before running setup_pipeline. Assert the forward and reverse flows
share the same lease, and verify masquerade cannot reserve that tuple while both
flows remain active, following the sharing and release pattern in the allocator
tests.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ac5b349e-0f9e-420b-b042-3a5d4c91c7ac
📒 Files selected for processing (33)
.gitignoreacl-filter/src/tests.rsconcurrency-macros/src/lib.rsconcurrency/src/macros.rsdataplane/src/packet_processor/mod.rsdevelopment/code/running-tests.mdflow-entry/src/flow_table/concurrent_fuzz.rsjustfilemiri.justnat/src/masquerade/allocation.rsnat/src/masquerade/allocator_writer.rsnat/src/masquerade/apalloc/alloc.rsnat/src/masquerade/apalloc/concurrent_fuzz.rsnat/src/masquerade/apalloc/display.rsnat/src/masquerade/apalloc/mod.rsnat/src/masquerade/apalloc/natip_with_bitmap.rsnat/src/masquerade/apalloc/pool_fuzz.rsnat/src/masquerade/apalloc/port_alloc.rsnat/src/masquerade/apalloc/region.rsnat/src/masquerade/apalloc/setup.rsnat/src/masquerade/apalloc/test_alloc.rsnat/src/masquerade/flows.rsnat/src/masquerade/icmp_handling.rsnat/src/masquerade/mod.rsnat/src/masquerade/natip.rsnat/src/masquerade/nf.rsnat/src/masquerade/state.rsnat/src/masquerade/test.rsnat/src/portfw/flow_state.rsnat/src/portfw/mod.rsnat/src/portfw/nf.rsnat/src/portfw/test.rsnat/src/test.rs
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 33 changed files in this pull request and generated no new comments.
Suppressed comments (2)
nat/src/portfw/nf.rs:210
confidence: 7
tags: [logic]
If `update_port_forward_lease` fails after both flows have been inserted, the code invalidates the pair but leaves the entries in the flow table. Because the port-forward state can still hold the (old) lease until the table reaps the flow, this can unnecessarily pin the reserved public tuple.
Consider removing both flow entries from the flow table on this error path so the lease is released immediately.
**nat/src/portfw/nf.rs:200**
* ```yaml
confidence: 8
tags: [logic]
If inserting the reverse port-forwarding flow fails, the forward flow is only invalidated but not removed from the flow table. Since port-forward flows now carry a public-tuple lease, leaving the forward flow resident can keep the lease (and therefore masquerade capacity) held until the flow table later reaps it.
Prefer removing the forward entry from the flow table on this error path to ensure any attached lease is released promptly and to avoid leaving a one-sided, invalidated flow in the table.
This issue also appears on line 206 of the same file.
8fae1a9 to
c1af914
Compare
Private-prefix lookup keys omitted the source VPC, so tenants using the same private prefix and peer VPC overwrote one another. Packets could then use the public range belonging to another tenant. Add the source VPC to the lookup key. Identical public ranges remain shared. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Partially overlapping ranges had independent allocators that could lease the same address. Split public space into disjoint regions and share each region allocator among its owners, preferring exclusive regions during allocation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Checking only the nearest preceding prefix missed addresses covered by a wider enclosing prefix. Scan within the protocol and VPC pair, selecting the narrowest containing range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Race packet workers against allocator replacement under generated schedules. Check live-tuple uniqueness, carry-over, and release behavior. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Dropping the last upgraded address reference under a pool guard reacquired the same lock through the release path. Keep upgraded references alive until after the guard is released. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Add recipes to list and run Bolero libFuzzer targets, including sanitizer selection and TSan standard-library rebuilding. Document the workflow and ignore worker logs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Allocation tried the next address or region after every error. A real allocator failure could therefore be hidden by a later success or reported as resource exhaustion. Fall through only when the current space is exhausted, and propagate all other errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
A carried IPv6 address can fall outside the replacement region's u32-indexed span. Return NoPoolFound instead of panicking; log deallocation failures because Drop cannot return them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Cloning allocated tuples allowed one clone to release a tuple still named by another. Remove Clone so one object controls release, and report inconsistent deallocation instead of silently retaining bad bitmap state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Keep the lifetime and exclusivity invariants needed to modify the allocator safely; remove development history and implementation narration. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Scale internal flow timeouts for Miri and qemu-user so test flows do not expire between packet refreshes. Production values are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Pool-table tests do not cover source-VPC isolation through flow creation and allocator updates. Masquerade the same private prefix in two VPCs, update the generation, and verify each flow retains the public range configured for its VPC. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
c1af914 to
43b320a
Compare
Port forwarding and masquerade can overlap and claim the same public tuple. Lease overlapping tuples for the lifetime of forwarded flows and carry those leases across allocator replacement. Rules themselves reserve no capacity. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Keep neighbouring ports live, release one port, and verify the allocator returns it. This exposes per-port bitmap errors hidden when the entire block is dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Make concurrent carry-over and duplicate-reservation failures strict. Add a direct check that a live tuple remains unavailable until its allocation is dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
A reservation can observe the block bitmap between free-bit and map updates. Yield and retry that transient mismatch, and remove a map entry only if it still names the released block. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Replace the allocator while preserving flows that reuse private space across VPCs. Verify their tuples are reserved before publication and remain distinct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Delete NatIp::from_src_addr, NatIp::from_dst_addr, and their stale documentation. They have no callers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
IPv6 allocation maps 128-bit addresses to bitmap offsets, a path the IPv4 tests do not exercise. Allocate from an IPv6 range and reserve the live tuple in a replacement allocator, including the range-start offset. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Exhaust a two-address public pool, assert its allocation boundary, then release it and verify reuse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Carry-over must end a live flow when the replacement configuration no longer grants its tuple. Remove a flow's peering, exposed source, or masquerade allocator and verify the flow ends. Check that unaffected flows survive the same update. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Returning a block and retiring its address are separate paths; an error in either can leak capacity or revive retired space. Verify a freed block is reused while its address stays live, and that returning a block does not make a retired address allocatable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Skip exhaustive region walks under Miri while testing large offsets directly. Preserve repository flags and disable incremental compilation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
`just coverage` replayed binaries built without -Cinstrument-coverage, so no profile data was produced. Build instrumented nextest binaries, clear stale profiles, create the output directory, and forward test filters to nextest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Enumerate every AllocatorError and verify that only exhaustion falls through to another region or maps to NatOutOfResources. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Masquerade shortens a flow's lifetime during graceful close, but only reset behavior was covered. Exercise client- and server-initiated FIN transitions. Assert state changes without relying on wall-clock expiry under emulation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Reject uncheckable packets and invalidate uncheckable flows instead of unwrapping missing VPC metadata. ICMP error logging no longer requires source metadata. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
43b320a to
1b089d7
Compare
Reworked and consolidated version of the stack ending in #1700
Summary
Configured port-forward rules reserve no masquerade capacity. Well-known ports and tuples outside masquerade pools need no lease.