<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>

<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-ietf-tsvwg-udp-ecn-08" category="info" consensus="true" submissionType="IETF" xml:lang="en" number="10055" tocInclude="true" sortRefs="true" symRefs="true" version="3">

  <link href="https://datatracker.ietf.org/doc/draft-ietf-tsvwg-udp-ecn-08" rel="prev"/>
  <front>
    <title abbrev="UDP Sockets for ECN">Configuring UDP Sockets for Explicit Congestion Notification (ECN) for Common Platforms</title>
    <seriesInfo name="RFC" value="10055"/>
    <author initials="M." surname="Duke" fullname="Martin Duke">
      <organization>Google</organization>
      <address>
        <email>martin.h.duke@gmail.com</email>
      </address>
    </author>
    <date year="2026" month="October"/>
    <area>WIT</area>
    <workgroup>tsvwg</workgroup>
    <keyword>udp</keyword>
    <keyword>ecn</keyword>
    <abstract>

<t>Explicit Congestion Notification (ECN) applies to all transport protocols in
principle. However, it had limited deployment for UDP until QUIC became widely
adopted. As a result, documentation of UDP socket APIs for ECN on various
platforms is sparse. This document records the results of experimenting with
these APIs in order to get ECN working on UDP for Chromium on Apple, Linux, and
Windows platforms.</t>
      <t>This document provides a snapshot of the ECN state of affairs as supported by these
platforms at the time of writing. Readers should refer to the documentation of
the various platforms for more up-to-date information.</t>
    </abstract>
  </front>
  <middle>

<section anchor="introduction">
      <name>Introduction</name>
      <t><xref target="RFC3168"/> defines a 2-bit field in the IP header for Explicit Congestion
Notification (ECN), which provides network feedback to endpoint congestion
controllers. Historically, this has mostly been relevant to TCP <xref target="RFC9293"/>,
where any incoming ECN codepoints are internally consumed by the kernel and,
therefore, imply no application interface except for enabling and disabling the
capability.</t>
      <t>The Stream Control Transmission Protocol (SCTP) <xref target="RFC9260"/> has long supported
ECN in its design. SCTP is sometimes carried over DTLS and UDP <xref target="RFC8261"/>.
In principle, user-space implementers might have leveraged UDP ECN APIs to
deliver ECN codepoints between SCTP and the UDP socket. At the time of
publication, the IETF is not aware of any such efforts.</t>
      <t><xref target="RFC6679"/> defines ECN over RTP over UDP. The IETF is aware of a research
implementation but cannot confirm any commercial deployments. <xref target="RFC8888"/>
proposes a different feedback format to work with <xref target="RFC6679"/>, and there are
a few implementations of this.</t>
      <t>QUIC <xref target="RFC9000"/> runs over UDP and has seen wider deployment than SCTP. The Low
Latency, Low Loss, Scalable Throughput (L4S) architecture <xref target="RFC9330"/> and
QUIC have combined to increase interest in ECN over UDP.</t>
      <t>The Chromium Projects <xref target="CHROMIUM"/> provide a widely deployed protocol library
that includes QUIC. An effort to provide ECN support for QUIC on the many
platforms on which Chromium is deployed revealed that many ECN-related UDP
socket interfaces are poorly documented.</t>
      <t>This Informational document provides a record of that experience to encourage
further support for ECN in other QUIC implementations and any consumer
of ECN codepoints that operates over UDP. It is not a Standards Track document
and does not bind platforms to any API nor suggest any such API.</t>
      <t>Many socket APIs continue to reference the "ToS (Type of Service) byte",
including the IP_TOS label, even though <xref target="RFC2474"/> obsoleted that in 1998.
That 8-bit field now contains a 6-bit Differentiated Services Code Point (DSCP)
and the 2-bit ECN field.</t>
      <t>This document focuses on the APIs for the C and C++ languages. Other languages
are likely to have different syntax and capabilities.</t>
      <t>The "ecn-examples" code repository <xref target="EXAMPLES"/> is extremely compact code that
can verify the information in this document.</t>
      <t>This document addresses access to the ECN field in the IP header via socket
APIs. It does not address UDP transport-layer options <xref target="RFC9868"/>, which are a
separate extension mechanism operating at a different layer.</t>
    </section>
    <section anchor="conventions-and-definitions">
      <name>Conventions and Definitions</name>
      <t>This document is not a general tutorial on UDP socket programming and does assume
familiarity with basic socket concepts like binding, socket options, and
common system error codes.</t>
      <t>Throughout this document, "Apple" refers to both macOS and iOS. "Linux" guidance
also applies to Android.</t>
    </section>
    <section anchor="receiving-ecn-codepoints">
      <name>Receiving ECN Codepoints</name>
      <t>Network devices can change the ECN codepoint in the IP header. Since this
