Skip to content

RFC 7915 Review

Alberto Leiva Popper edited this page Dec 28, 2023 · 6 revisions

RFC 7915 Review

These are issues I came up while reviewing and patching #221. The IETF should probably be made aware of these if they ever iterate over the stateless IP/ICMP translation RFC again. (Though I'm aware they're sick of it.)

Some metadata: SIIT was originally defined in RFC 2765. Then in became 6145, and then 7915.

I think that's all. Beginning issues:

Authentication Header

RFC 7915:

For the first 'next header' that does not match one of the cases above, its Next Header value (which contains the transport protocol number) is copied to the protocol field in the IPv4 header. This means that all transport protocols are translated. Note: Some translated protocols will fail at the receiver for various reasons: some are known to fail when translated (e.g., IPsec Authentication Header (51))

Also RFC 7915:

If the Next Header field of the Fragment Header is an extension header (except ESP, but including the Authentication Header (AH)), then the packet SHOULD be dropped and logged.

It seems as though the RFC is contradicting itself. Should we "translate" AH or not? Though this can be sort of amended by noting that the second requirement is in a fragment-enabled context, while the first one is not.

I find it hard to believe that AH translation is supposed to depend on fragmentation, though. This is most likely a bug born from the 6145 errata merge. Both 2765 and 6145 assume that AH is always translated; the 6145 errata is the only part of 7915 that contradicts this.

The way I see it, if AH is not bound to work through the translator, responding an ICMP error is probably most user-friendly. However, I suppose it would trump any future AH extension attempts to work around the problem. On the other hand, such an extension is unlikely. But then again, I'm an engineer, and I like future-proof.

For all I know, there's probably a reason why AH was, for a long time, meant not to be dropped, and I don't see any real arguments challenging this. Or a debate, for that matter. The idea that AH should be dropped appears to be just an interpretation that sneaked its way into the standard through a bug report that was otherwise relatively correct. (I'm not fond of defining length fields by way of transformations rather than end results, myself.)

I mean, the errata says this:

AH is not supposed to be translated. RFC 2765 had explicitly stated this and RFC 6145 also should continue to have this stated. (https://www.rfc-editor.org/errata_search.php?rfc=6145)

It's plainly untrue. 2765 says "AH does not work through a translator," not that it shouldn't be translated. The idea that AH should cause a packet drop is emergent. It's certainly not there in 2765, it's arguably there in 6145's introduction ("it does not translate IPv6 extension headers except the Fragment Header") but not in its specification ("For the first 'next header' that does not match one of the cases above, its Next Header value (...) is copied to the protocol field"), and 7915 specifies plainly contradicting rules (seen above). But regardless, it doesn't look like it was ever properly discussed during the development of these RFC updates.

And by the way: The expression "X will not be translated" should be defined in a glossary. Does it mean that packets containing X should be dropped, does it mean that X should be copied without modifications, or does it mean that X should be skipped while translating? All of these spell "not translated" to me.

(From my reading of them, 6145 means the former, while 2765 means the latter. I didn't bother to look what 7915 means.)

For the sake of implementation, Jool goes with the original intent. It feels like it's the most properly thought-out one. SIIT Jool treats AH as a transparent transport protocol. It does not drop it.

NAT64 Jool drops AH, along with every other "unknown" transport protocol.

Simple Extension Headers

"Simple Extension Headers" is an expression I made up for the purposes of this discussion. I use it to refer to the Hop-By-Hop, Destination Options and Routing IPv6 headers. I group them because they have basically identical semantics from a translator's point of view.

RFC 7915:

If any of an IPv6 Hop-by-Hop Options header, Destination Options header, or Routing header with the Segments Left field equal to zero are present in the IPv6 packet, those IPv6 extension headers MUST be ignored (...) and the packet translated normally.

Also RFC 7915:

If the Next Header field of the Fragment Header is an extension header (except ESP), then the packet SHOULD be dropped and logged.

Should Simple Extension Headers be "ignored" or "dropped"?

This has the same quirks as the previous problem: One of them exists within a Fragmentation context and the other one doesn't, and also, frankly, it looks like an erratum hiccup.

I suppose it's interesting to note that the IPv6 spec defines a recommended order for these headers, and according to it, Destination is the only allowed one before and after Fragment. It's only a recommendation, though. But the semantics of the post-Fragment Destination are different from the pre-Fragment Destination, so overall, I guess the Fragmentation context makes a little more sense in this case. It's kind of hard to tell; RFC 2460 never defines actual Destination options so I have no idea what they do.

Therefore, Jool implements this exactly as specified: It "ignores" (ie. pretends they're not there, but adjusts headers lengths accordingly) pre-Fragment Simple Extension Headers, but triggers a packet drop on post-Fragment Simple Extension Headers.

Extension Headers (general notes)

Meh. When it comes to extension headers, the SIIT RFCs sort of brainfart. The information is scattered all over the place and there's some undefined behavior. If 7915bis ever comes along, I would recommend adding a new section entirely dedicated to this topic.

(There's no reason why people should look into specific fields of section 5.1.1 to figure this out.)

From my reading of it, this seems to be the overall intention. Lower bullet points take precedence:

  1. If AH is present, the packet will be dropped at some point. There's no consensus on who's supposed to do this. If the translator drops it, it seems it's supposed to return ICMPv6 Type/Code 1/1 (per rfc7915#section-5.4).
  2. If ESP is present, it should be treated like an unknown layer 4 header. (ie. copied, along with the rest of the packet, as is.)
  3. Routing Header with nonzero segments left should cause a packet drop with ICMPv6 4/0.
  4. Otherwise, Simple Extension Headers between IPv6 and Fragment should be "ignored." (Word needs glossary definition, along with "dropped" and "not translated.")
  5. Known extension headers (as defined above) after Fragment should cause a packet drop, and perhaps also an ICMPv6 1/1 (per rfc7915#section-5.4).

According to my intuition, those 1/1 ICMPv6 errors are rather dubious because they come from 6145 erratas, which use the expression "dropped and logged" unlike the rest of the RFC. This suggests to me that there was no effort to imitate the style, which means they might have forgotten the "1/1 unless otherwise stated" rule. But whatever; Jool implements exactly what it says. It says "dropped and logged," not "silently dropped and logged."

Also, 1/1 (Prohibited) is pretty strange. Isn't Parameter Problem a closer fit?

Other extension headers (such as No Next Header and extension headers defined by other RFCs) seemingly cause undefined behavior. In particular, it's fairly irritating that extension headers and transport protocols share the same nexthdr range, so there's no way to tell the difference between unknown extension headers and unknown transport protocols.

Therefore, Jool groups unknown extension headers with unknown transport protocols, which means they are copied as-is.

Extension Headers, another one

I've already quoted these paragraphs, but they're guilty of yet another contradiction:

Total Length: If the Next Header field of the Fragment Header is an extension header (except ESP, but including the Authentication Header (AH)), then the packet SHOULD be dropped and logged.

Fragment Offset: (...) If the Next Header field of the Fragment Header is an extension header (except ESP), then the packet SHOULD be dropped and logged.

They are at odds with this one:

Protocol: For ICMPv6 (58), it is changed to ICMPv4 (1); otherwise, extension headers are skipped, and the Next Header field is copied from the last IPv6 header.

These are all from the same section (5.1.1).

Naming

At this point, I think it's pretty clear that the terms "SIIT", "Stateless NAT64", "NAT46" and "CLAT" (and maybe also "Stateful NAT64" and "NAT64") all deserve glossary presence. Everyone (even different RFCs) use different names and it confuses new users. I'd recommend adding "IIT" as well.

...Well, 6877 already defines "CLAT." There's that.

Perhaps "SIIT" could mean the "-ion" version, while "Stateless NAT64" could be the "-or" version. ("translation" vs "translator.")

Just a thought.

Illegal Source Address

Source Address: (...) If the translator gets an illegal source address (e.g., 0.0.0.0, 127.0.0.1, etc.), the translator SHOULD silently discard the packet (as discussed in Section 5.3.7 of [RFC1812]).

The IPv4 version of the Source Address translation has a serviceable explanation of what constitutes an "illegal" source address.

But the IPv6 version doesn't:

If the translator gets an illegal source address (e.g., ::1, etc.), the translator SHOULD silently drop the packet.

Multicast

This paragraph is seemingly unaware of EAM:

The IP/ICMP header translation specified in this document is consistent with requirements of multicast IP/ICMP headers. However, IPv4 multicast addresses [RFC5771] cannot be mapped to IPv6 multicast addresses [RFC3307] based on the unicast mapping rule [RFC6052]. An example of experiments of the multicast address mapping can be found in [RFC6219].

Can't multicast work with EAM? (I honestly don't know.)

DF and the Fragment Header

When the IPv4 sender does not set the DF bit, the translator MUST NOT include the Fragment Header for the non-fragmented IPv6 packets.

This requirement is phrased awkwardly. (They were likely thinking a little too hard on atomic fragment deprecation. The context it's placed in also didn't help; The previous paragraph also starts with "(...) when the IPv4 sender does not set the DF bit.")

The way I understand it, the translator MUST NOT include the Fragment Header on non-fragmented IPv6 packets even if DF is enabled. The presence of the Fragment Header really does not depend on DF at all. (I mean, what happens if DF is enabled but the packet is already fragmented anyway? Should the translator merge the fragments so the header can be removed? Of course not.)

It should read

The translator MUST NOT include the Fragment Header on non-fragmented IPv6 packets.

Or rather,

The translator must include a Fragment Header if, and only if, the translated IPv6 packet is fragmented.

DF and Fragments

The RFC doesn't really specify what happens when the translator receives a fragmented packet with DF enabled. This is for obvious reasons, but it does lead me to some tumbling around since I want to do something meaningful anyway.

Drop packet? Translate like a DF-enabled packet, but skip the lowest-ipv6-mtu fragmentation? Could be interesting.

Usage of lowest-ipv6-mtu

(the packet is a fragment or the DF bit is not set and the packet size is greater than the minimum IPv6 MTU in the network set by the translator configuration function)

Inconsistent, and ruins Ctrl+F. It should read

(the packet is a fragment or the DF bit is not set and the packet size is greater than lowest-ipv6-mtu)

Fragmented UDP Checksum Zero

RFC 7915:

Fragmented IPv4 UDP packets that do not contain a UDP checksum (i.e., the UDP checksum field is zero) are not of significant use on the Internet, and in general will not be translated by the IP/ICMP translator (Section 4.5). However, when the translator is configured to forward the packet without a UDP checksum, the fragmented IPv4 UDP packets will be translated.

"Translation of fragmented UDP zero-checksum is configuration dependent."

Also RFC 7915:

A stateless translator cannot compute the UDP checksum of fragmented packets, so when a stateless translator receives the first fragment of a fragmented UDP IPv4 packet and the checksum field is zero, the translator SHOULD drop the packet and generate a system management event that specifies at least the IP addresses and port numbers in the packet.

"Translation of fragmented UDP zero-checksum is not happening."

The former is nonsense to me, so Jool implements the latter. There IS a configuration option that deals with zero checksum, but it only applies to non-fragmented packets.

This bug was introduced in RFC 6145. 2765 persistently advocates the drop.

Header Lengths

This one is a whim of mine, but I'll mention in nonetheless.

Total Length and Payload Length are a mess:

Payload Length: Total length value from the IPv4 header, minus the size of the IPv4 header and IPv4 options, if present.

Payload Length: Total length value from the IPv4 header, plus 8 for the Fragment Header, minus the size of the IPv4 header and IPv4 options, if present.

If the translation of this "packet in error" changes the length of the datagram, the Total Length field in the outer IPv6 header MUST be updated.

Total Length: Payload length value from the IPv6 header, plus the size of the IPv4 header.

However, the Total Length field and the Protocol field are adjusted to "skip" these extension headers.

Total Length: If the Next Header field of the Fragment Header is an extension header (except ESP, but including the Authentication Header (AH)), then the packet SHOULD be dropped and logged. For other cases, the Total Length MUST be set to Payload Length value from IPv6 header, minus the length of the extension headers up to the Fragmentation Header, minus 8 for the Fragment Header, plus the size of the IPv4 header.

The translation of this "packet in error" is likely to change the length of the datagram; thus, the Total Length field in the outer IPv4 header MUST be updated.

As a Linux kernel citizen, I have to deal with dumb kernel hacks when computing these lengths. (Well, the current code is messy and I want to simplify it, so I don't know if this will still hold true after a bit of hammering.) In addition, the requirements above seem to be incognizant of the maximum ICMP error lengths (576 and 1280), which as far as I know, have not been officially deprecated anywhere.

(I'm aware that no one takes those maximum lengths seriously, but I need to because of kernel quirks.)

So, speaking as an implementor, defining the lengths in terms of transformations is not useful to me. I'd recommend something more along the lines of

Total Length: Length of the final IPv4 packet.

Payload Length: Length of the final IPv6 packet - Length of the IPv6 header (40).

That would also allow big chunks of the earlier quotes to be obviated.

Source Routing

if an unexpired source route option is present, then the packet MUST instead be discarded, and an ICMPv4 "Destination Unreachable, Source Route Failed" (Type 3, Code 5) error message SHOULD be returned to the sender.

Since the translator has an IPv4 address and belongs to the path, then if its own address matches the last route data address, shouldn't it increase the pointer before testing for expiration?

Default ICMP errors

4.4. Generation of ICMPv4 Error Message

If the IPv4 packet is discarded, then the translator SHOULD be able to send back an ICMPv4 error message to the original sender of the packet, unless the discarded packet is itself an ICMPv4 error message. The ICMPv4 message, if sent, has a Type of 3 (Destination Unreachable) and a Code of 13 (Communication Administratively Prohibited), unless otherwise specified in this document or in [RFC6146]. The translator SHOULD allow an administrator to configure whether the ICMPv4 error messages are sent, rate-limited, or not sent.

First: I'm assuming that, when the RFC says "Silently drop," the "silent" part of it refers to an ICMP error. In other words, when the RFC says "Silently drop," then the packet should be black-holed and the source should not be informed.

Second: It is not clear whether this section is talking about

  1. Packet drops which are not defined as "silent" in the rest of the RFC (or in 6146).
  2. Packet drops caused by implementation idiosyncrasies (such as memory allocation failures).
  3. Both of the above.

I don't think 2 and 3 are correct because implementation-caused errors are usually supposed to be silent. Responding ICMP errors to things like shared socket buffers, detected programming errors or memory allocations failures would probably just fuel the fire.

It is therefore my belief that this section is talking about RFC-defined packet drops which do not include ICMP errors mentions, positive or negative.

That being the case, my opinion is that this section should be removed. Reasons:

  1. It's error prone, because it relies on the reader to find these undefined ICMP errors in the rest of the RFC.
  2. It's error prone, because it means that any new revisions of the RFC need to be aware of this default ICMP error, and I don't think this has been a success so far. (Arguments below in section 5.4)
  3. I wonder whether these errors should be Parameter Problems instead of 3/13s.
  4. I don't think there are enough undefined ICMPv4 errors to justify an entire section dedicated to them. (I only found two.)

Here are the 7915-undefined ICMP errors that I found:

Fragmented IPv4 UDP packets that do not contain a UDP checksum (i.e., the UDP checksum field is zero) are not of significant use on the Internet, and in general will not be translated by the IP/ICMP translator (Section 4.5).

(...)

  1. Dropping the packet and generating a system management event that specifies at least the IP addresses and port numbers of the packet.

(...)

A stateless translator cannot compute the UDP checksum of fragmented packets, so when a stateless translator receives the first fragment of a fragmented UDP IPv4 packet and the checksum field is zero, the translator SHOULD drop the packet and generate a system management event that specifies at least the IP addresses and port numbers in the packet.

A Parameter Problem pointing at the checksum would probably be more helpful to the user. We're blocking these packets because they're untranslatable; not because of policies.

Fragmented ICMP/ICMPv6 packets will not be translated by IP/ICMP translators.

A Parameter Problem pointing at MF or Fragment Offset would probably be more helpful to the user. We're blocking these packets because they're untranslatable; not because of policies.

5.4. Generation of ICMPv6 Error Messages

If the IPv6 packet is discarded, then the translator SHOULD send back an ICMPv6 error message to the original sender of the packet, unless the discarded packet is itself an ICMPv6 message.

The ICMPv6 message MUST have Type 1 (Destination Unreachable) and Code 1 (Communication with destination administratively prohibited), unless otherwise specified in this document or [RFC6146]. The translator SHOULD allow an administrator to configure whether the ICMPv6 error messages are sent, rate-limited, or not sent.

Same issues as section 4.4. I'd like this section removed as well. Here are the undefined ICMPv6 errors I found:

Total Length: If the Next Header field of the Fragment Header is an extension header (except ESP, but including the Authentication Header (AH)), then the packet SHOULD be dropped and logged.

(...)

If the Next Header field of the Fragment Header is an extension header (except ESP), then the packet SHOULD be dropped and logged.

As I mentioned in Extension Headers (general notes), I have my doubts whether this ICMP error was intended by the WG or not. It came from an errata I disagree with, and which might have been unaware of section 5.4.

Fragmented ICMP/ICMPv6 packets will not be translated by IP/ICMP translators.

A Parameter Problem pointing at M or Fragment Offset would probably be more helpful to the user.

Nitpicks

If the translation of this "packet in error" changes the length of the datagram, the Total Length field in the outer IPv6 header MUST be updated.

There is no "Total Length" field in the IPv6 header.

The translation of this "packet in error" is likely to change the length of the datagram; thus, the Total Length field in the outer IPv4 header MUST be updated.

I don't think there's a situation in which the translation of the inner ICMP error will not change the size of the overall datagram. The translator never generates IPv4 options, so the packet is guaranteed to shrink.

For extensions not defined in [RFC4884], the translator passes the extensions as opaque bit strings

(...)

For extensions not defined in [RFC4884], the translator passes the extensions as opaque bit strings

Ok, but this RFC appears to have forgotten to specify what to do with the one extension that IS defined in 4884.

Regardless, it seems extensions should always be passed as opaque bit strings, regardless of whether they are defined in 4884 or not.