RFC 9623

Implementing Interfaces to Transport Services

๐Ÿ“… published January 2025, from ietf

๐Ÿ’– Implementing Interfaces to Transport Services ๐Ÿ’–

Okay so bestie, welcome to RFC 9623! ๐ŸŽ€ This one is an Informational doc, meaning it's a helpful guide and not a strict rulebook, so it's giving "here's how to build this, babe" energy. ๐Ÿฆ‹

Here's the title block tea โ˜•:

  • Who wrote it: A. Brunstrom (Ed.) of Karlstad University, T. Pauly (Ed.) of Apple Inc., R. Enghardt of Netflix, P.S. Tiesel of SAP SE, and M. Welzl of the University of Oslo. Slay, what a squad! ๐Ÿ‘‘
  • Category: Informational ๐ŸŒธ
  • Date: January 2025 ๐Ÿ“…
  • Obsoletes / Updates: The original header doesn't list either, so this doc isn't replacing or changing any older RFCs. Clean slate, no drama! โœจ
  • ISSN: 2070-1721 ๐Ÿ”ฅ

The whole thing is about the Transport Services System, which lets apps use transport protocols flexibly. It has a protocol-independent Transport Services API that works asynchronously and event-driven. This doc is the implementation guide for building one of these systems. Iconic. ๐Ÿ’…


Status of This Memo ๐Ÿ“œ

This doc is not an Internet Standards Track spec. It's published for info purposes only, not the official law. ๐Ÿ™ˆ

It's a product of the IETF and reflects consensus from the IETF community. It got public review and was approved by the IESG. Not every IESG-approved doc is a candidate for any level of Internet Standard, though. See Section 2 of RFC 7841 for the deets. ๐Ÿงš

You can check the current status, any errata, and how to send feedback at https://www.rfc-editor.org/info/rfc9623. ๐Ÿ’œ

Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved. ๐ŸŒˆ

This doc is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as of the publication date. Read those carefully, bestie, because they explain your rights and restrictions. ๐Ÿ“–

Code Components pulled from this doc must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come without warranty as described in the Revised BSD License. โš ๏ธ


Table of Contents ๐Ÿ—‚๏ธ

1.  Introduction
2.  Implementing Connection Objects
3.  Implementing Preestablishment
  3.1.  Configuration-Time Errors
  3.2.  Role of System Policy
4.  Implementing Connection Establishment
  4.1.  Structuring Candidates as a Tree
    4.1.1.  Branch Types
    4.1.2.  Branching Order-of-Operations
    4.1.3.  Sorting Branches
  4.2.  Candidate Gathering
    4.2.1.  Gathering Endpoint Candidates
  4.3.  Candidate Racing
    4.3.1.  Simultaneous
    4.3.2.  Staggered
    4.3.3.  Failover
  4.4.  Completing Establishment
    4.4.1.  Determining Successful Establishment
  4.5.  Establishing Multiplexed Connections
  4.6.  Handling Connectionless Protocols
  4.7.  Implementing Listeners
    4.7.1.  Implementing Listeners for Connected Protocols
    4.7.2.  Implementing Listeners for Connectionless Protocols
    4.7.3.  Implementing Listeners for Multiplexed Protocols
5.  Implementing Sending and Receiving Data
  5.1.  Sending Messages
    5.1.1.  Message Properties
    5.1.2.  Send Completion
    5.1.3.  Batching Sends
  5.2.  Receiving Messages
  5.3.  Handling of Data for Fast-Open Protocols
6.  Implementing Message Framers
  6.1.  Defining Message Framers
  6.2.  Sender-Side Message Framing
  6.3.  Receiver-Side Message Framing
7.  Implementing Connection Management
  7.1.  Pooled Connection
  7.2.  Handling Path Changes
8.  Implementing Connection Termination
9.  Cached State
  9.1.  Protocol State Caches
  9.2.  Performance Caches
10. Specific Transport Protocol Considerations
  10.1.  TCP
  10.2.  MPTCP
  10.3.  UDP
  10.4.  UDP-Lite
  10.5.  UDP Multicast Receive
  10.6.  SCTP
11. IANA Considerations
12. Security Considerations
  12.1.  Considerations for Candidate Gathering
  12.2.  Considerations for Candidate Racing
13. References
  13.1.  Normative References
  13.2.  Informative References
Appendix A.  API Mapping Template
Appendix B.  Reasons for Errors
Appendix C.  Existing Implementations
Acknowledgements
Authors' Addresses

1. Introduction โœจ

The Transport Services Architecture [RFC9621] defines a system that lets apps flexibly use transport networking protocols. ๐Ÿฆ‹ The API that system shows to apps is the Transport Services API [RFC9622], and it's designed to be generic across multiple transport protocols and sets of protocol features. Generic and iconic. ๐Ÿ’…

This doc is a guide to implementing a system that provides a Transport Services API. It offers suggestions to devs, but it's not prescriptive, so implementations can take any form they want as long as the API spec in [RFC9622] is honored. ๐ŸŒธ The job of a Transport Services System is to turn an app's requests into decisions about how to set up connections and how to move data over them once they're up. ๐Ÿš€ The terminology here is based on the Transport Services Architecture [RFC9621]. ๐Ÿ“š

2. Implementing Connection Objects ๐ŸŽ€

The Connection objects exposed to apps for Transport Services are:

  • the Preconnection, the bundle of Properties that describes the app's constraints on, and preferences for, the transport;
  • the Connection, the basic object that represents a flow of data as Messages in either direction between the Local and Remote Endpoints;
  • and the Listener, a passive waiting object that delivers new Connections. ๐Ÿ‘‚

Preconnection objects should be implemented as bundles of Properties that an app can both read and write. A Preconnection only influences a Connection at one point in time: when the Connection is created. โฐ Connection objects are the interface between the app and the implementation for managing transport state and doing data transfer. During establishment (Section 4), the Connection won't necessarily be bound to a transport protocol instance right away, since multiple candidate Protocol Stacks might be raced. ๐Ÿ

Once a Preconnection has been used to create an outbound Connection or a Listener, the implementation should make sure the app can't mutate the copy of the Properties held by the Connection or Listener by changing the original Preconnection. ๐Ÿ”’ This may involve a deep-copy, copying the object along with all the objects it references. ๐Ÿงฌ

Once the Connection is established, the Transport Services Implementation maps actions and events to the details of the chosen Protocol Stack. ๐Ÿ—บ๏ธ For example, the same Connection object may end up representing a single transport protocol instance, like a TCP connection, a TLS session over TCP, a UDP flow with fully specified Local and Remote Endpoint Identifiers, a DTLS session, a Stream Control Transmission Protocol (SCTP) stream, a QUIC stream, or an HTTP/2 stream. ๐ŸŒˆ The Connection Properties held by a Connection or Listener are independent of other Connections that aren't part of the same Connection Group. ๐Ÿ’œ

Connection establishment is only a local operation for connectionless protocols. It exists to simplify the local send/receive functions and to filter traffic for the specified addresses and ports [RFC8085], for example when using UDP or UDP-Lite transport without a connection handshake procedure. ๐Ÿงน

Once Initiate has been called, the Selection Properties and Endpoint information of the created Connection are immutable. In other words, an app can't later change the Properties of a Connection by manipulating the original Preconnection object. ๐Ÿ™…โ€โ™€๏ธ Listener objects are created with a Preconnection, and at that point their configuration should be treated as immutable by the implementation. The listening process is described in Section 4.7. ๐Ÿ‘‚โœจ

3. Implementing Preestablishment ๐ŸŒท

The preestablishment phase lets apps specify Properties for the Connections they're about to make, or ask the API about potential Connections they could make. ๐Ÿ”

During preestablishment, the app specifies one or more Endpoints to use for communication, along with protocol preferences and constraints via Selection Properties and, if it wants, Connection Properties too. ๐ŸŽฏ Section 4 of [RFC9622] says Connection Properties should preferably be configured during preestablishment, because they can feed into the implementation's decisions. For example, the capacity profile can guide the use of a protocol offering scavenger-type congestion control. ๐Ÿง 

The implementation stores these Properties as part of the Preconnection object for use during Connection establishment. For Selection Properties the app doesn't provide, the implementation uses the default values specified in the Transport Services API ([RFC9622]). ๐Ÿงพ

3.1. Configuration-Time Errors ๐Ÿšจ

The Transport Services System should have a list of supported protocols available, and each one has transport features reflecting that protocol's capabilities. ๐Ÿ“‹ Once an app specifies its Transport Properties, the system matches the required and prohibited Properties against the transport features of the available protocols (see Section 6.2 of [RFC9622] for the definition of Property Preferences). ๐Ÿ”Ž

Failure should be detected during preestablishment in these cases:

  • An app requests Properties that can't be satisfied by any of the available protocols. For example, if an app requires perMsgReliability but no protocol on the host running the Transport Services System has that feature, this should result in an error. ๐Ÿ’”
  • An app requests Properties that conflict with each other, such as requiring and prohibiting Properties that no protocol can satisfy together. For example, if an app prohibits reliability and then requires perMsgReliability, this mismatch should result in an error. ๐Ÿ™ƒ

To avoid allocating resources that aren't needed, configuration-time errors need to fail as early as possible. โฉ

Part 1 of 12 done, bestie! ๐Ÿซถ Sections 3.2 onward are coming in the next parts, so stay tuned. ๐Ÿ’…

3.2. Role of System Policy ๐ŸŽ€

Okay so the Properties you set during preestablishment are super tied to System Policy, bestie ๐Ÿ’… The implementation is responsible for combining and reconciling several different sources of preferences when it sets up Connections. The list isn't exhaustive (it's giving "and more" energy โœจ), but these are the main ones:

  1. Application preferences, i.e., preferences specified during preestablishment via Selection Properties. This is the app's wishlist ๐Ÿงš
  2. Dynamic System Policy, i.e., policy compiled from internally and externally acquired information about available network interfaces, supported transport protocols, and current/previous Connections. Some ways to pull in policy-support info from outside the system are OS-specific statistics/measurement tools and tools that live on middleboxes and routers. It's the real-time vibe check ๐Ÿ‘€
  3. Default implementation policy, i.e., predefined policy by the OS or application. Think of it as the baseline defaults ๐ŸŒธ

In general, any protocol or path used for a Connection must conform to all three sources of constraints. A violation at any of the policy layers should cause a protocol or path to be considered ineligible for use. If such a violation prevents a Connection from being established, this should be communicated to the application, e.g., via the EstablishmentError event. For example, an application may prohibit the use of metered network interfaces for a given Connection to avoid user cost ๐Ÿ’ธ Similarly, the System Policy at a given time may prohibit the use of such a metered network interface from the application's process. Lastly, the implementation itself may default to disallowing certain network interfaces unless explicitly requested by the application. Main character rules, no cap ๐ŸŽฌ

It is expected that the database of system policies and the method of looking up these policies will vary across various platforms. An implementation should attempt to look up the relevant policies for the system in a dynamic way, so it reflects an accurate version of the System Policy. The system's policy about the application's traffic may change over time due to user or administrative changes, so it has to stay current ๐Ÿ”„


4. Implementing Connection Establishment โœจ

Okay, this is where it gets fun ๐Ÿฆ‹ The process of establishing a network connection begins when an application expresses intent to communicate with a Remote Endpoint by calling Initiate. At that point, the Preconnection object contains all the constraints or requirements the application has configured. Establishment can be considered complete once there is at least one Protocol Stack that has completed any required setup to the point that it can transmit and receive the application's data. Ready to slay ๐Ÿ’–

Connection establishment is divided into two top-level steps:

  • Candidate Gathering (defined in Section 4.2.1 of [RFC9621]) to identify the paths, protocols, and endpoints to use (see Section 4.2) and
  • Candidate Racing (defined in Section 4.2.2 of [RFC9621]), in which the necessary protocol handshakes are conducted so that the Transport Services System can select which set to use (see Section 4.3).

Candidate Racing means attempting multiple options for Connection establishment and choosing the first option to succeed as the Protocol Stack to use for the Connection. These attempts are usually staggered, with each next option starting after a delay. They can also be performed in parallel or after failures occur, so it's flexible ๐Ÿƒโ€โ™€๏ธ

For ease of illustration, this document structures the candidates for racing as a tree (see Section 4.1). This is not meant to restrict implementations from structuring racing candidates differently. You do you ๐ŸŒˆ

The simplest example of this process might involve identifying the single IP address to which the implementation wishes to connect, using the system's current default path (i.e., using the default interface), and starting a TCP handshake to establish a stream to the specified IP address. However, each step may also differ depending on the requirements of the connection:

  • if the Endpoint Identifier is a hostname and port, then there may be multiple resolved addresses that are available;
  • there may also be multiple paths available (in this case using an interface other than the default system interface); and
  • some protocols may not need any transport handshake to be considered "established" (such as UDP), while other connections may utilize layered protocol handshakes, such as TLS over TCP.

Whenever an implementation has multiple options for Connection establishment, it can view the set of all individual Connection establishment options as a single aggregate Connection establishment. The aggregate set conceptually includes every valid combination of endpoints, paths, and protocols. Picture this: an implementation initiates a TCP connection to a hostname + port Endpoint Identifier and has two valid interfaces available (Wi-Fi and LTE). The hostname resolves to a single IPv4 address on the Wi-Fi network, to the same IPv4 address on the LTE network, and to a single IPv6 address. The aggregate set of Connection establishment options can be viewed as follows, with the Endpoint Identifier abbreviated as "EId":