feedback is required at the packet sender, the packet receiver needs to extract
this codepoint from the UDP socket in order to report to the sender.</t>
      <t>There are two components to this: 1) setting the socket to report incoming ECN
marks and 2) retrieving the ECN codepoint for each incoming packet.</t>
      <t>Note that Apple platforms additionally provide a framework for network
connections that allows receiving ECN flags when using UDP without traditional
socket option semantics. When sending or receiving UDP datagrams, IP protocol
metadata carries ECN information in both directions. See
<xref target="APPLE-NETWORK-FRAMEWORK"/>.</t>
      <section anchor="setting-the-socket-to-report-incoming-ecn-codepoints">
        <name>Setting the Socket to Report Incoming ECN Codepoints</name>
        <section anchor="linux-apple-and-freebsd">
          <name>Linux, Apple, and FreeBSD</name>
          <t>To receive ECN codepoints, applications set a socket option to "true" using a setsockopt()
call.</t>
          <t>On all platforms, IPv4 sockets require the IPPROTO_IP-level socket option with the
name IP_RECVTOS to be set.</t>
          <t>On all platforms, IPv6 sockets require the IPPROTO_IPV6-level socket option with the
name IPV6_RECVTCLASS to be set.</t>
          <t>If the IPv6 socket is not IPv6 only, on Linux hosts it is required to also set
the IPPROTO_IP-level socket option IP_RECVTOS to receive ECN codepoints for
UDP/IPv4 packets. This step is not necessary for Apple or FreeBSD.</t>
          <t>At the time of writing, an example implementation can be found at
<xref target="CHROMIUM-POSIX"/>.</t>
        </section>
        <section anchor="windows">
          <name>Windows</name>
          <t>Windows documentation recommends using the function WSASetRecvIPEcn() to
enable ECN codepoint reporting regardless of the IP version. This function dates
to Windows 10 Build 20348, according to <xref target="WINDOWS-DOC"/>.</t>
          <t>However, this can also be accomplished by calling setsockopt() and using
options of level IPPROTO_IP and name IP_RECVECN for IPv4 and level IPPROTO_IPV6
and name IPV6_RECVECN for IPv6. These options are documented at
<xref target="WINDOWS-SOCKOPT"/>.</t>
          <t>For IPv6 sockets that are not IPv6 only, WSASetRecvIPEcn() will not enable ECN
reporting for IPv4. This requires a separate setsockopt() call using the
IP_RECVECN option.</t>
          <t>If a socket is bound to an IPv4-mapped IPv6 address (i.e., it is of the format
::ffff:&lt;IPv4 address&gt;; see <xref section="2.5.5.2" sectionFormat="of" target="RFC4291"/>), then calls to
WSASetRecvIpEcn() return the error EINVAL. These sockets should instead use an
explicit setsockopt() call to set IP_RECVECN.</t>
          <t>At the time of writing, an example implementation can be found at
<xref target="CHROMIUM-WINDOWS"/>.</t>
        </section>
      </section>
      <section anchor="retrieving-ecn-codepoints-on-incoming-packets">
        <name>Retrieving ECN Codepoints on Incoming Packets</name>
        <t>All platforms described in this document require the use of a recvmsg() call to
read data from the socket and to retrieve the ECN codepoint, because that information
is provided as ancillary data.
Those platforms all return zero or more "cmsg"s that contain requested information
about the arriving packet.</t>
        <t>Examples of the technique described below can be found at <xref target="CHROMIUM-POSIX"/>
and <xref target="CHROMIUM-WINDOWS"/>.</t>
        <section anchor="linux">
          <name>Linux</name>
          <t>If a UDP/IPv4 message is received, Linux will include a cmsg of level IPPROTO_IP
and type IP_TOS. The cmsg data contains an unsigned char.
This applies to IPv4 sockets and to IPv6 sockets that are not IPv6 only.</t>
          <t>If a UDP/IPv6 message is received, Linux will include a cmsg of level IPPROTO_IPV6
and type IPV6_TCLASS. The cmsg data contains an int.
This applies to IPv6 sockets.</t>
          <t>The cmsg data contains the entire IP header byte, which includes the DSCP
and the ECN codepoint.
The ECN codepoint constitutes the two least significant bits of this byte.</t>
          <t>The same applies to the Linux-specific recvmmsg() call.</t>
        </section>
        <section anchor="apple-and-freebsd">
          <name>Apple and FreeBSD</name>
          <t>If a UDP/IPv4 message is received on an IPv4 socket, the ancillary data
will contain a cmsg of level IPPROTO_IP and type IP_RECVTOS. The cmsg data
contains an unsigned char.</t>
          <t>If a UDP/IPv6 or UDP/IPv4 message is received on an IPv6 socket, the
