Skip to content

Extend support for port forwarding with masquerading (stateful NAT)#1274

Closed
qmonnet wants to merge 20 commits intopr/fredi/port-forwarding-contfrom
pr/qmonnet/port-forwarding
Closed

Extend support for port forwarding with masquerading (stateful NAT)#1274
qmonnet wants to merge 20 commits intopr/fredi/port-forwarding-contfrom
pr/qmonnet/port-forwarding

Conversation

@qmonnet
Copy link
Member

@qmonnet qmonnet commented Feb 10, 2026

@qmonnet qmonnet added the area/nat Related to Network Address Translation (NAT) label Feb 10, 2026
@qmonnet qmonnet changed the title Extend suppot for port forwarding with masquerading (stateful NAT) Extend support for port forwarding with masquerading (stateful NAT) Feb 10, 2026
@qmonnet qmonnet force-pushed the pr/qmonnet/port-forwarding branch 9 times, most recently from 72fc24d to 2d677ef Compare February 16, 2026 12:35
@Fredi-raspall Fredi-raspall force-pushed the pr/fredi/port-forwarding branch from cf14551 to d46ff5c Compare February 18, 2026 11:52
@qmonnet qmonnet force-pushed the pr/qmonnet/port-forwarding branch 2 times, most recently from 7640774 to a298fb0 Compare February 18, 2026 23:58
@Fredi-raspall Fredi-raspall force-pushed the pr/fredi/port-forwarding branch from 6098287 to 6fd1ccd Compare February 19, 2026 21:34
@qmonnet qmonnet force-pushed the pr/qmonnet/port-forwarding branch 2 times, most recently from e93f404 to 4e34d7e Compare February 20, 2026 02:37
@qmonnet qmonnet changed the base branch from pr/fredi/port-forwarding to pr/fredi/port-forwarding-cont February 20, 2026 02:38
@qmonnet qmonnet force-pushed the pr/qmonnet/port-forwarding branch from 4e34d7e to 53fc0de Compare February 20, 2026 02:39
@Fredi-raspall Fredi-raspall force-pushed the pr/fredi/port-forwarding-cont branch from 76a622f to 6e2eae4 Compare February 20, 2026 19:34
@qmonnet qmonnet force-pushed the pr/qmonnet/port-forwarding branch from 53fc0de to 2199041 Compare February 23, 2026 13:11
Add the PortForwarding variant to struct VpcExposeNatConfig. We also add
the related methods:

- VpcExposeNat.is_port_forwarding()
- VpcExpose.make_port_forwarding()
- VpcExpose.has_port_forwarding()

Consequence of the addition of the new variant, we also update function
get_nat_requirement() in the flow-filter stage setup code, basing it on
a new From<&VpcExposeNatConfig> for NatRequirement implementation.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
As part of the port forwarding support, we are able to change the
structure of enum VpcdLookupResult, and we won't be able to derive the
Copy trait for the new one. Let's remove it now, and adjust the code
where necessary.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
As part of the work for port forwarding support, and in particular, to
support both port forwarding and masquerading at the same time on the
same end of a given VPC peering, we need to keep track of some
destination-related information even when several destination blocks can
match during the flow-filter lookup.

To that end, we add a set of RemoteData objects to the MultipleMatches
variant. This will be used in follow-up commit to handle overlap between
the prefixes of masquerading and port forwarding.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
In preparation for adjusting tests for the flow-filter stage setup
submodule, introduce (and use) small helpers to produce VpcDiscriminant
or Vni objects in a less verbose way.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
As part of the work for port forwarding support, we want to update the
flow-filter stage and have it work with a specific configuration,
namely: when both port forwarding and masquerading are configured for
distinct expose blocks, but on the same side of one given VPC peering.

So far, when the flow-filter stage would find conflicting results for
the destination VPC lookup (when several destinations may match), it
would simply return the MultipleMatches variant and leave it at that. In
a previous commit, we extended this variant to make it able to hold
data: in the current commit, we populate this data to be able to
validate whether a flow is legit, and whether the NAT requirement for
the packet should be masquerading (stateful NAT) or port forwarding.

Tests come in a follow-up commit.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Now that we've updated, in a recent commit, the flow-filter table to
contain information in case of overlap between expose blocks on the same
side of a VPC peering with masquerading (stateful NAT) and port
forwarding, we need to update the logic in the packet-processing code to
adjust the flow-filter decisions accordingly. In particular, when we
have multiple matches (for the same destination VPC), but not flow table
entry, determine the NAT requirements based on the direction; when the
flow table entry is present, determine the requirements based on the
nature of that entry (whether it's been created for stateful NAT or for
port forwarding).

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Remove the description of validation steps on top of
VpcExpose.validate(): we haven't kept them up-to-date, and we don't
really need to anyway, all checks are documented in the body of the
method.

