From c4d818b677618433cbb1697b7f524d4c16430770 Mon Sep 17 00:00:00 2001 From: Fredi Raspall Date: Thu, 13 Aug 2026 19:30:12 +0200 Subject: [PATCH 1/2] feat(masquerade): log masquerade allocation failures Log anytime we fail to masquerade a flow due to an ip/port allocation failure. Signed-off-by: Fredi Raspall --- nat/src/masquerade/nf.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/nat/src/masquerade/nf.rs b/nat/src/masquerade/nf.rs index e7e6137afd..6c65169f37 100644 --- a/nat/src/masquerade/nf.rs +++ b/nat/src/masquerade/nf.rs @@ -410,14 +410,20 @@ impl Masquerade { .copied() .unwrap_or(current_flow_key); - // Create a new session and translate the address + // allocate an ip and port for this flow let src_ip = *initial_flow_key.src_ip(); - let alloc = allocator - .allocate(src_vpcd, dst_vpcd, src_ip, initial_flow_key.proto()) - .map_err(MasqueradeError::AllocationFailure)?; - - // The generation the installed allocator serves - let genid = allocator.genid(); + let alloc = match allocator.allocate(src_vpcd, dst_vpcd, src_ip, initial_flow_key.proto()) { + Ok(alloc) => alloc, + Err(e) => { + if !matches!(e, AllocatorError::UnsupportedProtocol(_)) { + warn!( + "{nfi}: Ip/port allocation failed for flow {initial_flow_key} towards VPC {dst_vpcd}: {e}" + ); + } + return Err(MasqueradeError::AllocationFailure(e)); + } + }; + debug!("{nfi}: Allocated: {alloc}"); // Forbid addresses we won't know how to translate. This is a work around of a larger change if let Err(addr) = UnicastIpAddr::try_from(alloc.allocation.ip()) { @@ -425,7 +431,8 @@ impl Masquerade { return Err(MasqueradeError::Bug("allocated unusable ip")); } - debug!("{nfi}: Allocated: {alloc}"); + // The generation the installed allocator serves + let genid = allocator.genid(); // create flow pair let installed = From 298785f6f0ab9f4dda702e35c7424ec6b681d1b6 Mon Sep 17 00:00:00 2001 From: Fredi Raspall Date: Thu, 13 Aug 2026 21:00:33 +0200 Subject: [PATCH 2/2] feat(masquerade): avoid UnsupportedProtocol in nat allocator Packets with protocols that should not be masqueraded should not get to request an allocation from the allocator. In other words, "unsupported protocol" should not be an error reported by a port allocator. Move the check from the allocator to the masquerade NF, as it allows treating allocation errors as such. Signed-off-by: Fredi Raspall --- nat/src/masquerade/allocation.rs | 2 +- nat/src/masquerade/apalloc/mod.rs | 10 +++------- nat/src/masquerade/nf.rs | 32 +++++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/nat/src/masquerade/allocation.rs b/nat/src/masquerade/allocation.rs index aace14e208..da40ac1ffb 100644 --- a/nat/src/masquerade/allocation.rs +++ b/nat/src/masquerade/allocation.rs @@ -22,7 +22,7 @@ pub enum AllocatorError { #[error("failed to reserve port: {0}")] PortReservationFailed(u16), #[error("unsupported protocol: {0:?}")] - UnsupportedProtocol(NextHeader), + UnsupportedProtocol(NextHeader), // FIXME: remove this when possible #[error("missing VPC discriminant")] MissingDiscriminant, // Something has gone wrong, but user input or packet input are not responsible. diff --git a/nat/src/masquerade/apalloc/mod.rs b/nat/src/masquerade/apalloc/mod.rs index 67ee1460d6..8343dbe756 100644 --- a/nat/src/masquerade/apalloc/mod.rs +++ b/nat/src/masquerade/apalloc/mod.rs @@ -427,12 +427,7 @@ impl NatAllocator { }), } } - fn check_proto(next_header: NextHeader) -> Result<(), AllocatorError> { - match next_header { - NextHeader::TCP | NextHeader::UDP | NextHeader::ICMP | NextHeader::ICMP6 => Ok(()), - _ => Err(AllocatorError::UnsupportedProtocol(next_header)), - } - } + fn allocate_from_tables( src_ip: IpAddr, src_vpcd: VpcDiscriminant, @@ -440,7 +435,8 @@ impl NatAllocator { next_header: NextHeader, pools_src: &PoolTable, ) -> Result>, AllocatorError> { - Self::check_proto(next_header)?; + // TODO: here we should only allow next-header to be TCP/UDP/ICMP/ICMP6 as a SANITY. + // This can be done by a transparent wrapper of NextHeader that can only exist for that set // If we could not find an address pool for the source address, the user has not exposed // and configured NAT for that source address. Drop the packet instead of creating a session. diff --git a/nat/src/masquerade/nf.rs b/nat/src/masquerade/nf.rs index 6c65169f37..3edc15fb2f 100644 --- a/nat/src/masquerade/nf.rs +++ b/nat/src/masquerade/nf.rs @@ -20,7 +20,7 @@ use net::buffer::PacketBufferMut; use net::flow_key::IcmpProtoKey; use net::flows::{ExtractRef, FlowInfo, FlowInfoError}; use net::headers::{TryIp, TryTcp}; -use net::ip::UnicastIpAddr; +use net::ip::{NextHeader, UnicastIpAddr}; use net::packet::{DoneReason, Packet, VpcDiscriminant}; use net::{FlowKey, IpProtoKey}; use pipeline::{NetworkFunction, PipelineData}; @@ -61,6 +61,8 @@ pub(crate) enum MasqueradeError { NatError(#[from] NatPacketError), #[error("Failed to create flow state: {0}")] FlowError(#[from] FlowInfoError), + #[error("unsupported protocol: {0:?}")] + UnsupportedProtocol(NextHeader), } /// A stateful NAT processor, implementing the [`NetworkFunction`] trait. [`Masquerade`] processes @@ -370,6 +372,14 @@ impl Masquerade { )) } + /// Tell if a protocol can be masqueraded + fn can_be_masqueraded(next_header: NextHeader) -> bool { + matches!( + next_header, + NextHeader::TCP | NextHeader::UDP | NextHeader::ICMP | NextHeader::ICMP6 + ) + } + /// Main entry point for masquerading logic fn masquerade_packet( &self, @@ -410,16 +420,20 @@ impl Masquerade { .copied() .unwrap_or(current_flow_key); + // check if the flow can be masqueraded + let proto = initial_flow_key.proto(); + if !Self::can_be_masqueraded(proto) { + return Err(MasqueradeError::UnsupportedProtocol(proto)); + } + // allocate an ip and port for this flow let src_ip = *initial_flow_key.src_ip(); - let alloc = match allocator.allocate(src_vpcd, dst_vpcd, src_ip, initial_flow_key.proto()) { + let alloc = match allocator.allocate(src_vpcd, dst_vpcd, src_ip, proto) { Ok(alloc) => alloc, Err(e) => { - if !matches!(e, AllocatorError::UnsupportedProtocol(_)) { - warn!( - "{nfi}: Ip/port allocation failed for flow {initial_flow_key} towards VPC {dst_vpcd}: {e}" - ); - } + warn!( + "{nfi}: Ip/port allocation failed for flow {initial_flow_key} towards VPC {dst_vpcd}: {e}" + ); return Err(MasqueradeError::AllocationFailure(e)); } }; @@ -533,7 +547,9 @@ impl Masquerade { impl From<&MasqueradeError> for DoneReason { fn from(error: &MasqueradeError) -> Self { match error { - MasqueradeError::BadTransportHeader => DoneReason::NatUnsupportedProto, + MasqueradeError::BadTransportHeader | MasqueradeError::UnsupportedProtocol(_) => { + DoneReason::NatUnsupportedProto + } MasqueradeError::FlowKeyError | MasqueradeError::InvalidPort(_) => { DoneReason::Malformed }