ancillary data will contain a cmsg of level IPPROTO_IPV6 and type IPV6_TCLASS.
The cmsg data contains an int.</t>
          <t>The cmsg data contains the entire IP header byte, which includes the DSCP
and the ECN codepoint.
The ECN codepoint constitutes the two least significant bits of this byte.</t>
        </section>
        <section anchor="windows-1">
          <name>Windows</name>
          <t>If the incoming packet is UDP/IPv4, the socket will include a cmsg of level
IPPROTO_IP and type IP_ECN. The cmsg data contains an int.</t>
          <t>If the incoming packet is UDP/IPv6, the socket will include a cmsg of level
IPPROTO_IPV6 and type IPV6_ECN. The cmsg data contains an int.</t>
          <t>The cmsg data solely consists of the ECN codepoint and requires no
further bitwise operations.</t>
        </section>
      </section>
    </section>
    <section anchor="sending-ecn-codepoints">
      <name>Sending ECN Codepoints</name>
      <t>Existing ECN specifications (<xref target="RFC3168"/> and <xref target="RFC9330"/>) envision a particular
connection that is consistently sending the same ECN codepoint. It might transition that
marking after successfully completing a handshake, recognizing that the path or the
peer does not support ECN or transitioning to a new path. Therefore, using a
socket option to configure a consistent marking is generally more
resource efficient.</t>
      <t>However, some server designs receive all incoming packets on a single socket.
As the many connections that constitute this packet stream may have different
support for ECN, it is suitable to provide the ECN codepoint on a per-packet basis.</t>
      <t>Note that Apple platforms additionally provide a framework for network
connections that allows sending ECN flags when using UDP without traditional
socket option semantics. When sending or receiving UDP datagrams, IP protocol
metadata carries ECN information in both directions. See
<xref target="APPLE-NETWORK-FRAMEWORK"/>.</t>
      <section anchor="on-a-per-socket-basis">
        <name>On a Per-Socket Basis</name>
        <section anchor="apple-freebsd-and-linux">
          <name>Apple, FreeBSD, and Linux</name>
          <t>For sending UDP/IPv4 packets on an IPv4 socket, Apple, FreeBSD, and Linux platforms
allow the outgoing ECN codepoint to be configured by using the IPPROTO_IP-level socket
option with the name IP_TOS.
The value has the type int.</t>
          <t>For sending UDP/IPv6 packets on an IPv6 socket, Apple, FreeBSD, and Linux platforms
allow the outgoing ECN codepoint to be configured by using the IPPROTO_IPV6-level socket
option with the name IPV6_TCLASS.
The value has the type int.</t>
          <t>For sending UDP/IPv4 packets on an IPv6 socket, Linux platforms allow
the outgoing ECN codepoint to be configured by using the IPPROTO_IP-level socket
option with the name IP_TOS.</t>
          <t>For sending UDP/IPv4 packets on an IPv6 socket, Apple and FreeBSD platforms allow
the outgoing ECN codepoint to be configured by using the IPPROTO_IPV6-level socket
option with the name IPV6_TCLASS.</t>
          <t>Except for Apple platforms, this setsockopt() call also sets the
DSCP that comprises the rest of the header byte.
Applications making this call ought to preserve any existing DSCP setting, which
might require an additional getsockopt() call, to avoid overriding commands set
by other code in the stack. If there are multiple threads making changes to this
byte, further safeguards will be necessary.</t>
          <t>An example of the technique described above can be found at <xref target="CHROMIUM-POSIX"/>.</t>
        </section>
        <section anchor="windows-2">
          <name>Windows</name>
          <t>At the time of this writing, Windows does not provide a way to configure
marking on a per-socket basis.</t>
        </section>
      </section>
      <section anchor="on-a-per-packet-basis">
        <name>On a Per-Packet Basis</name>
        <t>Packets can be individually marked with ECN codepoints using the ancillary data
that accompanies a sendmsg() call.</t>
        <section anchor="apple-freebsd-and-linux-1">
          <name>Apple, FreeBSD, and Linux</name>
          <t>For sending UDP/IPv4 packets on an IPv4 socket, Apple, FreeBSD, and Linux use
a cmsg with level IPPROTO_IP and type IP_TOS. On Apple and Linux, the type of
data is int; and for FreeBSD, it is unsigned char.</t>
          <t>For sending UDP/IPv6 packets on an IPv6 socket, Apple, FreeBSD, and Linux use
a cmsg with level IPPROTO_IPV6 and type IPV6_TCLASS. The type of the data
is int.</t>
          <t>For sending UDP/IPv4 packets on an IPv6 socket, Linux requires a cmsg with
level IPPROTO_IP and type IP_TOS.
Apple and FreeBSD accept a cmsg with level IPPROTO_IPV6 and type IPV6_TCLASS.</t>
          <t>The same applies to the Linux-specific sendmmsg() call.</t>
        </section>
        <section anchor="windows-3">
          <name>Windows</name>
          <t>For IPv4 packets, Windows uses a cmsg with level IPPROTO_IP and type IP_ECN.</t>
          <t>For IPv6 packets, Windows uses a cmsg with level IPPROTO_IPV6 and type IPV6_ECN.</t>
          <t>An example of the technique described above can be found at
<xref target="CHROMIUM-WINDOWS"/>.</t>
        </section>
      </section>
    </section>
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <t>The security implications of ECN are documented in <xref target="RFC3168"/> and <xref target="RFC9330"/>.
This document is a guide to enabling these capabilities, which incurs no
additional security considerations.</t>
      <t>Note that implementing ECN capabilities on some platforms, but not others, can
help peers identify the operating system in use by a host, which can have
privacy implications. This document aims to mitigate that possibility.</t>
    </section>
    <section anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions.</t>
    </section>
  </middle>
  <back>
    <references anchor="sec-combined-references">
      <name>References</name>
      <references anchor="sec-normative-references">
        <name>Normative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3168.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9330.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2474.xml"/>
      </references>
      <references anchor="sec-informative-references">
        <name>Informative References</name>
        <reference anchor="APPLE-NETWORK-FRAMEWORK" target="https://developer.apple.com/documentation/network/nwprotocolip/metadata">
          <front>
            <title>NWProtocolIP.Metadata</title>
            <author>
              <organization/>
            </author>
            <date/>
          </front>
        </reference>
        <reference anchor="CHROMIUM" target="https://www.chromium.org/chromium-projects/">
          <front>
            <title>The Chromium Projects</title>
            <author>
              <organization/>
            </author>
            <date/>
          </front>
        </reference>
        <reference anchor="CHROMIUM-POSIX" target="https://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_socket_posix.cc">
          <front>
            <title>udp_socket_posix.cc</title>
            <author>
              <organization/>
            </author>
            <date year="2026" month="June" day="19"/>
          </front>
          <refcontent>commit 6bdc14d</refcontent>
        </reference>
        <reference anchor="CHROMIUM-WINDOWS" target="https://source.chromium.org/chromium/chromium/src/+/main:net/socket/udp_socket_win.cc">
          <front>
            <title>udp_socket_win.cc</title>
            <author>
              <organization/>
            </author>
            <date year="2026" month="June" day="10"/>
          </front>
          <refcontent>commit 85a1d1c</refcontent>
        </reference>
        <reference anchor="EXAMPLES" target="https://github.com/nplab/ecn-examples">
          <front>
            <title>ecn-examples</title>
            <author>
              <organization/>
            </author>
            <date year="2025" month="December" day="01"/>
          </front>
          <refcontent>commit 39e39bf</refcontent>
        </reference>
        <reference anchor="WINDOWS-DOC" target="https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-wsasetrecvipecn">
          <front>
            <title>WSASetRecvIPEcn function (ws2tcpip.h)</title>
            <author>
              <organization/>
            </author>
            <date year="2024" month="February" day="22"/>
          </front>
        </reference>
        <reference anchor="WINDOWS-SOCKOPT" target="https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options">
          <front>
            <title>MSDN - IPPROTO_IP socket options</title>
            <author>
              <organization/>
            </author>
            <date year="2026" month="May" day="07"/>
          </front>
        </reference>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9293.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9260.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8261.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6679.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8888.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9000.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9868.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.4291.xml"/>
      </references>
    </references>

<section numbered="false" anchor="acknowledgments">
      <name>Acknowledgments</name>
      <t>The author would like to thank <contact fullname="Ryan Hamilton"/>, who provided constant advice
through this effort. <contact fullname="Randall Meyer"/> from Apple and <contact fullname="Nick Grifka"/> from Microsoft
provided useful hints about the behavior of their respective operating systems.
However, the author takes full responsibility for any errors above.</t>
      <t><contact fullname="Michael Tüxen"/> wrote the "ecn-examples" code that was very helpful in verifying
the conclusions in this document. He also made multiple editorial contributions.</t>
      <t><contact fullname="Neal Cardwell"/>, <contact fullname="Gorry Fairhurst"/>, <contact fullname="Max Franke"/>, <contact fullname="Rodney Grimes"/>, <contact fullname="Will Hawkins"/>,
<contact fullname="Guillaume Hétier"/>, <contact fullname="Max Inden"/>, <contact fullname="Jonathan Lennox"/>, <contact fullname="Colin Perkins"/>, <contact fullname="Marten Seemann"/>, and <contact fullname="Greg White"/> made improvements to this document.</t>
    </section>
  </back>
</rfc>