Also remove numbers between the different checks. I find they make it
clearer to figure out where we are in the process, and to designate a
specific step when discussing the code, but it forces developers to make
contortions (step 0) or to adjust many numbers when doing some changes.
It's probably easier to just get rid of these step numbers.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Make sure that the config is valid for the needs of port forwarding.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
As part of the Peering validation steps, reject incompatible NAT modes
(when a Peering manifest has NAT configured for both sides, in a way
that is not currently supported).

This does not address multiple NAT modes on _one_ side of a peering.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
As part of the support for port forwarding, update the user
configuration validation to support the case when a VPC peering manifest
uses masquerading and port forwarding on a same end (via two expose
blocks).

We reject all other NAT combinations on a same end.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Signed-off-by: Quentin Monnet <qmo@qmon.net>
We've been growing a large portion of the vpcpeering.rs code to deal
with overlap validation for the expose blocks. In order to keep this
file clearer, move the overlap-related methods to a separate file under
the utils submodule.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Add some tests to check that the flow-filter stage works with port
forwarding and with the additional prefix overlap use case, in
particular when Expose objects on the same side of a peering use a
combination of stateful NAT + port forwarding.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
We plan to re-use some of these types in the stateful NAT code. They're
not proper to stateless NAT anyway; so let's move them to a dedicated
module at the top of the crate. We could even move them to another crate
in the future, if other components need to share them.

For types where all members are public, and "new()" implementation is a
straightforward struct build, remove the "new()" constructor and replace
it with a direct object build, to avoid having several ways to build the
objects. This change accounts for a good portion of the churn in the
range_builder stateless NAT submodule.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
As part of the work to add support for port forwarding, we want to
reserve some specific ports in the stateful NAT allocator, to prevent
the allocator from using them. This is the case of ports that should be
used for port forwarding only, when both stateful NAT and port
forwarding are in use on the same side of a VPC peering manifest.

Prepare the allocator for reserving these ports: compute the list of
ports to reserve, and attach it to NatPool objects when creating them.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
In a configuration when VPC 1 is peered with VPC 2, and VPC 1 uses both
port forwarding and masquerading (stateful NAT) on its side of the
peering to communicate with VPC 2, we may need to block some ports from
being allocated for stateful NAT.

What happens if we don't reserve ports? For example:

1. Endpoint A in VPC 1 attempts to open a connection with endpoint X in
   VPC 2.
2. The connection gets masqueraded; bad luck, the allocated IP and port
   are exactly the same as those exposed via port forwarding. Flow table
   sessions are created for both directions, based on this IP and port.
3. The packet is translated with these IP and port, and is sent to
   endpoint X.
4. The packet is either dropped later on the path, or endpoint X closes
   the connection for some reason.
5. Endpoint X attempts to reach the service exposed on VPC 1 via port
   forwarding. It uses the port forwarding IP and port and sends its
   packet. Bad luck (again), it also uses the same source port as the
   previous packet targeted.
6. Because we have a matching flow table entry, we translate back the
   packet, and send it to endpoint A. But wait, endpoint X wanted to use
   the service behind port forwarding - we have no guarantee that it is
   endpoint A, it could well be some endpoint B on VPC 1 instead!

To avoid this unlikely, but possible corner case, we need to block the
ports used for port forwarding from being allocated for masquerading.
Update the stateful NAT allocator to mark IPs (if all ports are
concerned) or port ranges (otherwise) in a port block allocator as
unavailable, when creating the object pools, so that stateful NAT can
never assigns them when they should be "reserved" for port forwarding.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
This is in preparation for port forwarding support. When converting the
K8s configuration into VpcExpose resources, we would produce internally
one VpcExpose for each expose blocks in the configuration. Now that
we're adding support for port forwarding, we're changing that: we want
to be able to produce several VpcExpose from one expose blocks.

The rationale for this is that in the new API, the "ports" block in the
port forwarding configuration object "links" original and target port
ranges for port forwarding _within_ an expose blocks - a structure that
we don't otherwise support internally, hence the need to convert into
separate VpcExpose objects.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Add support for converting port forwarding configuration received from
Kubernetes into the internal objects that we use to actually implement
the feature.

Also update the related Bolero generator accordingly.

Note: Support is not complete yet, we do not support the TCP/UDP
distinction.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
@qmonnet qmonnet force-pushed the pr/qmonnet/port-forwarding branch from 2199041 to 43d8c32 Compare February 23, 2026 16:47
@Fredi-raspall Fredi-raspall deleted the branch pr/fredi/port-forwarding-cont February 23, 2026 21:23
@qmonnet
Copy link
Member Author

qmonnet commented Feb 24, 2026

Moved to #1303

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/nat Related to Network Address Translation (NAT)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants