diff --git a/.duvet/config.toml b/.duvet/config.toml index 90718e9a69..2628ece0ca 100644 --- a/.duvet/config.toml +++ b/.duvet/config.toml @@ -23,3 +23,9 @@ source = "https://www.rfc-editor.org/rfc/rfc4787" [[specification]] source = "https://www.rfc-editor.org/rfc/rfc5508" + +[[specification]] +source = "https://www.rfc-editor.org/rfc/rfc6935" + +[[specification]] +source = "https://www.rfc-editor.org/rfc/rfc8200" diff --git a/.duvet/requirements/www.rfc-editor.org/rfc/rfc6935/section-1.toml b/.duvet/requirements/www.rfc-editor.org/rfc/rfc6935/section-1.toml new file mode 100644 index 0000000000..7769b3d201 --- /dev/null +++ b/.duvet/requirements/www.rfc-editor.org/rfc/rfc6935/section-1.toml @@ -0,0 +1,62 @@ +target = "https://www.rfc-editor.org/rfc/rfc6935#section-1" + +# Introduction +# +# This document constitutes an update of the IPv6 specification +# [RFC2460] for cases where a tunnel protocol uses UDP with IPv6 to +# tunnel packets. With the rapid growth of the Internet, tunnel +# protocols have become increasingly important to enable the deployment +# of new protocols. Tunnel protocols can be deployed rapidly, while +# the time to upgrade and deploy a new protocol on a critical mass of +# routers, middleboxes, and hosts on the global Internet is now +# measured in decades. At the same time, the increasing use of +# firewalls and other security-related middleboxes means that truly new +# tunnel protocols, with new protocol numbers, are also unlikely to be +# deployable in a reasonable time frame. The result is an increasing +# interest in and use of UDP-based tunnel protocols. In such +# protocols, there is an encapsulated "inner" packet, and the "outer" +# packet carrying the tunneled inner packet is a UDP packet, which can +# pass through firewalls and other middleboxes that perform the +# filtering that is a fact of life on the current Internet. +# +# Tunnel endpoints may be routers or middleboxes aggregating traffic +# from a number of tunnel users. Therefore, the computation of an +# additional checksum on the outer UDP packet may be seen as an +# unwarranted burden on nodes that implement a tunnel protocol, +# especially if the inner packets are already protected by a checksum. +# IPv4 has a checksum over the IP packet header, and the checksum on +# the outer UDP packet may be set to zero. However, IPv6 has no +# checksum in the IP header, and RFC 2460 [RFC2460] explicitly states +# that IPv6 receivers MUST discard UDP packets with a zero checksum. +# So, while sending a UDP datagram with a zero checksum is permitted in +# IPv4 packets, it is explicitly forbidden in IPv6 packets. To improve +# support for IPv6 UDP tunnels, this document updates RFC 2460 to allow +# endpoints to use a zero UDP checksum under constrained situations +# (primarily for IPv6 tunnel transports that carry checksum-protected +# packets), following the applicability statements and constraints in +# [RFC6936]. +# +# When reading this document, the advice in "Unicast UDP Usage +# Guidelines for Application Designers" [RFC5405] is applicable. It +# discusses both UDP tunnels (Section 3.1.3) and the usage of checksums +# (Section 3.4). +# +# While the origin of this specification is the problem raised by the +# draft titled "Automatic Multicast Tunnels", also known as "AMT" +# [AMT], we expect it to have wide applicability. Since the first +# draft of this RFC was written, the need for an efficient UDP +# tunneling mechanism has increased. Other IETF Working Groups, +# notably LISP [RFC6830] and Softwires [RFC5619], have expressed a need +# +# to update the UDP checksum processing in RFC 2460. We therefore +# expect this update to be applicable in the future to other tunnel +# protocols specified by these and other IETF Working Groups. + +[[spec]] +level = "MUST" +quote = ''' +However, IPv6 has no +checksum in the IP header, and RFC 2460 [RFC2460] explicitly states +that IPv6 receivers MUST discard UDP packets with a zero checksum. +''' + diff --git a/.duvet/requirements/www.rfc-editor.org/rfc/rfc6935/section-5.toml b/.duvet/requirements/www.rfc-editor.org/rfc/rfc6935/section-5.toml new file mode 100644 index 0000000000..8b101040c3 --- /dev/null +++ b/.duvet/requirements/www.rfc-editor.org/rfc/rfc6935/section-5.toml @@ -0,0 +1,113 @@ +target = "https://www.rfc-editor.org/rfc/rfc6935#section-5" + +# The Zero UDP Checksum Update +# +# This specification updates IPv6 to allow a zero UDP checksum in the +# outer encapsulating datagram of a tunnel protocol. UDP endpoints +# that implement this update MUST follow the node requirements in +# "Applicability Statement for the Use of IPv6 UDP Datagrams with Zero +# Checksums" [RFC6936]. +# +# The following text in [RFC2460], Section 8.1, fourth bullet should be +# deleted: +# +# Unlike IPv4, when UDP packets are originated by an IPv6 node, the +# UDP checksum is not optional. That is, whenever originating a UDP +# packet, an IPv6 node must compute a UDP checksum over the packet +# and the pseudo-header, and, if that computation yields a result of +# zero, it must be changed to hex FFFF for placement in the UDP +# header. IPv6 receivers must discard UDP packets containing a zero +# checksum, and should log the error. +# +# This text should be replaced by: +# +# An IPv6 node associates a mode with each used UDP port (for +# sending and/or receiving packets). +# +# Whenever originating a UDP packet for a port in the default mode, +# an IPv6 node MUST compute a UDP checksum over the packet and the +# pseudo-header, and, if that computation yields a result of zero, +# the checksum MUST be changed to hex FFFF for placement in the UDP +# header, as specified in [RFC2460]. IPv6 receivers MUST by default +# discard UDP packets containing a zero checksum and SHOULD log the +# error. +# +# As an alternative, certain protocols that use UDP as a tunnel +# encapsulation MAY enable zero-checksum mode for a specific port +# (or set of ports) for sending and/or receiving. Any node +# implementing zero-checksum mode MUST follow the node requirements +# specified in Section 4 of "Applicability Statement for the use of +# IPv6 UDP Datagrams with Zero Checksums" [RFC6936]. +# +# Any protocol that enables zero-checksum mode for a specific port +# or ports MUST follow the usage requirements specified in Section 5 +# of "Applicability Statement for the Use of IPv6 UDP Datagrams with +# Zero Checksums" [RFC6936]. +# +# Middleboxes supporting IPv6 MUST follow requirements 9, 10, and 11 +# of the usage requirements specified in Section 5 of "Applicability +# Statement for the Use of IPv6 UDP Datagrams with Zero Checksums" +# [RFC6936]. + +[[spec]] +level = "MUST" +quote = ''' +UDP endpoints +that implement this update MUST follow the node requirements in +"Applicability Statement for the Use of IPv6 UDP Datagrams with Zero +Checksums" [RFC6936]. +''' + +[[spec]] +level = "MUST" +quote = ''' +Whenever originating a UDP packet for a port in the default mode, +an IPv6 node MUST compute a UDP checksum over the packet and the +pseudo-header, and, if that computation yields a result of zero, +the checksum MUST be changed to hex FFFF for placement in the UDP +header, as specified in [RFC2460]. +''' + +[[spec]] +level = "MUST" +quote = ''' +IPv6 receivers MUST by default +discard UDP packets containing a zero checksum and SHOULD log the +error. +''' + +[[spec]] +level = "MAY" +quote = ''' +As an alternative, certain protocols that use UDP as a tunnel +encapsulation MAY enable zero-checksum mode for a specific port +(or set of ports) for sending and/or receiving. +''' + +[[spec]] +level = "MUST" +quote = ''' +Any node +implementing zero-checksum mode MUST follow the node requirements +specified in Section 4 of "Applicability Statement for the use of +IPv6 UDP Datagrams with Zero Checksums" [RFC6936]. +''' + +[[spec]] +level = "MUST" +quote = ''' +Any protocol that enables zero-checksum mode for a specific port +or ports MUST follow the usage requirements specified in Section 5 +of "Applicability Statement for the Use of IPv6 UDP Datagrams with +Zero Checksums" [RFC6936]. +''' + +[[spec]] +level = "MUST" +quote = ''' +Middleboxes supporting IPv6 MUST follow requirements 9, 10, and 11 +of the usage requirements specified in Section 5 of "Applicability +Statement for the Use of IPv6 UDP Datagrams with Zero Checksums" +[RFC6936]. +''' + diff --git a/.duvet/snapshot.txt b/.duvet/snapshot.txt index f1b97231a8..42e08a40fc 100644 --- a/.duvet/snapshot.txt +++ b/.duvet/snapshot.txt @@ -667,3 +667,40 @@ SPECIFICATION: https://www.rfc-editor.org/rfc/rfc5508 TEXT[!SHOULD]: known to break some protocol features (most notably path MTU TEXT[!SHOULD]: Discovery) and some applications (e.g., ping, traceroute), and such TEXT[!SHOULD]: blocking is NOT RECOMMENDED. + +SPECIFICATION: https://www.rfc-editor.org/rfc/rfc6935 + SECTION: [Introduction](#section-1) + TEXT[!MUST]: However, IPv6 has no + TEXT[!MUST]: checksum in the IP header, and RFC 2460 [RFC2460] explicitly states + TEXT[!MUST]: that IPv6 receivers MUST discard UDP packets with a zero checksum. + + SECTION: [The Zero UDP Checksum Update](#section-5) + TEXT[!MUST]: UDP endpoints + TEXT[!MUST]: that implement this update MUST follow the node requirements in + TEXT[!MUST]: "Applicability Statement for the Use of IPv6 UDP Datagrams with Zero + TEXT[!MUST]: Checksums" [RFC6936]. + TEXT[!MUST]: Whenever originating a UDP packet for a port in the default mode, + TEXT[!MUST]: an IPv6 node MUST compute a UDP checksum over the packet and the + TEXT[!MUST]: pseudo-header, and, + TEXT[!MUST,implementation,test]: if that computation yields a result of zero, + TEXT[!MUST,implementation,test]: the checksum MUST be changed to hex FFFF for placement in the UDP + TEXT[!MUST,implementation,test]: header + TEXT[!MUST]: , as specified in [RFC2460]. + TEXT[!MUST]: IPv6 receivers MUST by default + TEXT[!MUST]: discard UDP packets containing a zero checksum and SHOULD log the + TEXT[!MUST]: error. + TEXT[!MAY]: As an alternative, certain protocols that use UDP as a tunnel + TEXT[!MAY]: encapsulation MAY enable zero-checksum mode for a specific port + TEXT[!MAY]: (or set of ports) for sending and/or receiving. + TEXT[!MUST]: Any node + TEXT[!MUST]: implementing zero-checksum mode MUST follow the node requirements + TEXT[!MUST]: specified in Section 4 of "Applicability Statement for the use of + TEXT[!MUST]: IPv6 UDP Datagrams with Zero Checksums" [RFC6936]. + TEXT[!MUST]: Any protocol that enables zero-checksum mode for a specific port + TEXT[!MUST]: or ports MUST follow the usage requirements specified in Section 5 + TEXT[!MUST]: of "Applicability Statement for the Use of IPv6 UDP Datagrams with + TEXT[!MUST]: Zero Checksums" [RFC6936]. + TEXT[!MUST]: Middleboxes supporting IPv6 MUST follow requirements 9, 10, and 11 + TEXT[!MUST]: of the usage requirements specified in Section 5 of "Applicability + TEXT[!MUST]: Statement for the Use of IPv6 UDP Datagrams with Zero Checksums" + TEXT[!MUST]: [RFC6936]. diff --git a/.duvet/specifications/www.rfc-editor.org/rfc/rfc6935.txt b/.duvet/specifications/www.rfc-editor.org/rfc/rfc6935.txt new file mode 100644 index 0000000000..a9f43a89f2 --- /dev/null +++ b/.duvet/specifications/www.rfc-editor.org/rfc/rfc6935.txt @@ -0,0 +1,675 @@ + + + + + + +Internet Engineering Task Force (IETF) M. Eubanks +Request for Comments: 6935 AmericaFree.TV LLC +Updates: 2460 P. Chimento +Category: Standards Track Johns Hopkins University Applied +ISSN: 2070-1721 Physics Laboratory + M. Westerlund + Ericsson + April 2013 + + + IPv6 and UDP Checksums for Tunneled Packets + +Abstract + + This document updates the IPv6 specification (RFC 2460) to improve + performance when a tunnel protocol uses UDP with IPv6 to tunnel + packets. The performance improvement is obtained by relaxing the + IPv6 UDP checksum requirement for tunnel protocols whose header + information is protected on the "inner" packet being carried. + Relaxing this requirement removes the overhead associated with the + computation of UDP checksums on IPv6 packets that carry the tunnel + protocol packets. This specification describes how the IPv6 UDP + checksum requirement can be relaxed when the encapsulated packet + itself contains a checksum. It also describes the limitations and + risks of this approach and discusses the restrictions on the use of + this method. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Further information on + Internet Standards is available in Section 2 of RFC 5741. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc6935. + + + + + + + + + + + +Eubanks, et al. Standards Track [Page 1] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + +Copyright Notice + + Copyright (c) 2013 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3 + 2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 4 + 2.1. Requirements Language . . . . . . . . . . . . . . . . . . 4 + 3. Problem Statement . . . . . . . . . . . . . . . . . . . . . . 4 + 4. Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . 4 + 4.1. Analysis of Corruption in Tunnel Context . . . . . . . . . 5 + 4.2. Limitation to Tunnel Protocols . . . . . . . . . . . . . . 7 + 4.3. Middleboxes . . . . . . . . . . . . . . . . . . . . . . . 8 + 5. The Zero UDP Checksum Update . . . . . . . . . . . . . . . . . 9 + 6. Additional Observations . . . . . . . . . . . . . . . . . . . 10 + 7. Security Considerations . . . . . . . . . . . . . . . . . . . 10 + 8. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 11 + 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 11 + 9.1. Normative References . . . . . . . . . . . . . . . . . . . 11 + 9.2. Informative References . . . . . . . . . . . . . . . . . . 11 + + + + + + + + + + + + + + + + + + + +Eubanks, et al. Standards Track [Page 2] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + +1. Introduction + + This document constitutes an update of the IPv6 specification + [RFC2460] for cases where a tunnel protocol uses UDP with IPv6 to + tunnel packets. With the rapid growth of the Internet, tunnel + protocols have become increasingly important to enable the deployment + of new protocols. Tunnel protocols can be deployed rapidly, while + the time to upgrade and deploy a new protocol on a critical mass of + routers, middleboxes, and hosts on the global Internet is now + measured in decades. At the same time, the increasing use of + firewalls and other security-related middleboxes means that truly new + tunnel protocols, with new protocol numbers, are also unlikely to be + deployable in a reasonable time frame. The result is an increasing + interest in and use of UDP-based tunnel protocols. In such + protocols, there is an encapsulated "inner" packet, and the "outer" + packet carrying the tunneled inner packet is a UDP packet, which can + pass through firewalls and other middleboxes that perform the + filtering that is a fact of life on the current Internet. + + Tunnel endpoints may be routers or middleboxes aggregating traffic + from a number of tunnel users. Therefore, the computation of an + additional checksum on the outer UDP packet may be seen as an + unwarranted burden on nodes that implement a tunnel protocol, + especially if the inner packets are already protected by a checksum. + IPv4 has a checksum over the IP packet header, and the checksum on + the outer UDP packet may be set to zero. However, IPv6 has no + checksum in the IP header, and RFC 2460 [RFC2460] explicitly states + that IPv6 receivers MUST discard UDP packets with a zero checksum. + So, while sending a UDP datagram with a zero checksum is permitted in + IPv4 packets, it is explicitly forbidden in IPv6 packets. To improve + support for IPv6 UDP tunnels, this document updates RFC 2460 to allow + endpoints to use a zero UDP checksum under constrained situations + (primarily for IPv6 tunnel transports that carry checksum-protected + packets), following the applicability statements and constraints in + [RFC6936]. + + When reading this document, the advice in "Unicast UDP Usage + Guidelines for Application Designers" [RFC5405] is applicable. It + discusses both UDP tunnels (Section 3.1.3) and the usage of checksums + (Section 3.4). + + While the origin of this specification is the problem raised by the + draft titled "Automatic Multicast Tunnels", also known as "AMT" + [AMT], we expect it to have wide applicability. Since the first + draft of this RFC was written, the need for an efficient UDP + tunneling mechanism has increased. Other IETF Working Groups, + notably LISP [RFC6830] and Softwires [RFC5619], have expressed a need + + + + +Eubanks, et al. Standards Track [Page 3] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + + to update the UDP checksum processing in RFC 2460. We therefore + expect this update to be applicable in the future to other tunnel + protocols specified by these and other IETF Working Groups. + +2. Terminology + + This document discusses only IPv6, because the problem being + addressed does not exist for IPv4. Therefore, all references to "IP" + should be understood as references to IPv6. + + The document uses the terms "tunneling" and "tunneled" as adjectives + when describing packets. When we refer to "tunneling packets", we + refer to the outer packet header that provides the tunneling + function. When we refer to "tunneled packets", we refer to the inner + packet, i.e., the packet being carried in the tunnel. + +2.1. Requirements Language + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119 [RFC2119]. + +3. Problem Statement + + When using tunnel protocols based on UDP, there can be both a benefit + and a cost to computing and checking the UDP checksum of the outer + (encapsulating) UDP transport header. In certain cases, where + reducing the forwarding cost is important, the cost of the + computation may outweigh the benefit of the checksum. This document + provides an update for usage of the UDP checksum with IPv6. The + update is specified for use by a tunnel protocol that transports + packets that are themselves protected by a checksum. + +4. Discussion + + "Applicability Statement for the Use of IPv6 UDP Datagrams with Zero + Checksums" [RFC6936] describes issues related to allowing UDP over + IPv6 to have a valid zero UDP checksum and is the starting point for + this discussion. Sections 4 and 5 of [RFC6936], respectively, + identify node implementation and usage requirements for datagrams + sent and received with a zero UDP checksum. These sections introduce + constraints on the usage of a zero checksum for UDP over IPv6. The + remainder of this section analyzes the use of general tunnels and + explains the motivations for why tunnel protocols are being permitted + to use the method described in this update. It also discusses issues + with middleboxes. + + + + + +Eubanks, et al. Standards Track [Page 4] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + +4.1. Analysis of Corruption in Tunnel Context + + This section analyzes the impact of the different corruption modes in + the context of a tunnel protocol. It specifies what needs to be + considered by the designer and user of a tunnel protocol for the + protocol to be robust. It also summarizes why use of a zero UDP + checksum is thought to be safe for deployment. + + o Context (i.e., tunneling state) should be established by + exchanging application Protocol Data Units (PDUs) carried in + checksummed UDP datagrams or by using other protocols that provide + integrity protection against corruption. These control packets + should also carry any negotiation required to enable the tunnel + endpoint to accept UDP datagrams with a zero checksum and identify + the set of ports that are used. It is important that the control + traffic is robust against corruption, because undetected errors + can lead to long-lived and significant failures that may affect + much more than the single packet that was corrupted. + + o Keepalive datagrams with a zero UDP checksum should be sent to + validate the network path, because the path between tunnel + endpoints can change, and therefore, the set of middleboxes along + the path may change during the life of an association. Paths with + middleboxes that drop datagrams with a zero UDP checksum will drop + these keepalives. To enable the tunnel endpoints to discover and + react to this behavior in a timely way, the keepalive traffic + should include datagrams with a non-zero checksum and datagrams + with a zero checksum. + + o Receivers should attempt to detect corruption of the address + information in an encapsulating packet. A robust tunnel protocol + should track tunnel context based on the 5-tuple (tunneled + protocol number, IPv6 source address, IPv6 destination address, + UDP source port, UDP destination port). A corrupted datagram that + arrives at a destination may be filtered based on this check. + + * If the datagram header matches the 5-tuple and the node has + enabled the zero checksum for this port, the payload is matched + to the wrong context. The tunneled packet will then be + decapsulated and forwarded by the tunnel egress. + + * If a corrupted datagram matches a different 5-tuple and the + node has enabled zero checksum for the port, the datagram + payload is matched to the wrong context and may be processed by + the wrong tunnel protocol, provided that it also passes the + verification of that protocol. + + + + + +Eubanks, et al. Standards Track [Page 5] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + + * If a corrupted datagram matches a 5-tuple and node has not + enabled the zero checksum for this port, the datagram will be + discarded. + + When only the source information is corrupted, the datagram could + arrive at the intended applications or protocol, which will + process the datagram and try to match it against an existing + tunnel context. The likelihood that a corrupted packet enters a + valid context is reduced when the protocol restricts processing to + only the source addresses with established contexts. When both + source and destination fields are corrupted, this also decreases + the likelihood of matching a context. However, the exception is + when errors replace one packet header with another, so both + packets could be tunneled, and therefore the corrupted packet + could match a previously defined context. + + o Receivers should attempt to detect corruption of source-fragmented + encapsulating packets. A tunnel protocol may reassemble fragments + associated with the wrong context at the right tunnel endpoint, it + may reassemble fragments associated with a context at the wrong + tunnel endpoint, or corrupted fragments may be reassembled at the + right context at the right tunnel endpoint. In each of these + cases, the IPv6 length of the encapsulating header may be checked + (although [RFC6936] points out the weakness in this check). In + addition, if the encapsulated packet is protected by a transport + (or other) checksum, these errors can be detected (with some + probability). + + o Compared to other applications, tunnel protocols using UDP have + some advantages that reduce the risk for a corrupted tunnel packet + reaching a destination that will receive it. These advantages + result from processing by the network of the inner (tunneled) + packet after it is forwarded from the tunnel egress using a wrong + context: + + * A tunneled packet may be forwarded to the wrong address domain, + for example, to a private address domain where the inner + packet's address is not routable, or it may fail a source + address check, such as Unicast Reverse Path Forwarding + [RFC2827], resulting in the packet being dropped. + + * The destination address of a tunneled packet may not be + reachable at all from the delivered domain. An example is an + Ethernet frame where the destination MAC address is not present + on the LAN segment that was reached. + + + + + + +Eubanks, et al. Standards Track [Page 6] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + + * The type of the tunneled packet may prevent delivery. For + example, an attempt to interpret an IP packet payload as an + Ethernet frame would likely to result in the packet being + dropped as invalid. + + * The tunneled packet checksum or integrity mechanism may detect + corruption of the inner packet caused at the same time as + corruption to the outer packet header. The resulting packet + would likely be dropped as invalid. + + Each of these checks significantly reduces the likelihood that a + corrupted inner tunneled packet is finally delivered to a protocol + listener that can be affected by the packet. While the methods do + not guarantee correctness, they can reduce the risks of relaxing the + UDP checksum requirement for a tunnel application using IPv6. + +4.2. Limitation to Tunnel Protocols + + This document describes the applicability of using a zero UDP + checksum to support tunnel protocols. There are good motivations + behind this, and the arguments are provided here. + + o Tunnels carry inner packets that have their own semantics, which + may make any corruption less likely to reach the indicated + destination and be accepted as a valid packet. This is true for + IP packets with the addition of verification that can be made by + the tunnel protocol, the network processing of the inner packet + headers as discussed above, and verification of the inner packet + checksums. Non-IP inner packets are likely to be subject to + similar effects that may reduce the likelihood of a misdelivered + packet being delivered to a protocol listener that can be affected + by the packet. + + o Protocols that directly consume the payload must have sufficient + robustness against misdelivered packets (from any context), + including ones that are corrupted in tunnels or corrupted by other + usage of the zero checksum. This will require an integrity + mechanism. Using a standard UDP checksum reduces the + computational load in the receiver that is necessary to verify + this mechanism. + + o The design for stateful protocols or protocols where corruption + causes cascade effects requires extra care. In tunnel usage, each + encapsulating packet provides no functions other than a transport + from tunnel ingress to tunnel egress. A corruption will commonly + affect only the single tunneled packet, not the established + + + + + +Eubanks, et al. Standards Track [Page 7] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + + protocol state. One common effect is that the inner packet flow + will see only a corruption and a misdelivery of the outer packet + as a lost packet. + + o Some non-tunnel protocols operate with general servers that do not + know the source from which they will receive a packet. In such + applications, a zero UDP checksum is unsuitable, because it is + necessary to provide the first level of verification that the + packet was intended for the receiving server. A verification + prevents the server from processing the datagram payload; without + this, the server may spend significant resources processing the + packet, including sending replies or error messages. + + Tunnel protocols that encapsulate IP will generally be safe for + deployment, because all IPv4 and IPv6 packets include at least one + checksum at either the network or transport layer. The network + delivery of the inner packet will then further reduce the effects of + corruption. Tunnel protocols carrying non-IP packets may offer + equivalent protection when the non-IP networks reduce the risk of + misdelivery to applications. However, further analysis is necessary + to understand the implications of misdelivery of corrupted packets + for each non-IP protocol. The analysis above suggests that non- + tunnel protocols can be expected to have significantly more cases + where a zero checksum would result in misdelivery or negative side + effects. + + One unfortunate side effect of increased use of a zero checksum is + that it also increases the likelihood of acceptance when a datagram + with a zero UDP checksum is misdelivered. This requires all tunnel + protocols using this method to be designed to be robust in the face + of misdelivery. + +4.3. Middleboxes + + "Applicability Statement for the Use of IPv6 UDP Datagrams with Zero + Checksums" [RFC6936] specifies requirements for middleboxes and + tunnels that need to traverse middleboxes. Tunnel protocols + intending to use a zero UDP checksum need to ensure that they have + defined a method for handling cases when a middlebox prevents the + path between the tunnel ingress and egress from supporting + transmission of datagrams with a zero UDP checksum. This is + especially important as middleboxes that conform to RFC 2460 are + likely to discard datagrams with a zero UDP checksum. + + + + + + + + +Eubanks, et al. Standards Track [Page 8] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + +5. The Zero UDP Checksum Update + + This specification updates IPv6 to allow a zero UDP checksum in the + outer encapsulating datagram of a tunnel protocol. UDP endpoints + that implement this update MUST follow the node requirements in + "Applicability Statement for the Use of IPv6 UDP Datagrams with Zero + Checksums" [RFC6936]. + + The following text in [RFC2460], Section 8.1, fourth bullet should be + deleted: + + Unlike IPv4, when UDP packets are originated by an IPv6 node, the + UDP checksum is not optional. That is, whenever originating a UDP + packet, an IPv6 node must compute a UDP checksum over the packet + and the pseudo-header, and, if that computation yields a result of + zero, it must be changed to hex FFFF for placement in the UDP + header. IPv6 receivers must discard UDP packets containing a zero + checksum, and should log the error. + + This text should be replaced by: + + An IPv6 node associates a mode with each used UDP port (for + sending and/or receiving packets). + + Whenever originating a UDP packet for a port in the default mode, + an IPv6 node MUST compute a UDP checksum over the packet and the + pseudo-header, and, if that computation yields a result of zero, + the checksum MUST be changed to hex FFFF for placement in the UDP + header, as specified in [RFC2460]. IPv6 receivers MUST by default + discard UDP packets containing a zero checksum and SHOULD log the + error. + + As an alternative, certain protocols that use UDP as a tunnel + encapsulation MAY enable zero-checksum mode for a specific port + (or set of ports) for sending and/or receiving. Any node + implementing zero-checksum mode MUST follow the node requirements + specified in Section 4 of "Applicability Statement for the use of + IPv6 UDP Datagrams with Zero Checksums" [RFC6936]. + + Any protocol that enables zero-checksum mode for a specific port + or ports MUST follow the usage requirements specified in Section 5 + of "Applicability Statement for the Use of IPv6 UDP Datagrams with + Zero Checksums" [RFC6936]. + + Middleboxes supporting IPv6 MUST follow requirements 9, 10, and 11 + of the usage requirements specified in Section 5 of "Applicability + Statement for the Use of IPv6 UDP Datagrams with Zero Checksums" + [RFC6936]. + + + +Eubanks, et al. Standards Track [Page 9] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + +6. Additional Observations + + This update was motivated by the existence of a number of protocols + being developed in the IETF that are expected to benefit from the + change. The following observations are made: + + o An empirically based analysis of the probabilities of packet + corruption (with or without checksums) has not, to our knowledge, + been conducted since about 2000. At the time of publication, it + is now 2013. We strongly suggest that a new empirical study be + performed, along with extensive analysis of the corruption + probabilities of the IPv6 header. This could potentially allow + revising the recommendations in this document. + + o A key motivation for the increase in use of UDP in tunneling is a + lack of protocol support in middleboxes. Specifically, new + protocols, such as LISP [RFC6830], may prefer to use UDP tunnels + to traverse an end-to-end path successfully and avoid having their + packets dropped by middleboxes. If middleboxes were updated to + support UDP-Lite [RFC3828], UDP-Lite would provide better + protection than offered by this update. UDP-Lite may be suited to + a variety of applications and would be expected to be preferred + over this method for many tunnel protocols. + + o Another issue is that the UDP checksum is overloaded with the task + of protecting the IPv6 header for UDP flows (as is the TCP + checksum for TCP flows). Protocols that do not use a pseudo- + header approach to computing a checksum or CRC have essentially no + protection from misdelivered packets. + +7. Security Considerations + + Less work is required to generate an attack using a zero UDP checksum + than one using a standard full UDP checksum. However, this does not + lead to significant new vulnerabilities, because checksums are not a + security measure and can be easily generated by any attacker. + + In general, any user of zero UDP checksums should apply the checks + and context verification that are possible to minimize the risk of + unintended traffic to reach a particular context. This will, + however, not protect against an intentional attack that creates + packets with the correct information. Source address validation can + help prevent injection of traffic into contexts by an attacker. + + Depending on the hardware design, the processing requirements may + differ for tunnels that have a zero UDP checksum and those that + calculate a checksum. This processing overhead may need to be + considered when deciding whether to enable a tunnel and to determine + + + +Eubanks, et al. Standards Track [Page 10] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + + an acceptable rate for transmission. This processing overhead can + become a security risk for designs that can handle a significantly + larger number of packets with zero UDP checksums compared to + datagrams with a non-zero checksum, such as a tunnel egress. An + attacker could attempt to inject non-zero checksummed UDP packets + into a tunnel forwarding zero checksum UDP packets and cause overload + in the processing of the non-zero checksums, e.g., if this happens in + a router's slow path. Therefore, protection mechanisms should be + employed when this threat exists. Protection may include source- + address filtering to prevent an attacker from injecting traffic, as + well as throttling the amount of non-zero checksum traffic. The + latter may impact the functioning of the tunnel protocol. + +8. Acknowledgments + + We would like to thank Brian Haberman, Dan Wing, Joel Halpern, David + Waltermire, J.W. Atwood, Peter Yee, Joe Touch, and the IESG of 2012 + for discussions and reviews. Gorry Fairhurst has been very diligent + in reviewing and helping to ensure alignment between this document + and [RFC6936]. + +9. References + +9.1. Normative References + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version 6 + (IPv6) Specification", RFC 2460, December 1998. + + [RFC6936] Fairhurst, G. and M. Westerlund, "Applicability Statement + for the Use of IPv6 UDP Datagrams with Zero Checksums", + RFC 6936, April 2013. + +9.2. Informative References + + [AMT] Bumgardner, G., "Automatic Multicast Tunneling", Work + in Progress, June 2012. + + [RFC2827] Ferguson, P. and D. Senie, "Network Ingress Filtering: + Defeating Denial of Service Attacks which employ IP Source + Address Spoofing", BCP 38, RFC 2827, May 2000. + + [RFC3828] Larzon, L-A., Degermark, M., Pink, S., Jonsson, L-E., and + G. Fairhurst, "The Lightweight User Datagram Protocol + (UDP-Lite)", RFC 3828, July 2004. + + + + +Eubanks, et al. Standards Track [Page 11] + +RFC 6935 IPv6/UDP Checksums for Tunneled Packets April 2013 + + + [RFC5405] Eggert, L. and G. Fairhurst, "Unicast UDP Usage Guidelines + for Application Designers", BCP 145, RFC 5405, + November 2008. + + [RFC5619] Yamamoto, S., Williams, C., Yokota, H., and F. Parent, + "Softwire Security Analysis and Requirements", RFC 5619, + August 2009. + + [RFC6830] Farinacci, D., Fuller, V., Meyer, D., and D. Lewis, "The + Locator/ID Separation Protocol (LISP)", RFC 6830, + January 2013. + +Authors' Addresses + + Marshall Eubanks + AmericaFree.TV LLC + P.O. Box 141 + Clifton, Virginia 20124 + USA + + Phone: +1-703-501-4376 + EMail: marshall.eubanks@gmail.com + + + P.F. Chimento + Johns Hopkins University Applied Physics Laboratory + 11100 Johns Hopkins Road + Laurel, Maryland 20723 + USA + + Phone: +1-443-778-1743 + EMail: Philip.Chimento@jhuapl.edu + + + Magnus Westerlund + Ericsson + Farogatan 6 + SE-164 80 Kista + Sweden + + Phone: +46 10 719 00 00 + EMail: magnus.westerlund@ericsson.com + + + + + + + + + +Eubanks, et al. Standards Track [Page 12] + diff --git a/.duvet/specifications/www.rfc-editor.org/rfc/rfc8200.txt b/.duvet/specifications/www.rfc-editor.org/rfc/rfc8200.txt new file mode 100644 index 0000000000..5becd40c3a --- /dev/null +++ b/.duvet/specifications/www.rfc-editor.org/rfc/rfc8200.txt @@ -0,0 +1,2355 @@ + + + + + + +Internet Engineering Task Force (IETF) S. Deering +Request for Comments: 8200 Retired +STD: 86 R. Hinden +Obsoletes: 2460 Check Point Software +Category: Standards Track July 2017 +ISSN: 2070-1721 + + + Internet Protocol, Version 6 (IPv6) Specification + +Abstract + + This document specifies version 6 of the Internet Protocol (IPv6). + It obsoletes RFC 2460. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Further information on + Internet Standards is available in Section 2 of RFC 7841. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc8200. + + + + + + + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 1] + +RFC 8200 IPv6 Specification July 2017 + + +Copyright Notice + + Copyright (c) 2017 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + This document may contain material from IETF Documents or IETF + Contributions published or made publicly available before November + 10, 2008. The person(s) controlling the copyright in some of this + material may not have granted the IETF Trust the right to allow + modifications of such material outside the IETF Standards Process. + Without obtaining an adequate license from the person(s) controlling + the copyright in such materials, this document may not be modified + outside the IETF Standards Process, and derivative works of it may + not be created outside the IETF Standards Process, except to format + it for publication as an RFC or to translate it into languages other + than English. + + + + + + + + + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 2] + +RFC 8200 IPv6 Specification July 2017 + + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 4 + 2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 5 + 3. IPv6 Header Format . . . . . . . . . . . . . . . . . . . . . 6 + 4. IPv6 Extension Headers . . . . . . . . . . . . . . . . . . . 7 + 4.1. Extension Header Order . . . . . . . . . . . . . . . . . 10 + 4.2. Options . . . . . . . . . . . . . . . . . . . . . . . . . 11 + 4.3. Hop-by-Hop Options Header . . . . . . . . . . . . . . . . 13 + 4.4. Routing Header . . . . . . . . . . . . . . . . . . . . . 14 + 4.5. Fragment Header . . . . . . . . . . . . . . . . . . . . . 15 + 4.6. Destination Options Header . . . . . . . . . . . . . . . 23 + 4.7. No Next Header . . . . . . . . . . . . . . . . . . . . . 24 + 4.8. Defining New Extension Headers and Options . . . . . . . 24 + 5. Packet Size Issues . . . . . . . . . . . . . . . . . . . . . 25 + 6. Flow Labels . . . . . . . . . . . . . . . . . . . . . . . . . 26 + 7. Traffic Classes . . . . . . . . . . . . . . . . . . . . . . . 26 + 8. Upper-Layer Protocol Issues . . . . . . . . . . . . . . . . . 27 + 8.1. Upper-Layer Checksums . . . . . . . . . . . . . . . . . . 27 + 8.2. Maximum Packet Lifetime . . . . . . . . . . . . . . . . . 28 + 8.3. Maximum Upper-Layer Payload Size . . . . . . . . . . . . 29 + 8.4. Responding to Packets Carrying Routing Headers . . . . . 29 + 9. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 29 + 10. Security Considerations . . . . . . . . . . . . . . . . . . . 30 + 11. References . . . . . . . . . . . . . . . . . . . . . . . . . 32 + 11.1. Normative References . . . . . . . . . . . . . . . . . . 32 + 11.2. Informative References . . . . . . . . . . . . . . . . . 33 + Appendix A. Formatting Guidelines for Options . . . . . . . . . 36 + Appendix B. Changes Since RFC 2460 . . . . . . . . . . . . . . . 39 + Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 42 + Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 42 + + + + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 3] + +RFC 8200 IPv6 Specification July 2017 + + +1. Introduction + + IP version 6 (IPv6) is a new version of the Internet Protocol (IP), + designed as the successor to IP version 4 (IPv4) [RFC791]. The + changes from IPv4 to IPv6 fall primarily into the following + categories: + + o Expanded Addressing Capabilities + + IPv6 increases the IP address size from 32 bits to 128 bits, to + support more levels of addressing hierarchy, a much greater + number of addressable nodes, and simpler autoconfiguration of + addresses. The scalability of multicast routing is improved by + adding a "scope" field to multicast addresses. And a new type + of address called an "anycast address" is defined; it is used + to send a packet to any one of a group of nodes. + + o Header Format Simplification + + Some IPv4 header fields have been dropped or made optional, to + reduce the common-case processing cost of packet handling and + to limit the bandwidth cost of the IPv6 header. + + o Improved Support for Extensions and Options + + Changes in the way IP header options are encoded allows for + more efficient forwarding, less stringent limits on the length + of options, and greater flexibility for introducing new options + in the future. + + o Flow Labeling Capability + + A new capability is added to enable the labeling of sequences + of packets that the sender requests to be treated in the + network as a single flow. + + o Authentication and Privacy Capabilities + + Extensions to support authentication, data integrity, and + (optional) data confidentiality are specified for IPv6. + + This document specifies the basic IPv6 header and the initially + defined IPv6 extension headers and options. It also discusses packet + size issues, the semantics of flow labels and traffic classes, and + the effects of IPv6 on upper-layer protocols. The format and + semantics of IPv6 addresses are specified separately in [RFC4291]. + The IPv6 version of ICMP, which all IPv6 implementations are required + to include, is specified in [RFC4443]. + + + +Deering & Hinden Standards Track [Page 4] + +RFC 8200 IPv6 Specification July 2017 + + + The data transmission order for IPv6 is the same as for IPv4 as + defined in Appendix B of [RFC791]. + + Note: As this document obsoletes [RFC2460], any document referenced + in this document that includes pointers to RFC 2460 should be + interpreted as referencing this document. + +2. Terminology + + node a device that implements IPv6. + + router a node that forwards IPv6 packets not explicitly + addressed to itself. (See Note below.) + + host any node that is not a router. (See Note below.) + + upper layer a protocol layer immediately above IPv6. Examples are + transport protocols such as TCP and UDP, control + protocols such as ICMP, routing protocols such as OSPF, + and internet-layer or lower-layer protocols being + "tunneled" over (i.e., encapsulated in) IPv6 such as + Internetwork Packet Exchange (IPX), AppleTalk, or IPv6 + itself. + + link a communication facility or medium over which nodes can + communicate at the link layer, i.e., the layer + immediately below IPv6. Examples are Ethernets (simple + or bridged); PPP links; X.25, Frame Relay, or ATM + networks; and internet-layer or higher-layer "tunnels", + such as tunnels over IPv4 or IPv6 itself. + + neighbors nodes attached to the same link. + + interface a node's attachment to a link. + + address an IPv6-layer identifier for an interface or a set of + interfaces. + + packet an IPv6 header plus payload. + + link MTU the maximum transmission unit, i.e., maximum packet size + in octets, that can be conveyed over a link. + + path MTU the minimum link MTU of all the links in a path between + a source node and a destination node. + + + + + + +Deering & Hinden Standards Track [Page 5] + +RFC 8200 IPv6 Specification July 2017 + + + Note: it is possible for a device with multiple interfaces to be + configured to forward non-self-destined packets arriving from some + set (fewer than all) of its interfaces and to discard non-self- + destined packets arriving from its other interfaces. Such a device + must obey the protocol requirements for routers when receiving + packets from, and interacting with neighbors over, the former + (forwarding) interfaces. It must obey the protocol requirements for + hosts when receiving packets from, and interacting with neighbors + over, the latter (non-forwarding) interfaces. + +3. IPv6 Header Format + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Version| Traffic Class | Flow Label | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Payload Length | Next Header | Hop Limit | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + + + | | + + Source Address + + | | + + + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + + + | | + + Destination Address + + | | + + + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Version 4-bit Internet Protocol version number = 6. + + Traffic Class 8-bit Traffic Class field. See Section 7. + + Flow Label 20-bit flow label. See Section 6. + + Payload Length 16-bit unsigned integer. Length of the IPv6 + payload, i.e., the rest of the packet + following this IPv6 header, in octets. (Note + that any extension headers (see Section 4) + present are considered part of the payload, + i.e., included in the length count.) + + + + + +Deering & Hinden Standards Track [Page 6] + +RFC 8200 IPv6 Specification July 2017 + + + Next Header 8-bit selector. Identifies the type of header + immediately following the IPv6 header. Uses + the same values as the IPv4 Protocol field + [IANA-PN]. + + Hop Limit 8-bit unsigned integer. Decremented by 1 by + each node that forwards the packet. When + forwarding, the packet is discarded if Hop + Limit was zero when received or is decremented + to zero. A node that is the destination of a + packet should not discard a packet with Hop + Limit equal to zero; it should process the + packet normally. + + Source Address 128-bit address of the originator of the + packet. See [RFC4291]. + + Destination Address 128-bit address of the intended recipient of + the packet (possibly not the ultimate + recipient, if a Routing header is present). + See [RFC4291] and Section 4.4. + +4. IPv6 Extension Headers + + In IPv6, optional internet-layer information is encoded in separate + headers that may be placed between the IPv6 header and the upper- + layer header in a packet. There is a small number of such extension + headers, each one identified by a distinct Next Header value. + + Extension headers are numbered from IANA IP Protocol Numbers + [IANA-PN], the same values used for IPv4 and IPv6. When processing a + sequence of Next Header values in a packet, the first one that is not + an extension header [IANA-EH] indicates that the next item in the + packet is the corresponding upper-layer header. A special "No Next + Header" value is used if there is no upper-layer header. + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 7] + +RFC 8200 IPv6 Specification July 2017 + + + As illustrated in these examples, an IPv6 packet may carry zero, one, + or more extension headers, each identified by the Next Header field + of the preceding header: + + +---------------+------------------------ + | IPv6 header | TCP header + data + | | + | Next Header = | + | TCP | + +---------------+------------------------ + + +---------------+----------------+------------------------ + | IPv6 header | Routing header | TCP header + data + | | | + | Next Header = | Next Header = | + | Routing | TCP | + +---------------+----------------+------------------------ + + +---------------+----------------+-----------------+----------------- + | IPv6 header | Routing header | Fragment header | fragment of TCP + | | | | header + data + | Next Header = | Next Header = | Next Header = | + | Routing | Fragment | TCP | + +---------------+----------------+-----------------+----------------- + + Extension headers (except for the Hop-by-Hop Options header) are not + processed, inserted, or deleted by any node along a packet's delivery + path, until the packet reaches the node (or each of the set of nodes, + in the case of multicast) identified in the Destination Address field + of the IPv6 header. + + The Hop-by-Hop Options header is not inserted or deleted, but may be + examined or processed by any node along a packet's delivery path, + until the packet reaches the node (or each of the set of nodes, in + the case of multicast) identified in the Destination Address field of + the IPv6 header. The Hop-by-Hop Options header, when present, must + immediately follow the IPv6 header. Its presence is indicated by the + value zero in the Next Header field of the IPv6 header. + + NOTE: While [RFC2460] required that all nodes must examine and + process the Hop-by-Hop Options header, it is now expected that nodes + along a packet's delivery path only examine and process the + Hop-by-Hop Options header if explicitly configured to do so. + + + + + + + + +Deering & Hinden Standards Track [Page 8] + +RFC 8200 IPv6 Specification July 2017 + + + At the destination node, normal demultiplexing on the Next Header + field of the IPv6 header invokes the module to process the first + extension header, or the upper-layer header if no extension header is + present. The contents and semantics of each extension header + determine whether or not to proceed to the next header. Therefore, + extension headers must be processed strictly in the order they appear + in the packet; a receiver must not, for example, scan through a + packet looking for a particular kind of extension header and process + that header prior to processing all preceding ones. + + If, as a result of processing a header, the destination node is + required to proceed to the next header but the Next Header value in + the current header is unrecognized by the node, it should discard the + packet and send an ICMP Parameter Problem message to the source of + the packet, with an ICMP Code value of 1 ("unrecognized Next Header + type encountered") and the ICMP Pointer field containing the offset + of the unrecognized value within the original packet. The same + action should be taken if a node encounters a Next Header value of + zero in any header other than an IPv6 header. + + Each extension header is an integer multiple of 8 octets long, in + order to retain 8-octet alignment for subsequent headers. Multi- + octet fields within each extension header are aligned on their + natural boundaries, i.e., fields of width n octets are placed at an + integer multiple of n octets from the start of the header, for n = 1, + 2, 4, or 8. + + A full implementation of IPv6 includes implementation of the + following extension headers: + + Hop-by-Hop Options + Fragment + Destination Options + Routing + Authentication + Encapsulating Security Payload + + The first four are specified in this document; the last two are + specified in [RFC4302] and [RFC4303], respectively. The current list + of IPv6 extension headers can be found at [IANA-EH]. + + + + + + + + + + + +Deering & Hinden Standards Track [Page 9] + +RFC 8200 IPv6 Specification July 2017 + + +4.1. Extension Header Order + + When more than one extension header is used in the same packet, it is + recommended that those headers appear in the following order: + + IPv6 header + Hop-by-Hop Options header + Destination Options header (note 1) + Routing header + Fragment header + Authentication header (note 2) + Encapsulating Security Payload header (note 2) + Destination Options header (note 3) + Upper-Layer header + + note 1: for options to be processed by the first destination that + appears in the IPv6 Destination Address field plus + subsequent destinations listed in the Routing header. + + note 2: additional recommendations regarding the relative order of + the Authentication and Encapsulating Security Payload + headers are given in [RFC4303]. + + note 3: for options to be processed only by the final destination + of the packet. + + Each extension header should occur at most once, except for the + Destination Options header, which should occur at most twice (once + before a Routing header and once before the upper-layer header). + + If the upper-layer header is another IPv6 header (in the case of IPv6 + being tunneled over or encapsulated in IPv6), it may be followed by + its own extension headers, which are separately subject to the same + ordering recommendations. + + If and when other extension headers are defined, their ordering + constraints relative to the above listed headers must be specified. + + IPv6 nodes must accept and attempt to process extension headers in + any order and occurring any number of times in the same packet, + except for the Hop-by-Hop Options header, which is restricted to + appear immediately after an IPv6 header only. Nonetheless, it is + strongly advised that sources of IPv6 packets adhere to the above + recommended order until and unless subsequent specifications revise + that recommendation. + + + + + + +Deering & Hinden Standards Track [Page 10] + +RFC 8200 IPv6 Specification July 2017 + + +4.2. Options + + Two of the currently defined extension headers specified in this + document -- the Hop-by-Hop Options header and the Destination Options + header -- carry a variable number of "options" that are type-length- + value (TLV) encoded in the following format: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - + | Option Type | Opt Data Len | Option Data + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - + + Option Type 8-bit identifier of the type of option. + + Opt Data Len 8-bit unsigned integer. Length of the Option + Data field of this option, in octets. + + Option Data Variable-length field. Option-Type-specific + data. + + The sequence of options within a header must be processed strictly in + the order they appear in the header; a receiver must not, for + example, scan through the header looking for a particular kind of + option and process that option prior to processing all preceding + ones. + + The Option Type identifiers are internally encoded such that their + highest-order 2 bits specify the action that must be taken if the + processing IPv6 node does not recognize the Option Type: + + 00 - skip over this option and continue processing the header. + + 01 - discard the packet. + + 10 - discard the packet and, regardless of whether or not the + packet's Destination Address was a multicast address, send an + ICMP Parameter Problem, Code 2, message to the packet's + Source Address, pointing to the unrecognized Option Type. + + 11 - discard the packet and, only if the packet's Destination + Address was not a multicast address, send an ICMP Parameter + Problem, Code 2, message to the packet's Source Address, + pointing to the unrecognized Option Type. + + The third-highest-order bit of the Option Type specifies whether or + not the Option Data of that option can change en route to the + packet's final destination. When an Authentication header is present + + + + + +Deering & Hinden Standards Track [Page 11] + +RFC 8200 IPv6 Specification July 2017 + + + in the packet, for any option whose data may change en route, its + entire Option Data field must be treated as zero-valued octets when + computing or verifying the packet's authenticating value. + + 0 - Option Data does not change en route + + 1 - Option Data may change en route + + The three high-order bits described above are to be treated as part + of the Option Type, not independent of the Option Type. That is, a + particular option is identified by a full 8-bit Option Type, not just + the low-order 5 bits of an Option Type. + + The same Option Type numbering space is used for both the Hop-by-Hop + Options header and the Destination Options header. However, the + specification of a particular option may restrict its use to only one + of those two headers. + + Individual options may have specific alignment requirements, to + ensure that multi-octet values within Option Data fields fall on + natural boundaries. The alignment requirement of an option is + specified using the notation xn+y, meaning the Option Type must + appear at an integer multiple of x octets from the start of the + header, plus y octets. For example: + + 2n means any 2-octet offset from the start of the header. + 8n+2 means any 8-octet offset from the start of the header, plus + 2 octets. + + There are two padding options that are used when necessary to align + subsequent options and to pad out the containing header to a multiple + of 8 octets in length. These padding options must be recognized by + all IPv6 implementations: + + Pad1 option (alignment requirement: none) + + +-+-+-+-+-+-+-+-+ + | 0 | + +-+-+-+-+-+-+-+-+ + + NOTE! the format of the Pad1 option is a special case -- it does + not have length and value fields. + + The Pad1 option is used to insert 1 octet of padding into the + Options area of a header. If more than one octet of padding is + required, the PadN option, described next, should be used, rather + than multiple Pad1 options. + + + + +Deering & Hinden Standards Track [Page 12] + +RFC 8200 IPv6 Specification July 2017 + + + PadN option (alignment requirement: none) + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - + | 1 | Opt Data Len | Option Data + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- - - - - - - - - + + The PadN option is used to insert two or more octets of padding + into the Options area of a header. For N octets of padding, the + Opt Data Len field contains the value N-2, and the Option Data + consists of N-2 zero-valued octets. + + Appendix A contains formatting guidelines for designing new options. + +4.3. Hop-by-Hop Options Header + + The Hop-by-Hop Options header is used to carry optional information + that may be examined and processed by every node along a packet's + delivery path. The Hop-by-Hop Options header is identified by a Next + Header value of 0 in the IPv6 header and has the following format: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + | | + . . + . Options . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + Next Header 8-bit selector. Identifies the type of header + immediately following the Hop-by-Hop Options + header. Uses the same values as the IPv4 + Protocol field [IANA-PN]. + + Hdr Ext Len 8-bit unsigned integer. Length of the + Hop-by-Hop Options header in 8-octet units, + not including the first 8 octets. + + Options Variable-length field, of length such that the + complete Hop-by-Hop Options header is an + integer multiple of 8 octets long. Contains + one or more TLV-encoded options, as described + in Section 4.2. + + The only hop-by-hop options defined in this document are the Pad1 and + PadN options specified in Section 4.2. + + + +Deering & Hinden Standards Track [Page 13] + +RFC 8200 IPv6 Specification July 2017 + + +4.4. Routing Header + + The Routing header is used by an IPv6 source to list one or more + intermediate nodes to be "visited" on the way to a packet's + destination. This function is very similar to IPv4's Loose Source + and Record Route option. The Routing header is identified by a Next + Header value of 43 in the immediately preceding header and has the + following format: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len | Routing Type | Segments Left | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + . . + . type-specific data . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Next Header 8-bit selector. Identifies the type of header + immediately following the Routing header. + Uses the same values as the IPv4 Protocol + field [IANA-PN]. + + Hdr Ext Len 8-bit unsigned integer. Length of the Routing + header in 8-octet units, not including the + first 8 octets. + + Routing Type 8-bit identifier of a particular Routing + header variant. + + Segments Left 8-bit unsigned integer. Number of route + segments remaining, i.e., number of explicitly + listed intermediate nodes still to be visited + before reaching the final destination. + + type-specific data Variable-length field, of format determined by + the Routing Type, and of length such that the + complete Routing header is an integer multiple + of 8 octets long. + + + + + + + + + + + +Deering & Hinden Standards Track [Page 14] + +RFC 8200 IPv6 Specification July 2017 + + + If, while processing a received packet, a node encounters a Routing + header with an unrecognized Routing Type value, the required behavior + of the node depends on the value of the Segments Left field, as + follows: + + If Segments Left is zero, the node must ignore the Routing header + and proceed to process the next header in the packet, whose type + is identified by the Next Header field in the Routing header. + + If Segments Left is non-zero, the node must discard the packet and + send an ICMP Parameter Problem, Code 0, message to the packet's + Source Address, pointing to the unrecognized Routing Type. + + If, after processing a Routing header of a received packet, an + intermediate node determines that the packet is to be forwarded onto + a link whose link MTU is less than the size of the packet, the node + must discard the packet and send an ICMP Packet Too Big message to + the packet's Source Address. + + The currently defined IPv6 Routing Headers and their status can be + found at [IANA-RH]. Allocation guidelines for IPv6 Routing Headers + can be found in [RFC5871]. + +4.5. Fragment Header + + The Fragment header is used by an IPv6 source to send a packet larger + than would fit in the path MTU to its destination. (Note: unlike + IPv4, fragmentation in IPv6 is performed only by source nodes, not by + routers along a packet's delivery path -- see Section 5.) The + Fragment header is identified by a Next Header value of 44 in the + immediately preceding header and has the following format: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Reserved | Fragment Offset |Res|M| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Identification | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Next Header 8-bit selector. Identifies the initial header + type of the Fragmentable Part of the original + packet (defined below). Uses the same values + as the IPv4 Protocol field [IANA-PN]. + + Reserved 8-bit reserved field. Initialized to zero for + transmission; ignored on reception. + + + + + + +Deering & Hinden Standards Track [Page 15] + +RFC 8200 IPv6 Specification July 2017 + + + Fragment Offset 13-bit unsigned integer. The offset, in + 8-octet units, of the data following this + header, relative to the start of the + Fragmentable Part of the original packet. + + Res 2-bit reserved field. Initialized to zero for + transmission; ignored on reception. + + M flag 1 = more fragments; 0 = last fragment. + + Identification 32 bits. See description below. + + In order to send a packet that is too large to fit in the MTU of the + path to its destination, a source node may divide the packet into + fragments and send each fragment as a separate packet, to be + reassembled at the receiver. + + For every packet that is to be fragmented, the source node generates + an Identification value. The Identification must be different than + that of any other fragmented packet sent recently* with the same + Source Address and Destination Address. If a Routing header is + present, the Destination Address of concern is that of the final + destination. + + * "recently" means within the maximum likely lifetime of a + packet, including transit time from source to destination and + time spent awaiting reassembly with other fragments of the same + packet. However, it is not required that a source node knows + the maximum packet lifetime. Rather, it is assumed that the + requirement can be met by implementing an algorithm that + results in a low identification reuse frequency. Examples of + algorithms that can meet this requirement are described in + [RFC7739]. + + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 16] + +RFC 8200 IPv6 Specification July 2017 + + + The initial, large, unfragmented packet is referred to as the + "original packet", and it is considered to consist of three parts, as + illustrated: + + original packet: + + +------------------+-------------------------+---//----------------+ + | Per-Fragment | Extension & Upper-Layer | Fragmentable | + | Headers | Headers | Part | + +------------------+-------------------------+---//----------------+ + + The Per-Fragment headers must consist of the IPv6 header plus any + extension headers that must be processed by nodes en route to the + destination, that is, all headers up to and including the Routing + header if present, else the Hop-by-Hop Options header if present, + else no extension headers. + + The Extension headers are all other extension headers that are not + included in the Per-Fragment headers part of the packet. For this + purpose, the Encapsulating Security Payload (ESP) is not + considered an extension header. The Upper-Layer header is the + first upper-layer header that is not an IPv6 extension header. + Examples of upper-layer headers include TCP, UDP, IPv4, IPv6, + ICMPv6, and as noted ESP. + + The Fragmentable Part consists of the rest of the packet after the + upper-layer header or after any header (i.e., initial IPv6 header + or extension header) that contains a Next Header value of No Next + Header. + + The Fragmentable Part of the original packet is divided into + fragments. The lengths of the fragments must be chosen such that the + resulting fragment packets fit within the MTU of the path to the + packet's destination(s). Each complete fragment, except possibly the + last ("rightmost") one, is an integer multiple of 8 octets long. + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 17] + +RFC 8200 IPv6 Specification July 2017 + + + The fragments are transmitted in separate "fragment packets" as + illustrated: + + original packet: + + +-----------------+-----------------+--------+--------+-//-+--------+ + | Per-Fragment |Ext & Upper-Layer| first | second | | last | + | Headers | Headers |fragment|fragment|....|fragment| + +-----------------+-----------------+--------+--------+-//-+--------+ + + fragment packets: + + +------------------+---------+-------------------+----------+ + | Per-Fragment |Fragment | Ext & Upper-Layer | first | + | Headers | Header | Headers | fragment | + +------------------+---------+-------------------+----------+ + + +------------------+--------+-------------------------------+ + | Per-Fragment |Fragment| second | + | Headers | Header | fragment | + +------------------+--------+-------------------------------+ + o + o + o + +------------------+--------+----------+ + | Per-Fragment |Fragment| last | + | Headers | Header | fragment | + +------------------+--------+----------+ + + The first fragment packet is composed of: + + (1) The Per-Fragment headers of the original packet, with the + Payload Length of the original IPv6 header changed to contain + the length of this fragment packet only (excluding the length + of the IPv6 header itself), and the Next Header field of the + last header of the Per-Fragment headers changed to 44. + + (2) A Fragment header containing: + + The Next Header value that identifies the first header + after the Per-Fragment headers of the original packet. + + A Fragment Offset containing the offset of the fragment, + in 8-octet units, relative to the start of the + Fragmentable Part of the original packet. The Fragment + Offset of the first ("leftmost") fragment is 0. + + An M flag value of 1 as this is the first fragment. + + + +Deering & Hinden Standards Track [Page 18] + +RFC 8200 IPv6 Specification July 2017 + + + The Identification value generated for the original + packet. + + (3) Extension headers, if any, and the Upper-Layer header. These + headers must be in the first fragment. Note: This restricts + the size of the headers through the Upper-Layer header to the + MTU of the path to the packet's destinations(s). + + (4) The first fragment. + + The subsequent fragment packets are composed of: + + (1) The Per-Fragment headers of the original packet, with the + Payload Length of the original IPv6 header changed to contain + the length of this fragment packet only (excluding the length + of the IPv6 header itself), and the Next Header field of the + last header of the Per-Fragment headers changed to 44. + + (2) A Fragment header containing: + + The Next Header value that identifies the first header + after the Per-Fragment headers of the original packet. + + A Fragment Offset containing the offset of the fragment, + in 8-octet units, relative to the start of the + Fragmentable Part of the original packet. + + An M flag value of 0 if the fragment is the last + ("rightmost") one, else an M flag value of 1. + + The Identification value generated for the original + packet. + + (3) The fragment itself. + + Fragments must not be created that overlap with any other fragments + created from the original packet. + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 19] + +RFC 8200 IPv6 Specification July 2017 + + + At the destination, fragment packets are reassembled into their + original, unfragmented form, as illustrated: + + reassembled original packet: + + +---------------+-----------------+---------+--------+-//--+--------+ + | Per-Fragment |Ext & Upper-Layer| first | second | | last | + | Headers | Headers |frag data|fragment|.....|fragment| + +---------------+-----------------+---------+--------+-//--+--------+ + + The following rules govern reassembly: + + An original packet is reassembled only from fragment packets that + have the same Source Address, Destination Address, and Fragment + Identification. + + The Per-Fragment headers of the reassembled packet consists of all + headers up to, but not including, the Fragment header of the first + fragment packet (that is, the packet whose Fragment Offset is + zero), with the following two changes: + + The Next Header field of the last header of the Per-Fragment + headers is obtained from the Next Header field of the first + fragment's Fragment header. + + The Payload Length of the reassembled packet is computed from + the length of the Per-Fragment headers and the length and + offset of the last fragment. For example, a formula for + computing the Payload Length of the reassembled original packet + is: + + PL.orig = PL.first - FL.first - 8 + (8 * FO.last) + FL.last + + where + PL.orig = Payload Length field of reassembled packet. + PL.first = Payload Length field of first fragment packet. + FL.first = length of fragment following Fragment header of + first fragment packet. + FO.last = Fragment Offset field of Fragment header of last + fragment packet. + FL.last = length of fragment following Fragment header of + last fragment packet. + + The Fragmentable Part of the reassembled packet is constructed + from the fragments following the Fragment headers in each of + the fragment packets. The length of each fragment is computed + by subtracting from the packet's Payload Length the length of + the headers between the IPv6 header and fragment itself; its + + + +Deering & Hinden Standards Track [Page 20] + +RFC 8200 IPv6 Specification July 2017 + + + relative position in Fragmentable Part is computed from its + Fragment Offset value. + + The Fragment header is not present in the final, reassembled + packet. + + If the fragment is a whole datagram (that is, both the Fragment + Offset field and the M flag are zero), then it does not need + any further reassembly and should be processed as a fully + reassembled packet (i.e., updating Next Header, adjust Payload + Length, removing the Fragment header, etc.). Any other + fragments that match this packet (i.e., the same IPv6 Source + Address, IPv6 Destination Address, and Fragment Identification) + should be processed independently. + + The following error conditions may arise when reassembling fragmented + packets: + + o If insufficient fragments are received to complete reassembly + of a packet within 60 seconds of the reception of the first- + arriving fragment of that packet, reassembly of that packet + must be abandoned and all the fragments that have been received + for that packet must be discarded. If the first fragment + (i.e., the one with a Fragment Offset of zero) has been + received, an ICMP Time Exceeded -- Fragment Reassembly Time + Exceeded message should be sent to the source of that fragment. + + o If the length of a fragment, as derived from the fragment + packet's Payload Length field, is not a multiple of 8 octets + and the M flag of that fragment is 1, then that fragment must + be discarded and an ICMP Parameter Problem, Code 0, message + should be sent to the source of the fragment, pointing to the + Payload Length field of the fragment packet. + + o If the length and offset of a fragment are such that the + Payload Length of the packet reassembled from that fragment + would exceed 65,535 octets, then that fragment must be + discarded and an ICMP Parameter Problem, Code 0, message should + be sent to the source of the fragment, pointing to the Fragment + Offset field of the fragment packet. + + o If the first fragment does not include all headers through an + Upper-Layer header, then that fragment should be discarded and + an ICMP Parameter Problem, Code 3, message should be sent to + the source of the fragment, with the Pointer field set to zero. + + + + + + +Deering & Hinden Standards Track [Page 21] + +RFC 8200 IPv6 Specification July 2017 + + + o If any of the fragments being reassembled overlap with any + other fragments being reassembled for the same packet, + reassembly of that packet must be abandoned and all the + fragments that have been received for that packet must be + discarded, and no ICMP error messages should be sent. + + It should be noted that fragments may be duplicated in the + network. Instead of treating these exact duplicate fragments + as overlapping fragments, an implementation may choose to + detect this case and drop exact duplicate fragments while + keeping the other fragments belonging to the same packet. + + The following conditions are not expected to occur frequently but are + not considered errors if they do: + + The number and content of the headers preceding the Fragment + header of different fragments of the same original packet may + differ. Whatever headers are present, preceding the Fragment + header in each fragment packet, are processed when the packets + arrive, prior to queueing the fragments for reassembly. Only + those headers in the Offset zero fragment packet are retained in + the reassembled packet. + + The Next Header values in the Fragment headers of different + fragments of the same original packet may differ. Only the value + from the Offset zero fragment packet is used for reassembly. + + Other fields in the IPv6 header may also vary across the fragments + being reassembled. Specifications that use these fields may + provide additional instructions if the basic mechanism of using + the values from the Offset zero fragment is not sufficient. For + example, Section 5.3 of [RFC3168] describes how to combine the + Explicit Congestion Notification (ECN) bits from different + fragments to derive the ECN bits of the reassembled packet. + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 22] + +RFC 8200 IPv6 Specification July 2017 + + +4.6. Destination Options Header + + The Destination Options header is used to carry optional information + that need be examined only by a packet's destination node(s). The + Destination Options header is identified by a Next Header value of 60 + in the immediately preceding header and has the following format: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + | | + . . + . Options . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Next Header 8-bit selector. Identifies the type of header + immediately following the Destination Options + header. Uses the same values as the IPv4 + Protocol field [IANA-PN]. + + Hdr Ext Len 8-bit unsigned integer. Length of the + Destination Options header in 8-octet units, + not including the first 8 octets. + + Options Variable-length field, of length such that the + complete Destination Options header is an + integer multiple of 8 octets long. Contains + one or more TLV-encoded options, as described + in Section 4.2. + + The only destination options defined in this document are the Pad1 + and PadN options specified in Section 4.2. + + Note that there are two possible ways to encode optional destination + information in an IPv6 packet: either as an option in the Destination + Options header or as a separate extension header. The Fragment + header and the Authentication header are examples of the latter + approach. Which approach can be used depends on what action is + desired of a destination node that does not understand the optional + information: + + o If the desired action is for the destination node to discard + the packet and, only if the packet's Destination Address is not + a multicast address, send an ICMP Unrecognized Type message to + the packet's Source Address, then the information may be + encoded either as a separate header or as an option in the + + + +Deering & Hinden Standards Track [Page 23] + +RFC 8200 IPv6 Specification July 2017 + + + Destination Options header whose Option Type has the value 11 + in its highest-order 2 bits. The choice may depend on such + factors as which takes fewer octets, or which yields better + alignment or more efficient parsing. + + o If any other action is desired, the information must be encoded + as an option in the Destination Options header whose Option + Type has the value 00, 01, or 10 in its highest-order 2 bits, + specifying the desired action (see Section 4.2). + +4.7. No Next Header + + The value 59 in the Next Header field of an IPv6 header or any + extension header indicates that there is nothing following that + header. If the Payload Length field of the IPv6 header indicates the + presence of octets past the end of a header whose Next Header field + contains 59, those octets must be ignored and passed on unchanged if + the packet is forwarded. + +4.8. Defining New Extension Headers and Options + + Defining new IPv6 extension headers is not recommended, unless there + are no existing IPv6 extension headers that can be used by specifying + a new option for that IPv6 extension header. A proposal to specify a + new IPv6 extension header must include a detailed technical + explanation of why an existing IPv6 extension header can not be used + for the desired new function. See [RFC6564] for additional + background information. + + Note: New extension headers that require hop-by-hop behavior must not + be defined because, as specified in Section 4 of this document, the + only extension header that has hop-by-hop behavior is the Hop-by-Hop + Options header. + + New hop-by-hop options are not recommended because nodes may be + configured to ignore the Hop-by-Hop Options header, drop packets + containing a Hop-by-Hop Options header, or assign packets containing + a Hop-by-Hop Options header to a slow processing path. Designers + considering defining new hop-by-hop options need to be aware of this + likely behavior. There has to be a very clear justification why any + new hop-by-hop option is needed before it is standardized. + + Instead of defining new extension headers, it is recommended that the + Destination Options header is used to carry optional information that + must be examined only by a packet's destination node(s), because they + provide better handling and backward compatibility. + + + + + +Deering & Hinden Standards Track [Page 24] + +RFC 8200 IPv6 Specification July 2017 + + + If new extension headers are defined, they need to use the following + format: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + | | + . . + . Header-Specific Data . + . . + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Next Header 8-bit selector. Identifies the type of + header immediately following the extension + header. Uses the same values as the IPv4 + Protocol field [IANA-PN]. + + Hdr Ext Len 8-bit unsigned integer. Length of the + Destination Options header in 8-octet units, + not including the first 8 octets. + + Header Specific Data Variable-length field. Fields specific to + the extension header. + +5. Packet Size Issues + + IPv6 requires that every link in the Internet have an MTU of 1280 + octets or greater. This is known as the IPv6 minimum link MTU. On + any link that cannot convey a 1280-octet packet in one piece, link- + specific fragmentation and reassembly must be provided at a layer + below IPv6. + + Links that have a configurable MTU (for example, PPP links [RFC1661]) + must be configured to have an MTU of at least 1280 octets; it is + recommended that they be configured with an MTU of 1500 octets or + greater, to accommodate possible encapsulations (i.e., tunneling) + without incurring IPv6-layer fragmentation. + + From each link to which a node is directly attached, the node must be + able to accept packets as large as that link's MTU. + + It is strongly recommended that IPv6 nodes implement Path MTU + Discovery [RFC8201], in order to discover and take advantage of path + MTUs greater than 1280 octets. However, a minimal IPv6 + implementation (e.g., in a boot ROM) may simply restrict itself to + sending packets no larger than 1280 octets, and omit implementation + of Path MTU Discovery. + + + +Deering & Hinden Standards Track [Page 25] + +RFC 8200 IPv6 Specification July 2017 + + + In order to send a packet larger than a path's MTU, a node may use + the IPv6 Fragment header to fragment the packet at the source and + have it reassembled at the destination(s). However, the use of such + fragmentation is discouraged in any application that is able to + adjust its packets to fit the measured path MTU (i.e., down to 1280 + octets). + + A node must be able to accept a fragmented packet that, after + reassembly, is as large as 1500 octets. A node is permitted to + accept fragmented packets that reassemble to more than 1500 octets. + An upper-layer protocol or application that depends on IPv6 + fragmentation to send packets larger than the MTU of a path should + not send packets larger than 1500 octets unless it has assurance that + the destination is capable of reassembling packets of that larger + size. + +6. Flow Labels + + The 20-bit Flow Label field in the IPv6 header is used by a source to + label sequences of packets to be treated in the network as a single + flow. + + The current definition of the IPv6 Flow Label can be found in + [RFC6437]. + +7. Traffic Classes + + The 8-bit Traffic Class field in the IPv6 header is used by the + network for traffic management. The value of the Traffic Class bits + in a received packet or fragment might be different from the value + sent by the packet's source. + + The current use of the Traffic Class field for Differentiated + Services and Explicit Congestion Notification is specified in + [RFC2474] and [RFC3168]. + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 26] + +RFC 8200 IPv6 Specification July 2017 + + +8. Upper-Layer Protocol Issues + +8.1. Upper-Layer Checksums + + Any transport or other upper-layer protocol that includes the + addresses from the IP header in its checksum computation must be + modified for use over IPv6, to include the 128-bit IPv6 addresses + instead of 32-bit IPv4 addresses. In particular, the following + illustration shows the TCP and UDP "pseudo-header" for IPv6: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + + + | | + + Source Address + + | | + + + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + + + | | + + Destination Address + + | | + + + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Upper-Layer Packet Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | zero | Next Header | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + o If the IPv6 packet contains a Routing header, the Destination + Address used in the pseudo-header is that of the final + destination. At the originating node, that address will be in + the last element of the Routing header; at the recipient(s), + that address will be in the Destination Address field of the + IPv6 header. + + o The Next Header value in the pseudo-header identifies the + upper-layer protocol (e.g., 6 for TCP or 17 for UDP). It will + differ from the Next Header value in the IPv6 header if there + are extension headers between the IPv6 header and the upper- + layer header. + + + + + + + +Deering & Hinden Standards Track [Page 27] + +RFC 8200 IPv6 Specification July 2017 + + + o The Upper-Layer Packet Length in the pseudo-header is the + length of the upper-layer header and data (e.g., TCP header + plus TCP data). Some upper-layer protocols carry their own + length information (e.g., the Length field in the UDP header); + for such protocols, that is the length used in the pseudo- + header. Other protocols (such as TCP) do not carry their own + length information, in which case the length used in the + pseudo-header is the Payload Length from the IPv6 header, minus + the length of any extension headers present between the IPv6 + header and the upper-layer header. + + o Unlike IPv4, the default behavior when UDP packets are + originated by an IPv6 node is that the UDP checksum is not + optional. That is, whenever originating a UDP packet, an IPv6 + node must compute a UDP checksum over the packet and the + pseudo-header, and, if that computation yields a result of + zero, it must be changed to hex FFFF for placement in the UDP + header. IPv6 receivers must discard UDP packets containing a + zero checksum and should log the error. + + o As an exception to the default behavior, protocols that use UDP + as a tunnel encapsulation may enable zero-checksum mode for a + specific port (or set of ports) for sending and/or receiving. + Any node implementing zero-checksum mode must follow the + requirements specified in "Applicability Statement for the Use + of IPv6 UDP Datagrams with Zero Checksums" [RFC6936]. + + The IPv6 version of ICMP [RFC4443] includes the above pseudo-header + in its checksum computation; this is a change from the IPv4 version + of ICMP, which does not include a pseudo-header in its checksum. The + reason for the change is to protect ICMP from misdelivery or + corruption of those fields of the IPv6 header on which it depends, + which, unlike IPv4, are not covered by an internet-layer checksum. + The Next Header field in the pseudo-header for ICMP contains the + value 58, which identifies the IPv6 version of ICMP. + +8.2. Maximum Packet Lifetime + + Unlike IPv4, IPv6 nodes are not required to enforce maximum packet + lifetime. That is the reason the IPv4 "Time-to-Live" field was + renamed "Hop Limit" in IPv6. In practice, very few, if any, IPv4 + implementations conform to the requirement that they limit packet + lifetime, so this is not a change in practice. Any upper-layer + protocol that relies on the internet layer (whether IPv4 or IPv6) to + limit packet lifetime ought to be upgraded to provide its own + mechanisms for detecting and discarding obsolete packets. + + + + + +Deering & Hinden Standards Track [Page 28] + +RFC 8200 IPv6 Specification July 2017 + + +8.3. Maximum Upper-Layer Payload Size + + When computing the maximum payload size available for upper-layer + data, an upper-layer protocol must take into account the larger size + of the IPv6 header relative to the IPv4 header. For example, in + IPv4, TCP's Maximum Segment Size (MSS) option is computed as the + maximum packet size (a default value or a value learned through Path + MTU Discovery) minus 40 octets (20 octets for the minimum-length IPv4 + header and 20 octets for the minimum-length TCP header). When using + TCP over IPv6, the MSS must be computed as the maximum packet size + minus 60 octets, because the minimum-length IPv6 header (i.e., an + IPv6 header with no extension headers) is 20 octets longer than a + minimum-length IPv4 header. + +8.4. Responding to Packets Carrying Routing Headers + + When an upper-layer protocol sends one or more packets in response to + a received packet that included a Routing header, the response + packet(s) must not include a Routing header that was automatically + derived by "reversing" the received Routing header UNLESS the + integrity and authenticity of the received Source Address and Routing + header have been verified (e.g., via the use of an Authentication + header in the received packet). In other words, only the following + kinds of packets are permitted in response to a received packet + bearing a Routing header: + + o Response packets that do not carry Routing headers. + + o Response packets that carry Routing headers that were NOT + derived by reversing the Routing header of the received packet + (for example, a Routing header supplied by local + configuration). + + o Response packets that carry Routing headers that were derived + by reversing the Routing header of the received packet IF AND + ONLY IF the integrity and authenticity of the Source Address + and Routing header from the received packet have been verified + by the responder. + +9. IANA Considerations + + RFC 2460 is referenced in a number of IANA registries. These + include: + + o Internet Protocol Version 6 (IPv6) Parameters [IANA-6P] + + o Assigned Internet Protocol Numbers [IANA-PN] + + + + +Deering & Hinden Standards Track [Page 29] + +RFC 8200 IPv6 Specification July 2017 + + + o ONC RPC Network Identifiers (netids) [IANA-NI] + + o Network Layer Protocol Identifiers (NLPIDs) of Interest + [IANA-NL] + + o Protocol Registries [IANA-PR] + + The IANA has updated these references to point to this document. + +10. Security Considerations + + IPv6, from the viewpoint of the basic format and transmission of + packets, has security properties that are similar to IPv4. These + security issues include: + + o Eavesdropping, where on-path elements can observe the whole + packet (including both contents and metadata) of each IPv6 + datagram. + o Replay, where the attacker records a sequence of packets off of + the wire and plays them back to the party that originally + received them. + o Packet insertion, where the attacker forges a packet with some + chosen set of properties and injects it into the network. + o Packet deletion, where the attacker removes a packet from the + wire. + o Packet modification, where the attacker removes a packet from + the wire, modifies it, and reinjects it into the network. + o Man-in-the-middle (MITM) attacks, where the attacker subverts + the communication stream in order to pose as the sender to + receiver and the receiver to the sender. + o Denial-of-service (DoS) attacks, where the attacker sends large + amounts of legitimate traffic to a destination to overwhelm it. + + IPv6 packets can be protected from eavesdropping, replay, packet + insertion, packet modification, and MITM attacks by use of the + "Security Architecture for the Internet Protocol" [RFC4301]. In + addition, upper-layer protocols such as Transport Layer Security + (TLS) or Secure Shell (SSH) can be used to protect the application- + layer traffic running on top of IPv6. + + There is not any mechanism to protect against DoS attacks. Defending + against these type of attacks is outside the scope of this + specification. + + IPv6 addresses are significantly larger than IPv4 addresses making it + much harder to scan the address space across the Internet and even on + a single network link (e.g., Local Area Network). See [RFC7707] for + more information. + + + +Deering & Hinden Standards Track [Page 30] + +RFC 8200 IPv6 Specification July 2017 + + + IPv6 addresses of nodes are expected to be more visible on the + Internet as compared with IPv4 since the use of address translation + technology is reduced. This creates some additional privacy issues + such as making it easier to distinguish endpoints. See [RFC7721] for + more information. + + The design of IPv6 extension header architecture, while adding a lot + of flexibility, also creates new security challenges. As noted + below, issues relating to the Fragment extension header have been + resolved, but it's clear that for any new extension header designed + in the future, the security implications need to be examined + thoroughly, and this needs to include how the new extension header + works with existing extension headers. See [RFC7045] for more + information. + + This version of the IPv6 specification resolves a number of security + issues that were found with the previous version [RFC2460] of the + IPv6 specification. These include: + + o Revised the text to handle the case of fragments that are whole + datagrams (i.e., both the Fragment Offset field and the M flag + are zero). If received, they should be processed as a + reassembled packet. Any other fragments that match should be + processed independently. The Fragment creation process was + modified to not create whole datagram fragments (Fragment + Offset field and the M flag are zero). See [RFC6946] and + [RFC8021] for more information. + + o Removed the paragraph in Section 5 that required including a + Fragment header to outgoing packets if an ICMP Packet Too Big + message reporting a Next-Hop MTU is less than 1280. See + [RFC6946] for more information. + + o Changed the text to require that IPv6 nodes must not create + overlapping fragments. Also, when reassembling an IPv6 + datagram, if one or more of its constituent fragments is + determined to be an overlapping fragment, the entire datagram + (and any constituent fragments) must be silently discarded. + Includes clarification that no ICMP error message should be + sent if overlapping fragments are received. See [RFC5722] for + more information. + + o Revised the text to require that all headers through the first + upper-layer header are in the first fragment. See [RFC7112] + for more information. + + + + + + +Deering & Hinden Standards Track [Page 31] + +RFC 8200 IPv6 Specification July 2017 + + + o Incorporated the updates from [RFC5095] and [RFC5871] to remove + the description of the Routing Header type 0 (RH0), that the + allocations guidelines for Routing headers are specified in RFC + 5871, and removed RH0 from the list of required extension + headers. + + Security issues relating to other parts of IPv6 including addressing, + ICMPv6, Path MTU Discovery, etc., are discussed in the appropriate + specifications. + +11. References + +11.1. Normative References + + [RFC791] Postel, J., "Internet Protocol", STD 5, RFC 791, + DOI 10.17487/RFC0791, September 1981, + . + + [RFC2474] Nichols, K., Blake, S., Baker, F., and D. Black, + "Definition of the Differentiated Services Field (DS + Field) in the IPv4 and IPv6 Headers", RFC 2474, + DOI 10.17487/RFC2474, December 1998, + . + + [RFC3168] Ramakrishnan, K., Floyd, S., and D. Black, "The Addition + of Explicit Congestion Notification (ECN) to IP", + RFC 3168, DOI 10.17487/RFC3168, September 2001, + . + + [RFC4291] Hinden, R. and S. Deering, "IP Version 6 Addressing + Architecture", RFC 4291, DOI 10.17487/RFC4291, February + 2006, . + + [RFC4443] Conta, A., Deering, S., and M. Gupta, Ed., "Internet + Control Message Protocol (ICMPv6) for the Internet + Protocol Version 6 (IPv6) Specification", STD 89, + RFC 4443, DOI 10.17487/RFC4443, March 2006, + . + + [RFC6437] Amante, S., Carpenter, B., Jiang, S., and J. Rajahalme, + "IPv6 Flow Label Specification", RFC 6437, + DOI 10.17487/RFC6437, November 2011, + . + + + + + + + + +Deering & Hinden Standards Track [Page 32] + +RFC 8200 IPv6 Specification July 2017 + + +11.2. Informative References + + [Err2541] RFC Errata, Erratum ID 2541, RFC 2460. + + [Err4279] RFC Errata, Erratum ID 4279, RFC 2460. + + [Err4657] RFC Errata, Erratum ID 4657, RFC 2460. + + [Err4662] RFC Errata, Erratum ID 4662, RFC 2460. + + [IANA-6P] IANA, "Internet Protocol Version 6 (IPv6) Parameters", + . + + [IANA-EH] IANA, "IPv6 Extension Header Types", + . + + [IANA-NI] IANA, "ONC RPC Network Identifiers (netids)", + . + + [IANA-NL] IANA, "Network Layer Protocol Identifiers (NLPIDs) of + Interest", . + + [IANA-PN] IANA, "Protocol Numbers", + . + + [IANA-PR] IANA, "Protocol Registries", . + + [IANA-RH] IANA, "Routing Types", . + + [RFC1661] Simpson, W., Ed., "The Point-to-Point Protocol (PPP)", + STD 51, RFC 1661, DOI 10.17487/RFC1661, July 1994, + . + + [RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version 6 + (IPv6) Specification", RFC 2460, DOI 10.17487/RFC2460, + December 1998, . + + [RFC4301] Kent, S. and K. Seo, "Security Architecture for the + Internet Protocol", RFC 4301, DOI 10.17487/RFC4301, + December 2005, . + + [RFC4302] Kent, S., "IP Authentication Header", RFC 4302, + DOI 10.17487/RFC4302, December 2005, + . + + + + + +Deering & Hinden Standards Track [Page 33] + +RFC 8200 IPv6 Specification July 2017 + + + [RFC4303] Kent, S., "IP Encapsulating Security Payload (ESP)", + RFC 4303, DOI 10.17487/RFC4303, December 2005, + . + + [RFC5095] Abley, J., Savola, P., and G. Neville-Neil, "Deprecation + of Type 0 Routing Headers in IPv6", RFC 5095, + DOI 10.17487/RFC5095, December 2007, + . + + [RFC5722] Krishnan, S., "Handling of Overlapping IPv6 Fragments", + RFC 5722, DOI 10.17487/RFC5722, December 2009, + . + + [RFC5871] Arkko, J. and S. Bradner, "IANA Allocation Guidelines for + the IPv6 Routing Header", RFC 5871, DOI 10.17487/RFC5871, + May 2010, . + + [RFC6564] Krishnan, S., Woodyatt, J., Kline, E., Hoagland, J., and + M. Bhatia, "A Uniform Format for IPv6 Extension Headers", + RFC 6564, DOI 10.17487/RFC6564, April 2012, + . + + [RFC6936] Fairhurst, G. and M. Westerlund, "Applicability Statement + for the Use of IPv6 UDP Datagrams with Zero Checksums", + RFC 6936, DOI 10.17487/RFC6936, April 2013, + . + + [RFC6946] Gont, F., "Processing of IPv6 "Atomic" Fragments", + RFC 6946, DOI 10.17487/RFC6946, May 2013, + . + + [RFC7045] Carpenter, B. and S. Jiang, "Transmission and Processing + of IPv6 Extension Headers", RFC 7045, + DOI 10.17487/RFC7045, December 2013, + . + + [RFC7112] Gont, F., Manral, V., and R. Bonica, "Implications of + Oversized IPv6 Header Chains", RFC 7112, + DOI 10.17487/RFC7112, January 2014, + . + + [RFC7707] Gont, F. and T. Chown, "Network Reconnaissance in IPv6 + Networks", RFC 7707, DOI 10.17487/RFC7707, March 2016, + . + + + + + + + +Deering & Hinden Standards Track [Page 34] + +RFC 8200 IPv6 Specification July 2017 + + + [RFC7721] Cooper, A., Gont, F., and D. Thaler, "Security and Privacy + Considerations for IPv6 Address Generation Mechanisms", + RFC 7721, DOI 10.17487/RFC7721, March 2016, + . + + [RFC7739] Gont, F., "Security Implications of Predictable Fragment + Identification Values", RFC 7739, DOI 10.17487/RFC7739, + February 2016, . + + [RFC8021] Gont, F., Liu, W., and T. Anderson, "Generation of IPv6 + Atomic Fragments Considered Harmful", RFC 8021, + DOI 10.17487/RFC8021, January 2017, + . + + [RFC8201] McCann, J., Deering, S., Mogul, J., and R. Hinden, "Path + MTU Discovery for IP version 6", STD 87, RFC 8201, + DOI 10.17487/RFC8201, July 2017, + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 35] + +RFC 8200 IPv6 Specification July 2017 + + +Appendix A. Formatting Guidelines for Options + + This appendix gives some advice on how to lay out the fields when + designing new options to be used in the Hop-by-Hop Options header or + the Destination Options header, as described in Section 4.2. These + guidelines are based on the following assumptions: + + o One desirable feature is that any multi-octet fields within the + Option Data area of an option be aligned on their natural + boundaries, i.e., fields of width n octets should be placed at + an integer multiple of n octets from the start of the + Hop-by-Hop or Destination Options header, for n = 1, 2, 4, or + 8. + + o Another desirable feature is that the Hop-by-Hop or Destination + Options header take up as little space as possible, subject to + the requirement that the header be an integer multiple of 8 + octets long. + + o It may be assumed that, when either of the option-bearing + headers are present, they carry a very small number of options, + usually only one. + + These assumptions suggest the following approach to laying out the + fields of an option: order the fields from smallest to largest, with + no interior padding, then derive the alignment requirement for the + entire option based on the alignment requirement of the largest field + (up to a maximum alignment of 8 octets). This approach is + illustrated in the following examples: + + Example 1 + + If an option X required two data fields, one of length 8 octets and + one of length 4 octets, it would be laid out as follows: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Option Type=X |Opt Data Len=12| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + 8-octet field + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + + + + + +Deering & Hinden Standards Track [Page 36] + +RFC 8200 IPv6 Specification July 2017 + + + Its alignment requirement is 8n+2, to ensure that the 8-octet field + starts at a multiple-of-8 offset from the start of the enclosing + header. A complete Hop-by-Hop or Destination Options header + containing this one option would look as follows: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len=1 | Option Type=X |Opt Data Len=12| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + 8-octet field + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Example 2 + + If an option Y required three data fields, one of length 4 octets, + one of length 2 octets, and one of length 1 octet, it would be laid + out as follows: + + +-+-+-+-+-+-+-+-+ + | Option Type=Y | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Opt Data Len=7 | 1-octet field | 2-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Its alignment requirement is 4n+3, to ensure that the 4-octet field + starts at a multiple-of-4 offset from the start of the enclosing + header. A complete Hop-by-Hop or Destination Options header + containing this one option would look as follows: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len=1 | Pad1 Option=0 | Option Type=Y | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Opt Data Len=7 | 1-octet field | 2-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PadN Option=1 |Opt Data Len=2 | 0 | 0 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + + + + + + +Deering & Hinden Standards Track [Page 37] + +RFC 8200 IPv6 Specification July 2017 + + + Example 3 + + A Hop-by-Hop or Destination Options header containing both options X + and Y from Examples 1 and 2 would have one of the two following + formats, depending on which option appeared first: + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len=3 | Option Type=X |Opt Data Len=12| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + 8-octet field + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PadN Option=1 |Opt Data Len=1 | 0 | Option Type=Y | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Opt Data Len=7 | 1-octet field | 2-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PadN Option=1 |Opt Data Len=2 | 0 | 0 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Header | Hdr Ext Len=3 | Pad1 Option=0 | Option Type=Y | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |Opt Data Len=7 | 1-octet field | 2-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PadN Option=1 |Opt Data Len=4 | 0 | 0 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 0 | 0 | Option Type=X |Opt Data Len=12| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | 4-octet field | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + + 8-octet field + + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + + + + + + + + +Deering & Hinden Standards Track [Page 38] + +RFC 8200 IPv6 Specification July 2017 + + +Appendix B. Changes Since RFC 2460 + + This memo has the following changes from RFC 2460. + + o Removed IP Next Generation from the Abstract. + + o Added text in Section 1 that the data transmission order is the + same as IPv4 as defined in RFC 791. + + o Clarified the text in Section 3 about decrementing the Hop Limit. + + o Clarified that extension headers (except for the Hop-by-Hop + Options header) are not processed, inserted, or deleted by any + node along a packet's delivery path. + + o Changed requirement for the Hop-by-Hop Options header to a "may", + and added a note to indicate what is expected regarding the + Hop-by-Hop Options header. + + o Added a paragraph to Section 4 to clarify how extension headers + are numbered and which are upper-layer headers. + + o Added a reference to the end of Section 4 to the "IPv6 Extension + Header Types" IANA registry. + + o Incorporated the updates from RFCs 5095 and 5871 to remove the + description of RH0, that the allocations guidelines for routing + headers are specified in RFC 5871, and removed RH0 from the list + of required extension headers. + + o Revised Section 4.5 on IPv6 fragmentation based on updates from + RFCs 5722, 6946, 7112, and 8021. This includes: + + - Revised the text to handle the case of fragments that are whole + datagrams (i.e., both the Fragment Offset field and the M flag + are zero). If received, they should be processed as a + reassembled packet. Any other fragments that match should be + processed independently. The revised Fragment creation process + was modified to not create whole datagram fragments (Fragment + Offset field and the M flag are zero). + + - Changed the text to require that IPv6 nodes must not create + overlapping fragments. Also, when reassembling an IPv6 + datagram, if one or more its constituent fragments is + determined to be an overlapping fragment, the entire datagram + (and any constituent fragments) must be silently discarded. + Includes a clarification that no ICMP error message should be + sent if overlapping fragments are received. + + + +Deering & Hinden Standards Track [Page 39] + +RFC 8200 IPv6 Specification July 2017 + + + - Revised the text to require that all headers through the first + Upper-Layer header are in the first fragment. This changed the + text describing how packets are fragmented and reassembled and + added a new error case. + + - Added text to the Fragment header process on handling exact + duplicate fragments. + + - Updated the Fragmentation header text to correct the inclusion + of an Authentication Header (AH) and noted No Next Header case. + + - Changed terminology in the Fragment header section from + "Unfragmentable Headers" to "Per-Fragment headers". + + - Removed the paragraph in Section 5 that required including a + Fragment header to outgoing packets if an ICMP Packet Too Big + message reports a Next-Hop MTU less than 1280. + + - Changed the text to clarify MTU restriction and 8-byte + restrictions, and noted the restriction on headers in the first + fragment. + + o In Section 4.5, added clarification noting that some fields in the + IPv6 header may also vary across the fragments being reassembled, + and that other specifications may provide additional instructions + for how they should be reassembled. See, for example, Section 5.3 + of [RFC3168]. + + o Incorporated the update from RFC 6564 to add a new Section 4.8 + that describes recommendations for defining new extension headers + and options. + + o Added text to Section 5 to define "IPv6 minimum link MTU". + + o Simplified the text in Section 6 about Flow Labels and removed + what was Appendix A ("Semantics and Usage of the Flow Label + Field"); instead, pointed to the current specifications of the + IPv6 Flow Label field in [RFC6437] and the Traffic Class field in + [RFC2474] and [RFC3168]. + + o Incorporated the update made by RFC 6935 ("IPv6 and UDP Checksums + for Tunneled Packets") in Section 8. Added an exception to the + default behavior for the handling of UDP packets with zero + checksums for tunnels. + + o Added instruction to Section 9, "IANA Considerations", to change + references to RFC 2460 to this document. + + + + +Deering & Hinden Standards Track [Page 40] + +RFC 8200 IPv6 Specification July 2017 + + + o Revised and expanded Section 10, "Security Considerations". + + o Added a paragraph to the Acknowledgments section acknowledging the + authors of the updating documents. + + o Updated references to current versions and assigned references to + normative and informative. + + o Made changes to resolve the errata on RFC 2460. These are: + + Erratum ID 2541 [Err2541]: This erratum notes that RFC 2460 + didn't update RFC 2205 when the length of the flow label was + changed from 24 to 20 bits from RFC 1883. This issue was + resolved in RFC 6437 where the flow label is defined. This + specification now references RFC 6437. No change is required. + + Erratum ID 4279 [Err4279]: This erratum noted that the + specification doesn't handle the case of a forwarding node + receiving a packet with a zero Hop Limit. This is fixed in + Section 3 of this specification. + + Erratum ID 4657 [Err4657]: This erratum proposed text that + extension headers must never be inserted by any node other than + the source of the packet. This was resolved in Section 4, + "IPv6 Extension Headers". + + Erratum ID 4662 [Err4662]: This erratum proposed text that + extension headers, with one exception, are not examined, + processed, modified, inserted, or deleted by any node along a + packet's delivery path. This was resolved in Section 4, "IPv6 + Extension Headers". + + Erratum ID 2843: This erratum is marked "Rejected". No change + was made. + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 41] + +RFC 8200 IPv6 Specification July 2017 + + +Acknowledgments + + The authors gratefully acknowledge the many helpful suggestions of + the members of the IPng Working Group, the End-to-End Protocols + research group, and the Internet community at large. + + The authors would also like to acknowledge the authors of the + updating RFCs that were incorporated in this document to move the + IPv6 specification to Internet Standard. They are Joe Abley, Shane + Amante, Jari Arkko, Manav Bhatia, Ronald P. Bonica, Scott Bradner, + Brian Carpenter, P.F. Chimento, Marshall Eubanks, Fernando Gont, + James Hoagland, Sheng Jiang, Erik Kline, Suresh Krishnan, Vishwas + Manral, George Neville-Neil, Jarno Rajahalme, Pekka Savola, Magnus + Westerlund, and James Woodyatt. + +Authors' Addresses + + Stephen E. Deering + Retired + Vancouver, British Columbia + Canada + + + Robert M. Hinden + Check Point Software + 959 Skyway Road + San Carlos, CA 94070 + United States of America + + Email: bob.hinden@gmail.com + + + + + + + + + + + + + + + + + + + + + +Deering & Hinden Standards Track [Page 42] + diff --git a/dataplane/src/packet_processor/ipforward.rs b/dataplane/src/packet_processor/ipforward.rs index 061cd58249..2f7e485942 100644 --- a/dataplane/src/packet_processor/ipforward.rs +++ b/dataplane/src/packet_processor/ipforward.rs @@ -265,15 +265,13 @@ impl IpForwarder { return; } - // If packet requires updating checksums (e.g. because it was natted), do so. - // Otherwise, refresh at least the ipv4 checksum, as we decremented the TTL. + // Refresh requested checksums, or just the IPv4 checksum after decrementing TTL. + // IPv6 has no header checksum. if packet.meta().checksum_refresh() { packet.update_checksums(); } else if let Some(ipv4) = packet.headers_mut().try_ipv4_mut() { ipv4.update_checksum(&()) .unwrap_or_else(|()| unreachable!()); // IPv4 checksum update never fails - } else { - unreachable!() } // build vxlan headers for encapsulation @@ -412,3 +410,39 @@ impl NetworkFunction for IpForwarder { }) } } + +#[cfg(test)] +mod test { + use super::IpForwarder; + use net::eth::mac::{Mac, SourceMac}; + use net::ip::NextHeader; + use net::packet::test_utils::build_test_ipv6_packet_with_transport; + use net::vxlan::Vni; + use routing::testing::RouterTables; + use routing::{ResolvedVxlan, Vtep}; + use std::net::IpAddr; + + /// IPv6 packets need no header checksum refresh before encapsulation. + #[test] + fn an_ipv6_packet_without_a_refresh_encapsulates() { + let forwarder = IpForwarder::new("test", RouterTables::new().fibs()); + let vtep = + Vtep::with_ip_and_mac(IpAddr::from([192, 0, 2, 1]), Mac([0x02, 0, 0, 0, 0, 0x01])); + let vxlan = ResolvedVxlan { + vni: Vni::new_checked(100).unwrap(), + remote: IpAddr::from([192, 0, 2, 2]), + dmac: SourceMac::new(Mac([0x02, 0, 0, 0, 0, 0x02])).unwrap(), + }; + + let mut packet = build_test_ipv6_packet_with_transport(64, Some(NextHeader::UDP)).unwrap(); + assert!(!packet.meta().checksum_refresh()); + + forwarder.vxlan_encap(&mut packet, &vxlan, &vtep); + + assert_eq!(packet.get_done(), None); + assert!( + packet.meta().dst_vpcd.is_some(), + "the packet was not encapsulated" + ); + } +} diff --git a/nat/src/masquerade/icmp_handling.rs b/nat/src/masquerade/icmp_handling.rs index 1f4af97246..50dce569b4 100644 --- a/nat/src/masquerade/icmp_handling.rs +++ b/nat/src/masquerade/icmp_handling.rs @@ -37,6 +37,9 @@ pub(crate) fn handle_icmp_error_masquerading( debug!("(masquerade): Translation of ICMP error inner packet failed: {e}"); return Err(DoneReason::InternalFailure); } + // Recompute the outer ICMP checksum after changing the quoted packet. + // The quote's checksum may be absent or truncated, so its updates may change the outer sum. + packet.meta_mut().set_checksum_refresh(true); // translate the ICMP error packet (outer) let xlate = state.as_translate(); diff --git a/nat/src/masquerade/nf.rs b/nat/src/masquerade/nf.rs index d775a415ba..09c30bf505 100644 --- a/nat/src/masquerade/nf.rs +++ b/nat/src/masquerade/nf.rs @@ -618,9 +618,8 @@ impl Masquerade { if let Err(error) = self.masquerade_packet(packet) { packet.done((&error).into()); debug!("Did not masquerade packet: {error}"); - } else { - packet.meta_mut().set_checksum_refresh(true); } + // The translation helpers request checksum refresh only when needed. } } diff --git a/nat/src/masquerade/packet.rs b/nat/src/masquerade/packet.rs index 9d93183685..2cd6c86a9f 100644 --- a/nat/src/masquerade/packet.rs +++ b/nat/src/masquerade/packet.rs @@ -7,7 +7,7 @@ use crate::NatPort; use crate::common::NatAction; use net::buffer::PacketBufferMut; use net::headers::Net; -use net::headers::{NetError, Transport, TransportError, TryHeadersMut}; +use net::headers::{NetError, Transport, TransportError, TryHeaders, TryHeadersMut}; use net::icmp4::Icmp4Error; use net::icmp6::Icmp6Error; use net::ip::UnicastIpAddr; @@ -22,9 +22,9 @@ pub(crate) enum NatPacketError { UpdateNet(#[from] NetError), #[error("Failed to update transport header")] UpdateTransport(#[from] TransportError), - #[error("Failed to update ICMPv4 header")] + #[error("Failed to update `ICMPv4` header")] UpdateIcmpv4(#[from] Icmp4Error), - #[error("Failed to update ICMPv6 header")] + #[error("Failed to update `ICMPv6` header")] UpdateIcmpv6(#[from] Icmp6Error), #[error("Failed to NAT packet: unsupported traffic")] UnsupportedTraffic, @@ -36,6 +36,9 @@ fn snat( natport: NatPort, ) -> Result<(), NatPacketError> { let mut modified = false; + // Request a full recomputation when a checksum delta cannot be applied, including to an IPv6 + // UDP checksum of zero. + let mut needs_full_recompute = packet.headers().has_zero_ipv6_udp_checksum(); match packet .headers_mut() .pat_mut() @@ -46,40 +49,42 @@ fn snat( { Some((_, ip, tp)) if matches!(tp, Transport::Udp(_) | Transport::Tcp(_)) => { if ip.src_addr() != new_src.inner() { - ip.try_set_source(new_src)?; + ip.try_set_source_updating_checksum(new_src, Some(tp))?; modified = true; } if let NatPort::Port(port) = natport { + // Capture the old value before applying its checksum delta. + let old_port = tp.src_port(); tp.try_set_source(port)?; + if let Some(old_port) = old_port { + if !tp.increment_checksum_for_u16(old_port.get(), port.get()) { + needs_full_recompute = true; + } + } else { + needs_full_recompute = true; + } modified = true; } } Some((_, Net::Ipv6(_), Transport::Icmp4(_)) | (_, Net::Ipv4(_), Transport::Icmp6(_))) => { return Err(NatPacketError::UnsupportedTraffic); } - Some((_, ip, Transport::Icmp4(icmp))) => { + // Keep `Transport` to apply the checksum rules for each ICMP version. + Some((_, ip, tp)) if matches!(tp, Transport::Icmp4(_) | Transport::Icmp6(_)) => { if ip.src_addr() != new_src.inner() { - ip.try_set_source(new_src)?; + // Only ICMPv6 includes the address in its checksum. + ip.try_set_source_updating_checksum(new_src, Some(tp))?; modified = true; } if let NatPort::Identifier(id) = natport - && let Some(current) = icmp.identifier() + && let Some(current) = tp.identifier() && current != id { - icmp.try_set_identifier(id)?; - modified = true; - } - } - Some((_, ip, Transport::Icmp6(icmp))) => { - if ip.src_addr() != new_src.inner() { - ip.try_set_source(new_src)?; - modified = true; - } - if let NatPort::Identifier(id) = natport - && let Some(current) = icmp.identifier() - && current != id - { - icmp.try_set_identifier(id)?; + tp.try_set_identifier(id)?; + if !tp.increment_checksum_for_u16(current, id) { + // ICMPv4 zero results require full recomputation. + needs_full_recompute = true; + } modified = true; } } @@ -87,7 +92,10 @@ fn snat( } if modified { packet.meta_mut().src_natted(true); - packet.meta_mut().set_checksum_refresh(true); + if needs_full_recompute { + // Preserve refresh requests from earlier stages. + packet.meta_mut().set_checksum_refresh(true); + } } Ok(()) } @@ -98,6 +106,7 @@ fn dnat( natport: NatPort, ) -> Result<(), NatPacketError> { let mut modified = false; + let mut needs_full_recompute = packet.headers().has_zero_ipv6_udp_checksum(); match packet .headers_mut() @@ -110,11 +119,19 @@ fn dnat( // ipv4|ipv6 + UDP/TCP Some((_, ip, tp)) if matches!(tp, Transport::Udp(_) | Transport::Tcp(_)) => { if ip.dst_addr() != new_dst_ip { - ip.try_set_destination(new_dst_ip)?; + ip.try_set_destination_updating_checksum(new_dst_ip, Some(tp))?; modified = true; } if let NatPort::Port(port) = natport { + let old_port = tp.dst_port(); tp.try_set_destination(port)?; + if let Some(old_port) = old_port { + if !tp.increment_checksum_for_u16(old_port.get(), port.get()) { + needs_full_recompute = true; + } + } else { + needs_full_recompute = true; + } modified = true; } } @@ -122,32 +139,21 @@ fn dnat( return Err(NatPacketError::UnsupportedTraffic); } - // ipv4 + Icmp4 - Some((_, ip, Transport::Icmp4(icmp))) => { - if ip.dst_addr() != new_dst_ip { - ip.try_set_destination(new_dst_ip)?; - modified = true; - } - if let NatPort::Identifier(id) = natport - && let Some(current) = icmp.identifier() - && current != id - { - icmp.try_set_identifier(id)?; - modified = true; - } - } - - // ipv6 + Icmp6 - Some((_, ip, Transport::Icmp6(icmp))) => { + // Keep `Transport` to apply the checksum rules for each ICMP version. + Some((_, ip, tp)) if matches!(tp, Transport::Icmp4(_) | Transport::Icmp6(_)) => { if ip.dst_addr() != new_dst_ip { - ip.try_set_destination(new_dst_ip)?; + ip.try_set_destination_updating_checksum(new_dst_ip, Some(tp))?; modified = true; } if let NatPort::Identifier(id) = natport - && let Some(current) = icmp.identifier() + && let Some(current) = tp.identifier() && current != id { - icmp.try_set_identifier(id)?; + tp.try_set_identifier(id)?; + if !tp.increment_checksum_for_u16(current, id) { + // ICMPv4 zero results require full recomputation. + needs_full_recompute = true; + } modified = true; } } @@ -156,7 +162,10 @@ fn dnat( if modified { packet.meta_mut().dst_natted(true); - packet.meta_mut().set_checksum_refresh(true); + if needs_full_recompute { + // Preserve refresh requests from earlier stages. + packet.meta_mut().set_checksum_refresh(true); + } } Ok(()) } @@ -188,3 +197,189 @@ pub(super) fn masquerade( NatAction::DstNat => dnat(packet, xlate.use_ip.inner(), xlate.nat_port), } } + +#[cfg(test)] +#[allow(clippy::unwrap_used, clippy::expect_used)] +mod checksum_test { + use super::*; + use net::buffer::TestBuffer; + use net::checksum::Checksum; + use net::headers::{TryHeaders, TryTransport}; + use net::ip::NextHeader; + use net::packet::test_utils::build_test_tcp_ipv4_packet; + use std::num::NonZero; + + /// Check address and identifier updates for both ICMP versions. + /// Only `ICMPv6` includes addresses in its checksum. + fn icmp_translation_agrees_with_a_recompute(next_header: NextHeader, ipv6: bool, source: bool) { + use net::packet::test_utils::{ + build_test_ipv4_packet_with_transport, build_test_ipv6_packet_with_transport, + }; + + let mut packet: Packet = if ipv6 { + build_test_ipv6_packet_with_transport(64, Some(next_header)).unwrap() + } else { + build_test_ipv4_packet_with_transport(64, Some(next_header)).unwrap() + }; + packet.update_checksums(); + + let new_src = if ipv6 { + UnicastIpAddr::try_from(IpAddr::from([ + 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + ])) + .unwrap() + } else { + UnicastIpAddr::try_from(IpAddr::from([9, 9, 9, 9])).unwrap() + }; + if source { + snat(&mut packet, new_src, NatPort::Identifier(4321)).expect("snat"); + } else { + dnat(&mut packet, new_src.inner(), NatPort::Identifier(4321)).expect("dnat"); + } + + let before = icmp_checksum(&packet); + if packet.meta().checksum_refresh() { + // ICMPv4 zero results may require full recomputation. + return; + } + packet.update_checksums(); + assert_eq!( + before, + icmp_checksum(&packet), + "after an ICMP translation the checksum disagrees with a full recompute, and \ + checksum_refresh was not set either -- so nothing would ever fix it" + ); + } + + fn icmp_checksum(packet: &Packet) -> Option { + packet.headers().try_transport().and_then(|tp| match tp { + Transport::Icmp4(icmp) => icmp.checksum().map(u16::from), + Transport::Icmp6(icmp) => icmp.checksum().map(u16::from), + _ => None, + }) + } + + #[test] + fn icmp4_snat_agrees_with_a_recompute() { + icmp_translation_agrees_with_a_recompute(NextHeader::ICMP, false, true); + } + + #[test] + fn icmp6_snat_agrees_with_a_recompute() { + icmp_translation_agrees_with_a_recompute(NextHeader::ICMP6, true, true); + } + + #[test] + fn icmp4_dnat_agrees_with_a_recompute() { + icmp_translation_agrees_with_a_recompute(NextHeader::ICMP, false, false); + } + + #[test] + fn icmp6_dnat_agrees_with_a_recompute() { + icmp_translation_agrees_with_a_recompute(NextHeader::ICMP6, true, false); + } + + /// An IPv6 UDP checksum of zero cannot take a delta, so translation must request a recompute. + #[test] + fn a_zero_ipv6_udp_checksum_asks_for_a_recompute() { + use net::headers::TryTransportMut; + use net::packet::test_utils::build_test_ipv6_packet_with_transport; + + for source in [true, false] { + let mut packet: Packet = + build_test_ipv6_packet_with_transport(64, Some(NextHeader::UDP)).unwrap(); + match packet.headers_mut().try_transport_mut() { + Some(Transport::Udp(udp)) => { + udp.set_checksum(net::udp::UdpChecksum::new(0)).unwrap(); + } + _ => unreachable!(), + } + let new = UnicastIpAddr::try_from(IpAddr::from([ + 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + ])) + .unwrap(); + let port = NatPort::Port(NonZero::new(4321).unwrap()); + if source { + snat(&mut packet, new, port).expect("snat"); + } else { + dnat(&mut packet, new.inner(), port).expect("dnat"); + } + assert!( + packet.meta().checksum_refresh(), + "a zero IPv6 UDP checksum was translated without requesting a recompute" + ); + } + } + + /// Check that destination NAT updates the checksum without requesting recomputation. + #[test] + fn dnat_leaves_a_checksum_a_full_recompute_would_agree_with() { + let mut packet: Packet = + build_test_tcp_ipv4_packet("1.2.3.4", "5.6.7.8", 1234, 80); + packet.update_checksums(); + + let new_dst = IpAddr::from([9, 9, 9, 9]); + dnat( + &mut packet, + new_dst, + NatPort::Port(NonZero::new(4321).unwrap()), + ) + .expect("dnat"); + + assert!( + !packet.meta().checksum_refresh(), + "dnat asked for a full payload recompute; the incremental path was not taken" + ); + + let checksum_of = |p: &Packet| { + p.headers().try_transport().map(|tp| match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + _ => None, + }) + }; + let incremental = checksum_of(&packet).expect("a transport header"); + packet.update_checksums(); + assert_eq!( + incremental, + checksum_of(&packet).expect("a transport header"), + "the incremental checksum after dnat disagrees with a full recompute" + ); + } + + /// Check that source NAT updates the checksum without requesting recomputation. + #[test] + fn snat_leaves_a_checksum_a_full_recompute_would_agree_with() { + let mut packet: Packet = + build_test_tcp_ipv4_packet("1.2.3.4", "5.6.7.8", 1234, 80); + // Incremental updates require a valid starting checksum. + packet.update_checksums(); + + let new_src = UnicastIpAddr::try_from(IpAddr::from([9, 9, 9, 9])).unwrap(); + let new_port = NatPort::Port(NonZero::new(4321).unwrap()); + snat(&mut packet, new_src, new_port).expect("snat"); + + assert!( + !packet.meta().checksum_refresh(), + "snat asked for a full payload recompute; the incremental path was not taken" + ); + + let checksum_of = |p: &Packet| { + p.headers().try_transport().map(|tp| match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + _ => None, + }) + }; + let incremental = checksum_of(&packet).expect("a transport header"); + + // Compare with a full recomputation of the translated packet. + packet.update_checksums(); + let expected = checksum_of(&packet).expect("a transport header"); + + assert_eq!( + incremental, expected, + "the incremental checksum after snat disagrees with a full recompute" + ); + } +} diff --git a/nat/src/masquerade/test.rs b/nat/src/masquerade/test.rs index af4d241c84..c9e41c8496 100644 --- a/nat/src/masquerade/test.rs +++ b/nat/src/masquerade/test.rs @@ -35,7 +35,7 @@ use net::packet::test_utils::build_test_ipv6_packet_with_transport; use net::packet::test_utils::build_test_tcp_ipv4_packet; use net::packet::test_utils::{ Icmp6ErrorAddrs, IcmpEchoDirection, IcmpErrorAddrs, - build_test_icmp4_destination_unreachable_packet, + assert_checksum_current_or_refresh_requested, build_test_icmp4_destination_unreachable_packet, build_test_icmp4_destination_unreachable_packet_with_code, build_test_icmp4_echo, build_test_icmp6_echo, build_test_icmp6_error_packet, build_test_udp_ipv4_frame, build_test_udp_ipv4_packet, @@ -534,6 +534,40 @@ fn translation(packet: &Packet) -> (Ipv4Addr, u16) { ) } +/// Check that forwarded TCP/UDP translations have valid checksums without a refresh request. +fn assert_masquerade_checksum_is_incremental(packet: &mut Packet) { + use net::checksum::Checksum; + use net::headers::{Transport, TryHeaders, TryTransport}; + + if packet.get_done().is_some() { + return; // dropped or filtered; nothing was translated + } + if !packet.meta().is_src_natted() && !packet.meta().is_dst_natted() { + return; // no translation happened, so there is no delta to check + } + let checksum_of = |p: &Packet| -> Option { + TryHeaders::headers(p) + .try_transport() + .and_then(|tp| match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + _ => None, + }) + }; + assert!( + !packet.meta().checksum_refresh(), + "a masqueraded packet asked for a full payload recompute; the incremental path was \ + overridden somewhere above `snat`/`dnat`" + ); + let incremental = checksum_of(packet); + packet.update_checksums(); + assert_eq!( + incremental, + checksum_of(packet), + "a masqueraded packet's incremental checksum disagrees with a full recompute" + ); +} + fn check_packet( nat: &mut Masquerade, src_vni: Vni, @@ -557,8 +591,11 @@ fn check_packet( packet.meta_mut().dst_vpcd = Some(VpcDiscriminant::VNI(dst_vni)); flow_lookup(nat.sessions(), &mut packet); + // Incremental updates require a valid starting checksum. + packet.update_checksums(); - let packets_out: Vec<_> = nat.process(vec![packet].into_iter()).collect(); + let mut packets_out: Vec<_> = nat.process(vec![packet].into_iter()).collect(); + assert_masquerade_checksum_is_incremental(&mut packets_out[0]); let hdr_out = packets_out[0].try_ipv4().unwrap(); let udp_out = packets_out[0].try_udp().unwrap(); let done_reason = packets_out[0].get_done(); @@ -952,8 +989,14 @@ fn check_packet_icmp_error( packet.meta_mut().src_vpcd = Some(VpcDiscriminant::VNI(src_vni)); packet.meta_mut().dst_vpcd = Some(VpcDiscriminant::VNI(dst_vni)); packet.meta_mut().dst_vpcd.take(); // remove to force processing by masquerade + // Initialize the outer checksum before translating the quoted packet. + packet.update_checksums(); let packets_out: Vec<_> = pipeline.process(std::iter::once(packet)).collect(); + if packets_out[0].get_done().is_none() { + // The outer ICMP checksum covers the quote the error handler rewrote. + assert_checksum_current_or_refresh_requested(&packets_out[0]); + } let hdr_out = packets_out[0].try_ipv4().unwrap(); let inner_ip_out = packets_out[0].try_inner_ipv4().unwrap(); diff --git a/nat/src/portfw/icmp_handling.rs b/nat/src/portfw/icmp_handling.rs index 75fa03531b..55e9fb40a7 100644 --- a/nat/src/portfw/icmp_handling.rs +++ b/nat/src/portfw/icmp_handling.rs @@ -82,6 +82,9 @@ pub(crate) fn handle_icmp_error_port_forwarding( debug!("(port-forwarding): Translation of ICMP error inner packet failed: {e}"); return Err(DoneReason::InternalFailure); } + // Recompute the outer ICMP checksum after changing the quoted packet. + // The quote's checksum may be absent or truncated, so its updates may change the outer sum. + packet.meta_mut().set_checksum_refresh(true); // NAT the ICMP packet according to the port-fw state of the reverse flow of the offending packet if let Err(e) = nat_packet(packet, state) { diff --git a/nat/src/portfw/packet.rs b/nat/src/portfw/packet.rs index e0cac99431..f1c2a9cf48 100644 --- a/nat/src/portfw/packet.rs +++ b/nat/src/portfw/packet.rs @@ -7,7 +7,9 @@ use net::buffer::PacketBufferMut; use net::headers::Net; -use net::headers::{NetError, Transport, TransportError, TryHeadersMut, UpperLayerProto}; +use net::headers::{ + NetError, Transport, TransportError, TryHeaders, TryHeadersMut, UpperLayerProto, +}; use net::ip::{NextHeader, UnicastIpAddr}; use net::packet::Packet; use std::net::IpAddr; @@ -57,6 +59,9 @@ fn snat_packet( let mut modified = false; let proto = packet.upper_layer_proto(); + // Request a full recomputation when a checksum delta cannot be applied, including to an IPv6 + // UDP checksum of zero. + let mut needs_full_recompute = packet.headers().has_zero_ipv6_udp_checksum(); match packet .headers_mut() .pat_mut() @@ -68,20 +73,25 @@ fn snat_packet( // traffic can be port forwarded: it's Ip + UDP/TCP Some((_, ip, tp)) if is_port_forwardable(proto) => { if ip.src_addr() != new_src_ip.inner() { - ip.try_set_source(new_src_ip)?; + ip.try_set_source_updating_checksum(new_src_ip, Some(tp))?; modified = true; } if let Some(p) = tp.src_port() && p != new_src_port { tp.try_set_source(new_src_port)?; + if !tp.increment_checksum_for_u16(p.get(), new_src_port.get()) { + needs_full_recompute = true; + } modified = true; } } - // needed for ICMP error handling - Some((_, ip, Transport::Icmp4(_) | Transport::Icmp6(_))) if is_icmp(proto, ip) => { + // Translate ICMP error addresses using each version's pseudo-header rules. + Some((_, ip, tp)) + if matches!(tp, Transport::Icmp4(_) | Transport::Icmp6(_)) && is_icmp(proto, ip) => + { if ip.src_addr() != new_src_ip.inner() { - ip.try_set_source(new_src_ip)?; + ip.try_set_source_updating_checksum(new_src_ip, Some(tp))?; modified = true; } } @@ -90,7 +100,10 @@ fn snat_packet( } } if modified { - packet.meta_mut().set_checksum_refresh(true); + if needs_full_recompute { + // Preserve refresh requests from earlier stages. + packet.meta_mut().set_checksum_refresh(true); + } packet.meta_mut().src_natted(true); } Ok(modified) @@ -110,6 +123,7 @@ fn dnat_packet( let mut modified = false; let proto = packet.upper_layer_proto(); + let mut needs_full_recompute = packet.headers().has_zero_ipv6_udp_checksum(); match packet .headers_mut() .pat_mut() @@ -120,20 +134,25 @@ fn dnat_packet( { Some((_, ip, tp)) if is_port_forwardable(proto) => { if ip.dst_addr() != new_dst_ip { - ip.try_set_destination(new_dst_ip)?; + ip.try_set_destination_updating_checksum(new_dst_ip, Some(tp))?; modified = true; } if let Some(p) = tp.dst_port() && p != new_dst_port { tp.try_set_destination(new_dst_port)?; + if !tp.increment_checksum_for_u16(p.get(), new_dst_port.get()) { + needs_full_recompute = true; + } modified = true; } } - // needed for ICMP error handling - Some((_, ip, Transport::Icmp4(_) | Transport::Icmp6(_))) if is_icmp(proto, ip) => { + // Translate the outer address of an ICMP error. + Some((_, ip, tp)) + if matches!(tp, Transport::Icmp4(_) | Transport::Icmp6(_)) && is_icmp(proto, ip) => + { if ip.dst_addr() != new_dst_ip { - ip.try_set_destination(new_dst_ip)?; + ip.try_set_destination_updating_checksum(new_dst_ip, Some(tp))?; modified = true; } } @@ -142,7 +161,10 @@ fn dnat_packet( } } if modified { - packet.meta_mut().set_checksum_refresh(true); + if needs_full_recompute { + // Preserve refresh requests from earlier stages. + packet.meta_mut().set_checksum_refresh(true); + } packet.meta_mut().dst_natted(true); } Ok(modified) @@ -158,3 +180,149 @@ pub(crate) fn nat_packet( NatAction::SrcNat => snat_packet(packet, state.use_ip(), state.use_port()), } } + +#[cfg(test)] +#[allow(clippy::unwrap_used, clippy::expect_used)] +mod checksum_test { + use super::*; + use net::buffer::TestBuffer; + use net::checksum::Checksum; + use net::headers::{TryHeaders, TryTransport}; + use net::ip::NextHeader; + use net::packet::test_utils::{ + build_test_ipv4_packet_with_transport, build_test_ipv6_packet_with_transport, + build_test_tcp_ipv4_packet, + }; + + fn transport_checksum(packet: &Packet) -> Option { + packet.headers().try_transport().and_then(|tp| match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + Transport::Icmp4(icmp) => icmp.checksum().map(u16::from), + Transport::Icmp6(icmp) => icmp.checksum().map(u16::from), + }) + } + + /// Check checksum correctness and that no full recomputation was requested. + fn agrees_with_recompute(mut packet: Packet, source: bool) { + packet.update_checksums(); + + let new_ip = UnicastIpAddr::try_from(IpAddr::from([9, 9, 9, 9])).unwrap(); + let new_port = NonZero::new(4321).unwrap(); + let modified = if source { + snat_packet(&mut packet, new_ip, new_port).expect("snat_packet") + } else { + dnat_packet(&mut packet, new_ip.inner(), new_port).expect("dnat_packet") + }; + assert!(modified, "the test packet was not actually translated"); + assert!( + !packet.meta().checksum_refresh(), + "port forwarding asked for a full payload recompute; the incremental path was skipped" + ); + + let incremental = transport_checksum(&packet); + packet.update_checksums(); + assert_eq!( + incremental, + transport_checksum(&packet), + "the incremental checksum disagrees with a full recompute" + ); + } + + #[test] + fn tcp_snat_agrees_with_a_recompute() { + agrees_with_recompute( + build_test_tcp_ipv4_packet("1.2.3.4", "5.6.7.8", 1234, 80), + true, + ); + } + + #[test] + fn tcp_dnat_agrees_with_a_recompute() { + agrees_with_recompute( + build_test_tcp_ipv4_packet("1.2.3.4", "5.6.7.8", 1234, 80), + false, + ); + } + + /// An IPv6 UDP checksum of zero cannot take a delta, so translation must request a recompute. + #[test] + fn a_zero_ipv6_udp_checksum_asks_for_a_recompute() { + use net::headers::TryTransportMut; + + for source in [true, false] { + let mut packet: Packet = + build_test_ipv6_packet_with_transport(64, Some(NextHeader::UDP)).unwrap(); + match packet.headers_mut().try_transport_mut() { + Some(Transport::Udp(udp)) => { + udp.set_checksum(net::udp::UdpChecksum::new(0)).unwrap(); + } + _ => unreachable!(), + } + let new_ip = UnicastIpAddr::try_from(IpAddr::from([ + 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + ])) + .unwrap(); + let new_port = NonZero::new(4321).unwrap(); + let modified = if source { + snat_packet(&mut packet, new_ip, new_port).expect("snat_packet") + } else { + dnat_packet(&mut packet, new_ip.inner(), new_port).expect("dnat_packet") + }; + assert!(modified, "the test packet was not actually translated"); + assert!( + packet.meta().checksum_refresh(), + "a zero IPv6 UDP checksum was translated without requesting a recompute" + ); + } + } + + /// `ICMPv4` has no pseudo-header, so address translation must preserve its checksum. + #[test] + fn icmp4_address_translation_does_not_move_the_checksum() { + let mut packet: Packet = + build_test_ipv4_packet_with_transport(64, Some(NextHeader::ICMP)).unwrap(); + packet.update_checksums(); + let before = transport_checksum(&packet); + + let new_ip = UnicastIpAddr::try_from(IpAddr::from([9, 9, 9, 9])).unwrap(); + snat_packet(&mut packet, new_ip, NonZero::new(4321).unwrap()).expect("snat_packet"); + + assert_eq!( + transport_checksum(&packet), + before, + "an ICMPv4 checksum moved when only the IP address changed; ICMPv4 has no \ + pseudo-header, so the address is not covered" + ); + packet.update_checksums(); + assert_eq!(transport_checksum(&packet), before); + } + + /// `ICMPv6` includes addresses in its pseudo-header, so translation must update its checksum. + #[test] + fn icmp6_address_translation_does_move_the_checksum() { + let mut packet: Packet = + build_test_ipv6_packet_with_transport(64, Some(NextHeader::ICMP6)).unwrap(); + packet.update_checksums(); + let before = transport_checksum(&packet); + + let new_ip = UnicastIpAddr::try_from(IpAddr::from([ + 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + ])) + .unwrap(); + snat_packet(&mut packet, new_ip, NonZero::new(4321).unwrap()).expect("snat_packet"); + + let incremental = transport_checksum(&packet); + assert_ne!( + incremental, before, + "an ICMPv6 checksum did not move when the address changed; ICMPv6 has a \ + pseudo-header, so the address is covered" + ); + packet.update_checksums(); + assert_eq!( + incremental, + transport_checksum(&packet), + "the incremental ICMPv6 checksum disagrees with a full recompute" + ); + } +} diff --git a/nat/src/portfw/test.rs b/nat/src/portfw/test.rs index ef2335da44..9c1cdeb19b 100644 --- a/nat/src/portfw/test.rs +++ b/nat/src/portfw/test.rs @@ -398,6 +398,64 @@ mod nf_test { establish_tcp_connection(&mut pipeline); } + /// The outer checksum of a translated ICMP error covers the quote the handler rewrote. + #[cfg_attr(not(emulated), traced_test)] + #[tokio::test] + async fn an_icmp_error_about_a_forwarded_tcp_flow_keeps_a_valid_outer_checksum() { + use net::headers::TryInnerIpv4; + use net::packet::test_utils::{ + assert_checksum_current_or_refresh_requested, + build_test_icmp4_destination_unreachable_packet, + }; + use std::net::Ipv4Addr; + + // As `setup_pipeline`, with the ICMP error handler in front, where it runs in production. + let mut writer = PortFwTableWriter::new(); + let flow_table = Arc::new(FlowTable::default()); + let mut pipeline: DynPipeline = DynPipeline::new() + .add_stage(crate::IcmpErrorHandler::new(flow_table.clone())) + .add_stage(FlowLookup::new("flow-lookup", flow_table.clone())) + .add_stage(TestFlowFilter) + .add_stage(PortForwarder::new( + "port-forwarder", + writer.reader(), + flow_table.clone(), + )); + writer + .update_table(&build_test_port_forwarding_ruleset()) + .unwrap(); + let mut syn = tcp_packet_to_port_forward(); + syn.try_tcp_mut().unwrap().set_syn(true); + assert!(!process_packet(&mut pipeline, syn).is_done()); + + // The backend reports the forwarded segment unreachable, quoting it as translated. + let backend = Ipv4Addr::new(192, 168, 1, 1); + let client = Ipv4Addr::new(10, 0, 0, 2); + let mut error = build_test_icmp4_destination_unreachable_packet( + backend, + client, + client, + backend, + NextHeader::TCP, + 7777, + 22, + ) + .unwrap(); + error.meta_mut().set_overlay(true); + error.meta_mut().src_vpcd = Some(vpcd2()); + error.meta_mut().set_port_forwarding(true); + error.update_checksums(); + + let output = process_packet(&mut pipeline, error); + assert_eq!(output.get_done(), None); + assert_eq!( + output.try_inner_ipv4().unwrap().destination(), + Ipv4Addr::new(70, 71, 72, 73), + "the quote was not translated back; the test is not exercising the handler" + ); + assert_checksum_current_or_refresh_requested(&output); + } + #[cfg_attr(not(emulated), traced_test)] #[tokio::test] async fn test_nf_port_forwarding_tcp_close_server() { diff --git a/nat/src/static_nat/fuzz.rs b/nat/src/static_nat/fuzz.rs index 632f37c314..0e04aeb8c7 100644 --- a/nat/src/static_nat/fuzz.rs +++ b/nat/src/static_nat/fuzz.rs @@ -427,6 +427,21 @@ fn a_packet_that_cannot_be_looked_up_says_so() { drive_attribution!(Scenario::addresses(true)); } +fn transport_checksum( + packet: &net::packet::Packet, +) -> Option { + use net::checksum::Checksum; + use net::headers::{Transport, TryHeaders, TryTransport}; + TryHeaders::headers(packet) + .try_transport() + .and_then(|tp| match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + Transport::Icmp4(icmp) => icmp.checksum().map(u16::from), + Transport::Icmp6(icmp) => icmp.checksum().map(u16::from), + }) +} + macro_rules! drive_marking { ($scenario:expr) => {{ let tally = Tally::default(); @@ -443,8 +458,11 @@ macro_rules! drive_marking { for spec in &probes { let mut probe = (*spec).resolve(&fabric); let before = (probe.source, probe.sport); - let out = run(&mut nf, vec![probe.take()]); - let packet = &out[0]; + let mut input = probe.take(); + // Initialize the probe's checksum before applying incremental updates. + input.update_checksums(); + let mut out = run(&mut nf, vec![input]); + let packet = &mut out[0]; if five_tuple_source(packet) == before { continue; @@ -456,11 +474,17 @@ macro_rules! drive_marking { "{source}:{sport} was translated without the source-natted mark, so a later \ stage would translate it again" ); - assert!( - packet.meta().checksum_refresh(), - "{source}:{sport} was translated without asking for a checksum refresh, so the \ - packet goes out with a checksum for headers it no longer carries" - ); + // A packet without a refresh request must already have a valid transport checksum. + if !packet.meta().checksum_refresh() { + let incremental = transport_checksum(packet); + packet.update_checksums(); + assert_eq!( + incremental, + transport_checksum(packet), + "{source}:{sport} was translated, did not ask for a checksum refresh, and \ + its checksum does not match a recompute -- so it goes out wrong" + ); + } tally.reached.fetch_add(1, Ordering::Relaxed); } }, diff --git a/nat/src/static_nat/nf.rs b/nat/src/static_nat/nf.rs index 62f690791a..888f206809 100644 --- a/nat/src/static_nat/nf.rs +++ b/nat/src/static_nat/nf.rs @@ -10,7 +10,10 @@ use crate::icmp_handler::icmp_error_msg::{ }; pub use crate::static_nat::natrw::{NatTablesReader, NatTablesWriter}; // re-export use net::buffer::PacketBufferMut; -use net::headers::{Net, NetError, TryEmbeddedTransport, TryInnerIp, TryIpMut, TryTcpUdpMut}; +use net::headers::{ + Net, NetError, Transport, TryEmbeddedTransport, TryHeaders, TryHeadersMut, TryInnerIp, + TryTcpUdpMut, TryTransportMut, +}; use net::ip::UnicastIpAddr; use net::packet::{DoneReason, Packet, VpcDiscriminant}; use net::tcp_udp::TcpUdpMut; @@ -75,18 +78,24 @@ impl StaticNat { fn translate_src( &self, net: &mut Net, + transport: Option<&mut Transport>, target_src: UnicastIpAddr, ) -> Result<(), StaticNatError> { let nfi = self.name(); debug!("{nfi}: Changing IP src: {} -> {target_src}", net.src_addr()); - net.try_set_source(target_src) + net.try_set_source_updating_checksum(target_src, transport) .map_err(StaticNatError::FailedToSetSourceIp) } - fn translate_dst(&self, net: &mut Net, target_dst: IpAddr) -> Result<(), StaticNatError> { + fn translate_dst( + &self, + net: &mut Net, + transport: Option<&mut Transport>, + target_dst: IpAddr, + ) -> Result<(), StaticNatError> { let nfi = self.name(); debug!("{nfi}: Changing IP dst: {} -> {target_dst}", net.dst_addr()); - net.try_set_destination(target_dst) + net.try_set_destination_updating_checksum(target_dst, transport) .map_err(StaticNatError::FailedToSetDestIp) } @@ -108,6 +117,21 @@ impl StaticNat { transport.set_dst_port(new_port); } + /// Apply a port checksum delta, requesting full recomputation if needed. + /// + /// `TcpUdpMut` cannot access the checksum, so callers capture old values before translation. + fn fold_port_checksum_delta( + packet: &mut Packet, + (old, new): (NonZero, NonZero), + ) { + let Some(transport) = packet.headers_mut().try_transport_mut() else { + return; + }; + if !transport.increment_checksum_for_u16(old.get(), new.get()) { + packet.meta_mut().set_checksum_refresh(true); + } + } + fn translate_icmp_inner_packet_src_if_any( table: &PerVniTable, packet: &mut Packet, @@ -176,23 +200,38 @@ impl StaticNat { if let Some((new_src_addr, new_src_port_opt)) = table.find_src_mapping(&src_addr, src_port_opt, dst_vni) { - let net = packet.try_ip_mut().ok_or(StaticNatError::NoIpHeader)?; if new_src_addr.inner() != src_addr { - self.translate_src(net, new_src_addr)?; + let (net, transport) = packet + .headers_mut() + .net_and_transport_mut() + .ok_or(StaticNatError::NoIpHeader)?; + self.translate_src(net, transport, new_src_addr)?; modified = true; } + // Save the port delta until the `TcpUdpMut` borrow ends. + let mut port_change = None; if let (Some(mut transport), Some(new_src_port)) = (packet.try_tcp_udp_mut(), new_src_port_opt) && new_src_port.get() != transport.src_port().get() { + port_change = Some((transport.src_port(), new_src_port)); self.translate_src_port(&mut transport, new_src_port); modified = true; } + if let Some(port_change) = port_change { + Self::fold_port_checksum_delta(packet, port_change); + } + if modified && packet.headers().has_zero_ipv6_udp_checksum() { + packet.meta_mut().set_checksum_refresh(true); + } } - // ICMP Error messages + // The outer ICMP checksum must include changes to the quoted packet. if icmp_err { modified |= Self::translate_icmp_inner_packet_dst_if_any(table, packet, dst_vni)?; + if modified { + packet.meta_mut().set_checksum_refresh(true); + } } if modified { @@ -221,23 +260,37 @@ impl StaticNat { if let Some((new_dst_addr, new_dst_port_opt)) = table.find_dst_mapping(&dst_addr, dst_port_opt) { - let net = packet.try_ip_mut().ok_or(StaticNatError::NoIpHeader)?; if new_dst_addr != dst_addr { - self.translate_dst(net, new_dst_addr)?; + let (net, transport) = packet + .headers_mut() + .net_and_transport_mut() + .ok_or(StaticNatError::NoIpHeader)?; + self.translate_dst(net, transport, new_dst_addr)?; modified = true; } + let mut port_change = None; if let (Some(mut transport), Some(new_dst_port)) = (packet.try_tcp_udp_mut(), new_dst_port_opt) && new_dst_port.get() != transport.dst_port().get() { + port_change = Some((transport.dst_port(), new_dst_port)); self.translate_dst_port(&mut transport, new_dst_port); modified = true; } + if let Some(port_change) = port_change { + Self::fold_port_checksum_delta(packet, port_change); + } + if modified && packet.headers().has_zero_ipv6_udp_checksum() { + packet.meta_mut().set_checksum_refresh(true); + } } - // ICMP Error messages + // The outer ICMP checksum must include changes to the quoted packet. if icmp_err { modified |= Self::translate_icmp_inner_packet_src_if_any(table, packet)?; + if modified { + packet.meta_mut().set_checksum_refresh(true); + } } if modified { @@ -322,7 +375,7 @@ impl StaticNat { } Ok(modified) => { if modified { - packet.meta_mut().set_checksum_refresh(true); + // Translation helpers request checksum refresh when needed. debug!("{nfi}: Packet was NAT'ed"); } else { debug!("{nfi}: No NAT translation needed"); diff --git a/nat/src/static_nat/test.rs b/nat/src/static_nat/test.rs index c2d7100149..691e8e2e71 100644 --- a/nat/src/static_nat/test.rs +++ b/nat/src/static_nat/test.rs @@ -1256,3 +1256,138 @@ fn test_config_with_port_ranges_with_default() { assert_eq!(output_dst_port, orig_src_port); assert_eq!(done_reason, None); } + +/// Check address and port checksum updates through `StaticNat::process`. +#[test] +#[cfg_attr(not(emulated), traced_test)] +fn static_nat_leaves_a_checksum_a_full_recompute_would_agree_with() { + use net::checksum::Checksum; + use net::headers::{Transport, TryHeaders, TryTransport}; + + let expose1 = VpcExpose::empty() + .make_static_nat() + .unwrap() + .ip(PrefixWithOptionalPorts::new( + "1.1.0.0/16".into(), + Some(PortRange::new(4001, 5000).unwrap()), + )) + .as_range(PrefixWithOptionalPorts::new( + "10.1.0.0/16".into(), + Some(PortRange::new(8001, 9000).unwrap()), + )) + .unwrap(); + let expose2 = VpcExpose::empty().ip(PrefixWithOptionalPorts::new( + "10.2.0.0/16".into(), + Some(PortRange::new(1, 5).unwrap()), + )); + + let gw_config = build_gwconfig_from_exposes(vec![expose1], vec![expose2]); + let nat_tables = build_nat_configuration(gw_config.external().overlay().vpc_table()).unwrap(); + let (mut nat, mut tablesw) = StaticNat::new("static-nat"); + tablesw.update_nat_tables(nat_tables); + + let src_vni = Vni::new_checked(100).unwrap(); + let dst_vni = Vni::new_checked(200).unwrap(); + + let mut packet = build_test_ipv4_packet_with_transport(u8::MAX, Some(NextHeader::TCP)).unwrap(); + packet.meta_mut().set_overlay(true); + packet.meta_mut().set_static_nat_src(true); + packet.meta_mut().src_vpcd = Some(VpcDiscriminant::VNI(src_vni)); + packet.meta_mut().dst_vpcd = Some(VpcDiscriminant::VNI(dst_vni)); + set_addresses_v4( + &mut packet, + Ipv4Addr::new(1, 1, 0, 1), + Ipv4Addr::new(10, 2, 0, 1), + ); + set_ports(&mut packet, 4001, 1); + // Incremental updates require a valid starting checksum. + packet.update_checksums(); + + let packets_out: Vec<_> = nat.process(vec![packet].into_iter()).collect(); + let mut pkt_out = packets_out.into_iter().next().expect("one packet out"); + + // Require a translation so the checksum check is meaningful. + assert_ne!( + get_src_ip_v4(&pkt_out), + Ipv4Addr::new(1, 1, 0, 1), + "the packet was not translated; the test is not exercising anything" + ); + + let checksum_of = |p: &_| -> Option { + TryHeaders::headers(p) + .try_transport() + .and_then(|tp| match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + _ => None, + }) + }; + + assert!( + !pkt_out.meta().checksum_refresh(), + "static NAT asked for a full payload recompute; the incremental path was not taken" + ); + let incremental = checksum_of(&pkt_out); + pkt_out.update_checksums(); + assert_eq!( + incremental, + checksum_of(&pkt_out), + "the incremental checksum after static NAT disagrees with a full recompute" + ); +} + +/// An IPv6 UDP checksum of zero cannot take a delta, so static NAT must request a recompute. +#[test] +fn a_zero_ipv6_udp_checksum_asks_for_a_recompute() { + use net::checksum::Checksum; + use net::headers::{Transport, TryIpMut, TryTransportMut}; + use net::ip::UnicastIpAddr; + use net::packet::test_utils::build_test_ipv6_packet_with_transport; + use net::udp::UdpChecksum; + use std::net::{IpAddr, Ipv6Addr}; + + let expose1 = VpcExpose::empty() + .make_static_nat() + .unwrap() + .ip(PrefixWithOptionalPorts::new("2001:db8:1::/64".into(), None)) + .as_range(PrefixWithOptionalPorts::new("2001:db8:a::/64".into(), None)) + .unwrap(); + let expose2 = + VpcExpose::empty().ip(PrefixWithOptionalPorts::new("2001:db8:2::/64".into(), None)); + + let gw_config = build_gwconfig_from_exposes(vec![expose1], vec![expose2]); + let nat_tables = build_nat_configuration(gw_config.external().overlay().vpc_table()).unwrap(); + let (mut nat, mut tablesw) = StaticNat::new("static-nat"); + tablesw.update_nat_tables(nat_tables); + + let src = Ipv6Addr::new(0x2001, 0xdb8, 1, 0, 0, 0, 0, 1); + let mut packet = build_test_ipv6_packet_with_transport(u8::MAX, Some(NextHeader::UDP)).unwrap(); + packet.meta_mut().set_overlay(true); + packet.meta_mut().set_static_nat_src(true); + packet.meta_mut().src_vpcd = Some(VpcDiscriminant::VNI(Vni::new_checked(100).unwrap())); + packet.meta_mut().dst_vpcd = Some(VpcDiscriminant::VNI(Vni::new_checked(200).unwrap())); + let net = packet.try_ip_mut().unwrap(); + net.try_set_source(UnicastIpAddr::try_from(IpAddr::V6(src)).unwrap()) + .unwrap(); + net.try_set_destination(IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 2, 0, 0, 0, 0, 1))) + .unwrap(); + match packet.headers_mut().try_transport_mut() { + Some(Transport::Udp(udp)) => { + udp.set_checksum(UdpChecksum::new(0)).unwrap(); + } + _ => unreachable!(), + } + + let packets_out: Vec<_> = nat.process(vec![packet].into_iter()).collect(); + let pkt_out = packets_out.into_iter().next().expect("one packet out"); + + assert_ne!( + pkt_out.ip_source(), + Some(IpAddr::V6(src)), + "the packet was not translated; the test is not exercising anything" + ); + assert!( + pkt_out.meta().checksum_refresh(), + "a zero IPv6 UDP checksum was translated without requesting a recompute" + ); +} diff --git a/nat/src/test.rs b/nat/src/test.rs index 5821962ed4..8bb958f20b 100644 --- a/nat/src/test.rs +++ b/nat/src/test.rs @@ -29,7 +29,8 @@ use net::headers::{EmbeddedTransport, TryInnerIpv4}; use net::ip::NextHeader; use net::packet::Packet; use net::packet::test_utils::{ - addr_v4, build_test_icmp4_destination_unreachable_packet, build_test_udp_ipv4_frame, + addr_v4, assert_checksum_current_or_refresh_requested, + build_test_icmp4_destination_unreachable_packet, build_test_udp_ipv4_frame, }; use net::vxlan::Vni; use pipeline::{DynPipeline, NetworkFunction}; @@ -576,6 +577,7 @@ async fn test_nat_combination_static_masq_icmp_error() { .unwrap(); icmp_error.meta_mut().set_overlay(true); icmp_error.meta_mut().src_vpcd = Some(vni2.into()); + icmp_error.update_checksums(); println!("built packet:\n{icmp_error}"); let output_reply: Vec<_> = pipeline.process(std::iter::once(icmp_error)).collect(); @@ -583,6 +585,8 @@ async fn test_nat_combination_static_masq_icmp_error() { let packet_out_reply = output_reply.first().unwrap(); println!("packet_out_reply:\n{packet_out_reply}\n{flow_table}"); assert_eq!(packet_out_reply.get_done(), None); + // The outer ICMP checksum covers the quote the error handler rewrote. + assert_checksum_current_or_refresh_requested(packet_out_reply); // Check outer IP addresses assert_eq!(packet_out_reply.ip_source(), Some(addr(router_ip))); @@ -710,6 +714,7 @@ async fn test_nat_combination_static_portfwd_icmp_error() { .unwrap(); icmp_error.meta_mut().set_overlay(true); icmp_error.meta_mut().src_vpcd = Some(vni2.into()); + icmp_error.update_checksums(); println!("built packet:\n{icmp_error}"); let output_reply: Vec<_> = pipeline.process(std::iter::once(icmp_error)).collect(); @@ -717,6 +722,8 @@ async fn test_nat_combination_static_portfwd_icmp_error() { let packet_out_reply = output_reply.first().unwrap(); println!("packet_out_reply:\n{packet_out_reply}\n{flow_table}"); assert_eq!(packet_out_reply.get_done(), None); + // The outer ICMP checksum covers the quote the error handler rewrote. + assert_checksum_current_or_refresh_requested(packet_out_reply); // Check outer IP addresses and ports assert_eq!(packet_out_reply.ip_source(), Some(addr(orig_dst_ip))); // Note: We always overwrite the router IP with the original destination IP. diff --git a/net/src/headers/embedded.rs b/net/src/headers/embedded.rs index d2bda8d694..8852085c0d 100644 --- a/net/src/headers/embedded.rs +++ b/net/src/headers/embedded.rs @@ -618,12 +618,7 @@ impl EmbeddedTransport { old_value, new_value, ); - let updated = if u16::from(updated) == 0 { - UdpChecksum::new(u16::MAX) - } else { - updated - }; - let _ = udp.set_checksum(updated); + let _ = udp.set_checksum(UdpChecksum::from_computed(u16::from(updated))); } EmbeddedTransport::Icmp4(icmp) => { let updated = icmp.increment_update_checksum( diff --git a/net/src/headers/mod.rs b/net/src/headers/mod.rs index 35f311a2e4..ba8044b5d6 100644 --- a/net/src/headers/mod.rs +++ b/net/src/headers/mod.rs @@ -8,10 +8,10 @@ use crate::checksum::Checksum; use crate::eth::ethtype::EthType; use crate::eth::{Eth, EthError}; use crate::icmp_any::{IcmpAny, IcmpAnyMut}; -use crate::icmp4::Icmp4; -use crate::icmp6::{Icmp6, Icmp6ChecksumPayload}; +use crate::icmp4::{Icmp4, Icmp4Checksum}; +use crate::icmp6::{Icmp6, Icmp6Checksum, Icmp6ChecksumPayload}; use crate::impl_from_for_enum; -use crate::ip::{NextHeader, UnicastIpAddr}; +use crate::ip::{IpAddress, NextHeader, UnicastIpAddr}; use crate::ip_auth::{Ipv4Auth, Ipv6Auth}; use crate::ipv4::Ipv4; use crate::ipv6::{DestOpts, Fragment, HopByHop, Ipv6, Routing}; @@ -19,9 +19,9 @@ use crate::parse::{ DeParse, DeParseError, IllegalBufferLength, IntoNonZeroUSize, LengthError, Parse, ParseError, Reader, Writer, }; -use crate::tcp::{Tcp, TcpChecksumPayload, TcpPort}; +use crate::tcp::{Tcp, TcpChecksum, TcpChecksumPayload, TcpPort}; use crate::tcp_udp::{TcpUdp, TcpUdpMut}; -use crate::udp::{Udp, UdpChecksumPayload, UdpEncap, UdpPort}; +use crate::udp::{Udp, UdpChecksum, UdpChecksumPayload, UdpEncap, UdpPort}; use crate::vlan::{Pcp, Vid, Vlan}; use crate::vxlan::Vxlan; use arrayvec::ArrayVec; @@ -152,6 +152,90 @@ impl Net { } Ok(()) } + + /// Sets the source address and folds the change into `transport`'s checksum (RFC 1624). + /// + /// # Errors + /// + /// Returns [`NetError::InvalidIpVersion`], changing nothing, if the IP version of `addr` does + /// not match the IP version of the network header. + pub fn try_set_source_updating_checksum( + &mut self, + addr: UnicastIpAddr, + transport: Option<&mut Transport>, + ) -> Result<(), NetError> { + match (self, addr) { + (Net::Ipv4(ip), UnicastIpAddr::V4(addr)) => { + let old = ip.source(); + ip.set_source(addr); + if let Some(transport) = transport { + transport.increment_checksum_for_address(old, addr); + } + } + (Net::Ipv6(ip), UnicastIpAddr::V6(addr)) => { + let old = ip.source(); + ip.set_source(addr); + if let Some(transport) = transport { + transport.increment_checksum_for_address(old, addr); + } + } + _ => { + return Err(NetError::InvalidIpVersion); + } + } + Ok(()) + } + + /// Sets the destination address and folds the change into `transport`'s checksum (RFC 1624). + /// + /// # Errors + /// + /// Returns [`NetError::InvalidIpVersion`], changing nothing, if the IP version of `addr` does + /// not match the IP version of the network header. + pub fn try_set_destination_updating_checksum( + &mut self, + addr: IpAddr, + transport: Option<&mut Transport>, + ) -> Result<(), NetError> { + match (self, addr) { + (Net::Ipv4(ip), IpAddr::V4(addr)) => { + let old = ip.destination(); + ip.set_destination(addr); + if let Some(transport) = transport { + transport.increment_checksum_for_address(old, addr); + } + } + (Net::Ipv6(ip), IpAddr::V6(addr)) => { + let old = ip.destination(); + ip.set_destination(addr); + if let Some(transport) = transport { + transport.increment_checksum_for_address(old, addr); + } + } + _ => { + return Err(NetError::InvalidIpVersion); + } + } + Ok(()) + } +} + +/// One's-complement sum of an address's 16-bit words, which is all a pseudo-header checksum sees +/// of it. +fn address_sum(addr: A) -> u16 { + let bits = addr.to_addr_bits(); + let mut sum = 0u32; + for word in 0..u32::from(A::BITS / 16) { + #[allow(clippy::cast_possible_truncation)] // selects one 16-bit word + let word = (bits >> (16 * word)) as u16; + sum += u32::from(word); + } + // At most eight words, so two folds bring the carries back into 16 bits. + sum = (sum & 0xFFFF) + (sum >> 16); + sum = (sum & 0xFFFF) + (sum >> 16); + #[allow(clippy::cast_possible_truncation)] // folded into 16 bits above + let sum = sum as u16; + sum } impl DeParse for Net { @@ -264,6 +348,115 @@ impl Net { } impl Transport { + /// Return the checksum if present and enabled. + /// + /// A zero UDP checksum is disabled over IPv4 and must stay zero. Over IPv6 it is invalid, and + /// only a full recompute can fix it; callers detect that case with + /// [`Headers::has_zero_ipv6_udp_checksum`]. + #[must_use] + fn checksum_for_increment(&self) -> Option { + match self { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => match udp.checksum().map(u16::from) { + Some(0) | None => None, + Some(current) => Some(current), + }, + Transport::Icmp4(icmp) => icmp.checksum().map(u16::from), + Transport::Icmp6(icmp) => icmp.checksum().map(u16::from), + } + } + + /// Store the updated checksum, mapping zero to `0xFFFF` for UDP. + fn set_checksum_from_increment(&mut self, updated: u16) { + match self { + Transport::Tcp(tcp) => { + let _ = tcp.set_checksum(TcpChecksum::new(updated)); + } + Transport::Udp(udp) => { + let _ = udp.set_checksum(UdpChecksum::from_computed(updated)); + } + Transport::Icmp4(icmp) => { + let _ = icmp.set_checksum(Icmp4Checksum::new(updated)); + } + Transport::Icmp6(icmp) => { + let _ = icmp.set_checksum(Icmp6Checksum::new(updated)); + } + } + } + + /// Apply RFC 1624 to a changed 16-bit field without reading or writing the stored checksum. + fn increment_step(&mut self, current: u16, old_value: u16, new_value: u16) -> u16 { + match self { + Transport::Tcp(tcp) => u16::from(tcp.increment_update_checksum( + TcpChecksum::new(current), + old_value, + new_value, + )), + Transport::Udp(udp) => u16::from(udp.increment_update_checksum( + UdpChecksum::new(current), + old_value, + new_value, + )), + Transport::Icmp4(icmp) => u16::from(icmp.increment_update_checksum( + Icmp4Checksum::new(current), + old_value, + new_value, + )), + Transport::Icmp6(icmp) => u16::from(icmp.increment_update_checksum( + Icmp6Checksum::new(current), + old_value, + new_value, + )), + } + } + + /// Store the updated checksum, or return `false` if full recomputation is needed. + fn finish_increment(&mut self, updated: u16) -> bool { + // An incremental ICMPv4 result of 0x0000 is ambiguous: all-zero data needs 0xFFFF. + // Distinguishing these cases requires the payload. + // Other transports have a nonzero protocol number in their pseudo-header. + if updated == 0 && matches!(self, Transport::Icmp4(_)) { + return false; + } + self.set_checksum_from_increment(updated); + true + } + + /// Update the checksum for a changed 16-bit field, such as a port or ICMP identifier (RFC 1624). + /// + /// Returns `true` if updated or no update is needed, including absent or disabled checksums. + /// Returns `false` if an `ICMPv4` update computes to `0x0000`: full recomputation is needed to + /// distinguish this from the all-zero case, which requires `0xFFFF`. + #[must_use] + pub fn increment_checksum_for_u16(&mut self, old_value: u16, new_value: u16) -> bool { + if old_value == new_value { + return true; + } + let Some(current) = self.checksum_for_increment() else { + return true; + }; + let updated = self.increment_step(current, old_value, new_value); + self.finish_increment(updated) + } + + /// Update the TCP, UDP, or `ICMPv6` checksum for an IP address change. + /// `ICMPv4` has no pseudo-header, so its checksum is unchanged. + /// + /// `old` and `new` share one address type, so an IP version change cannot be expressed. + /// Absent or disabled checksums are left alone. + pub fn increment_checksum_for_address(&mut self, old: A, new: A) { + if old == new || matches!(self, Transport::Icmp4(_)) { + return; + } + let Some(current) = self.checksum_for_increment() else { + return; + }; + // The address enters the checksum only through the sum of its words, so one step covers it. + let updated = self.increment_step(current, address_sum(old), address_sum(new)); + // ICMPv4 returned above, so `finish_increment`'s zero refusal cannot apply. + self.set_checksum_from_increment(updated); + } + pub(crate) fn update_checksum( &mut self, net: &Net, @@ -771,6 +964,28 @@ impl Headers { self.net.as_mut() } + /// Get mutable references to the network header and, if present, the transport header. + /// + /// Borrowing both at once lets an address change fold into the transport checksum. + #[must_use] + pub fn net_and_transport_mut(&mut self) -> Option<(&mut Net, Option<&mut Transport>)> { + Some((self.net.as_mut()?, self.transport.as_mut())) + } + + /// Whether this is an IPv6 UDP datagram with a zero checksum. + /// + /// IPv6 forbids a zero UDP checksum outside RFC 6935 tunnels, and an incremental update cannot + /// repair one, so a translation must request a full recompute. + #[must_use] + pub fn has_zero_ipv6_udp_checksum(&self) -> bool { + match (&self.net, &self.transport) { + (Some(Net::Ipv6(_)), Some(Transport::Udp(udp))) => { + udp.checksum().map(u16::from) == Some(0) + } + _ => false, + } + } + /// Get a reference to the network extension headers (e.g. IPv6 extensions, /// IP Authentication headers). #[must_use] @@ -1760,6 +1975,468 @@ mod test { assert_eq!(bytes_parsed, headers.size()); } + /// Compare incremental port updates with full recomputation for TCP and UDP over both IP versions. + fn incremental_matches_recompute(headers: &Headers) { + use crate::headers::TryVxlan; + use std::num::NonZero; + + let Some(net) = headers.net.clone() else { + return; + }; + let Some(transport) = headers.transport.clone() else { + return; + }; + // Skip VXLAN: `Headers::update_checksums` does not update encapsulated transport checksums. + if headers.try_vxlan().is_some() { + return; + } + let payload: &[u8] = b"the payload the full recompute has to read"; + + // Incremental updates require a valid starting checksum. + let mut base = headers.clone(); + base.update_checksums(payload); + + for (old_port, new_port) in [(1234u16, 4321u16), (80, 8080), (65535, 1)] { + let mut incremental = base.clone(); + let mut recomputed = base.clone(); + + let old_nz = NonZero::new(old_port).unwrap(); + let new_nz = NonZero::new(new_port).unwrap(); + // Set the same starting port in both copies; skip ICMP, which has no ports. + { + let inc_tp = incremental.transport.as_mut().unwrap(); + if inc_tp.try_set_source(old_nz).is_err() { + continue; + } + let rec_tp = recomputed.transport.as_mut().unwrap(); + rec_tp.try_set_source(old_nz).unwrap(); + } + incremental.update_checksums(payload); + recomputed.update_checksums(payload); + + let inc_tp = incremental.transport.as_mut().unwrap(); + inc_tp.try_set_source(new_nz).unwrap(); + assert!( + inc_tp.increment_checksum_for_u16(old_port, new_port), + "a port update never needs a full recompute" + ); + + let rec_tp = recomputed.transport.as_mut().unwrap(); + rec_tp.try_set_source(new_nz).unwrap(); + recomputed.update_checksums(payload); + + assert_eq!( + incremental.transport.as_ref().map(transport_checksum), + recomputed.transport.as_ref().map(transport_checksum), + "incremental port update {old_port}->{new_port} disagreed with a full recompute \ + for {:?} over {:?}", + transport_kind(&transport), + net_kind(&net), + ); + } + } + + /// Compare source/destination address and ICMP identifier updates with full recomputation. + /// Cover every transport and both IP versions, including `ICMPv4`'s lack of a pseudo-header. + fn address_and_identifier_match_recompute((headers, bits, identifier): &(Headers, u128, u16)) { + use crate::headers::TryVxlan; + use crate::ip::UnicastIpAddr; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + + // Skip VXLAN: `Headers::update_checksums` does not update encapsulated transport checksums. + if headers.try_vxlan().is_some() { + return; + } + let (Some(net), Some(transport)) = (&headers.net, &headers.transport) else { + return; + }; + let payload: &[u8] = b"the payload the full recompute has to read"; + + let mut base = headers.clone(); + base.update_checksums(payload); + + let new = match net { + Net::Ipv4(_) => { + let [a, b, c, d, ..] = bits.to_be_bytes(); + IpAddr::V4(Ipv4Addr::new(a, b, c, d)) + } + Net::Ipv6(_) => IpAddr::V6(Ipv6Addr::from(*bits)), + }; + for source in [true, false] { + // Only a unicast address can be a source. + let new_source = UnicastIpAddr::try_from(new); + if source && new_source.is_err() { + continue; + } + let mut incremental = base.clone(); + let mut recomputed = base.clone(); + let old = if source { + net.src_addr() + } else { + net.dst_addr() + }; + let Headers { + net: Some(incremental_net), + transport: incremental_transport, + .. + } = &mut incremental + else { + unreachable!() + }; + let recomputed_net = recomputed.net.as_mut().unwrap(); + if source { + incremental_net + .try_set_source_updating_checksum( + new_source.unwrap(), + incremental_transport.as_mut(), + ) + .unwrap(); + recomputed_net.try_set_source(new_source.unwrap()).unwrap(); + } else { + incremental_net + .try_set_destination_updating_checksum(new, incremental_transport.as_mut()) + .unwrap(); + recomputed_net.try_set_destination(new).unwrap(); + } + recomputed.update_checksums(payload); + assert_eq!( + incremental.transport.as_ref().map(transport_checksum), + recomputed.transport.as_ref().map(transport_checksum), + "incremental {} address update {old}->{new} disagreed with a full recompute for \ + {:?} over {:?}", + if source { "source" } else { "destination" }, + transport_kind(transport), + net_kind(net), + ); + } + + let Some(old_identifier) = transport.identifier() else { + return; + }; + let mut incremental = base.clone(); + let mut recomputed = base; + for copy in [&mut incremental, &mut recomputed] { + if copy + .transport + .as_mut() + .unwrap() + .try_set_identifier(*identifier) + .is_err() + { + return; + } + } + let updated = incremental + .transport + .as_mut() + .unwrap() + .increment_checksum_for_u16(old_identifier, *identifier); + recomputed.update_checksums(payload); + if !updated { + // An ICMPv4 result of zero requires full recomputation. + assert!(matches!(transport, Transport::Icmp4(_))); + return; + } + assert_eq!( + incremental.transport.as_ref().map(transport_checksum), + recomputed.transport.as_ref().map(transport_checksum), + "incremental identifier update {old_identifier}->{identifier} disagreed with a full \ + recompute for {:?}", + transport_kind(transport), + ); + } + + /// Reach what a checksum-correct packet never does: an unchanged value, an address change + /// with no transport, and a disabled UDP checksum, which must stay zero over either version. + fn unchanged_or_disabled_checksums_stay_put((headers, bits): &(Headers, u128)) { + use crate::checksum::Checksum; + use crate::headers::TryVxlan; + use crate::ip::UnicastIpAddr; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + + if headers.try_vxlan().is_some() { + return; + } + let Some(net) = &headers.net else { + return; + }; + let payload: &[u8] = b"the payload the full recompute has to read"; + let mut base = headers.clone(); + base.update_checksums(payload); + let new = match net { + Net::Ipv4(_) => { + let [a, b, c, d, ..] = bits.to_be_bytes(); + IpAddr::V4(Ipv4Addr::new(a, b, c, d)) + } + Net::Ipv6(_) => IpAddr::V6(Ipv6Addr::from(*bits)), + }; + let new_source = UnicastIpAddr::try_from(new).ok(); + + // Rewriting an address or field with its own value changes nothing. + let mut unchanged = base.clone(); + let Headers { + net: Some(unchanged_net), + transport, + .. + } = &mut unchanged + else { + unreachable!() + }; + let src = UnicastIpAddr::try_from(unchanged_net.src_addr()).unwrap(); + let dst = unchanged_net.dst_addr(); + unchanged_net + .try_set_source_updating_checksum(src, transport.as_mut()) + .unwrap(); + unchanged_net + .try_set_destination_updating_checksum(dst, transport.as_mut()) + .unwrap(); + if let Some(transport) = transport.as_mut() { + assert!(transport.increment_checksum_for_u16(0x1234, 0x1234)); + } + assert_eq!(unchanged, base); + + // With no transport to update, only the address moves. + let mut no_transport = base.clone(); + let no_transport_net = no_transport.net.as_mut().unwrap(); + no_transport_net + .try_set_destination_updating_checksum(new, None) + .unwrap(); + if let Some(new_source) = new_source { + no_transport_net + .try_set_source_updating_checksum(new_source, None) + .unwrap(); + } + assert_eq!(no_transport.net.as_ref().unwrap().dst_addr(), new); + assert_eq!(no_transport.transport, base.transport); + + // A zero UDP checksum is left alone; over IPv6 the caller learns it must recompute. + let mut disabled = base; + let Headers { + net: Some(disabled_net), + transport: Some(Transport::Udp(udp)), + .. + } = &mut disabled + else { + return; + }; + udp.set_checksum(UdpChecksum::new(0)).unwrap(); + let mut transport = Transport::Udp(udp.clone()); + disabled_net + .try_set_destination_updating_checksum(new, Some(&mut transport)) + .unwrap(); + if let Some(new_source) = new_source { + disabled_net + .try_set_source_updating_checksum(new_source, Some(&mut transport)) + .unwrap(); + } + assert!(transport.increment_checksum_for_u16(0x1234, 0x4321)); + assert_eq!(transport_checksum(&transport), Some(0)); + disabled.transport = Some(transport); + assert_eq!( + disabled.has_zero_ipv6_udp_checksum(), + matches!(net, Net::Ipv6(_)) + ); + } + + fn transport_checksum(tp: &Transport) -> Option { + match tp { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + Transport::Icmp4(icmp) => icmp.checksum().map(u16::from), + Transport::Icmp6(icmp) => icmp.checksum().map(u16::from), + } + } + + fn transport_kind(tp: &Transport) -> &'static str { + match tp { + Transport::Tcp(_) => "tcp", + Transport::Udp(_) => "udp", + Transport::Icmp4(_) => "icmp4", + Transport::Icmp6(_) => "icmp6", + } + } + + fn net_kind(net: &Net) -> &'static str { + match net { + Net::Ipv4(_) => "ipv4", + Net::Ipv6(_) => "ipv6", + } + } + + //= https://www.rfc-editor.org/rfc/rfc6935#section-5 + //= type=test + //# if that computation yields a result of zero, + //# the checksum MUST be changed to hex FFFF for placement in the UDP + //# header + /// Exercise a computed UDP checksum of zero, which random inputs rarely produce. + #[test] + fn a_udp_checksum_that_updates_to_zero_is_written_as_ffff() { + use crate::checksum::Checksum; + use crate::headers::Transport; + use crate::udp::{Udp, UdpChecksum, UdpPort}; + use std::num::NonZero; + + let mut udp = Udp::new( + UdpPort::new(NonZero::new(1234).unwrap()), + UdpPort::new(NonZero::new(4321).unwrap()), + ); + let start = 0x1234u16; + udp.set_checksum(UdpChecksum::new(start)).unwrap(); + let mut transport = Transport::Udp(udp); + + // Find a field value that produces zero through the RFC 1624 primitive. + let old_value = 0xABCDu16; + let zeroing = (0..=u16::MAX).find(|new_value| { + let Transport::Udp(probe) = &mut transport.clone() else { + unreachable!() + }; + u16::from(probe.increment_update_checksum( + UdpChecksum::new(start), + old_value, + *new_value, + )) == 0 + }); + let zeroing = zeroing.expect("some new value drives the update to zero"); + + assert!(transport.increment_checksum_for_u16(old_value, zeroing)); + + let Transport::Udp(result) = &transport else { + unreachable!() + }; + assert_eq!( + result.checksum().map(u16::from), + Some(u16::MAX), + "an incremental update that computes to 0 must be stored as 0xFFFF, or the datagram \ + reads as having no checksum at all" + ); + } + + /// Clearing this echo reply's identifier leaves all covered data zero, requiring `0xFFFF`. + /// The incremental result is `0x0000`, so the helper must request full recomputation. + #[test] + fn an_icmp4_update_to_zero_asks_for_a_recompute() { + use crate::checksum::Checksum; + use crate::icmp4::{Icmp4, Icmp4EchoReply, Icmp4Type}; + + let payload: &[u8] = &[]; + let mut icmp = Icmp4::with_type(Icmp4Type::EchoReply(Icmp4EchoReply { id: 1, seq: 0 })); + icmp.update_checksum(payload).unwrap(); + assert_eq!(icmp.checksum().map(u16::from), Some(0xFFFE)); + + let mut transport = Transport::Icmp4(icmp); + transport.try_set_identifier(0).unwrap(); + assert!( + !transport.increment_checksum_for_u16(1, 0), + "an ICMPv4 update that computes to 0x0000 claimed to be done" + ); + + let Transport::Icmp4(mut icmp) = transport else { + unreachable!() + }; + icmp.update_checksum(payload).unwrap(); + assert_eq!( + icmp.checksum().map(u16::from), + Some(0xFFFF), + "all-zero ICMPv4 data needs checksum 0xFFFF" + ); + } + + /// Only an IPv6 UDP datagram with a zero checksum needs a full recompute; over IPv4 a zero + /// means the checksum is disabled. + #[test] + fn only_a_zero_ipv6_udp_checksum_is_flagged() { + use crate::headers::TryHeaders; + use crate::ip::NextHeader; + use crate::packet::test_utils::{ + build_test_ipv4_packet_with_transport, build_test_ipv6_packet_with_transport, + }; + + for (ipv6, checksum, flagged) in [ + (true, 0, true), + (true, 0x1234, false), + (false, 0, false), + (false, 0x1234, false), + ] { + let packet = if ipv6 { + build_test_ipv6_packet_with_transport(64, Some(NextHeader::UDP)) + } else { + build_test_ipv4_packet_with_transport(64, Some(NextHeader::UDP)) + } + .unwrap(); + let mut headers = packet.headers().clone(); + let Some(Transport::Udp(udp)) = headers.transport.as_mut() else { + unreachable!() + }; + udp.set_checksum(UdpChecksum::new(checksum)).unwrap(); + assert_eq!( + headers.has_zero_ipv6_udp_checksum(), + flagged, + "ipv6={ipv6} checksum={checksum:#06x}" + ); + } + let tcp = build_test_ipv6_packet_with_transport(64, Some(NextHeader::TCP)).unwrap(); + assert!(!tcp.headers().has_zero_ipv6_udp_checksum()); + } + + /// A version mismatch is refused before anything changes, address or checksum. + #[test] + fn a_version_mismatch_changes_neither_address_nor_checksum() { + use crate::ip::UnicastIpAddr; + use crate::ipv4::Ipv4; + use crate::tcp::Tcp; + use std::net::{IpAddr, Ipv6Addr}; + use std::num::NonZero; + + let mut net = Net::Ipv4(Ipv4::default()); + let mut tcp = Tcp::new( + TcpPort::new(NonZero::new(1234).unwrap()), + TcpPort::new(NonZero::new(80).unwrap()), + ); + tcp.set_checksum(TcpChecksum::new(0x1234)).unwrap(); + let mut transport = Transport::Tcp(tcp); + let before = net.clone(); + + let v6 = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + assert!( + net.try_set_source_updating_checksum( + UnicastIpAddr::try_from(v6).unwrap(), + Some(&mut transport) + ) + .is_err() + ); + assert!( + net.try_set_destination_updating_checksum(v6, Some(&mut transport)) + .is_err() + ); + assert_eq!(net, before); + assert_eq!(transport_checksum(&transport), Some(0x1234)); + } + + #[test] + fn incremental_update_matches_full_recompute() { + bolero::check!() + .with_generator(CommonHeaders) + .for_each(incremental_matches_recompute); + } + + #[test] + fn unchanged_or_disabled_checksums_stay_put_property() { + bolero::check!() + .with_generator((CommonHeaders, bolero::produce::())) + .for_each(unchanged_or_disabled_checksums_stay_put); + } + + #[test] + fn address_and_identifier_updates_match_full_recompute() { + bolero::check!() + .with_generator(( + CommonHeaders, + bolero::produce::(), + bolero::produce::(), + )) + .for_each(address_and_identifier_match_recompute); + } + #[test] fn parse_back() { bolero::check!().with_type().for_each(parse_back_test); diff --git a/net/src/packet/test_utils.rs b/net/src/packet/test_utils.rs index 77f088ca58..bb5c5e7662 100644 --- a/net/src/packet/test_utils.rs +++ b/net/src/packet/test_utils.rs @@ -52,6 +52,13 @@ fn make_default_for_transport(transport_type: Option) -> Option Some(Transport::Icmp4(crate::icmp4::Icmp4::with_type( + crate::icmp4::Icmp4Type::EchoRequest(crate::icmp4::Icmp4EchoRequest { id: 18, seq: 2 }), + ))), + Some(NextHeader::ICMP6) => Some(Transport::Icmp6(crate::icmp6::Icmp6::with_type( + crate::icmp6::Icmp6Type::EchoRequest(crate::icmp6::Icmp6EchoRequest { id: 18, seq: 2 }), + ))), Some(transport_type) => { panic!("make_default_for_transport: Unsupported transport type: {transport_type:?}") } @@ -84,12 +91,14 @@ pub fn make_default_for_eth(ethtype: EthType) -> Eth { /// /// # Panics /// -/// Panics if the transport type is anything other than `Some(NextHeader::TCP)`, `Some(NextHeader::UDP)`, or None +/// Panics if the transport type is anything other than `Some(NextHeader::TCP)`, +/// `Some(NextHeader::UDP)`, `Some(NextHeader::ICMP)`, or None /// pub fn build_test_ipv4_packet_with_transport( ttl: u8, transport_type: Option, ) -> Result, InvalidPacket> { + assert_ne!(transport_type, Some(NextHeader::ICMP6), "ICMPv6 over IPv4"); let mut headers = HeadersBuilder::default(); let mut transport = make_default_for_transport(transport_type); @@ -261,11 +270,13 @@ pub fn build_test_ipv6_packet(ttl: u8) -> Result, InvalidPack /// /// # Panics /// -/// Panics if the transport type is anything other than `Some(NextHeader::TCP)`, `Some(NextHeader::UDP)`, or None +/// Panics if the transport type is anything other than `Some(NextHeader::TCP)`, +/// `Some(NextHeader::UDP)`, `Some(NextHeader::ICMP6)`, or None pub fn build_test_ipv6_packet_with_transport( hl: u8, transport_type: Option, ) -> Result, InvalidPacket> { + assert_ne!(transport_type, Some(NextHeader::ICMP), "ICMPv4 over IPv6"); let mut headers = HeadersBuilder::default(); headers.eth(Some(make_default_for_eth(EthType::IPV6))); @@ -865,3 +876,31 @@ fn assemble_quoted(headers: &HeadersBuilder, quote: &[u8]) -> Option) { + fn transport_checksum(packet: &Packet) -> Option { + match packet.headers.transport.as_ref()? { + Transport::Tcp(tcp) => tcp.checksum().map(u16::from), + Transport::Udp(udp) => udp.checksum().map(u16::from), + Transport::Icmp4(icmp) => icmp.checksum().map(u16::from), + Transport::Icmp6(icmp) => icmp.checksum().map(u16::from), + } + } + if packet.meta().checksum_refresh() { + return; + } + let mut recomputed = packet.clone(); + recomputed.update_checksums(); + assert_eq!( + transport_checksum(packet), + transport_checksum(&recomputed), + "the packet asked for no checksum recompute, but its transport checksum is stale:\n{packet}" + ); +} diff --git a/net/src/udp/checksum.rs b/net/src/udp/checksum.rs index 53c4c8ca31..b40d0df99a 100644 --- a/net/src/udp/checksum.rs +++ b/net/src/udp/checksum.rs @@ -36,6 +36,23 @@ impl UdpChecksum { pub const fn new(raw: u16) -> UdpChecksum { UdpChecksum(raw) } + + //= https://www.rfc-editor.org/rfc/rfc6935#section-5 + //= reason=RFC 8200 section 8.1 restates this in lowercase, which duvet does not extract + //# if that computation yields a result of zero, + //# the checksum MUST be changed to hex FFFF for placement in the UDP + //# header + /// Convert a computed checksum to its wire value. + /// + /// Send a computed zero as `0xFFFF`, since a stored zero disables the checksum (RFC 768). + #[must_use] + pub const fn from_computed(computed: u16) -> UdpChecksum { + if computed == 0 { + UdpChecksum(u16::MAX) + } else { + UdpChecksum(computed) + } + } } impl AsRef for UdpChecksum {