Aggregate [EId: example.com:443] [Interface: Any]   [Protocol: TCP]
|-> [EId: [3fff:23::1]:443]      [Interface: Wi-Fi] [Protocol: TCP]
|-> [EId: 192.0.2.1:443]         [Interface: LTE]   [Protocol: TCP]
|-> [EId: [3fff:42::1]:443]      [Interface: LTE]   [Protocol: TCP]

Any one of these subentries on the aggregate connection attempt would satisfy the original application intent. The concern of this section is the algorithm defining which of these options to try, when to try them, and in what order ๐Ÿง 

During Candidate Gathering (Section 4.2), an implementation prunes and sorts branches according to the Selection Property Preferences (Section 6.2 of [RFC9622]). First, it excludes all protocols and paths that match a prohibited Property or do not match all required Properties. Then, it will sort branches according to preferred Properties, avoided Properties, and, possibly, other criteria. Filter first, then sort, periodt ๐Ÿ’…


4.1. Structuring Candidates as a Tree ๐ŸŒณ

As noted above, the consideration of multiple candidates in a gathering and racing process can be conceptually structured as a tree. This terminological convention is used throughout this document, so keep it in your back pocket ๐Ÿงš

Each leaf node of the tree represents a single coherent connection attempt with an endpoint, a network path, and a set of protocols that can directly negotiate and send data on the network. Each node in the tree that is not a leaf represents a connection attempt that is either underspecified or includes multiple distinct options. For example, when connecting on an IP network, a connection attempt to a hostname and port is underspecified, because the connection attempt requires a resolved IP address as its Remote Endpoint Identifier. In this case, the node represented by the connection attempt to the hostname is a parent node with child nodes for each IP address. Similarly, an implementation that is allowed to connect using multiple interfaces will have a parent node of the tree for the decision between the network paths, with a branch for each interface ๐Ÿฆ‹

The example aggregate connection attempt above can be drawn as a tree by grouping the addresses resolved on the same interface into branches:

                               ||
                 +============================+
                  www.example.com:443/any path
                 +============================+
                   //                     \\
   +=========================+      +=======================+
    www.example.com:443/Wi-Fi        www.example.com:443/LTE
   +=========================+      +=======================+
               ||                      //               \\
   +======================+  +=================+  +====================+
    [3fff:23::1]:443/Wi-Fi    192.0.2.1:443/LTE    [3fff:42::1]:443/LTE
   +======================+  +=================+  +====================+

The rest of this section uses a notation scheme to represent this tree. The root node (or parent node) of the tree is represented by a single integer, such as "1". ("1" is used assuming this is the first connection made by the system. Future connections created by the application would allocate numbers in an increasing manner ๐Ÿ”ข) Each child of that node has an integer that identifies it, from 1 to the number of children. That child node is uniquely identified by concatenating its integer to its parent's identifier with a dot character (".") in between, such as "1.1" and "1.2". Each node is summarized by a tuple of three elements: endpoint, path (labeled here by interface), and protocol. In Protocol Stacks, the layers are separated by a slash character ("/") and ordered with the protocol closest to the application first. The above example can now be written more succinctly as:

1 [www.example.com:443, any path, TCP]
  1.1 [www.example.com:443, Wi-Fi, TCP]
    1.1.1 [[2001:db8:23::1]:443, Wi-Fi, TCP]
  1.2 [www.example.com:443, LTE, TCP]
    1.2.1 [192.0.2.1:443, LTE, TCP]
    1.2.2 [[2001:db8.42::1]:443, LTE, TCP]

When an implementation is asked to establish a single connection, only one of the leaf nodes in the candidate set is needed to transfer data. Thus, once a single leaf node becomes ready to use, the Connection establishment tree is considered ready. One way to implement this is to have every leaf node update the state of its parent node when it becomes ready, until the root node of the tree is ready. The root then notifies the application that the Connection as a whole is ready to use ๐ŸŽ‰

A Connection establishment tree may consist of only a single node, such as a connection attempt to an IP address over a single interface with a single protocol:

1 [[2001:db8:23::1]:443, Wi-Fi, TCP]

A root node may also have only one child (or leaf) node, such as when a hostname resolves to only a single IP address:

1 [www.example.com:443, Wi-Fi, TCP]
  1.1 [[2001:db8:23::1]:443, Wi-Fi, TCP]

4.1.1. Branch Types ๐Ÿงฉ

There are three types of branching from a parent node into one or more child nodes: Derived Endpoints, network paths, and protocol options. Any parent node of the tree must use only one type of branching. One flavor per parent, no mixing ๐Ÿ“

4.1.1.1. Derived Endpoints

Okay so, if a connection starts out aimed at one Endpoint Identifier, there can be several endpoint candidates of different types that get derived from it ๐Ÿ’… โœจ These form an ordered list based on what the app prefers, the System Policy, and how well each one is expected to perform ๐ŸŽ€

DNS hostname-to-address resolution is the most common way to derive endpoints ๐ŸŒธ When you're connecting to a hostname Endpoint Identifier on an IP network, the implementation should send all the applicable DNS queries ๐Ÿฆ‹ Usually that means both A (IPv4) and AAAA (IPv6) records, as long as both address families work on the local interface ๐Ÿ’– It can also include SRV records [RFC2782], SVCB and HTTPS records [RFC9460], or other record types that show up later ๐Ÿงš The way these addresses get ordered and raced should follow the recommendations in Happy Eyeballs [RFC8305] ๐Ÿซถ

1 [www.example.com:443, Wi-Fi, TCP]
  1.1 [[2001:db8::1]:443, Wi-Fi, TCP]
  1.2 [192.0.2.1:443, Wi-Fi, TCP]
  1.3 [[2001:db8::2]:443, Wi-Fi, TCP]
  1.4 [[2001:db8::3]:443, Wi-Fi, TCP]

DNS-Based Service Discovery [RFC6763] can also do an endpoint derivation step ๐Ÿ‘‘ When you're trying to reach a named service, the client might discover one or more hostname and port pairs on the local network using multicast DNS [RFC6762] ๐ŸŒˆ Each of these hostnames should be treated as its own branch that can be attempted independently of the other hostnames โญ Each hostname might resolve to one or more addresses, which creates multiple layers of branching ๐Ÿ˜ญ

1 [term-printer._ipp._tcp.meeting.example.com, Wi-Fi, TCP]
  1.1 [term-printer.meeting.example.com:631, Wi-Fi, TCP]
    1.1.1 [31.133.160.18:631, Wi-Fi, TCP]

Apps can steer which derived Endpoints are allowed and preferred using Selection Properties set on the Preconnection ๐Ÿ”ฅ For example, setting a preference for useTemporaryLocalAddress would prefer IPv6 over IPv4, and requiring useTemporaryLocalAddress would eliminate IPv4 options, since IPv4 doesn't support temporary addresses ๐Ÿ’€

4.1.1.2. Network Paths

If a client has multiple network paths available, e.g., a mobile client with both Wi-Fi and Cellular interfaces, it can attempt a connection over any of those paths ๐Ÿ“ฑ This is a branch point in Connection establishment ๐Ÿฆ‹ Just like with derived endpoints, the paths should be ranked by preference, policy, and performance ๐Ÿ’… Attempts should start on one path (e.g., a specific interface) and then move on to other paths (or interfaces) after delays based on the expected path RTT or other available metrics โœจ

1 [192.0.2.1:443, any path, TCP]
  1.1 [192.0.2.1:443, Wi-Fi, TCP]
  1.2 [192.0.2.1:443, LTE, TCP]

The same idea applies anywhere the client knows about multiple links or views of the network ๐ŸŒธ A single interface can be shared by multiple network paths, each with its own coherent set of addresses, routes, DNS server, and more ๐Ÿงš A path can also be a virtual interface service, like a Virtual Private Network (VPN) ๐Ÿ”

The list of available paths should be constrained by any requirements the application sets, as well as by the System Policy ๐Ÿซถ

4.1.1.3. Protocol Options

Different possible protocol compositions and options can also create a branching point in Connection establishment ๐ŸŒˆ This lets clients bounce back when a certain protocol isn't working on a server or network ๐Ÿ’ช

This is commonly used for connections with optional proxy server configurations ๐ŸŽ€ A single connection might have several options available: an HTTP-based proxy, a SOCKS-based proxy, or no proxy at all ๐Ÿ“ Same as above, these options should be ranked by preference, System Policy, and performance, and attempted in succession ๐Ÿ’–

1 [www.example.com:443, any path, HTTP/TCP]
  1.1 [192.0.2.8:443, any path, HTTP/HTTP Proxy/TCP]
  1.2 [192.0.2.7:10234, any path, HTTP/SOCKS/TCP]
  1.3 [www.example.com:443, any path, HTTP/TCP]
    1.3.1 [192.0.2.1:443, any path, HTTP/TCP]

This approach also lets a client try different sets of application and transport protocols that, when available, could give it nicer features ๐Ÿฆ‹ For example, the protocol options could include QUIC [RFC9000] over UDP on one branch and HTTP/2 [RFC9113] over TLS over TCP on the other ๐Ÿซง

1 [www.example.com:443, any path, HTTP]
  1.1 [www.example.com:443, any path, HTTP3/QUIC/UDP]
    1.1.1 [192.0.2.1:443, any path, HTTP3/QUIC/UDP]
  1.2 [www.example.com:443, any path, HTTP2/TLS/TCP]
    1.2.1 [192.0.2.1:443, any path, HTTP2/TLS/TCP]

Another example is racing SCTP against TCP ๐Ÿงœโ€โ™€๏ธ

1 [www.example.com:4740, any path, reliable-inorder-stream]
  1.1 [www.example.com:4740, any path, SCTP]
    1.1.1 [192.0.2.1:4740, any path, SCTP]
  1.2 [www.example.com:4740, any path, TCP]
    1.2.1 [192.0.2.1:4740, any path, TCP]

Implementations that support racing protocols and protocol options should keep a history of which protocols and protocol options were successfully established on a per-network and per-endpoint basis (see Section 9.2) ๐Ÿ“š That info can then shape future racing decisions, helping to prioritize or prune branches ๐Ÿง 

4.1.2. Branching Order-of-Operations

Branch types ought to happen in a specific order relative to one another, so you don't end up with leaf nodes that have invalid or incompatible settings ๐Ÿ™ˆ In the example above, it would be invalid to branch for derived endpoints (the DNS results for www.example.com) before branching between interface paths ๐Ÿ˜ฌ That's because results can differ across networks due to private names or different supported IP versions ๐ŸŒ Implementations need to be careful to branch in a consistent order that produces usable leaf nodes whenever multiple branch types could be used from a single node ๐Ÿ’…

This document recommends the following order of operations for branching:

  1. Network paths
  2. Protocol options
  3. Derived Endpoints

Lower numbers mean higher precedence, and therefore higher placement in the tree โœจ Branching between paths comes first because results across multiple interfaces probably aren't related to each other ๐Ÿซถ Endpoint resolution may return different results, especially when using locally resolved host and service names, and the protocols supported and preferred may differ across interfaces ๐ŸŒˆ So if multiple paths get attempted, the overall Connection establishment process can be seen as a race between the available paths or interfaces ๐Ÿ

Protocol options are checked next, in order ๐ŸŽ€ Whether a set of protocols, or protocol-specific options, can successfully connect generally doesn't depend on which specific IP address is used ๐Ÿ’œ Also, the Protocol Stacks being attempted may influence or even change the Endpoint Identifiers being used ๐Ÿฆ‹ Adding a proxy to a connection's branch changes the Endpoint Identifier to the proxy's IP address or hostname ๐Ÿงš Choosing an alternate protocol may also change which ports should be selected ๐Ÿ“

Branching for derived endpoints is the final step, and it may have multiple layers of derivation or resolution, such as DNS service resolution and DNS hostname resolution ๐Ÿ‘‘

For example, say the application has indicated both a preference for Wi-Fi over LTE and for a feature only available in SCTP ๐Ÿค” Branches get sorted by path selection first, so Wi-Fi gets attempted as the first path ๐Ÿ›œ Then, branches with SCTP get attempted within their subtree according to the Properties influencing protocol selection ๐Ÿ’… But if the implementation has current cache information that SCTP isn't available on the Wi-Fi path, there won't be an SCTP node in the Wi-Fi subtree ๐Ÿ˜ญ Here, the Wi-Fi path gets attempted first and, if connection establishment succeeds, TCP gets used ๐ŸŽ‰ So the Selection Property preferring Wi-Fi takes precedence over the Property that led to a preference for SCTP ๐Ÿ‘‘

1. [www.example.com:80, any path, reliable-inorder-stream]
1.1 [192.0.2.1:443, Wi-Fi, reliable-inorder-stream]
1.1.1 [192.0.2.1:443, Wi-Fi, TCP]
1.2 [192.0.3.1:443, LTE, reliable-inorder-stream]
1.2.1 [192.0.3.1:443, LTE, SCTP]
1.2.2 [192.0.3.1:443, LTE, TCP]

4.1.3. Sorting Branches โœจ

okay so bestie, now we're sorting the branches of the tree of connection options ๐ŸŒณ๐Ÿ’… from most preferred to least preferred, going by the preference rank from Selection Properties [RFC9622]. The higher-ranked branches have leaf nodes that get raced first, so they're the VIPs of the line ๐Ÿ‘‘

