Preliminary work for static NAT source/destination split#1419
Preliminary work for static NAT source/destination split#1419
Conversation
420bcdb to
69d870b
Compare
69d870b to
f794ef5
Compare
There was a problem hiding this comment.
Pull request overview
Prepares the stateless NAT implementation for an eventual split between source-NAT and destination-NAT by refactoring ICMP error-message inner-packet translation into dedicated src/dst helpers, and by tightening/clarifying related packet/test utilities.
Changes:
- Refactors ICMP error inner-packet translation into
*_srcand*_dstfunctions and updates stateless NAT to call them from separatesource_nat()/destination_nat()steps. - Adds
Packet::is_icmp_inner_translation_candidate()to centralize the “ICMP error with embedded packet” predicate. - Cleans up ICMP error-message tests and exposes a shared Ethernet test header builder.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| net/src/packet/utils.rs | Adds an ICMP-inner translation candidate predicate on Packet. |
| net/src/packet/test_utils.rs | Documents and exports make_default_for_eth() for reuse in tests. |
| nat/src/stateless/mod.rs | Splits translation into source_nat() / destination_nat() and uses new ICMP inner src/dst translation helpers. |
| nat/src/icmp_handler/icmp_error_msg.rs | Splits ICMP inner translation into src/dst functions; changes checksum validation API and updates tests accordingly. |
fcfd79f to
14a72f4
Compare
daniel-noland
left a comment
There was a problem hiding this comment.
I'm really confused by the whole approach taken here. Maybe there is some reason for this approach I can't see, but I think that
- completely refusing to use the qualified types from
netand - dealing with ipv4 and ipv6 at the same time and
- taking
Packetwhere you could takeHeaders
has made life hard here for no real reason.
I would also really like to see fuzz tests to catch edge cases in this type of logic.
Granted, many of these problems (if problems they are, I may have missed something fundamental) predate this PR, but some of it does not.
I'm concerned about the whole direction here. Especially as regards the global reasoning and use of unreachable!() when the methods are not
- marked
unsafeor - structurally defended against arbitrary crash by the type system.
unreachable!() isn't for code that is actually unreachable. Not for code which is simply not reached in the current impl or tests.
That said, do let me know if I missed something major here. I'm happy to chat about it
|
|
||
| pub(crate) fn nat_translate_icmp_inner_src<Buf: PacketBufferMut>( | ||
| packet: &mut Packet<Buf>, | ||
| target_addr: IpAddr, |
There was a problem hiding this comment.
why not accept UnicastIpAddr instead of all the extra error handling and branches?
There was a problem hiding this comment.
It would move the same error handling (and branches) to the caller, possibly at multiple calling locations, and I'm not sure what the advantage of that would be ?
| pub(crate) fn nat_translate_icmp_inner_dst<Buf: PacketBufferMut>( | ||
| packet: &mut Packet<Buf>, | ||
| target_addr: IpAddr, | ||
| target_port: Option<NatPort>, | ||
| ) -> Result<(), IcmpErrorMsgError> { |
There was a problem hiding this comment.
this type of thing would be much easier as a method which accepted an Ipv4, an Icmp4 and a UnicastIpv4Addr + Option<NatPort> + another method with ipv6 equivalents
As is, this
- requires the method to be generic over the buffer (which is not needed)
- requires a bunch of error handling which could be avoided by moving it closer to the front of the pipeline
- doesn't structurally catch errors regarding various packet shapes.
I don't really understand why we are trying to handle ipv4 and ipv6 at the same time. That is making this much harder than is needed (and likely slowing it down a fair bit)
There was a problem hiding this comment.
Not sure I follow all of this. We need to chat.
| let old_port = transport | ||
| .destination() | ||
| .unwrap_or_else(|| unreachable!()) | ||
| .into(); |
There was a problem hiding this comment.
crash on invalid data!?!
There was a problem hiding this comment.
We need to have a discussion about this.
| fn destination_nat<Buf: PacketBufferMut>( | ||
| &self, | ||
| table: &PerVniTable, | ||
| packet: &mut Packet<Buf>, | ||
| ) -> Result<bool, StatelessNatError> { |
There was a problem hiding this comment.
why accept Packet instead of Headers? Aren't we just making our lives hard here?
There was a problem hiding this comment.
I don't know. Are we? Without the Packet we need to pass down all the different header layers, plus the packet metadata, plus the attached flow information, to all subfunctions that need it, I think? And it doesn't sound easier, so I'm not sure I follow what you're suggesting. Let's chat?
14a72f4 to
95339c5
Compare
I'm ok with this provisionally (mostly because it is an "in flight" project and many of my concerns are out of scope).
That said, I would like to address some of these patterns for future work
daniel-noland
left a comment
There was a problem hiding this comment.
approved under the theory that we discuss and address the patterns in this code tomorrow.
We don't need to change instantly, but I would like this pattern to change
95339c5 to
53ad645
Compare
We can make the function slightly less verbose by setting ports directly from the packet object, rather than the transport layer. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Not setting the EtherType for the packet results in a failure to parse the IP header, and to actually test the different code paths in validate_checksums_icmp(). Signed-off-by: Quentin Monnet <qmo@qmon.net>
53ad645 to
b7411e9
Compare
This is in preparation for splitting source and destination NAT in the static NAT pipeline stage. To do so, we need the translation of an ICMP Error inner packet header to happen in two steps, as well. So in this commit, we split nat_translate_icmp_inner() into two functions, nat_translate_icmp_inner_src() and nat_translate_icmp_inner_dst(), taking care of the source and destination translation, respectively. This requires splitting translate_inner_tcp_udp() into two functions as well; translate_inner_icmp() needs to be called only once and does not need as many changes. Note that we refactor _but keep_ the original function nat_translate_icmp_inner() as well, because it is still in use in the masquerading pipeline stage. It now delegates to the two source and destination functions. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Function validate_checksums_icmp() used to return a Result<bool, IcmpErrorMsgError> to indicate, in case of success, whether the packet should go through translation for the inner packet embedded in an ICMP Error outer packet. This is not very readable. Instead, split this logic into two steps, the check on whether we should attempt translation, and the checksum validation. As part of this we make the function return a Result<(), IcmpErrorMsgError> instead; and we call the newly-defined method is_icmp_inner_translation_candidate(packet) to check whether the inner packet should endure translation and checksum validation at all, before that. Signed-off-by: Quentin Monnet <qmo@qmon.net>
In preparation for splitting the code for static NAT into source NAT and destination NAT, we want to perform translation of ICMP Error messages' embedded headers in two steps, destination and source. To this aim, we replace translate_icmp_inner_packet_if_any() into two sub-step variants, translate_icmp_inner_packet_dst_if_any() (for the destination) and translate_icmp_inner_packet_src_if_any() (for the source). Method find_translation_icmp_inner() gets factored in the others in the process. Signed-off-by: Quentin Monnet <qmo@qmon.net>
In order to support static NAT on one end of a peering, and masquerading on the other end, we'll need to split and reorganise the static NAT code. In preparation for this work, move toward an accentuated dissociation of the source and destination translations for the stateless NAT code: move each part, source and destination translation, including ICMP Error messages' inner packets translation for each direction, into its own method for the StatelessNat object, source_nat() and destination_nat(), respectively. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Daniel reports that we lose information by using error variant StatelessNatError::UnsupportedTranslation at several locations, where distinct events cause the error to happen. Add other enum variants instead, and log the error before converting it to more generic DoneReason variants. Reported-by: Daniel Noland <daniel@githedgehog.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
Keep more information about the error we hit when propagating the result from ICMP Error messages processing, by adding more variants to the error enum to better distinguish the different error cases. Reported-by: Daniel Noland <daniel@githedgehog.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
b7411e9 to
018ce1f
Compare
As the title says, this is some preliminary work for splitting the current static NAT stage into two steps, source and destination NAT. This will be necessary for supporting static NAT + masquerade at opposite ends of a peering. Here we do not split the stage, but we prepare for that by moving source and destination translation into individual functions. The biggest chunk of these changes is the modifications brought to the code for translating ICMP Error messages' inner packets (and the biggest part of the diff is a not-so-related clean-up in NAT tests, but it's mostly an indentation change).
Will need to be rebased on top of #1414 and possibly #1389.I'll handle conflicts if necessary.