The application's Properties aren't the only thing that can shape the ranking ๐Ÿงš. An implementation may also toss in other criteria, like cached performance estimates (see Section 9.2) or System Policy (see Section 3.2). Two examples of how Selection and Connection Properties can sort branches are below ๐ŸŽ€

"Interface Instance or Type" (Property name interface):

If the app says it wants a certain interface type preferred or avoided, implementations should rank the paths to match ๐Ÿฆ‹ If the app says an interface type is required or prohibited, an implementation is expected to exclude the paths that don't conform ๐Ÿšซ no exceptions, periodt ๐Ÿ’€

"Capacity Profile" (Property name connCapacityProfile):

An implementation can use the capacity profile to prefer paths that fit the app's expected traffic vibe ๐ŸŒธ This matching uses cached performance estimates (see Section 9.2). Some examples of path preferences based on capacity profiles:

  • Low Latency/Interactive: Prefer paths with the lowest expected Round-Trip Time (RTT), based on observed RTT estimates ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ
  • Low Latency/Non-Interactive: Prefer paths with a low expected Round-Trip Time (RTT) and possible delay variation ๐ŸŒŠ
  • Constant-Rate Streaming: Prefer paths that are expected to satisfy the requested stream send or receive bitrate, based on the observed maximum throughput ๐Ÿ“บโœจ
  • Capacity-Seeking: Prefer adapting to paths to determine the highest available capacity, based on the observed maximum throughput ๐Ÿš€

As another example, branch sorting can also be influenced by bounds on the send or receive rate (Selection Properties minSendRate / minRecvRate / maxSendRate / maxRecvRate) ๐Ÿ’– If the app indicates a bound on the expected send or receive bitrate, an implementation may prefer a path that can likely provide the desired bandwidth, based on cached maximum throughput (see Section 9.2). Apps may know that bitrate from metadata in adaptive HTTP streaming, like MPEG-DASH ๐ŸŽฌ

Implementations process the Properties (Section 6.2 of [RFC9622]) in this order: Prohibit, Require, Prefer, Avoid ๐Ÿ”ฅ If the Selection Properties contain any prohibited Properties, the implementation should first purge branches containing nodes with those Properties. For required Properties, it should only keep branches that satisfy them. Finally, it should order the branches according to the preferred Properties and use any avoided Properties as a tiebreaker ๐Ÿง When ordering branches, an implementation can give more weight to Properties the application explicitly set, rather than to the ones set by default ๐Ÿซถ

The available protocols and paths on a system can change depending on the context ๐ŸŒˆ So the result of sorting and the outcome of racing may vary, even with the same Selection and Connection Properties. Still, an implementation ought to give applications a consistent outcome ๐Ÿ™ˆ for example, by preferring protocols and paths that existing Connections with similar Properties are already using โœจ


4.2. Candidate Gathering ๐Ÿ—บ๏ธ

okay so this step is all about figuring out which paths, protocols, and endpoints could be used for a given Connection ๐Ÿงญ The list is shaped by the app's requirements, prohibitions, preferences, and avoidances, as specified in the Selection Properties ๐Ÿ’…

4.2.1. Gathering Endpoint Candidates ๐ŸŽ€

Both Local and Remote Endpoint Candidates must be discovered during Connection establishment ๐Ÿ’– To support Interactive Connectivity Establishment (ICE) [RFC8445], or similar protocols that do out-of-band indirect signaling to swap candidates with the Remote Endpoint, it's important to query the set of candidate Local Endpoints and hand the Protocol Stack a set of candidate Remote Endpoints before the Local Endpoint tries to establish connections ๐Ÿซถ

4.2.1.1. Local Endpoint Candidates ๐Ÿ 

The set of possible Local Endpoints gets gathered ๐Ÿงบ In a simple case, this just enumerates the local interfaces and protocols and allocates ephemeral source ports. For example, a system with Wi-Fi and Ethernet that supports IPv4 and IPv6 might gather four candidate Local Endpoints (IPv4 on Ethernet, IPv6 on Ethernet, IPv4 on Wi-Fi, and IPv6 on Wi-Fi) that can form the source for a transient ๐Ÿฆ‹

If NAT traversal is required, gathering Local Endpoints becomes broadly equivalent to the ICE Candidate Gathering phase (see Section 5.1.1 of [RFC8445]) ๐ŸŒ The endpoint works out its server-reflexive Local Endpoints (the translated address of a Local Endpoint, on the other side of a NAT, e.g., via a STUN server [RFC8489]) and relayed Local Endpoints (e.g., via a TURN server [RFC8656] or other relay) for each interface and network protocol. These get added to the set of candidate Local Endpoint Identifiers for this connection ๐Ÿ”ฅ

Gathering Local Endpoints is mainly a local operation ๐Ÿก It might involve exchanges with a STUN server to derive server-reflexive Local Endpoints, or with a TURN server or other relay to derive relayed Local Endpoints. But it does not involve communication with the Remote Endpoint, so no one else is in the chat yet ๐Ÿ™ˆ

4.2.1.2. Remote Endpoint Candidates ๐ŸŒ

The Remote Endpoint Identifier is typically a name that needs resolving into a set of possible addresses that can be used for communication ๐Ÿ“› Resolving the Remote Endpoint means recursively performing name lookups until it's fully resolved, returning the set of candidates for the Remote Endpoint of this Connection ๐Ÿ”

How this resolution happens depends on the type of Remote Endpoint, and can also be specific to each Local Endpoint ๐Ÿ’ซ A common case is when the Remote Endpoint Identifier is a DNS name, which gets resolved into a set of IPv4 and IPv6 addresses representing that name ๐ŸŒธ Some types of Remote Endpoint Identifiers might need more complex resolution. Resolving the Remote Endpoint for a peer-to-peer connection might involve communicating with a rendezvous server. The server then contacts the peer to get consent to communicate and retrieve its set of candidate Local Endpoints ๐Ÿค These Endpoints are returned and form the candidate remote addresses for contacting that peer ๐Ÿ’œ

Resolving the Remote Endpoint is not a local operation ๐Ÿšซ It involves a directory service and can require communication between the Remote Endpoint and a rendezvous server, plus the exchange of peer addresses. This can expose some or all of the candidate Local Endpoints to the Remote Endpoint ๐Ÿ‘€ so be mindful of what you're sharing, bestie ๐Ÿซฃ

4.3. Candidate Racing ๐Ÿ

The main goal of the Candidate Racing process is to successfully negotiate a Protocol Stack to an Endpoint over an interface, connecting a single leaf node of the tree with as little delay and as few unnecessary connection attempts as possible โšก Optimizing these two things improves the user experience while keeping network load down ๐Ÿง˜โ€โ™€๏ธ

This section covers the dynamic side of Connection establishment ๐ŸŽข The tree described above is a useful conceptual and architectural model. But an implementation can't know all the nodes it'll use until steps like name resolution have happened, and many of the possible branches might never get attempted ๐Ÿคทโ€โ™€๏ธ

There are three different approaches to racing the attempts for different nodes of the Connection establishment tree:

  1. Simultaneous
  2. Staggered
  3. Failover

Each approach fits different use cases and branch types ๐Ÿ’… However, to avoid wasting network resources, implementations should not use simultaneous racing as the default approach ๐Ÿšซ

The timing algorithms for racing should stay independent across branches of the tree ๐Ÿงฉ Any timer or racing logic is isolated to a given parent node and is not ordered precisely with regard to the children of other nodes ๐Ÿ’œ

4.3.1. Simultaneous ๐Ÿ’ฅ

Simultaneous racing is when multiple alternate branches start without waiting for any one branch to make progress before starting the next alternative ๐ŸŽ๏ธ So the attempts are effectively simultaneous. Simultaneous racing should be avoided by implementations, since it eats extra network resources and sets up state that might never get used ๐Ÿ˜ญ

4.3.2. Staggered ๐ŸŒŸ

Staggered racing can be used whenever a single node of the tree has multiple child nodes ๐Ÿชœ Based on the order determined when building the tree, the first child node gets initiated immediately, followed by the next child node after some delay. Once that second child node is initiated, the third child node (if present) begins after another delay, and so on, until all child nodes have been initiated or one of them successfully completes its negotiation ๐Ÿ

Staggered racing attempts can proceed in parallel. Implementations should not terminate an earlier child connection attempt upon starting a secondary child ๐Ÿซถ

If a child node fails to establish connectivity (as in Section 4.4.1) before the delay time has expired for the next child, the next child should be started immediately โฉ

Staggered racing between IP addresses for a generic Connection should follow the Happy Eyeballs algorithm described in [RFC8305]. Guidance for racing when performing ICE can be found in [RFC8421] ๐Ÿ“š

Generally, the delay before starting a given child node ought to be based on how long the previously started child node is expected to take before it succeeds or makes progress in connection establishment โฑ๏ธ Algorithms like Happy Eyeballs choose a delay based on how long the transport connection handshake is expected to take ๐Ÿค When performing staggered races across multiple branch types (such as racing between network interfaces and then racing between IP addresses), a longer delay may be chosen for some branch types. For example, when racing between network interfaces, the delay should also account for the time it takes to prepare the network interface (such as radio association) and name resolution over that interface, in addition to the delay that would be added for a single transport connection handshake ๐Ÿ“ถโœจ

Since the staggered delay can be chosen based on dynamic information, such as predicted RTT, implementations should define upper and lower bounds for delay times ๐Ÿง  These bounds are implementation specific and may differ based on which branch type is being used ๐Ÿ’–

4.3.3. Failover ๐Ÿฆ‹

okay so if an implementation or application has a strong preference for one branch over another, the branching node may choose to wait until one child has failed before starting the next. ๐Ÿซถ a leaf node counts as failed when its protocol negotiation fails or times out, and a parent branching node counts as failed once all of its children have failed.

an example where failover is recommended is a race between a preferred Protocol Stack that uses a proxy and an alternate Protocol Stack that bypasses the proxy. failover is useful if the proxy is down or misconfigured, but any more aggressive kind of racing may end up unnecessarily avoiding a proxy that was preferred by policy. ๐Ÿ’€

4.4. Completing Establishment ๐Ÿ‘‘

the process of Connection establishment completes when one leaf node of the tree has successfully completed negotiation with the Remote Endpoint, or when all nodes of the tree have failed to connect. โœจ the first leaf node to finish connecting is then used by the application to send and receive data. the application gets this signal through the Ready event in the API (Section 7.1 of [RFC9622]).

successes and failures of a given attempt should be reported up to parent nodes (toward the root of the tree). for example, in the following case, if 1.1.1 fails to connect, it reports the failure to 1.1. since 1.1 has no other child nodes, it also has failed and reports that failure to 1. because 1.2 has not yet failed, 1 is not considered to have failed. since 1.2 has not yet started, it is started and the process continues. similarly, if 1.1.1 successfully connects, then it marks 1.1 as connected, which propagates to the root node 1. at this point, the Connection as a whole is considered to be successfully connected and ready to process application data. ๐ŸŒธ

1 [www.example.com:443, Any, TCP]
  1.1 [www.example.com:443, Wi-Fi, TCP]
    1.1.1 [192.0.2.1:443, Wi-Fi, TCP]
  1.2 [www.example.com:443, LTE, TCP]
...

if a leaf node has successfully completed its connection, all other attempts should be made ineligible for use by the application for the original request. new connection attempts that involve transmitting data on the network ought not to be started after another leaf node has already successfully completed, because the Connection as a whole has now been established. an implementation could choose to let certain handshakes and negotiations complete to gather metrics that influence future connections. keeping additional connections is generally not recommended, because those attempts were slower to connect and may exhibit less desirable properties. ๐Ÿ™ˆ

4.4.1. Determining Successful Establishment ๐Ÿ”ฅ

on a per-protocol basis, implementations may select different criteria for deciding when a leaf node is successfully connected. if the only protocol being used is a transport protocol with a clear handshake, like TCP, then the obvious choice is to declare that node "connected" when the three-way handshake completes. ๐Ÿงš if the only protocol being used is a connectionless protocol, like UDP, the implementation may consider the node fully "connected" the moment it determines a route is present, before sending any packets on the network. see further in Section 4.6.

depending on the protocols involved, there is no guarantee that the Remote Endpoint will be notified when the Initiate action is called without any Messages being sent at the same time. ๐Ÿ˜ญ so a passive Endpoint's application may not receive a ConnectionReceived event until it receives the first Message on the new Connection.

for Protocol Stacks with multiple handshakes, the decision gets more nuanced. if the Protocol Stack involves both TLS and TCP, an implementation could determine that a leaf node is connected after the TCP handshake is complete, or it can wait for the TLS handshake to complete as well. the benefit of declaring completion when the TCP handshake finishes (and therefore stopping the race for other branches of the tree) is reduced burden on the network and Remote Endpoints from further connection attempts that are likely to be abandoned. on the other hand, by waiting until the TLS handshake is complete, an implementation avoids the scenario where a TCP handshake completes quickly, but TLS negotiation is either very slow or fails altogether in particular network conditions or to a particular endpoint. to avoid the issue of TLS possibly failing, the implementation should not generate a Ready event for the Connection until the TLS handshake is complete. ๐Ÿฆ‹

if all of the leaf nodes fail to connect during racing, i.e., none of the configurations that satisfy all requirements given in the Transport Properties actually work over the available paths, then the Transport Services System should report an EstablishmentError to the application. an EstablishmentError event should also be generated if the Transport Services System finds no usable candidates to race. ๐Ÿฅ€

4.5. Establishing Multiplexed Connections ๐ŸŒˆ

multiplexing several Connections over a single underlying transport connection requires that the multiplexed Connections belong to the same Connection Group (as indicated by the application using the Clone action). โœจ when the underlying transport connection supports multistreaming, the Transport Services System can map each Connection in the Connection Group to a different stream of that connection.

for such streams, there is often no explicit connection establishment procedure for the new stream before sending data on it (e.g., with SCTP). in this case, the same considerations apply to determining stream establishment as apply to establishing a UDP connection, as discussed in Section 4.4.1. this means there might not be any "establishment" message (like a TCP SYN). ๐Ÿซง

4.6. Handling Connectionless Protocols ๐Ÿงš

while protocols that use an explicit handshake to validate a connection to a peer can be used for racing multiple establishment attempts in parallel, connectionless protocols such as raw UDP do not offer a way to validate the presence of a peer or the usability of a Connection without application feedback. an implementation should consider such a Protocol Stack to be established as soon as the Transport Services System has selected a path on which to send data. ๐Ÿ›ธ

however, this can cause a problem if a specific peer is not reachable over the network using the connectionless protocol, or if data cannot be exchanged with the peer for any other reason. ๐Ÿ˜ฌ to handle the lack of an explicit handshake in the underlying protocol, an application can use a Message Framer (Section 6) on top of a connectionless protocol. the Message Framer can then mark a specific connection attempt as ready only when some data has been received, or after some application-level handshake has been performed. ๐Ÿ’œ

4.7. Implementing Listeners ๐ŸŽ€

when an implementation is asked to Listen, it registers with the system to wait for incoming traffic to the Local Endpoint. if no Local Endpoint Identifier is specified, the implementation should use an ephemeral port. ๐Ÿ“

if the Selection Properties do not require a single network interface or path but allow the use of multiple paths, the Listener object should register for incoming traffic on all of the network interfaces or paths that conform to the Properties. the set of available paths can change over time, so the implementation should monitor network path changes and change the registration of the Listener across all usable paths as appropriate. when using multiple paths, the Listener is generally expected to use the same port for listening on each. ๐ŸŒท

if the Selection Properties allow multiple protocols to be used for listening and the implementation supports it, the Listener object should support receiving inbound connections for each eligible protocol on each eligible path. ๐Ÿซถ

4.7.1. Implementing Listeners for Connected Protocols ๐Ÿ’…

connected protocols such as TCP and TLS-over-TCP have a strong mapping between the Local and Remote Endpoint Identifiers (four-tuple) and their protocol connection state. these map to Connection objects. whenever a new inbound handshake is being started, the Listener should generate a new Connection object and pass it to the application. ๐Ÿ”—

4.7.2. Implementing Listeners for Connectionless Protocols ๐ŸŒŠ

connectionless protocols such as UDP and UDP-Lite generally do not provide the same mechanisms that connected protocols do to offer Connection objects. implementations should wait for incoming packets for connectionless protocols on a listening port, and should perform four-tuple matching of packets to existing Connection objects if possible. if a matching Connection object does not exist, an incoming packet from a connectionless protocol should cause a new Connection object to be created. ๐Ÿ“ฆ

4.7.3. Implementing Listeners for Multiplexed Protocols ๐Ÿช„

protocols that provide multiplexing of streams can listen for entirely new connections as well as for new subconnections (streams of an already-existing connection). a new stream arrival on an existing connection is presented to the application as a new Connection. this new Connection is grouped with all other Connections that are multiplexed via the same protocol. ๐Ÿงฉ

5. Implementing Sending and Receiving Data ๐Ÿ’–

the most basic mapping for sending a Message is an abstraction of datagrams, in which the transport protocol naturally deals in discrete packets (such as UDP). each Message here corresponds to a single datagram. ๐Ÿ“ฌ

for protocols that expose byte-streams (such as TCP), the only delineation provided by the protocol is the end of the stream in a given direction. each Message in this case corresponds to the entire stream of bytes in a direction. these Messages may be quite long, in which case they can be sent in multiple parts. ๐ŸŒŠ

protocols that provide framing (such as length-value protocols, or protocols that use delimiters like HTTP/1.1) may support Message sizes that do not fit within a single datagram. each Message for framing protocols corresponds to a single frame, which may be sent either as a complete Message in the underlying protocol or in multiple parts. ๐ŸŽž๏ธ

Messages themselves generally consist of bytes passed in the messageData parameter, intended to be processed at an application layer. however, Message objects presented through the API can carry associated Message Properties passed through the messageContext parameter. when these are Protocol-specific Properties, they can include metadata that exists separately from a byte encoding. for example, these Properties can include name-value pairs of information, like HTTP header fields. in such cases, Messages might be "empty" insofar as they contain zero bytes in the messageData parameter, but they can still include data in the messageContext that is interpreted by the Protocol Stack. ๐Ÿซง

5.1. Sending Messages ๐Ÿ“

the effect of the application sending a Message is determined by the top-level protocol in the established Protocol Stack. that is, if the top-level protocol provides an abstraction of framed Messages over a connection, the receiving application will be able to obtain multiple Messages on that connection, even if the framing protocol is built on a byte-stream protocol like TCP. ๐Ÿ’ซ

5.1.1. Message Properties โœจ

okay so the API lets you attach a bunch of Properties to each Message ๐Ÿ’…, and each one should be implemented like this:

msgLifetime: ๐Ÿ•ฐ๏ธ This should be implemented by removing the Message from the queue of pending Messages once its Lifetime has expired. A queue of pending Messages inside the Transport Services Implementation (the ones that haven't been handed to the Protocol Stack yet) can always support this Property. But once a Message has been sent into a protocol's send buffer, only certain protocols can remove it from there. For example, a Transport Services Implementation cannot remove bytes from a TCP send buffer, but it can remove data from an SCTP send buffer using the partial reliability extension [RFC8303]. When there is no standing queue of Messages within the system, and the Protocol Stack doesn't support removing a Message from the stack's send buffer, this Property may be ignored. ๐Ÿซ 

msgPriority: ๐Ÿ‘‘ This is about being able to prioritize one Message over the others. It can be implemented by the Transport Services System reordering Messages that haven't been handed to the Protocol Stack yet, or by giving relative priority hints to protocols that support priorities per Message. For example, an implementation of HTTP/2 could choose to send Messages of different priority on streams of different priority. main character energy ๐ŸŒŸ

msgOrdered: ๐Ÿงฉ When this is false, it turns off the requirement of in-order delivery for protocols that support configurable ordering. When the Protocol Stack doesn't support configurable ordering, this Property may be ignored.

safelyReplayable: ๐Ÿ” When this is true, it means the Message can be used by a transport mechanism that might deliver it multiple times. That happens, for example, when you race multiple transports or use TCP Fast Open (TFO). Also, protocols that don't protect against duplicated Messages, such as UDP (when used directly, without a protocol layered on top), can only be used with Messages that are safely replayable. ๐Ÿ™ˆ When a Transport Services System is allowed to replay Messages, the application could provide replay protection.

final: ๐Ÿ When this is true, it means the sender won't send any further Messages. The Connection doesn't need to be closed (if the Protocol Stack supports half-closed operations, like TCP). Any Messages sent after a Message marked Final will result in a SendError. ๐Ÿ’€

msgChecksumLen: ๐Ÿงฎ When this is set to any value other than Full Coverage, it sets the minimum protection in protocols that let you limit the checksum length (like UDP-Lite). If the Protocol Stack doesn't support checksum length limitation, this Property may be ignored.

msgReliable: ๐Ÿ’Œ When true, this Property specifies that the Message must be reliably transmitted. When false, and if the underlying protocol supports unreliable transmission, the Message should be transmitted unreliably. If the underlying protocol doesn't support unreliable transmission, the Message should be transmitted reliably.

msgCapacityProfile: ๐Ÿšฆ When true, this expresses a wish to override the Generic Connection Property connCapacityProfile for this Message. Depending on the value, this can be implemented by changing the Differentiated Services Code Point (DSCP) value of the associated packet. Just note that the guidelines in Section 6 of [RFC7657] apply. For example, the DSCP value should not be changed for different packets within a reliable transport protocol session or DCCP connection. Don't mess with it mid-session, bestie ๐Ÿ™…โ€โ™€๏ธ

noFragmentation: ๐Ÿงฑ Setting this avoids network-layer fragmentation. Messages bigger than the transport's current estimate of its maximum packet size (the singularTransmissionMsgMaxLen Connection Property) can either result in transport segmentation, when that's permitted, or generate an error. When used with transports running over IPv4, the Don't Fragment (DF) bit should be set to avoid on-path IP fragmentation [RFC8304]. โœจ

noSegmentation: ๐Ÿšซ When set, this Property limits the Message size to the transport's current estimate of its maximum packet size (the singularTransmissionMsgMaxLen Connection Property). Messages larger than this size generate an error. Setting this avoids both transport-layer segmentation and network-layer fragmentation. When used with transports running over IPv4, the DF bit should be set to avoid on-path IP fragmentation ([RFC8304]). ๐ŸŽ€

5.1.2. Send Completion ๐ŸŽ‰

The application should be notified (using a Sent, Expired, or SendError event) whenever a Message or partial Message has been consumed by the Protocol Stack or has failed to send. ๐Ÿ“ฌ When a Message counts as consumed by the Protocol Stack can vary by protocol. For a basic datagram protocol like UDP, it may be the moment the packet is sent into the interface driver. For a protocol that buffers data in queues, like TCP, it may be when the data has entered the send buffer. A Message failed to send when the Transport Services Implementation (including the Protocol Stack) has hit a failure related to sending, and this can depend on protocol-specific timeouts. โฑ๏ธ

5.1.3. Batching Sends ๐Ÿ“

Sending lots of Messages can rack up high overhead if each one gets enqueued separately. (Each Message might mean a context switch between the application and the Transport Services System, and that's a whole vibe we don't want ๐Ÿ˜ค.) To avoid this, the application can indicate a batch of Send actions through the API. When it does, the implementation can defer processing the Messages until the batch is complete. ๐Ÿงบ

5.2. Receiving Messages ๐Ÿ“ฅ

Receiving works a lot like sending, in that the top-level protocol in the established Protocol Stack decides how it goes. The big difference is that the size and boundaries of the Message aren't known beforehand. ๐Ÿคทโ€โ™€๏ธ The application can share the parameters for the Message in its Receive action, which helps the Transport Services Implementation know how much data to deliver and when. For example, if the application only wants a complete Message, the implementation should wait until the entire Message (datagram, stream, or frame) has been read before delivering any Message content to the application. This means the implementation has to understand where Messages end, either through a supplied Message Framer or because the top-level protocol in the established Protocol Stack preserves Message boundaries. The application can also control the flow of received data by specifying the minimum and maximum number of bytes of Message content it wants to receive at one time. ๐ŸŽ›๏ธ

If a Connection finishes before a requested Receive action can be satisfied, the Transport Services System should deliver any outstanding partial Message content. If none is available, the system should indicate that there will be no additional received Messages. ๐ŸŒ™

5.3. Handling of Data for Fast-Open Protocols ๐Ÿš€

Several protocols let you send higher-level protocol or application data during their protocol establishment, like TFO [RFC7413] and TLS 1.3 [RFC8446]. This is called sending Zero-RTT (0-RTT) data. It's a really desirable feature, but it creates challenges for an implementation that uses racing during Connection establishment. ๐Ÿ˜ฎโ€๐Ÿ’จ

The application can express its preference for sending Messages as 0-RTT data by using the zeroRttMsg Selection Property on the Preconnection. Then the application can provide the Message to send as 0-RTT data via the InitiateWithSend action. To actually be sent as 0-RTT data, the Message needs to be marked with the safelyReplayable Property. In general, 0-RTT data may be replayed. For example, if a TCP SYN contains data and the SYN gets retransmitted, the data will be retransmitted too, but it may be considered a new connection instead of a retransmission. When connections are raced, different leaf nodes have the chance to send the same data independently. If the data is truly safely replayable, this is fine. ๐Ÿ‘ฏโ€โ™€๏ธ

Once the application has provided its 0-RTT data, a Transport Services Implementation should keep a copy of it and provide it to each new leaf node that is started and for which a protocol instance supporting 0-RTT is being used. Note that the amount of data that can actually be sent as 0-RTT varies by protocol, so any given Protocol Stack might only consume part of the saved data before it becomes established. The implementation needs to keep track of how much data a particular Protocol Stack has consumed, and make sure any pending 0-RTT-eligible data from the application is handled before subsequent Messages. ๐Ÿง 

It's also possible for Protocol Stacks within a particular leaf node to use a 0-RTT handshake in a lower-level protocol without any safely replayable application data. This happens if a higher-level protocol in the stack has idempotent handshake data to send. For example, TFO could use a Client Hello from TLS as its 0-RTT data without any data being provided by the application. ๐Ÿค

0-RTT handshakes often rely on previous state, such as TFO cookies, previously established TLS tickets, or out-of-band distributed pre-shared keys (PSKs). Implementations should be aware of security concerns around using these tokens across multiple addresses or paths when racing. ๐Ÿšจ In the case of TLS, any given ticket or PSK should only be used on one leaf node, since servers will likely reject duplicate tickets in order to prevent replays (see Section 8.1 of [RFC8446]). If implementations have multiple tickets available from a previous connection, each leaf node attempt can use a different ticket. In effect, each leaf node will send the same early application data, but the data will be encoded (encrypted) differently on the wire. ๐Ÿ”๐Ÿ’–

6. Implementing Message Framers โœจ

Okay bestie, so Message Framers are functions that do simple transformations between application Message data and raw transport protocol data ๐ŸŽ€. They're basically the translators between what your app wants to say and the raw bytes that actually go over the wire ๐Ÿ’…. Generally, a Message Framer implements a simple application protocol, and that protocol can come from either the Transport Services Implementation or the application itself. It is optional for Transport Services Implementations to provide Message Framers, because the API specification [RFC9622] does not prescribe any particular Message Framers to be implemented ๐Ÿฆ‹. A Framer can encapsulate or encode outbound Messages, decapsulate or decode inbound data into Messages, and implement parts of protocols that don't directly map to application Messages, like protocol handshakes or preludes before Message exchange ๐Ÿงš.

While lots of protocols can be represented as Message Framers, for the purposes of the Transport Services API these are ways for applications or application frameworks to define their own Message parsing, to be included within a Connection's Protocol Stack ๐Ÿ‘‘. As an example, TLS is a protocol that is built into the Transport Services API by default, even though it could also serve the purpose of framing data over TCP ๐Ÿ”ฅ.

Most Message Framers fall into one of two categories:

  • Header-prefixed record formats, such as a basic Type-Length-Value (TLV) structure
  • Delimiter-separated formats, such as HTTP/1.1

Common Message Framers can be provided by a Transport Services Implementation, but an implementation ought to allow custom Message Framers to be defined by the application or some other piece of software ๐ŸŒธ. This section describes one possible API for defining Message Framers, just as an example โœจ.

6.1. Defining Message Framers

A Message Framer is primarily defined by the code that handles events for a Framer implementation, specifically how it handles inbound and outbound data parsing ๐Ÿ’–. The function that implements custom framing logic is referred to as the "Framer implementation", and it may be provided by a Transport Services Implementation or by the application itself ๐Ÿซถ. The Message Framer holds a reference to the object or function within the main Connection implementation that delivers events to the custom Framer implementation whenever data is ready to be parsed or framed ๐Ÿงš.

The API examples in this section use the notation conventions for the Transport Services API defined in Section 1.1 of [RFC9622] ๐Ÿ“.

The Transport Services Implementation needs to make sure that all of the events and actions taken on a Message Framer are synchronized, so behavior stays consistent ๐Ÿ”„. For example, some of the actions defined below (such as PrependFramer and StartPassthrough) change how data flows in a Protocol Stack, so they need to be synchronized with sending and parsing data in the Message Framer ๐Ÿฆ‹.

When a Connection establishment attempt begins, an event can be delivered to tell the Framer implementation that a new Connection is being created ๐ŸŒˆ. Similarly, a Stop event can be delivered when a Connection is being torn down ๐Ÿฅ€. The Framer implementation can use the Connection object to look up specific Properties of the Connection, or of the network being used, that may influence how to frame Messages ๐Ÿ”.

MessageFramer -> Start<connection>
MessageFramer -> Stop<connection>

When a Message Framer generates a Start event, the Framer implementation gets the chance to start writing some data before the Connection delivers its Ready event ๐Ÿš€. This lets the implementation send control data to the Remote Endpoint, which can then be used to parse Messages ๐Ÿ’Œ.

Once the Framer implementation has finished its setup or handshake, it can tell the application that it's ready for handling data with this call ๐Ÿ’….

MessageFramer.MakeConnectionReady(connection)

Similarly, when a Message Framer generates a Stop event, the Framer implementation gets the chance to write some final data or clean up its local state before the Closed event is delivered to the application ๐Ÿงน. The Framer implementation can indicate that it's finished with this call ๐ŸŽฌ.

MessageFramer.MakeConnectionClosed(connection)

If the implementation runs into a fatal error at any time, it can also cause the Connection to fail and provide an error ๐Ÿ’€.

MessageFramer.FailConnection(connection, error)

Should the Framer implementation decide that the candidate selected during racing is unsuitable, it can signal this to the Transport Services API by failing the Connection before marking it as ready ๐Ÿ™…โ€โ™€๏ธ. If there are no other candidates available, the Connection will fail ๐Ÿ˜ญ. Otherwise, the Connection selects a different candidate and the Message Framer generates a new Start event ๐Ÿ”.

Before an implementation marks a Message Framer as ready, it can also dynamically add a protocol or Framer above it in the stack ๐Ÿง. This is what lets protocols that need to add TLS conditionally, like STARTTLS [RFC3207], modify the Protocol Stack based on a handshake result ๐Ÿคฏ.

otherFramer := NewMessageFramer()
MessageFramer.PrependFramer(connection, otherFramer)

A Message Framer might also choose to go into a passthrough mode once an initial exchange or handshake has been completed, like in the STARTTLS case mentioned above ๐ŸŒŠ. This can also be useful for proxy protocols like SOCKS [RFC1928] or HTTP CONNECT [RFC9110] ๐Ÿ›ฐ๏ธ. In these cases, a Message Framer implementation can initially intercept Messages being sent and received, and then indicate that no further processing is needed ๐Ÿ™Œ.

MessageFramer.StartPassthrough()

6.2. Sender-Side Message Framing

Message Framers generate an event whenever a Connection sends a new Message ๐Ÿ“ค. The parameters to the event line up with the Send action in the API (Section 9.2 of [RFC9622]) ๐ŸŽฏ.

                       MessageFramer
                             |
                             V
NewSentMessage<connection, messageData, messageContext, endOfMessage>

Upon receiving this event, a Framer implementation is responsible for performing any necessary transformations and sending the resulting data back to the Message Framer, which in turn sends it to the next protocol ๐ŸŽ. To improve performance, implementations should make sure there's a way to pass the original data through without copying ๐Ÿš„.

MessageFramer.Send(connection, messageData)

To give an example: a simple protocol that adds the length of the Message data as a header would receive the NewSentMessage event, create a data representation of the length of the Message data, and then send a block of data that is the concatenation of the length header and the original Message data ๐Ÿ“๐Ÿ’•.

6.3. Receiver-Side Message Framing

To parse a received flow of data into Messages, the Message Framer notifies the Framer implementation whenever new data is available to parse ๐Ÿ‘€.

The parameters to the events and calls for receiving data with a Framer line up with the Receive action in the API (Section 9.3 of [RFC9622]) ๐Ÿ“ฅ.

MessageFramer -> HandleReceivedData<connection>

Upon receiving this event, the Framer implementation can inspect the inbound data ๐Ÿ”Ž. The data is parsed from a particular cursor that represents the unprocessed data ๐Ÿงญ. The application requests a specific amount of data it needs to have available in order to parse. If the data isn't available, the parse fails ๐Ÿ˜ค.

 MessageFramer.Parse(connection, minimumIncompleteLength, maximumLength)
                                  |
                                  V
              (messageData, messageContext, endOfMessage)

The Framer implementation can directly advance the receive cursor once it has parsed data, which effectively discards that data (for example, discarding a header once the content has been parsed) ๐Ÿ—‘๏ธ.

To deliver a Message to the application, the Framer implementation can either directly deliver data it has allocated, or deliver a range of data directly from the underlying transport and advance the receive cursor at the same time ๐ŸŽ‰.

MessageFramer.AdvanceReceiveCursor(connection, length)
MessageFramer.DeliverAndAdvanceReceiveCursor(connection, messageContext,
                                             length, endOfMessage)
MessageFramer.Deliver(connection, messageContext, messageData,
                      endOfMessage)

Note that MessageFramer.DeliverAndAdvanceReceiveCursor lets the Framer implementation earmark bytes as part of a Message even before they're received by the transport ๐Ÿง . This makes it possible to deliver very large Messages without requiring the implementation to directly inspect all of the bytes ๐Ÿ˜.

To give an example: a simple protocol that parses the length of the Message data as a header value would receive the HandleReceivedData event and call Parse with a minimum and maximum set to the length of the header field ๐Ÿ”ข. Once the parse succeeds, it would call AdvanceReceiveCursor with the length of the header field, and then call DeliverAndAdvanceReceiveCursor with the length of the body that was parsed from the header, marking the new Message as complete ๐Ÿฅณ๐Ÿ’œ.

7. Implementing Connection Management โœจ

Okay so once a Connection is established ๐Ÿฆ‹, the Transport Services API lets apps poke at it by modifying or inspecting its Connection Properties ๐Ÿ’…. A Connection can also generate error events in the form of SoftError events ๐Ÿซถ.

The Connection Properties you can set and get are listed in [RFC9622] ๐Ÿ“š. For generic Properties that could apply to every protocol a Connection is using, the Transport Services Implementation should store them in storage common to all protocols and notify the Protocol Stack as a whole whenever the application modifies them โœจ. [RFC8303] and [RFC8304] give guidance on how to do this for TCP, Multipath TCP (MPTCP), SCTP, UDP, and UDP-Lite ๐ŸŒˆ. See Section 10 for a backtracking method that uses these documents to find the relevant protocol primitives ๐Ÿ”. For Protocol-specific Properties, like the User Timeout that applies to TCP, the Transport Services Implementation only needs to update the relevant protocol instance ๐ŸŽ€.

Some Connection Properties might apply to multiple protocols within a Protocol Stack ๐Ÿงš. Depending on the specific Property, it might make sense to apply it across several protocols at once, or to apply it to just one ๐Ÿ’œ. In general, the Transport Services Implementation should let the protocol closest to the application interpret Connection Properties and, potentially, modify the set of Connection Properties passed down to the next protocol in the stack ๐Ÿ‘‘. For example, say the application asks for keep-alives with the keepAlive Property, and the Protocol Stack contains both HTTP/2 and TCP. The HTTP/2 protocol can choose to enable its own keep-alives to satisfy the request and disable TCP-level keep-alives ๐Ÿ“. When an application needs fine-grained, per-protocol control, the Transport Services Implementation can expose Protocol-specific Properties ๐Ÿ”ฅ.

If an error is encountered in setting a Property (for example, if the application tries to set a TCP-specific Property on a Connection that is not using TCP), the action must fail gracefully ๐Ÿ˜ญ. The application must be informed of the error, but the Connection itself must not be terminated ๐Ÿ™ˆ.

When protocol instances in the Protocol Stack report generic or protocol-specific errors, the API will deliver them to the application as SoftError events ๐Ÿ’–. These let the application know about ICMP errors and other similar events ๐ŸŒธ.

7.1. Pooled Connection

For applications that don't need in-order delivery of Messages, the Transport Services Implementation may distribute Messages of a single Connection across several underlying transport connections, or across multiple streams of multistreaming connections, between endpoints ๐Ÿงœโ€โ™€๏ธ. This is fine as long as all of them satisfy the Selection Properties ๐Ÿ’…. The Transport Services Implementation then hides this connection management and only exposes a single Connection object, which we call a Pooled Connection ๐ŸŽ€. This is different from Connection Groups, which explicitly expose combined treatment of Connections and give the application control over things like multiplexing ๐Ÿ‘ฏโ€โ™€๏ธ.

Pooled Connections are super useful when the application using the Transport Services System implements a protocol such as HTTP, which uses request/response pairs and doesn't require in-order delivery of responses ๐Ÿ“ฌ. This lets Transport Services Systems realize transparent connection coalescing and connection migration, and do per-Message endpoint and path selection by choosing among multiple underlying connections ๐Ÿฆ‹. Main character energy, honestly ๐Ÿ‘‘.

7.2. Handling Path Changes

When a path change happens, like when the IP address of an interface changes or a new interface becomes available ๐Ÿ“ก, the Transport Services Implementation is responsible for telling the protocol instance about it ๐Ÿซถ. The change might interrupt connectivity on a path for an active Connection, or it might give a transport that supports multipath or migration a chance to adapt to the new paths ๐Ÿ”€. Note that, in the model of the Transport Services API, migration counts as part of multipath connectivity. It's just a limiting policy on multipath usage ๐Ÿ’…. If the multipath Selection Property is set to Disabled, migration is disallowed ๐Ÿšซ.

For protocols that don't support multipath or migration, the protocol instances should be informed of the path change, but they should not be forcibly disconnected if the previously used path becomes unavailable ๐Ÿงš. A path can become temporarily unavailable and then recover before the transport protocol hits a timeout error, and this happens all the time, especially on mobile devices ๐Ÿ“ฑ. Examples include:

  • an Ethernet cable getting unplugged and then plugged back in ๐Ÿ”Œ;
  • a device losing a Wi-Fi signal while a user is in an elevator, then reattaching once they leave the elevator ๐Ÿ›—; and
  • a user losing the radio signal while riding a train through a tunnel ๐Ÿš‡.

If the device can rejoin a network with the same IP address, a stateful transport connection can generally resume ๐Ÿ”„. So while it's useful for a protocol instance to know about a temporary loss of connectivity, the Transport Services Implementation should not aggressively close Connections in these scenarios ๐Ÿฅน.

If the Protocol Stack includes a transport protocol that supports multipath connectivity, the Transport Services Implementation should also tell the protocol instance about potentially new paths that become permissible based on the multipath Selection Property and the multipathPolicy Connection Property choices the application made ๐Ÿ—บ๏ธ. A protocol can then establish new subflows over new paths while an active path is still available, or after a break has been detected โœจ. It should also try to tear down subflows over paths that are no longer used ๐Ÿงน. The multipathPolicy Connection Property lets an application indicate when and how different paths should be used ๐ŸŽ€. Detailed handling of these policies is implementation specific, though ๐Ÿคทโ€โ™€๏ธ. For example, if the multipath Selection Property is set to Active, deciding when to create a new path, or when to announce a new path or set of paths to the Remote Endpoint (for instance, in the form of additional IP addresses), is up to the implementation ๐ŸŒˆ. If the Protocol Stack includes a transport protocol that doesn't support multipath but does support migrating between paths, an update to the set of available paths can trigger the connection to be migrated ๐Ÿฆ‹.

In the case of a Pooled Connection (Section 7.1), the Transport Services Implementation may add connections over new paths to the pool, if that's permissible based on the multipathPolicy and Selection Properties ๐Ÿซง. If a previously used path becomes unavailable, the Transport Services System may disconnect all connections that require this path, but it should not disconnect the Pooled Connection object exposed to the application ๐Ÿ’–. How it does this is implementation specific, but it should be consistent with the behavior of multipath transports ๐ŸŒธ.

8. Implementing Connection Termination

For Close (which leads to a Closed event) and Abort (which leads to a ConnectionError event), the application might find it useful to know when a peer closes or aborts a Connection ๐Ÿ‘€. Whether that's possible depends on the underlying protocol, and no guarantees can be given ๐Ÿ˜ฌ. When an underlying transport connection supports multistreaming (such as SCTP), the Transport Services System can use a stream reset procedure to cause a Finish event upon a Close action from the peer ๐Ÿ”š [NEAT-flow-mapping].

9. Cached State

Beyond a single Connection's lifetime, it's useful for an implementation to keep state and history ๐Ÿง . This cached state can help improve future Connection establishment by reusing results and credentials, and by favoring paths and protocols that performed well in the past ๐Ÿš€.

Cached state may be associated with different endpoints for the same Connection, depending on the protocol generating the cached content ๐Ÿ—‚๏ธ. For example, session tickets for TLS are associated with specific endpoints, so they should be cached based on a connection's hostname Endpoint Identifier (if applicable) ๐ŸŽซ. Performance characteristics of a path, however, are more likely tied to the IP address and subnet being used ๐Ÿ“.

9.1. Protocol State Caches

Some protocols have long-term state that gets cached in association with endpoints ๐Ÿ’พ. This state often has some time after which it expires, so the implementation should allow each protocol to specify an expiration for cached content โฐ.

Examples of cached protocol state include:

  • The DNS protocol can cache resolved addresses (such as those retrieved from A and AAAA queries) with a Time To Live (TTL) attached, so future hostname resolutions don't require asking the DNS resolver again ๐Ÿ”Ž.
  • TLS caches session state and tickets based on a hostname, which can be used for resuming sessions with a server ๐Ÿ”.
  • TCP can cache cookies for use in TFO ๐Ÿช.

Cached protocol state is primarily used during Connection establishment for a single Protocol Stack, but it may also influence an implementation's preference between several Candidate Protocol Stacks ๐Ÿ’œ. For example, if two IP address Endpoint Identifiers are otherwise equally preferred, an implementation may choose to attempt a connection to the address it has a TFO cookie for ๐ŸŽฏ.

Applications can use the Transport Services API to request that a Connection Group maintain a separate cache for protocol state ๐Ÿ”. Connections in the group won't use Cached State from Connections outside the group, and Connections outside the group won't use state cached from Connections inside the group ๐Ÿ›ก๏ธ. This may be necessary if application-layer identifiers rotate and clients want to avoid linkability via trackable TLS tickets or TFO cookies ๐Ÿ•ต๏ธโ€โ™€๏ธ๐Ÿ’….

9.2. Performance Caches ๐Ÿง 

Okay bestie, so besides protocol state, protocol instances should also pour data into a performance-oriented cache ๐Ÿ’– that helps guide future protocol and path selection โœจ. Some performance info can be gathered generically across several protocols, which lets you make predictive comparisons between protocols on given paths ๐Ÿ”:

  • Observed RTT
  • Connection establishment latency
  • Connection establishment success rate

These items can be cached at per-address and per-subnet granularity and averaged between different values ๐Ÿ“Š. The info should be cached per-network, because different network attachments are expected to have very different performance characteristics ๐ŸŒ. And it's not just protocol instances either! Other system entities can feed performance-oriented caches too. For example, signal strength info reported by radio modems like Wi-Fi and mobile broadband ๐Ÿ“ถ, or info about the device's battery level ๐Ÿ”‹. The system may also cache the observed maximum throughput on a path as an estimate of the available bandwidth ๐Ÿš€. Iconic multitasking ๐Ÿ‘‘.

An implementation should use this info, when possible, to influence preference between Candidate Paths, endpoints, and protocol options ๐ŸŽฏ. Eligible options that historically performed significantly better than others should be selected first when gathering candidates (see Section 4.2), so the application gets better performance โญ.

The reasonable lifetime for cached performance values depends on what the value actually is ๐Ÿ•ฐ๏ธ. Stuff like the connection establishment success rate to a Remote Endpoint using a given Protocol Stack can be stored for a long time (hours or longer), since the Remote Endpoint's capabilities aren't changing very fast ๐ŸงŠ. On the other hand, the RTT that TCP observes over a particular network path can change over a relatively short time interval ๐ŸŒŠ. For values like that, the implementation should remove them from the cache more quickly, or treat older values with less confidence/weight ๐Ÿซถ.

[RFC9040] provides guidance about sharing of TCP Control Block information between connections on initialization ๐ŸŽ€.


10. Specific Transport Protocol Considerations ๐Ÿฆ‹

Each protocol supported by a Transport Services Implementation should have a well-defined API mapping ๐Ÿ“. API mappings matter most for Connections where a given protocol is the "top" of the Protocol Stack ๐Ÿ‘‘. For example, the mapping of the Send action for TCP applies to Connections where the application sends directly over TCP ๐Ÿ’Œ.

Each protocol has a notion of "Connectedness" ๐Ÿ”—. Here are the possible definitions of Connectedness for different types of protocols:

Connectionless: Connectionless protocols do not establish explicit state between endpoints, and they do not perform a handshake during connection establishment ๐Ÿƒ.

Connected: Connected (also called "connection-oriented") protocols establish state between endpoints and perform a handshake during connection establishment ๐Ÿค. The handshake may be 0-RTT to send data or resume a session, but bidirectional traffic is required to confirm Connectedness ๐Ÿ”.

Multiplexing connected: Multiplexing connected protocols share properties with connected protocols, but they also explicitly support opening multiple application-level flows ๐ŸŒธ. That means they can support cloning new Connection objects without a new explicit handshake ๐Ÿงš.

Protocols also have a notion of "Data Unit" ๐Ÿ“ฆ. The possible values for Data Unit are:

Byte-stream: Byte-stream protocols do not define any message boundaries of their own, apart from the end of a stream in each direction ๐ŸŒŠ.

Datagram: Datagram protocols define message boundaries at the same level of transmission, so only complete (not partial) messages are supported ๐Ÿ’œ.

Message: Message protocols support message boundaries that can be sent and received as either complete or partial messages ๐Ÿ’ฌ. Maximum message lengths can be defined, and messages can be partially reliable ๐Ÿ“.

Below, terms in capitals with a dot character (".") (e.g., "CONNECT.SCTP") refer to the primitives with the same name in Section 4 of [RFC8303] ๐Ÿ”‘. For further implementation details, the description of these primitives in [RFC8303] points to Section 3 of [RFC8303] and Section 3 of [RFC8304], which refers back to the relevant specifications for each protocol ๐Ÿ“š. This applies to all elements of [RFC8923] (see Appendix C of [RFC9622]): they are listed in Appendix A of [RFC8923] with an implementation hint in the same style, pointing back to Section 4 of [RFC8303] โœจ.

This document presents the protocol mappings defined in [RFC8923] ๐ŸŽ. Other protocol mappings can be provided as separate documents, following the mapping template in Appendix A ๐Ÿงฉ.

10.1. TCP ๐ŸŒท

Connectedness: Connected ๐Ÿค

Data Unit: Byte-stream ๐ŸŒŠ

Connection Object: TCP connections between two hosts map directly to Connection objects ๐Ÿ’—.

Initiate: CONNECT.TCP. Calling Initiate on a TCP connection causes it to reserve a local port and send a SYN to the Remote Endpoint ๐Ÿš€.

InitiateWithSend: CONNECT.TCP with parameter user message. Early safely replayable data is sent on a TCP connection in the SYN, as TFO data ๐ŸŽ€.

Ready: A TCP connection is ready once the three-way handshake is complete โœ….

EstablishmentError: Failure of CONNECT.TCP. TCP can throw various errors during connection setup ๐Ÿ˜ฌ. Specifically, it is important to handle a RST being sent by the peer during the handshake ๐Ÿšจ.

ConnectionError: Once established, TCP throws errors whenever the connection is disconnected, such as due to receiving a RST from the peer ๐Ÿ’”.

Listen: LISTEN.TCP. Calling Listen for TCP binds a local port and prepares it to receive inbound SYN packets from peers ๐Ÿ‘‚.

ConnectionReceived: TCP Listeners will deliver new connections once they have replied to an inbound SYN with a SYN-ACK ๐Ÿฅณ.

Clone: Calling Clone on a TCP connection creates a new TCP connection with equivalent parameters ๐Ÿ‘ฏ. The two associated Connection objects, and Connections generated via later calls to Clone on an Established Connection, form a Connection Group ๐Ÿซ‚. To realize entanglement for these Connections, with the exception of connPriority, changing a Connection Property on one of them must affect the Connection Properties of the others too ๐Ÿ•ธ๏ธ. No guarantees of honoring the connPriority Connection Property are given, so it is safe for an implementation of a Transport Services System to ignore this Property ๐Ÿ™ˆ. When it is reasonable to assume that Connections traverse the same path (e.g., when they share the same encapsulation), support for it can also experimentally be implemented using a congestion control coupling mechanism (for example, see [TCP-COUPLING] or [RFC3124]) ๐Ÿงช.

Send: SEND.TCP. On its own, TCP does not preserve Message boundaries ๐Ÿซ . Calling Send on a TCP connection lays out the bytes on the TCP send stream without any other delineation. Any Message marked as Final will cause TCP to send a FIN once the Message has been completely written, by calling CLOSE.TCP immediately upon successful termination of SEND.TCP ๐Ÿ“ค. Note that transmitting a Message marked as Final should not cause the Closed event to be delivered to the application, because it will still be possible to receive data until the peer closes or aborts the TCP connection ๐Ÿ™Œ.

Receive: With RECEIVE.TCP, TCP delivers a stream of bytes without any Message delineation ๐Ÿ“ฅ. All data delivered in the Received or ReceivedPartial event will be part of a single stream-wide Message that is marked Final (unless a Message Framer is used) ๐Ÿงต. The value of the endOfMessage Property will be delivered when the TCP connection has received a FIN (CLOSE-EVENT.TCP) from the peer ๐Ÿ. Note that reception of a FIN should not cause the Closed event to be delivered to the application, as it will still be possible for the application to send data ๐Ÿ’….

Close: Calling Close on a TCP connection indicates that the TCP connection should be gracefully closed (CLOSE.TCP) by sending a FIN to the peer ๐Ÿ‘‹. It will then still be possible to receive data until the peer closes or aborts the TCP connection. The Closed event will be issued upon reception of a FIN ๐ŸŒ™.

Abort: Calling Abort on a TCP connection indicates that the TCP connection should be immediately closed by sending a RST to the peer (ABORT.TCP) ๐Ÿ’€.

CloseGroup: Calling CloseGroup on a TCP connection (CLOSE.TCP) is identical to calling Close on its Connection object and on all Connections in the same ConnectionGroup ๐Ÿซถ.

AbortGroup: Calling AbortGroup on a TCP connection (ABORT.TCP) is identical to calling Abort on its Connection object and on all Connections in the same ConnectionGroup ๐Ÿงจ.

10.2. MPTCP ๐Ÿฆ„

Connectedness: Connected ๐Ÿค

Data Unit: Byte-stream ๐ŸŒŠ

The Transport Services API mappings for MPTCP are identical to TCP ๐Ÿ‘ฏโ€โ™€๏ธ. MPTCP adds support for multipath Properties, such as multipath and multipathPolicy, and actions for managing paths, such as AddRemote and RemoveRemote ๐Ÿ›ฃ๏ธโœจ.

10.3. UDP โœจ

okay bestie, UDP is giving low-maintenance and I'm obsessed ๐Ÿซถ

  • Connectedness: Connectionless ๐Ÿฆ‹
  • Data Unit: Datagram ๐Ÿ’Œ
  • Connection Object: UDP connections represent a pair of specific IP addresses and ports on two hosts. It's literally the couple of addresses and ports on each side ๐ŸŒธ
  • Initiate: CONNECT.UDP. Calling Initiate on a UDP connection makes it reserve a local port, but it does not generate any traffic. No cap, it just holds the spot ๐Ÿ’…
  • InitiateWithSend: Early data on a UDP connection doesn't have any special meaning ngl. The data is sent whenever the connection is Ready โœจ
  • Ready: A UDP connection is ready once the system has reserved a local port and has a path to send to the Remote Endpoint ๐ŸŽ€
  • EstablishmentError: UDP connections can only generate errors on initiation, and that's only due to port conflicts on the local system ๐Ÿ™ˆ
  • ConnectionError: UDP connections can only generate Connection errors in response to Abort actions. (Once they're in use, UDP connections can also generate SoftError events, ERROR.UDP, when they get ICMP notifications about failures in the network. Lowkey dramatic, but iconic ๐Ÿ’€)
  • Listen: LISTEN.UDP. Calling Listen for UDP binds a local port and gets it ready to receive inbound UDP datagrams from peers ๐Ÿงš
  • ConnectionReceived: UDP Listeners deliver new Connections once they've received traffic from a new Remote Endpoint ๐Ÿ’œ
  • Clone: Calling Clone on a UDP connection creates a new UDP connection with equivalent parameters. The two associated Connection objects are otherwise independent, so they don't share drama ๐Ÿ‘‘
  • Send: SEND.UDP. Calling Send on a UDP connection sends the data as the payload of a complete UDP datagram. Marking Messages as Final does not change anything in the datagram's contents, so don't expect it to ๐Ÿ™…โ€โ™€๏ธ Upon sending a UDP datagram, some relevant fields and flags in the IP header can be controlled: DSCP (SET_DSCP.UDP), DF in IPv4 (SET_DF.UDP), and the ECN flag (SET_ECN.UDP) ๐Ÿ”ฅ
  • Receive: RECEIVE.UDP. UDP only delivers complete Messages to Received, and each one represents a single datagram received in a UDP packet ๐Ÿ“ฆ Upon receiving a UDP datagram, the ECN flag from the IP header can be obtained (GET_ECN.UDP) ๐ŸŒˆ
  • Close: Calling Close on a UDP connection (ABORT.UDP) releases the local port reservation. After that, a Closed event is issued ๐Ÿซง
  • Abort: Calling Abort on a UDP connection (ABORT.UDP) is identical to calling Close, except a ConnectionError event is issued instead of a Closed event. Plot twist, it's still the same energy ๐Ÿ˜ญ
  • CloseGroup: Calling CloseGroup on a UDP connection (ABORT.UDP) is identical to calling Close on its Connection object and on all Connections in the same ConnectionGroup ๐Ÿ“
  • AbortGroup: Calling AbortGroup on a UDP connection (ABORT.UDP) is identical to calling Close on its Connection object and on all Connections in the same ConnectionGroup ๐ŸŒธ

10.4. UDP-Lite ๐Ÿฆ‹

  • Connectedness: Connectionless โœจ
  • Data Unit: Datagram ๐Ÿ’Œ

The Transport Services API mappings for UDP-Lite are identical to UDP, so if you already know UDP, you're already halfway there ๐ŸŽ€ On top of that, UDP-Lite supports the msgChecksumLen and recvChecksumLen Properties. These let an application specify the minimum number of bytes in a Message that need to be covered by a checksum. It's giving customization ๐Ÿ’…

This includes: CONNECT.UDP-Lite; LISTEN.UDP-Lite; SEND.UDP-Lite; RECEIVE.UDP-Lite; ABORT.UDP-Lite; ERROR.UDP-Lite; SET_DSCP.UDP-Lite; SET_DF.UDP-Lite; SET_ECN.UDP-Lite; GET_ECN.UDP-Lite. ๐Ÿงš


10.5. UDP Multicast Receive ๐Ÿ“ก

  • Connectedness: Connectionless ๐ŸŒˆ
  • Data Unit: Datagram ๐Ÿ’Œ
  • Connection Object: Established UDP Multicast Receive connections represent a pair of specific IP addresses and ports. The direction Selection Property must be set to Unidirectional receive, and the Local Endpoint must be configured with a group IP address and a port ๐ŸŽ€
  • Initiate: Calling Initiate on a UDP Multicast Receive connection causes an immediate EstablishmentError. This is an unsupported operation, sorry bestie ๐Ÿ™ˆ
  • InitiateWithSend: Calling InitiateWithSend on a UDP Multicast Receive connection causes an immediate EstablishmentError. Also unsupported ๐Ÿ’€
  • Ready: A UDP Multicast Receive connection is ready once the system has received traffic for the appropriate group and port ๐Ÿงš
  • EstablishmentError: UDP Multicast Receive connections cause an EstablishmentError indicating that joining a multicast group failed if Initiate is called ๐Ÿ“
  • ConnectionError: The only ConnectionError generated by a UDP Multicast Receive connection is in response to an Abort action ๐Ÿซง
  • Listen: LISTEN.UDP. Calling Listen for UDP Multicast Receive binds a local port, prepares it to receive inbound UDP datagrams from peers, and issues a multicast host join. If a Remote Endpoint Identifier with an address is supplied, the join is Source-Specific Multicast, and the path selection is based on the route to the Remote Endpoint. If a Remote Endpoint Identifier is not supplied, the join is Any-Source Multicast, and the path selection is based on the outbound route to the group supplied in the Local Endpoint ๐ŸŒธ

There are cases where it is required to open multiple connections for the same address(es). For example, one Connection might be opened for a multicast group used for a shared control bus, and another application later opens a separate Connection to the same group to send signals to and/or receive signals from the common bus. In such cases, the Transport Services System needs to explicitly enable reuse of the same set of addresses (equivalent to setting SO_REUSEADDR in the Socket API). It's a whole group project, iykyk ๐Ÿ‘ฏโ€โ™€๏ธ

  • ConnectionReceived: UDP Multicast Receive Listeners deliver new Connections once they've received traffic from a new Remote Endpoint ๐Ÿ’œ
  • Clone: Calling Clone on a UDP Multicast Receive connection creates a new UDP Multicast Receive connection with equivalent parameters. The two associated Connection objects are otherwise independent ๐Ÿ‘‘
  • Send: SEND.UDP. Calling Send on a UDP Multicast Receive connection causes an immediate SendError. This is an unsupported operation, periodt ๐Ÿ’…
  • Receive: RECEIVE.UDP. UDP Multicast Receive only delivers complete Messages to Received, and each one represents a single datagram received in a UDP packet. Upon receiving a UDP datagram, the ECN flag from the IP header can be obtained (GET_ECN.UDP) ๐ŸŒˆ
  • Close: Calling Close on a UDP Multicast Receive connection (ABORT.UDP) releases the local port reservation and leaves the group. A Closed event is then issued ๐Ÿฆ‹
  • Abort: Calling Abort on a UDP Multicast Receive connection (ABORT.UDP) is identical to calling Close, except a ConnectionError event is issued instead of a Closed event ๐Ÿ˜ญ
  • CloseGroup: Calling CloseGroup on a UDP Multicast Receive connection (ABORT.UDP) is identical to calling Close on its Connection object and on all Connections in the same ConnectionGroup ๐Ÿ“
  • AbortGroup: Calling AbortGroup on a UDP Multicast Receive connection (ABORT.UDP) is identical to calling Close on its Connection object and on all Connections in the same ConnectionGroup โœจ

10.6. SCTP ๐Ÿฆ‹

Connectedness: Connected โœจ

Data Unit: Message ๐Ÿ’Œ

Connection Object: okay so, Connection objects can be mapped to an SCTP association or to a stream in an SCTP association ๐Ÿซถ. Mapping Connection objects to SCTP streams is called "stream mapping," and it comes with extra requirements ๐Ÿ’…. The following explanation assumes a client-server communication model.

Stream mapping needs an association to already be in place between the client and the server. It also needs the server to understand that a new incoming stream SHOULD be represented as a new Connection object by the Transport Services System ๐Ÿงš. A new SCTP stream gets created by sending an SCTP message with a new stream id. So to implement stream mapping, the Transport Services API MUST provide a newly created Connection object to the application when such a message arrives. The Close and Abort primitives of a Transport Services System get their semantics from the stream reconfiguration (reset) procedure in [RFC6525], which also lets a stream id be reused after resetting ("closing") the stream ๐Ÿ”„. To make this work, SCTP stream reconfiguration [RFC6525] MUST be supported by both the client and the server side.

To avoid head-of-line blocking, stream mapping SHOULD only be implemented when both sides support message interleaving [RFC8260] ๐Ÿšฆ. This lets a sender schedule transmissions between multiple streams without a large message on one stream blocking the others for a long time.

To avoid stream id conflicts, the following procedure is RECOMMENDED ๐Ÿ“‹. The first Connection, for which the SCTP association has been created, MUST always use stream id zero. All additional Connections get assigned to unused stream ids in ascending order. To avoid a conflict when both endpoints map new Connections at the same time, the peer that initiated the association MUST use even stream ids, and the remote side MUST map its Connections to odd stream ids ๐ŸŽ€. Both sides keep a status map of the assigned stream ids. Generally, new streams SHOULD consume the lowest available stream id (even or odd, depending on the side). This rule matters when lower stream ids become free because the Connection objects tied to those streams get closed ๐ŸŒธ.

SCTP stream mapping as described here has been implemented in a research prototype, and you can read about that implementation in [NEAT-flow-mapping] ๐Ÿ”ฌ.

Initiate: If this is the only Connection object assigned to the SCTP association, or stream mapping isn't used, CONNECT.SCTP is called. Otherwise, unless the Selection Property activeReadBeforeSend is preferred or required, a new stream gets used. If there are enough streams available, Initiate is a local operation that assigns a new stream id to the Connection object ๐Ÿ“. The number of streams is negotiated as a parameter of the earlier CONNECT.SCTP call, and it's a trade-off between local resource usage and how many Connection objects can be mapped without needing a reconfiguration signal. When running out of streams, ADD_STREAM.SCTP MUST be called ๐Ÿ’ซ.

InitiateWithSend: If this is the only Connection object assigned to the SCTP association, or stream mapping isn't used, CONNECT.SCTP is called with the user message parameter. Otherwise, a new stream gets used (see Initiate for how to handle running out of streams), and this simply sends the first message on that new stream ๐Ÿ“จ.

Ready: Initiate or InitiateWithSend returns without an error, meaning SCTP's four-way handshake has completed ๐Ÿค. If an association with the peer already exists, stream mapping is used, and enough streams are available, a Connection object instantly becomes Ready after calling Initiate or InitiateWithSend โšก.

EstablishmentError: Failure of CONNECT.SCTP ๐Ÿ™ˆ.

ConnectionError: TIMEOUT.SCTP or ABORT-EVENT.SCTP ๐Ÿ’€.

Listen: LISTEN.SCTP. If an association with the peer already exists and stream mapping is used, Listen just waits to receive a new message with a new stream id (chosen according to the stream id assignment procedure described above) ๐Ÿ‘‚.

ConnectionReceived: LISTEN.SCTP returns without an error (which means the peer's CONNECT.SCTP succeeded), or, in the case of stream mapping, the first message has arrived on a new stream. In that second case, Receive is also invoked ๐ŸŽ‰.

Clone: Calling Clone on an SCTP association creates a new Connection object and assigns it a new stream id, following the stream id assignment procedure described above. If there aren't enough streams available, ADD_STREAM.SCTP MUST be called ๐Ÿงฌ.

Send: SEND.SCTP. Message Properties such as msgLifetime and msgOrdered map to parameters of this primitive ๐Ÿ“ฌ.

Receive: RECEIVE.SCTP. The "partial flag" of RECEIVE.SCTP invokes a ReceivedPartial event ๐Ÿ“ฅ.

Close: If this is the only Connection object assigned to the SCTP association, CLOSE.SCTP is called, and the Closed event gets delivered to the application when CLOSE-EVENT.SCTP arrives ๐Ÿ‘‹. Otherwise, the Connection object is one of several Connection objects assigned to the same SCTP association. In that case, RESET_STREAM.SCTP MUST be called, which tells the peer that the stream won't be used for mapping anymore and can be used by a future Initiate, InitiateWithSend, or Listen action ๐Ÿช„. At the peer, the RESET_STREAM-EVENT.SCTP gets initiated, and the peer MUST answer by issuing RESET_STREAM.SCTP too. The resulting local RESET_STREAM-EVENT.SCTP tells the Transport Services System that the stream id can now be reused by the next Initiate, InitiateWithSend, or Listen action, and it invokes a Closed event toward the application ๐Ÿซง.

Abort: If this is the only Connection object assigned to the SCTP association, ABORT.SCTP is called. Otherwise, the Connection object is one of several Connection objects assigned to the same SCTP association, and shutdown proceeds as described under Close ๐Ÿ›‘.

CloseGroup: Calling CloseGroup calls CLOSE.SCTP, which closes all Connections in the SCTP association ๐Ÿงบ.

AbortGroup: Calling AbortGroup calls ABORT.SCTP, which immediately closes all Connections in the SCTP association ๐Ÿ’ฅ.

In addition to the API mappings described above, when multiple Connection objects are assigned to the same SCTP association, SCTP can support Connection Properties such as connPriority and connScheduler. CONFIGURE_STREAM_SCHEDULER.SCTP can be called to adjust the priorities of streams in the SCTP association ๐ŸŽ›๏ธโœจ.


11. IANA Considerations ๐ŸŒท

Good news, bestie: this document has no IANA actions. ๐Ÿ’–

12. Security Considerations ๐Ÿ”

[RFC9621] outlines general security considerations and requirements for any system that implements the Transport Services Architecture ๐Ÿ›ก๏ธ. [RFC9622] goes further into the security and privacy implications of the Transport Services API ๐Ÿ•ต๏ธ. This document adds implementation-specific guidance for the Transport Services API, so the security considerations in both of these documents apply. The next two subsections cover further considerations specific to the mechanisms in this document ๐Ÿซถ.

12.1. Considerations for Candidate Gathering

As discussed in Sections 3 and 6 of [RFC9621], gathering and racing with Protocol Stacks that don't have equivalent security properties ought not be attempted ๐Ÿšซ. Therefore, implementations need to avoid downgrade attacks that allow network interference to cause the implementation to select less secure, or entirely insecure, combinations of paths and protocols ๐Ÿ˜ฑ.

12.2. Considerations for Candidate Racing

See Section 5.3 for security considerations around racing with 0-RTT data ๐Ÿ”.

An attacker that knows a particular device is racing several options during Connection establishment may be able to block packets for the first connection attempt. That could push the device into a secondary attempt. This is a problem if the secondary attempts have worse security properties that enable further attacks ๐Ÿšจ. Implementations SHOULD ensure that all options have equivalent security properties, so there's no incentive for attacks ๐Ÿ”’.

Since results from the network can determine how a connection attempt tree is built, such as when DNS returns a list of resolved endpoints, the network could cause an implementation to consume significant on-device resources ๐Ÿ˜ญ. Implementations SHOULD limit the maximum amount of state allowed for any given node, including the number of child nodes, especially when the state is based on results from the network ๐Ÿงฏ.

13. References ๐Ÿ“š

13.1. Normative References

Here are the normative references, all of them, no skipping ๐Ÿ’….

[RFC7413]  Cheng, Y., Chu, J., Radhakrishnan, S., and A. Jain, "TCP
           Fast Open", RFC 7413, DOI 10.17487/RFC7413, December 2014,
           <https://www.rfc-editor.org/info/rfc7413>.

[RFC8303]  Welzl, M., Tuexen, M., and N. Khademi, "On the Usage of
           Transport Features Provided by IETF Transport Protocols",
           RFC 8303, DOI 10.17487/RFC8303, February 2018,
           <https://www.rfc-editor.org/info/rfc8303>.

[RFC8304]  Fairhurst, G. and T. Jones, "Transport Features of the
           User Datagram Protocol (UDP) and Lightweight UDP (UDP-
           Lite)", RFC 8304, DOI 10.17487/RFC8304, February 2018,
           <https://www.rfc-editor.org/info/rfc8304>.

[RFC8305]  Schinazi, D. and T. Pauly, "Happy Eyeballs Version 2:
           Better Connectivity Using Concurrency", RFC 8305,
           DOI 10.17487/RFC8305, December 2017,
           <https://www.rfc-editor.org/info/rfc8305>.

[RFC8421]  Martinsen, P., Reddy, T., and P. Patil, "Guidelines for
           Multihomed and IPv4/IPv6 Dual-Stack Interactive
           Connectivity Establishment (ICE)", BCP 217, RFC 8421,
           DOI 10.17487/RFC8421, July 2018,
           <https://www.rfc-editor.org/info/rfc8421>.

[RFC8446]  Rescorla, E., "The Transport Layer Security (TLS) Protocol
           Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
           <https://www.rfc-editor.org/info/rfc8446>.

[RFC8923]  Welzl, M. and S. Gjessing, "A Minimal Set of Transport
           Services for End Systems", RFC 8923, DOI 10.17487/RFC8923,
           October 2020, <https://www.rfc-editor.org/info/rfc8923>.

[RFC9113]  Thomson, M., Ed. and C. Benfield, Ed., "HTTP/2", RFC 9113,
           DOI 10.17487/RFC9113, June 2022,
           <https://www.rfc-editor.org/info/rfc9113>.

[RFC9621]  Pauly, T., Ed., Trammell, B., Ed., Brunstrom, A.,
           Fairhurst, G., and C. S. Perkins, "Architecture and
           Requirements for Transport Services", RFC 9621,
           DOI 10.17487/RFC9621, January 2025,
           <https://www.rfc-editor.org/info/rfc9621>.

[RFC9622]  Trammell, B., Ed., Welzl, M., Ed., Enghardt, R.,
           Fairhurst, G., Kรผhlewind, M., Perkins, C. S., Tiesel, P.
           S., and T. Pauly, "An Abstract Application Programming
           Interface (API) for Transport Services", RFC 9622,
           DOI 10.17487/RFC9622, January 2025,
           <https://www.rfc-editor.org/info/rfc9622>.

13.2. Informative References โœจ

[NEAT-flow-mapping] Weinrank, F. and M. Tuxen, "Transparent flow mapping for NEAT", 2017 IFIP Networking Conference (IFIP Networking) and Workshops, DOI 10.23919/IFIPNetworking.2017.8264876, June 2017, https://ieeexplore.ieee.org/document/8264876.

[RFC1928] Leech, M., Ganis, M., Lee, Y., Kuris, R., Koblas, D., and L. Jones, "SOCKS Protocol Version 5", RFC 1928, DOI 10.17487/RFC1928, March 1996, https://www.rfc-editor.org/info/rfc1928.

[RFC2782] Gulbrandsen, A., Vixie, P., and L. Esibov, "A DNS RR for specifying the location of services (DNS SRV)", RFC 2782, DOI 10.17487/RFC2782, February 2000, https://www.rfc-editor.org/info/rfc2782.

[RFC3124] Balakrishnan, H. and S. Seshan, "The Congestion Manager", RFC 3124, DOI 10.17487/RFC3124, June 2001, https://www.rfc-editor.org/info/rfc3124.

[RFC3207] Hoffman, P., "SMTP Service Extension for Secure SMTP over Transport Layer Security", RFC 3207, DOI 10.17487/RFC3207, February 2002, https://www.rfc-editor.org/info/rfc3207.

[RFC6525] Stewart, R., Tuexen, M., and P. Lei, "Stream Control Transmission Protocol (SCTP) Stream Reconfiguration", RFC 6525, DOI 10.17487/RFC6525, February 2012, https://www.rfc-editor.org/info/rfc6525.

[RFC6762] Cheshire, S. and M. Krochmal, "Multicast DNS", RFC 6762, DOI 10.17487/RFC6762, February 2013, https://www.rfc-editor.org/info/rfc6762.

[RFC6763] Cheshire, S. and M. Krochmal, "DNS-Based Service Discovery", RFC 6763, DOI 10.17487/RFC6763, February 2013, https://www.rfc-editor.org/info/rfc6763.

[RFC7657] Black, D., Ed. and P. Jones, "Differentiated Services (Diffserv) and Real-Time Communication", RFC 7657, DOI 10.17487/RFC7657, November 2015, https://www.rfc-editor.org/info/rfc7657.

[RFC8085] Eggert, L., Fairhurst, G., and G. Shepherd, "UDP Usage Guidelines", BCP 145, RFC 8085, DOI 10.17487/RFC8085, March 2017, https://www.rfc-editor.org/info/rfc8085.

[RFC8260] Stewart, R., Tuexen, M., Loreto, S., and R. Seggelmann, "Stream Schedulers and User Message Interleaving for the Stream Control Transmission Protocol", RFC 8260, DOI 10.17487/RFC8260, November 2017, https://www.rfc-editor.org/info/rfc8260.

[RFC8445] Keranen, A., Holmberg, C., and J. Rosenberg, "Interactive Connectivity Establishment (ICE): A Protocol for Network Address Translator (NAT) Traversal", RFC 8445, DOI 10.17487/RFC8445, July 2018, https://www.rfc-editor.org/info/rfc8445.

[RFC8489] Petit-Huguenin, M., Salgueiro, G., Rosenberg, J., Wing, D., Mahy, R., and P. Matthews, "Session Traversal Utilities for NAT (STUN)", RFC 8489, DOI 10.17487/RFC8489, February 2020, https://www.rfc-editor.org/info/rfc8489.

[RFC8656] Reddy, T., Ed., Johnston, A., Ed., Matthews, P., and J. Rosenberg, "Traversal Using Relays around NAT (TURN): Relay Extensions to Session Traversal Utilities for NAT (STUN)", RFC 8656, DOI 10.17487/RFC8656, February 2020, https://www.rfc-editor.org/info/rfc8656.

[RFC9000] Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based Multiplexed and Secure Transport", RFC 9000, DOI 10.17487/RFC9000, May 2021, https://www.rfc-editor.org/info/rfc9000.

[RFC9040] Touch, J., Welzl, M., and S. Islam, "TCP Control Block Interdependence", RFC 9040, DOI 10.17487/RFC9040, July 2021, https://www.rfc-editor.org/info/rfc9040.

[RFC9110] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., "HTTP Semantics", STD 97, RFC 9110, DOI 10.17487/RFC9110, June 2022, https://www.rfc-editor.org/info/rfc9110.

[RFC9460] Schwartz, B., Bishop, M., and E. Nygren, "Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records)", RFC 9460, DOI 10.17487/RFC9460, November 2023, https://www.rfc-editor.org/info/rfc9460.

[TCP-COUPLING] Islam, S., Welzl, M., Hiorth, K., Hayes, D., Armitage, G., and S. Gjessing, "ctrlTCP: Reducing latency through coupled, heterogeneous multi-flow TCP congestion control", IEEE INFOCOM 2018 - IEEE Conference on Computer Communications Workshops (INFOCOM WKSHPS), DOI 10.1109/INFCOMW.2018.8406887, 2018, https://ieeexplore.ieee.org/document/8406887.


Appendix A. API Mapping Template ๐Ÿงš

okay so every protocol mapping for the Transport Services API should follow this same little template, so everyone's speaking the same language ๐Ÿ’…

Connectedness: (Connectionless/Connected/Multiplexing Connected)

Data Unit: (Byte-stream/Datagram/Message)

Connection Object:

Initiate:

InitiateWithSend:

Ready:

EstablishmentError:

ConnectionError:

Listen:

ConnectionReceived:

Clone:

Send:

Receive:

Close:

Abort:

CloseGroup:

AbortGroup:

Appendix B. Reasons for Errors ๐Ÿšจ

okay so the Transport Services API [RFC9622] lets you give a more detailed reason when a generic error pops up. this appendix is basically the cheat sheet of possible reasons, and it's giving very helpful energy ๐Ÿซถ

  • InvalidConfiguration: the Properties and Endpoint Identifiers the app gave are either contradictory or incomplete. for example, no Remote Endpoint Identifier on an active open, or using a multicast group address without asking for a Unidirectional receive. the vibe is "your setup doesn't add up" ๐Ÿ˜ญ
  • NoCandidates: the configuration is totally valid, but none of the available transport protocols can meet the Properties the app asked for. no cap, they just can't do it ๐Ÿ’€
  • ResolutionFailed: the remote or local specifier the app gave can't be resolved ๐Ÿ™ˆ
  • EstablishmentFailed: the Transport Services System couldn't set up a transport-layer connection to the Remote Endpoint the app asked for ๐Ÿฅฒ
  • PolicyProhibited: the System Policy won't let the Transport Services System do what the app asked. rules are rules bestie ๐Ÿ™…โ€โ™€๏ธ
  • NotCloneable: the Protocol Stack can't be cloned ๐Ÿฆ‹
  • MessageTooLarge: the Message is too big for the Transport Services System to handle ๐Ÿ“
  • ProtocolFailed: the underlying Protocol Stack failed. literally the worst ๐Ÿ˜ฉ
  • InvalidMessageProperties: the Message Properties either contradict the Transport Properties, or the Transport Services System can't satisfy them. two things that don't match, very awkward ๐Ÿ™ƒ
  • DeframingFailed: the data the underlying Protocol Stack received couldn't be processed by the Message Framer ๐Ÿงถ
  • ConnectionAborted: the peer aborted the connection ๐Ÿ‘‹
  • Timeout: delivering a Message wasn't possible before a timeout hit โฐ

Appendix C. Existing Implementations ๐ŸŒˆ

this appendix is the highlight reel of existing implementations (as of when this was written) of Transport Services Systems that are, to some degree, in line with this document. slay ๐Ÿ‘‘


Acknowledgements ๐Ÿซถ

this work got funding from the European Union's Horizon 2020 research and innovation programme under grant agreement No. 644334 (NEAT) and No. 815178 (5GENESIS) ๐Ÿ’œ

this work has also been supported by:

  • Leibniz Prize project funds from the DFG - German Research Foundation: Gottfried Wilhelm Leibniz-Preis 2011 (FKZ FE 570/4-1). ๐Ÿ†
  • the UK Engineering and Physical Sciences Research Council under grant EP/R04144X/1. ๐Ÿ‡ฌ๐Ÿ‡ง
  • the Research Council of Norway under its "Toppforsk" programme through the "OCARINA" project. ๐Ÿ‡ณ๐Ÿ‡ด

big thanks to Colin S. Perkins, Tom Jones, Karl-Johan Grinnemo, and Gorry Fairhurst for their contributions to the design of this specification ๐ŸŒธ and to Stuart Cheshire, Josh Graessley, David Schinazi, and Eric Kinnear for their implementation and design efforts, including Happy Eyeballs, which heavily influenced this work. the whole group chat is iconic ๐Ÿ‘‘


Authors' Addresses ๐Ÿ’Œ

Anna Brunstrom (editor)

Anna Brunstrom (editor)
Karlstad University
Universitetsgatan 2
651 88 Karlstad
Sweden
Email: anna.brunstrom@kau.se
Tommy Pauly (editor)
Apple Inc.
One Apple Park Way
Cupertino, CA 95014
United States of America
Email: tpauly@apple.com
Reese Enghardt
Netflix
121 Albright Way
Los Gatos, CA 95032
United States of America
Email: ietf@tenghardt.net
Philipp S. Tiesel
SAP SE
George-Stephenson-Str. 7-13
10557 Berlin
Germany
Email: philipp@tiesel.net
Michael Welzl
University of Oslo
PO Box 1080 Blindern
0316 Oslo
Norway
Email: michawe@ifi.uio.no
๐Ÿ“ฆ where this page comes from