RFC 8684

TCP Extensions for Multipath Operation with Multiple Addresses

๐Ÿ“… published March 2020, from ietf

RFC 8684: TCP Extensions for Multipath Operation with Multiple Addresses โœจ

okay bestie, this is RFC 8684 ๐Ÿฆ‹

It's a Standards Track document from March 2020 (ISSN 2070-1721) by a squad of five: A. Ford of Pexip, C. Raiciu of U. Politehnica of Bucharest, M. Handley of U. College London, O. Bonaventure of U. catholique de Louvain, and C. Paasch of Apple, Inc. ๐Ÿ’… It obsoletes RFC 6824, which is MPTCP v0, and it doesn't list any updates. So this is the glow-up. ๐ŸŒธ


Abstract

TCP/IP is currently stuck with one path per connection, even though there are often multiple paths between two peers ๐Ÿ™ƒ Using several of those paths at the same time for one TCP/IP session would use network resources better and give users higher throughput and better resilience when something breaks. ๐Ÿ”ฅ

Multipath TCP is the thing that lets peers use multiple paths simultaneously. This document adds a set of extensions to regular TCP to make that possible. It still gives applications the same kind of service as TCP (a reliable bytestream โœจ), and it includes the parts needed to set up and use multiple TCP flows across paths that might be totally separate from each other.

This document specifies v1 of Multipath TCP and obsoletes v0 from RFC 6824. The changes are clarifications and modifications, mostly driven by what people learned from actually deploying it ๐ŸŽ€


Status of This Memo

This is an Internet Standards Track document. ๐Ÿ’œ

It's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. It went through public review and got approved for publication by the Internet Engineering Steering Group (IESG). If you want more on Internet Standards, check out Section 2 of RFC 7841 ๐Ÿงš

For the current status, any errata, and how to send feedback, go to https://www.rfc-editor.org/info/rfc8684. ๐ŸŒˆ


Copyright (c) 2020 IETF Trust and the persons identified as the document authors. All rights reserved. ๐ŸŽ€

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the publication date. Read those carefully because they spell out your rights and restrictions for this document ๐Ÿ“œ Code Components pulled out of this document have to include Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come with no warranty, as described in the Simplified BSD License. ๐Ÿ’–


Table of Contents

1.  Introduction
  1.1.  Design Assumptions
  1.2.  Multipath TCP in the Networking Stack
  1.3.  Terminology
  1.4.  MPTCP Concept
  1.5.  Requirements Language
2.  Operation Overview
  2.1.  Initiating an MPTCP Connection
  2.2.  Associating a New Subflow with an Existing MPTCP Connection
  2.3.  Informing the Other Host about Another Potential Address
  2.4.  Data Transfer Using MPTCP
  2.5.  Requesting a Change in a Path's Priority
  2.6.  Closing an MPTCP Connection
  2.7.  Notable Features
3.  MPTCP Operations: An Overview
  3.1.  Connection Initiation
  3.2.  Starting a New Subflow
  3.3.  MPTCP Operation and Data Transfer
    3.3.1.  Data Sequence Mapping
    3.3.2.  Data Acknowledgments
    3.3.3.  Closing a Connection
    3.3.4.  Receiver Considerations
    3.3.5.  Sender Considerations
    3.3.6.  Reliability and Retransmissions
    3.3.7.  Congestion Control Considerations
    3.3.8.  Subflow Policy
  3.4.  Address Knowledge Exchange (Path Management)
    3.4.1.  Address Advertisement
    3.4.2.  Remove Address
  3.5.  Fast Close
  3.6.  Subflow Reset
  3.7.  Fallback
  3.8.  Error Handling
  3.9.  Heuristics
    3.9.1.  Port Usage
    3.9.2.  Delayed Subflow Start and Subflow Symmetry
    3.9.3.  Failure Handling
4.  Semantic Issues
5.  Security Considerations
6.  Interactions with Middleboxes
7.  IANA Considerations
  7.1.  TCP Option Kind Numbers
  7.2.  MPTCP Option Subtypes
  7.3.  MPTCP Handshake Algorithms
  7.4.  MP_TCPRST Reason Codes
8.  References
  8.1.  Normative References
  8.2.  Informative References
Appendix A.  Notes on Use of TCP Options
Appendix B.  TCP Fast Open and MPTCP
  B.1.  TFO Cookie Request with MPTCP
  B.2.  Data Sequence Mapping under TFO
  B.3.  Connection Establishment Examples
Appendix C.  Control Blocks
  C.1.  MPTCP Control Block
    C.1.1.  Authentication and Metadata
    C.1.2.  Sending Side
    C.1.3.  Receiving Side
  C.2.  TCP Control Blocks
    C.2.1.  Sending Side
    C.2.2.  Receiving Side
Appendix D.  Finite State Machine
Appendix E.  Changes from RFC 6824
Acknowledgments
Authors' Addresses

1. Introduction ๐Ÿฆ‹

Multipath TCP (MPTCP) is a set of extensions to regular TCP [RFC0793]. It provides a Multipath TCP service [RFC6182], which lets one transport connection run across multiple paths at once. ๐Ÿš€

This document covers the protocol changes needed to add multipath to TCP. That means signaling and setting up multiple paths (called "subflows"), managing those subflows, putting data back together in order, and ending sessions. ๐Ÿ’… It isn't the only info you need to build an MPTCP implementation, though. This document has three companion docs:

  • [RFC6182] (MPTCP architecture): explains why Multipath TCP exists, talks through the high-level design decisions this design is built on, and explains a functional separation that lets an extensible MPTCP implementation get developed. ๐Ÿง 
  • [RFC6356] (congestion control): presents a safe congestion control algorithm that couples the behavior of the multiple paths so they "do no harm" to other network users. ๐Ÿซถ
  • [RFC6897] (application considerations): covers what MPTCP does to applications, what applications will want to do with it, and, because of all that, which API extensions an MPTCP implementation should expose. โœจ

This document obsoletes the v0 spec of Multipath TCP [RFC6824]. It specifies MPTCP v1, which is not backward compatible with MPTCP v0. It also defines version negotiation procedures for implementations that support both versions. ๐Ÿ”ฅ

1.1. Design Assumptions ๐ŸŒธ

To keep the potentially huge design space under control, the MPTCP Working Group put two key constraints on the design in this document:

  • It must be backward compatible with current, regular TCP, so it has a better shot at getting deployed. ๐Ÿ’–
  • We can assume that one or both hosts are multihomed and multiaddressed. ๐ŸŒ

To keep things simple, we assume that having multiple addresses at a host is enough to mean there are multiple paths. Those paths don't have to be totally disjoint. They might share one or more routers. Even then, using multiple paths helps: it improves resource utilization and resilience to some node failures. The congestion control algorithm in [RFC6356] makes sure that using multiple paths doesn't hurt anyone else. ๐Ÿ›ก๏ธ Also, in some setups, different TCP ports on one host can give you disjoint paths (for example, through certain Equal-Cost Multipath (ECMP) implementations [RFC2992]), so the MPTCP design also supports using ports as part of path identifiers. ๐Ÿงš

There are three aspects to the backward compatibility above (covered in more detail in [RFC6182]):

External Constraints: The protocol has to work through the vast majority of existing middleboxes, like NATs, firewalls, and proxies. So it has to look as much like existing TCP as possible on the wire. The protocol also can't assume that the segments it sends arrive at the destination unmodified. They may be split or coalesced, and TCP options may be removed or duplicated. ๐Ÿ‘€

Application Constraints: The protocol has to work with no changes to existing applications that use the common TCP API. (It's reasonable that not every feature will be available to those legacy apps.) The protocol also has to give applications the same service model as regular TCP. ๐Ÿ’…

Fallback: The protocol should be able to fall back to standard TCP without any interference from the user, so it can talk to legacy hosts. ๐Ÿฆ‹

The companion application considerations doc [RFC6897] goes over what an API needs for backward compatibility, plus API extensions that show MPTCP's behavior at the same level of control and information you'd get with regular single-path TCP. ๐ŸŽ€

More on the design constraints and the decisions made around them is in the MPTCP architecture doc [RFC6182] and in [howhard]. ๐Ÿ“š

1.2. Multipath TCP in the Networking Stack ๐Ÿงš

MPTCP works at the transport layer, and it's meant to be transparent to both the higher and lower layers. It's a set of extra features stacked on top of standard TCP. Figure 1 shows that layering ๐Ÿฅž MPTCP is designed so legacy applications can use it with no changes. For a detailed look at how it interacts with applications, see [RFC6897]. ๐Ÿ’œ

                                 +-------------------------------+
                                 |           Application         |
    +---------------+            +-------------------------------+
    |  Application  |            |             MPTCP             |
    +---------------+            + - - - - - - - + - - - - - - - +
    |      TCP      |            | Subflow (TCP) | Subflow (TCP) |
    +---------------+            +-------------------------------+
    |      IP       |            |       IP      |      IP       |
    +---------------+            +-------------------------------+

     Figure 1: Comparison of Standard TCP and MPTCP Protocol Stacks
Part 1 of 20 is done, bestie ๐Ÿซถ Part 2 is coming with Terminology, the MPTCP Concept, and the Requirements Language ๐ŸŒˆ

1.3. Terminology

okay so before we dive in, bestie, there are a few MPTCP-specific terms we need to be on the same page about ๐ŸŽ€ these are either brand new to MPTCP or they mean something special here:

  • Path: a sequence of links between a sender and a receiver. In this context it's defined by a 4-tuple of source and destination address/port pairs ๐Ÿฆ‹
  • Subflow: a flow of TCP segments running over one individual path, which forms part of a bigger MPTCP connection. It gets started and terminated pretty much the same way a regular TCP connection does ๐Ÿ’…
  • (MPTCP) Connection: a set of one or more subflows that an application uses to communicate between two hosts. There's a one-to-one mapping between a connection and an application socket ๐ŸŒธ
  • Data-level: the payload data is nominally transferred over a connection, which is then carried over subflows. So "data-level" is basically a synonym for "connection-level," as opposed to "subflow-level," which is about the properties of one individual subflow ๐Ÿงš
  • Token: a locally unique identifier that a host gives to a multipath connection. It can also be called a "Connection ID" ๐Ÿ”‘
  • Host: an end host running an MPTCP implementation that either starts or accepts an MPTCP connection ๐Ÿ’œ

In addition to these terms, MPTCP's interpretation of, and effect on, regular single-path TCP semantics is covered in Section 4. Don't skip it, it's giving important ๐Ÿ‘‘

1.4. MPTCP Concept

this section is the highlight reel of how MPTCP normally works โœจ it's a high-level summary, and you'll see the whole scenario in Figure 2. if you want the full details, Section 3 has them ๐Ÿซถ

           Host A                               Host B
  ------------------------             ------------------------
  Address A1    Address A2             Address B1    Address B2
  ----------    ----------             ----------    ----------
      |             |                      |             |
      |     (initial connection setup)     |             |
      |----------------------------------->|             |
      |<-----------------------------------|             |
      |             |                      |             |
      |            (additional subflow setup)            |
      |             |--------------------->|             |
      |             |<---------------------|             |
      |             |                      |             |
      |             |                      |             |

Figure 2: Example MPTCP Usage Scenario

  • to a non-MPTCP-aware application, MPTCP behaves the same as normal TCP ๐ŸŒˆ extended APIs could give MPTCP-aware applications extra control [RFC6897]. the app starts by opening a TCP socket in the normal way, and MPTCP signaling and operation are handled by the MPTCP implementation. no drama for the app ๐Ÿ’…
  • an MPTCP connection starts out just like a regular TCP connection. Figure 2 shows an MPTCP connection being set up between addresses A1 and B1 on Hosts A and B, respectively ๐Ÿฆ‹
  • if extra paths are available, additional TCP sessions (called MPTCP "subflows") get created on those paths and combined with the existing session. the existing session keeps looking like one single connection to the applications at both ends ๐ŸŽ€ the extra session in the figure is being created between Address A2 on Host A and Address B1 on Host B.
  • MPTCP identifies multiple paths by the presence of multiple addresses at hosts. Combinations of those addresses equal the additional paths. in the example, other potential paths that could be set up are A1<->B2 and A2<->B2. even though this extra session is shown starting from A2, it could just as easily have started from B1 or B2 ๐Ÿ“
  • the discovery and setup of additional subflows gets done through a path management method. this document describes a mechanism where a host can start new subflows using its own additional addresses, or by signaling its available addresses to the other host ๐Ÿงš
  • MPTCP adds connection-level sequence numbers so segments that arrive on multiple subflows, with differing network delays, can be reassembled ๐ŸŒธ
  • subflows get terminated like regular TCP connections, with a four-way FIN handshake. the MPTCP connection itself is terminated by a connection-level FIN ๐Ÿซถ

1.5. Requirements Language

okay, this part is the rulebook for capital letters ๐Ÿ™ˆ the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. so if you see them in ALL CAPS, they're doing the official BCP 14 thing ๐Ÿ‘‘ lowercase ones are just regular English, no cap ๐Ÿ’€

2. Operation Overview

this section is one clean description of how MPTCP usually operates, with references to the protocol's behaviour. think of it as the high-level tour of the key functions ๐Ÿ—บ๏ธ the full specification comes later in Section 3. extensibility and negotiated features aren't covered here, so don't look for them yet ๐ŸŒธ a lot of this section refers to the symbolic names of MPTCP options. those are subtypes of the IANA-assigned MPTCP option (see Section 7), and their formats are defined in the detailed protocol specification in Section 3 ๐ŸŽ€

okay the big picture is that a Multipath TCP connection gives you a bidirectional bytestream between two hosts that communicate like normal TCP, so it doesn't require any changes to the applications ๐Ÿฆ‹ but MPTCP lets the hosts use different paths with different IP addresses to swap packets that belong to the MPTCP connection. to an application, a Multipath TCP connection still looks like a normal TCP connection ๐Ÿ’… to the network layer, though, each MPTCP subflow looks like a regular TCP flow whose segments carry a new TCP option type โœจ MPTCP handles creating, removing, and using these subflows to send data. the number of subflows isn't fixed; it can go up and down during the lifetime of the connection. that's giving flexible ๐Ÿงš

all MPTCP operations are signaled with a TCP option. it's a single numerical type for MPTCP, with "subtypes" for each MPTCP message ๐Ÿ”ฅ what comes next is a quick summary of what these messages are for and why they exist.

2.1. Initiating an MPTCP Connection

this is the same signaling as starting a normal TCP connection, except the SYN, SYN/ACK, and initial ACK (and data) packets also carry the MP_CAPABLE option ๐Ÿ“ this option has a variable length and does a few jobs. first, it checks whether the remote host supports Multipath TCP. second, it lets the hosts swap some information to authenticate the setup of additional subflows. more details are in Section 3.1 ๐Ÿ’–

   Host A                                  Host B
   ------                                  ------
   MP_CAPABLE                ->
   [flags]
                             <-            MP_CAPABLE
                                           [B's key, flags]
   ACK + MP_CAPABLE (+ data) ->
   [A's key, B's key, flags, (data-level details)]

okay now the retransmission part, which is lowkey important ๐Ÿ™ˆ an ACK + MP_CAPABLE can be retransmitted if it isn't known whether it was received. the following diagrams show all the possible exchanges for the initial subflow setup, so the reliability is covered ๐Ÿซถ

   Host A (with data to send immediately)  Host B
   ------                                  ------
   MP_CAPABLE                ->
   [flags]
                             <-            MP_CAPABLE
                                           [B's key, flags]
   ACK + MP_CAPABLE + data   ->
   [A's key, B's key, flags, data-level details]
   Host A (with data to send later)        Host B
   ------                                  ------
   MP_CAPABLE                ->
   [flags]
                             <-            MP_CAPABLE
                                           [B's key, flags]
   ACK + MP_CAPABLE          ->
   [A's key, B's key, flags]

   ACK + MP_CAPABLE + data   ->
   [A's key, B's key, flags, data-level details]
   Host A                                  Host B (sending first)
   ------                                  ------
   MP_CAPABLE                ->
   [flags]
                             <-            MP_CAPABLE
                                           [B's key, flags]
   ACK + MP_CAPABLE          ->
   [A's key, B's key, flags]

                             <-            ACK + DSS + data
                                           [data-level details]

2.2. Associating a New Subflow with an Existing MPTCP Connection

the key exchange in the MP_CAPABLE handshake gives you material to authenticate the endpoints once new subflows get set up ๐Ÿ” additional subflows start the same way a normal TCP connection does, but the SYN, SYN/ACK, and ACK packets also carry the MP_JOIN option ๐Ÿฆ‹

Host A starts a new subflow between one of its addresses and one of Host B's addresses ๐ŸŒธ the token, which is generated from the key, identifies which MPTCP connection is being joined. the Hash-based Message Authentication Code (HMAC) handles authentication. the HMAC uses the keys exchanged in the MP_CAPABLE handshake, plus the random numbers (nonces) exchanged in these MP_JOIN options ๐Ÿงš MP_JOIN also carries flags and an Address ID, so the source address can be referred to without the sender needing to know whether a NAT changed it ๐Ÿ‘‘ more details are in Section 3.2.

   Host A                                  Host B
   ------                                  ------
   MP_JOIN               ->
   [B's token, A's nonce,
    A's Address ID, flags]
                         <-                MP_JOIN
                                           [B's HMAC, B's nonce,
                                            B's Address ID, flags]
   ACK + MP_JOIN         ->
   [A's HMAC]

                         <-                ACK

2.3. Informing the Other Host about Another Potential Address ๐Ÿฆ‹

okay so a multihomed host's set of IP addresses can totally change while an MPTCP connection is running ๐ŸŒธ MPTCP lets you add and remove addresses both implicitly and explicitly. If Host A already has a subflow going from address/port pair IP#-A1 and wants a second one starting at IP#-A2, it just starts setting up that subflow like we talked about earlier. The remote host finds out about the new address automatically, no extra paperwork needed โœจ

sometimes a host wants to tell the other side about an address without opening a new subflow yet. the classic example is when a NAT is blocking setup in one direction ๐Ÿ™ˆ In the example below, Host A tells Host B about its alternative IP address/port pair (IP#-A2). Host B can then send an MP_JOIN to that new address later on. The ADD_ADDR option carries an HMAC so the address is authenticated as coming from the connection's originator, and the receiver echoes it back to the client to say "yep, got it" ๐Ÿ’… The full details live in Section 3.4.1.

      Host A                                 Host B
      ------                                 ------
      ADD_ADDR                  ->
      [Echo-flag=0,
       IP#-A2,
       IP#-A2's Address ID,
       HMAC of IP#-A2]

                                <-          ADD_ADDR
                                            [Echo-flag=1,
                                             IP#-A2,
                                             IP#-A2's Address ID,
                                             HMAC of IP#-A2]

there's a matching signal for removing an address too, and it uses the Address ID that got signaled during the ADD_ADDR handshake ๐Ÿงš More details are in Section 3.4.2.

      Host A                                 Host B
      ------                                 ------
      REMOVE_ADDR               ->
      [IP#-A2's Address ID]

2.4. Data Transfer Using MPTCP ๐Ÿ’–

okay this part is so clever, bestie ๐Ÿ˜ญ To get reliable, in-order delivery over subflows that can show up and vanish at any moment, MPTCP numbers all the data on the connection with a 64-bit Data Sequence Number (DSN). Each subflow still has its own 32-bit sequence number space using the regular TCP sequence number header, and an MPTCP option maps that subflow space onto the data sequence space. So data can be retransmitted on a different subflow (mapped to the same DSN) if something fails ๐Ÿ”ฅ

the Data Sequence Signal (DSS) is what carries the Data Sequence Mapping. The mapping is made of the subflow sequence number, the data sequence number, and the length the mapping is valid for. The DSS can also carry a connection-level acknowledgment (the "Data ACK") for the DSN that was received ๐ŸŒˆ

all the subflows share one receive buffer and advertise the same receive window, and there are two levels of acknowledgment going on. Regular TCP ACKs happen on each subflow to acknowledge segments sent over that subflow, no matter what their DSN is. On top of that there are connection-level acknowledgments for the data sequence space. Those track how far the bytestream has moved along and slide the receive window forward ๐Ÿฆ‹

More details are in Section 3.3.

      Host A                                 Host B
      ------                                 ------
      DSS                       ->
      [Data Sequence Mapping]
      [Data ACK]
      [Checksum]

2.5. Requesting a Change in a Path's Priority ๐ŸŽ€

hosts can say at initial subflow setup whether a subflow should be a regular path or a backup path. A backup path only gets used if there are no regular paths available ๐Ÿซถ During a connection, Host A can ask for a priority change on a subflow using the MP_PRIO signal sent to Host B. Further details are given in Section 3.3.8.

      Host A                                 Host B
      ------                                 ------
      MP_PRIO                   ->

2.6. Closing an MPTCP Connection ๐ŸŒธ

if a host wants to close one subflow but not the whole connection, it can just do a regular TCP FIN/ACK exchange. easy peasy ๐Ÿ’…

when Host A has no more data to send, it signals a "Data FIN" as part of the DSS. It works with the same semantics and behavior as a regular TCP FIN, just at the connection level instead. Once all the data on the MPTCP connection has been received successfully, the Data FIN gets acknowledged at the connection level with a Data ACK โœจ More details are in Section 3.3.3.

      Host A                                 Host B
      ------                                 ------
      DSS                       ->
      [Data FIN]
                                <-           DSS
                                             [Data ACK]

there's also a method called "Fast Close," which is basically like closing a single-path TCP connection with a RST ๐Ÿ’€ The MP_FASTCLOSE signal tells the peer the connection is about to be slammed shut and no more data will be accepted. You can send it on an ACK (which makes the signal reliable) or on a RST (which doesn't). Both versions are shown in the diagrams below. Further details are in Section 3.5.

      Host A                                 Host B
      ------                                 ------
      ACK + MP_FASTCLOSE          ->
      [B's key]

      [RST on all other subflows] ->

                                  <-         [RST on all subflows]


      Host A                                 Host B
      ------                                 ------
      RST + MP_FASTCLOSE          ->
      [B's key] [on all subflows]

                                  <-         [RST on all subflows]

2.7. Notable Features ๐Ÿ‘‘

MPTCP's signaling was designed with a few key requirements in mind, and these are the highlights ๐ŸŒˆ

  • to deal with NATs on the path, addresses are referred to by Address IDs, in case a NAT changes the IP packet's source address. Setting up a new TCP flow isn't possible if the receiver of the SYN is behind a NAT, so to let subflows be created when either end is behind a NAT, MPTCP uses the ADD_ADDR message ๐Ÿ™ˆ
  • MPTCP falls back to ordinary TCP if MPTCP operation isn't possible. For example, that happens if one host isn't MPTCP capable or if a middlebox alters the payload. This is discussed in Section 3.7.
  • to address the threats identified in [RFC6181], the following steps are taken. Keys are sent in the clear in the MP_CAPABLE messages. MP_JOIN messages are secured with HMAC-SHA256 ([RFC2104] using the algorithm in [RFC6234]) using those keys. Standard TCP validity checks are made on the other messages, which ensures that sequence numbers are in-window [RFC5961]. Residual threats to MPTCP v0 were identified in [RFC7430], and the ones affecting the protocol (that is, modifications to ADD_ADDR) have been incorporated into this document. Further discussion of security can be found in Section 5 ๐Ÿ”ฅ

3. MPTCP Operations: An Overview ๐Ÿงš

this section is where the operation of MPTCP actually gets explained, and each subsection covers one key part of how the protocol works ๐Ÿ’œ

all MPTCP operations are signaled using optional TCP header fields. IANA assigned a single TCP option number ("Kind") for MPTCP (see Section 7). Individual messages are then identified by a "subtype," and those subtype values are also stored in an IANA registry (and listed in Section 7 too). Just like every TCP option, the Length field is in bytes and includes the 2 bytes of Kind and Length ๐ŸŽ€

when this document refers to an MPTCP option by its symbolic name, like "MP_CAPABLE," it means a TCP option with the single MPTCP option type and the subtype value that matches that symbolic name, as defined in Section 7. The subtype is a 4-bit field, namely the first 4 bits of the option payload, as shown in Figure 3. The MPTCP messages themselves get defined in the sections that follow โœจ

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------+---------------+-------+-----------------------+
 |     Kind      |    Length     |Subtype|                       |
 +---------------+---------------+-------+                       |
 |                     Subtype-specific data                     |
 |                       (variable length)                       |
 +---------------------------------------------------------------+

Figure 3: MPTCP Option Format ๐ŸŒธ

some MPTCP options are tied to subflow initiation, and those get used on packets with the SYN flag set ๐Ÿ“ There's also one MPTCP option for signaling metadata so that segmented data can be recombined for delivery to the application.

the remaining options are signals that don't need to sit on any particular packet, like the ones for signaling extra addresses. An implementation might want to send MPTCP options as soon as possible, but it may not be possible to fit every option on one packet. That includes both the MPTCP options and the regular TCP ones, like SACK (selective acknowledgment) [RFC2018]. So an implementation may choose to send duplicate ACKs that carry the extra signaling information. That changes what a duplicate ACK means ๐Ÿ˜ญ Duplicate ACKs are usually only sent in regular TCP as a signal of a lost segment [RFC5681]. So an MPTCP implementation that receives a duplicate ACK containing an MPTCP option MUST NOT treat it as a signal of congestion. Also, an MPTCP implementation SHOULD NOT send more than two duplicate ACKs in a row just to carry MPTCP options alone, so that no middleboxes misread it as a sign of congestion ๐Ÿ™ˆ

and one more thing: standard TCP validity checks (like making sure the sequence number and acknowledgment number are within the window) MUST be undertaken before any MPTCP signals get processed, as described in [RFC5961]. Initial subflow sequence numbers SHOULD be generated according to the recommendations in [RFC6528] ๐Ÿ’…

3.1. Connection Initiation ๐Ÿ’–

connection initiation kicks off with a SYN, SYN/ACK, ACK exchange on a single path ๐Ÿฆ‹ Each packet carries the Multipath Capable (MP_CAPABLE) MPTCP option (Figure 4). That option declares that its sender is capable of Multipath TCP and wants to use it on this particular connection โœจ

๐Ÿ’– Okay bestie, so here's the MP_CAPABLE layout. Lookin' cute, right? ๐ŸŽ€

                          1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +---------------+---------------+-------+-------+---------------+
     |     Kind      |    Length     |Subtype|Version|A|B|C|D|E|F|G|H|
     +---------------+---------------+-------+-------+---------------+
     |                   Option Sender's Key (64 bits)               |
     |                      (if option Length > 4)                   |
     |                                                               |
     +---------------------------------------------------------------+
     |                  Option Receiver's Key (64 bits)              |
     |                      (if option Length > 12)                  |
     |                                                               |
     +-------------------------------+-------------------------------+
     |  Data-Level Length (16 bits)  |  Checksum (16 bits, optional) |
     +-------------------------------+-------------------------------+

Figure 4: Multipath Capable (MP_CAPABLE) Option โœจ

Okay so the MP_CAPABLE exchange in this spec (v1) is different from what v0 did ๐Ÿ™ˆ If a host supports more than one version of MPTCP, the sender of the MP_CAPABLE option SHOULD signal the highest version number it supports. ๐Ÿงš Then the receiver answers in its own MP_CAPABLE option with the version it wants to use, and that version MUST be equal to or lower than the version number indicated in the initial MP_CAPABLE. Simple, right? ๐Ÿ’…

There's a little caveat though, with listeners that only support v0. A listener that supports v0 expects the MP_CAPABLE option in the SYN segment to include the initiator's key. If the initiator already upgraded to v1, it won't include the key in the SYN segment, so the listener ignores that MP_CAPABLE and replies with a SYN/ACK that doesn't include an MP_CAPABLE. ๐Ÿ˜ญ The initiator MAY choose to immediately fall back to TCP, or MAY choose to attempt a connection using MPTCP v0 (if the initiator supports v0), in order to discover whether the listener supports the earlier version. In general, an MPTCP v0 connection will likely be preferred over a TCP connection, but in a particular deployment scenario it may be known that the listener is unlikely to support MPTCP v0, so the initiator may prefer not to attempt a v0 connection. And an initiator MAY cache information for a peer about what version of MPTCP it supports, if any, and use this information for future connection attempts. ๐Ÿฆ‹

The MP_CAPABLE option is of variable length, with different fields included depending on which packet the option is used on. The full MP_CAPABLE option is shown in Figure 4 above. ๐ŸŒธ

The MP_CAPABLE option is carried on the SYN, SYN/ACK, and ACK packets that start the first subflow of an MPTCP connection, as well as the first packet that carries data, if the initiator wishes to send first. Here's the data each option carries, where A = initiator and B = listener:

  • SYN (A->B): only the first 4 octets (Length = 4).
  • SYN/ACK (B->A): B's key for this connection (Length = 12).
  • ACK (no data) (A->B): A's key followed by B's key (Length = 20).
  • ACK (with first data) (A->B): A's key followed by B's key followed by Data-Level Length, and optional Checksum (Length = 22 or 24).

The contents of the option are determined by the SYN and ACK flags of the packet, along with the option's Length field. In Figure 4, "Sender" and "Receiver" refer to the sender or receiver of the TCP packet (which can be either host). ๐Ÿ“

The initial SYN, containing just the MP_CAPABLE header, is used to define the version of MPTCP being requested and also to exchange flags to negotiate connection features, as described later. โœจ

This option is used to declare the 64-bit keys that the end hosts have generated for this MPTCP connection. ๐Ÿ”‘ These keys are used to authenticate the addition of future subflows to this connection. This is the only time the key will be sent in the clear on the wire (unless "Fast Close" (Section 3.5) is used). All future subflows will identify the connection using a 32-bit "token", which is a cryptographic hash of this key. The algorithm for this process depends on the authentication algorithm selected, and the method of selection is defined later in this section. ๐Ÿ’œ

Upon reception of the initial SYN segment, a stateful server generates a random key and replies with a SYN/ACK. The key's method of generation is implementation specific. The key MUST be hard to guess, and it MUST be unique for the sending host across all its current MPTCP connections. ๐Ÿ‘‘ Recommendations for generating random numbers for use in keys are given in [RFC4086]. Connections will be indexed at each host by the token (a one-way hash of the key). So an implementation will need a mapping from each token to the corresponding connection, and in turn to the keys for the connection.

There's a risk that two different keys will hash to the same token ๐Ÿ˜ฌ. The risk of hash collisions is usually small, unless the host is handling many tens of thousands of connections. Therefore, an implementation SHOULD check its list of connection tokens to ensure that there is no collision before sending its key, and if there is, then it should generate a new key. This would, however, be costly for a server with thousands of connections. The subflow handshake mechanism (Section 3.2) will ensure that new subflows only join the correct connection, though, through the cryptographic handshake, as well as checking the connection tokens in both directions and ensuring that sequence numbers are in-window. So, in the worst case, if there was a token collision, the new subflow would not succeed, but the MPTCP connection would continue to provide a regular TCP service. ๐Ÿ›Ÿ

Since key generation is implementation specific, there is no requirement that the keys simply be random numbers. An implementation is free to exchange cryptographic material out of band and generate these keys from this material, in order to provide additional mechanisms by which to verify the identity of the communicating entities. For example, an implementation could choose to link its MPTCP keys to those used in higher-layer TLS or SSH connections. ๐Ÿ”

If the server behaves in a stateless manner, it has to generate its own key in a verifiable fashion. This verifiable way of generating the key can be done by using a hash of the 4-tuple, sequence number, and a local secret (similar to what is done for the TCP sequence number [RFC4987]). It will thus be able to verify whether it is indeed the originator of the key echoed back in the subsequent MP_CAPABLE option. As for a stateful server, the tokens SHOULD be checked for uniqueness. However, if uniqueness is not met and there is no way to generate an alternative verifiable key, then the connection MUST fall back to using regular TCP by not sending an MP_CAPABLE in the SYN/ACK. ๐Ÿ•ต๏ธโ€โ™€๏ธ

The ACK carries both A's key and B's key. This is the first time that A's key is seen on the wire, although it is expected that A will have generated a key locally before the initial SYN. The echoing of B's key allows B to operate statelessly, as described above. Therefore, A's key must be delivered reliably to B, and in order to do this, the transmission of this packet must be made reliable. ๐Ÿซถ

If B has data to send first, then the reliable delivery of the ACK + MP_CAPABLE is ensured by the receipt of this data with an MPTCP Data Sequence Signal (DSS) option (Section 3.3) containing a DATA_ACK for the MP_CAPABLE (which is the first octet of the data sequence space). If, however, A wishes to send data first, it has two options to ensure the reliable delivery of the ACK + MP_CAPABLE. If it immediately has data to send, then the first ACK (with data) would also contain an MP_CAPABLE option with additional data parameters (the Data-Level Length and optional Checksum as shown in Figure 4). If A does not immediately have data to send, it MUST include the MP_CAPABLE on the first ACK, but without the additional data parameters. When A does have data to send, it must repeat the sending of the MP_CAPABLE option from the first ACK, with additional data parameters. ๐ŸŽฏ This MP_CAPABLE option is used in place of the DSS and simply specifies (1) the Data-Level Length of the payload and (2) the checksum (if the use of checksums is negotiated). This is the minimal data required to establish an MPTCP connection. It allows validation of the payload, and given that it is the first data, the Initial Data Sequence Number (IDSN) is also known (as it is generated from the key, as described below). Conveying the keys on the first data packet allows the TCP reliability mechanisms to ensure that the packet is successfully delivered. The receiver will acknowledge this data at the connection level with a Data ACK, as if a DSS option has been received. ๐ŸŽ€

There could be situations where both A and B attempt to transmit initial data at the same time. For example, if A did not initially have data to send but then needed to transmit data before it had received anything from B, it would use an MP_CAPABLE option with data parameters (since it would not know if the MP_CAPABLE on the ACK was received). In such a situation, B may also have transmitted data with a DSS option, but it had not yet been received at A. Therefore, B has received data with an MP_CAPABLE mapping after it has sent data with a DSS option. To ensure that these situations can be handled, it follows that the data parameters in an MP_CAPABLE are semantically equivalent to those in a DSS option and can be used interchangeably. Similar situations could occur when the MP_CAPABLE with data is lost and retransmitted. Furthermore, in the case of TCP segmentation offloading, the MP_CAPABLE with data parameters may be duplicated across multiple packets, and implementations must also be able to cope with duplicate MP_CAPABLE mappings as well as duplicate DSS mappings. ๐ŸŒˆ

Additionally, the MP_CAPABLE exchange allows the safe passage of MPTCP options on SYN packets to be determined. If any of these options are dropped, MPTCP will gracefully fall back to regular single-path TCP, as documented in Section 3.7. ๐Ÿฆ‹ If at any point in the handshake either party thinks the MPTCP negotiation is compromised (for example, by a middlebox corrupting the TCP options or by unexpected ACK numbers being present), the host MUST stop using MPTCP and no longer include MPTCP options in future TCP packets. The other host will then also fall back to regular TCP using the fallback mechanism. Note that new subflows MUST NOT be established (using the process documented in Section 3.2) until a DSS option has been successfully received across the path (as documented in Section 3.3). ๐Ÿšจ

Like all MPTCP options, the MP_CAPABLE option starts with the Kind and Length to specify the TCP option's kind and length. This information is followed by the MP_CAPABLE option. The first 4 bits of the first octet in the MP_CAPABLE option (Figure 4) define the MPTCP Option Subtype (see Section 7; for MP_CAPABLE, this value is 0x0), and the remaining 4 bits of this octet specify the MPTCP version in use (for this specification, this value is 1). ๐Ÿงš

The second octet is reserved for flags, allocated as follows:

  • A: The leftmost bit, labeled "A", SHOULD be set to 1 to indicate "Checksum required", unless the system administrator has decided that checksums are not required (for example, if the environment is controlled and no middleboxes exist that might adjust the payload). ๐ŸŽ€
  • B: The second bit, labeled "B", is an extensibility flag. It MUST be set to 0 for current implementations. This flag will be used for an extensibility mechanism in a future specification, and the impact of this flag will be defined at a later date. It is expected, but not mandated, that this flag would be used as part of an alternative security mechanism that does not require a full version upgrade of the protocol but does require redefining some elements of the handshake. If receiving a message with the "B" flag set to 1 and this is not understood, then the MP_CAPABLE in this SYN MUST be silently ignored, which triggers a fallback to regular TCP; the sender is expected to retry with a format compatible with this legacy specification. Note that the length of the MP_CAPABLE option, and the meanings of bits "D" through "H", may be altered by setting B=1. ๐Ÿฆ‹
  • C: The third bit, labeled "C", is set to 1 to indicate that the sender of this option will not accept additional MPTCP subflows to the source address and port, and therefore the receiver MUST NOT try to open any additional subflows toward this address and port. This improves efficiency in situations where the sender knows a restriction is in place -- for example, if the sender is behind a strict NAT or operating behind a legacy Layer 4 load balancer. ๐ŸŒธ
  • D through H: The remaining bits, labeled "D" through "H", are used for crypto algorithm negotiation. In this specification, only the rightmost bit, labeled "H", is assigned. Bit "H" indicates the use of HMAC-SHA256 (as defined in Section 3.2). An implementation that only supports this method MUST set bit "H" to 1 and bits "D" through "G" to 0. ๐Ÿ’œ

A crypto algorithm MUST be specified. If flag bits "D" through "H" are all 0, the MP_CAPABLE option MUST be treated as invalid and ignored (that is, it must be treated as a regular TCP handshake). ๐Ÿ™ˆ

The selection of the authentication algorithm also impacts the algorithm used to generate the token and the IDSN. In this specification, with only the SHA-256 algorithm (bit "H") specified and selected, the token MUST be a truncated (most significant 32 bits) SHA-256 hash [RFC6234] of the key. A different, 64-bit truncation (the least significant 64 bits) of the SHA-256 hash of the key MUST be used as the IDSN. Note that the key MUST be hashed in network byte order. Also note that the "least significant" bits MUST be the rightmost bits of the SHA-256 digest, as per [RFC6234]. Future specifications of the use of the crypto bits may choose to specify different algorithms for token and IDSN generation.

Both the crypto and checksum bits negotiate capabilities in similar ways. For the "Checksum required" bit (labeled "A"), if either host requires the use of checksums, checksums MUST be used. In other words, the only way for checksums not to be used is if both hosts in their SYNs set A=0. This decision is confirmed by the setting of the "A" bit in the third packet (the ACK) of the handshake. For example, if the initiator sets A=0 in the SYN but the responder sets A=1 in the SYN/ACK, checksums MUST be used in both directions, and the initiator will set A=1 in the ACK. The decision regarding whether to use checksums will be stored by an implementation in a per-connection binary state variable. If A=1 is received by a host that does not want to use checksums, it MUST fall back to regular TCP by ignoring the MP_CAPABLE option as if it was invalid. โœจ

For crypto negotiation, the responder has the choice. The initiator creates a proposal setting a bit for each algorithm it supports to 1 (in this version of the specification, there is only one proposal, so bit "H" will always be set to 1). The responder responds with only 1 bit set -- this is the chosen algorithm. The rationale for this behavior is that the responder will typically be a server with potentially many thousands of connections, so it may wish to choose an algorithm with minimal computational complexity, depending on the load. If a responder does not support (or does not want to support) any of the initiator's proposals, it MUST respond without an MP_CAPABLE option, thus forcing a fallback to regular TCP.

The MP_CAPABLE option is only used in the first subflow of a connection, in order to identify the connection; all subsequent subflows will use the MP_JOIN option (see Section 3.2) to join the existing connection.

If a SYN contains an MP_CAPABLE option but the SYN/ACK does not, it is assumed that the sender of the SYN/ACK is not multipath capable; thus, the MPTCP session MUST operate as a regular, single-path TCP session. If a SYN does not contain an MP_CAPABLE option, the SYN/ACK MUST NOT contain one in response. If the third packet (the ACK) does not contain the MP_CAPABLE option, then the session MUST fall back to operating as a regular, single-path TCP session. This is done to maintain compatibility with middleboxes on the path that drop some or all TCP options. Note that an implementation MAY choose to attempt sending MPTCP options more than one time before making this decision to operate as regular TCP (see Section 3.9). ๐Ÿ“

If the SYN packets are unacknowledged, it is up to local policy to decide how to respond. It is expected that a sender will eventually fall back to single-path TCP (i.e., without the MP_CAPABLE option) in order to work around middleboxes that may drop packets with unknown options; however, the number of multipath-capable attempts that are made first will be up to local policy. It is possible that MPTCP and non-MPTCP SYNs could get reordered in the network. Therefore, the final state is inferred from the presence or absence of the MP_CAPABLE option in the third packet of the TCP handshake. If this option is not present, the connection SHOULD fall back to regular TCP, as documented in Section 3.7.

The IDSN on an MPTCP connection is generated from the key. The algorithm for IDSN generation is also determined from the negotiated authentication algorithm. In this specification, with only the SHA-256 algorithm specified and selected, the IDSN of a host MUST be the least significant 64 bits of the SHA-256 hash of its key, i.e., IDSN-A = Hash(Key-A) and IDSN-B = Hash(Key-B). This deterministic generation of the IDSN allows a receiver to ensure that there are no gaps in sequence space at the start of the connection. The SYN with MP_CAPABLE occupies the first octet of data sequence space, although this does not need to be acknowledged at the connection level until the first data is sent (see Section 3.3). ๐Ÿงš

3.2. Starting a New Subflow ๐Ÿฆ‹

Once an MPTCP connection has begun with the MP_CAPABLE exchange, further subflows can be added to the connection. Hosts have knowledge of their own address(es) and can become aware of the other host's addresses through signaling exchanges as described in Section 3.4. Using this knowledge, a host can initiate a new subflow over a currently unused pair of addresses. It is permissible for either host in a connection to initiate the creation of a new subflow, but it is expected that this will normally be the original connection initiator (see Section 3.9 for heuristics).

A new subflow is started as a normal TCP SYN/ACK exchange. The Join Connection (MP_JOIN) MPTCP option is used to identify the connection to be joined by the new subflow. It uses keying material that was exchanged in the initial MP_CAPABLE handshake (Section 3.1), and that handshake also negotiates the crypto algorithm in use for the MP_JOIN handshake.

This section specifies the behavior of MP_JOIN using the HMAC-SHA256 algorithm. An MP_JOIN option is present in the SYN, SYN/ACK, and ACK of the three-way handshake, although in each case with a different format.

In the first MP_JOIN on the SYN packet, illustrated in Figure 5, the initiator sends a token, random number, and Address ID.

                       1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +---------------+---------------+-------+-----+-+---------------+
  |     Kind      |  Length = 12  |Subtype|(rsv)|B|   Address ID  |
  +---------------+---------------+-------+-----+-+---------------+
  |                   Receiver's Token (32 bits)                  |
  +---------------------------------------------------------------+
  |                Sender's Random Number (32 bits)               |
  +---------------------------------------------------------------+

Figure 5: Join Connection (MP_JOIN) Option (for Initial SYN)

The token is used to identify the MPTCP connection and is a cryptographic hash of the receiver's key, as exchanged in the initial MP_CAPABLE handshake (Section 3.1). In this specification, the tokens presented in this option are generated by the SHA-256 algorithm [RFC6234], truncated to the most significant 32 bits. The token included in the MP_JOIN option is the token that the receiver of the packet uses to identify this connection; i.e., Host A will send Token-B (which is generated from Key-B). Note that the hash generation algorithm can be overridden by the choice of cryptographic handshake algorithm, as defined in Section 3.1.

The MP_JOIN SYN sends not only the token (which is static for a connection) but also random numbers (nonces) that are used to prevent replay attacks on the authentication method. Recommendations for the generation of random numbers for this purpose are given in [RFC4086].

The MP_JOIN option includes an "Address ID". This is an identifier generated by the sender of the option, used to identify the source address of this packet, even if the IP header has been changed in transit by a middlebox. The numeric value of this field is generated by the sender and must map uniquely to a source IP address for the sending host. The Address ID allows address removal (Section 3.4.2) without needing to know what the source address at the receiver is, thus allowing address removal through NATs. The Address ID also allows correlation between new subflow setup attempts and address signaling (Section 3.4.1), to prevent setting up duplicate subflows on the same path, if an MP_JOIN and ADD_ADDR are sent at the same time.

The Address IDs of the subflow used in the initial SYN exchange of the first subflow in the connection are implicit and have the value zero. A host MUST store the mappings between Address IDs and addresses both for itself and the remote host. An implementation will also need to know which local and remote Address IDs are associated with which established subflows, for when addresses are removed from a local or remote host. ๐Ÿ’–

The MP_JOIN option on SYN packets also carries 4 bits of flags ๐ŸŽ€. Three of those are currently reserved, and the sender MUST set them to 0 ๐Ÿ™…โ€โ™€๏ธ. The last bit, labeled "B", tells the other host what the sender wants ๐Ÿฆ‹. B=1 means "keep this subflow as a backup in case other paths fail." B=0 means "use this subflow as part of the connection right away." So by setting B=1, the sender is asking the other host to only send data on this subflow when there are no available subflows where B=0 ๐Ÿ’œ. Subflow policy gets the full tea in Section 3.3.8 โœจ.

When a host gets a SYN with an MP_JOIN option that has a valid token for an existing MPTCP connection, the recipient SHOULD respond with a SYN/ACK that also has an MP_JOIN option. That option includes a random number and a truncated (leftmost 64 bits) HMAC ๐ŸŒธ. This version of the option is shown in Figure 6 ๐Ÿ’…. If the token is unknown, or the host doesn't want to allow the subflow (say, because it has a limit on how many subflows it'll allow), the receiver sends back a reset (RST). It's kind of like an unknown port in TCP ๐Ÿ™ˆ. The RST carries an MP_TCPRST option (Section 3.6) with an "MPTCP specific error" reason code ๐Ÿ’€.

Okay so the HMAC math does take some crypto work, but the 32-bit token in the MP_JOIN SYN is considered enough protection against blind state exhaustion attacks. So there's no need for a responder to work statelessly at the MP_JOIN stage ๐Ÿ”ฅ.

1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +---------------+---------------+-------+-----+-+---------------+

KindLength = 16Subtype(rsv)BAddress ID

+---------------+---------------+-------+-----+-+---------------+

Sender's Truncated HMAC (64 bits)

+---------------------------------------------------------------+

Sender's Random Number (32 bits)

+---------------------------------------------------------------+

Figure 6: Join Connection (MP_JOIN) Option (for Responding SYN/ACK) ๐Ÿฆ‹

An HMAC gets sent by both hosts. The initiator (Host A) sends one in the third packet (the ACK), and the responder (Host B) sends one in the second packet (the SYN/ACK) ๐Ÿ‘‘. Doing the HMAC exchange at this stage lets both hosts first swap random data (in the first two SYN packets), and that random data is what gets used as the "message" ๐Ÿงš. This spec says the HMAC is defined as in [RFC2104], it uses the SHA-256 hash algorithm [RFC6234], and the output is truncated to the leftmost 160 bits (20 octets) โœจ. Because of option space limits, the HMAC in the SYN/ACK gets truncated to the leftmost 64 bits. That's fine, since random numbers are used. So an attacker only gets one shot at guessing the HMAC that matches the random number the peer sent before ๐ŸŽฏ. If the HMAC is wrong, the TCP connection gets closed, so a new MP_JOIN negotiation with a new random number is required ๐Ÿ’”.

The initiator's authentication info goes in its first ACK (the third packet of the handshake), as shown in Figure 7 ๐Ÿซถ. This data needs to be sent reliably, since it's the only time this HMAC gets sent. So receiving this packet MUST trigger a regular TCP ACK in response, and the packet MUST be retransmitted if that ACK doesn't arrive ๐Ÿ˜ญ. Basically, sending the ACK/MP_JOIN packet puts the subflow in the PRE_ESTABLISHED state. It only moves to the ESTABLISHED state once it gets an ACK from the receiver โœจ. It is not permissible to send data while in the PRE_ESTABLISHED state ๐Ÿšซ. The reserved bits in this option MUST be set to 0 by the sender ๐Ÿ’….

1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +---------------+---------------+-------+-----------------------+

KindLength = 24Subtype(reserved)

+---------------+---------------+-------+-----------------------+

Sender's Truncated HMAC (160 bits)

+---------------------------------------------------------------+

Figure 7: Join Connection (MP_JOIN) Option (for Initiator's First ACK) ๐ŸŒˆ

For the HMAC key, when Host A sends the message, the key is Key-A followed by Key-B. When Host B sends it, the key is Key-B followed by Key-A. These are the keys that were swapped in the original MP_CAPABLE handshake ๐Ÿ”‘. The "message" is the concatenation of each host's random numbers (called R). For Host A, that's R-A followed by R-B. For Host B, it's R-B followed by R-A ๐Ÿง .

All these MPTCP options work together to set up authenticated subflows, as shown in Figure 8 ๐Ÿ’–.

Host A Host B ------------------------ ---------- Address A1 Address A2 Address B1 ---------- ---------- ----------

SYN + MP_CAPABLE
--------------------------------------------->
<---------------------------------------------
SYN/ACK + MP_CAPABLE(Key-B)
ACK + MP_CAPABLE(Key-A, Key-B)
--------------------------------------------->
SYN + MP_JOIN(Token-B, R-A)
------------------------------->
<-------------------------------
SYN/ACK + MP_JOIN(HMAC-B, R-B)
ACK + MP_JOIN(HMAC-A)
------------------------------->
<-------------------------------
ACK

HMAC-A = HMAC(Key=(Key-A + Key-B), Msg=(R-A + R-B)) HMAC-B = HMAC(Key=(Key-B + Key-A), Msg=(R-B + R-A))

Figure 8: Example Use of MPTCP Authentication

If the token Host B receives is unknown, or local policy won't accept the new subflow, the recipient MUST respond with a TCP RST for the subflow ๐Ÿšซ. If it makes sense, an MP_TCPRST option with an "Administratively prohibited" reason code (Section 3.6) should be included ๐Ÿ™…โ€โ™€๏ธ.

If the token is accepted at Host B but the HMAC sent back to Host A doesn't match what Host A expected, Host A MUST close the subflow with a TCP RST ๐Ÿ˜ค. In this case, and in every other case in this section where a RST gets sent, the sender SHOULD send an MP_TCPRST option (Section 3.6) on that RST packet, with the reason code for an "MPTCP-specific error" ๐ŸŽ€.

If Host B doesn't get the expected HMAC, or the MP_JOIN option is missing from the ACK, it MUST close the subflow with a TCP RST ๐Ÿ’€.

If both HMACs check out, then both hosts have verified each other as the same peers that existed at the start of the connection, and they've agreed on which connection this subflow will join ๐Ÿซถโœจ.

If the SYN/ACK that Host A receives doesn't have an MP_JOIN option, Host A MUST close the subflow with a TCP RST ๐Ÿ™ˆ.

This covers all the ways an MP_JOIN can get lost. More specifically, if an MP_JOIN gets stripped from the SYN on the path from A to B, and Host B doesn't have a listener on the relevant port, it responds with a RST in the normal way ๐Ÿ™ƒ. If a SYN with an MP_JOIN option gets a SYN/ACK back without the MP_JOIN option, then the subflow is unusable and Host A MUST close it with a RST. That can happen because the option was stripped on the return path, or because it was stripped on the outgoing path and Host B responded as if it were a new regular TCP session ๐Ÿ˜ตโ€๐Ÿ’ซ.

Note that you can create additional subflows between any pair of ports (but see Section 3.9 for heuristics) ๐Ÿฆ‹. You don't need any explicit application-level accept calls or bind calls to open additional subflows ๐Ÿ’…. To link a new subflow to an existing connection, the token in the subflow's SYN exchange is used for demultiplexing. That binds the 5-tuple of the TCP subflow to the connection's local token ๐Ÿ”—. One consequence is that any port pairs can be used for a connection ๐ŸŒธ.

Demultiplexing subflow SYNs MUST be done using the token. This is different from traditional TCP, where the destination port is used to demultiplex SYN packets ๐Ÿ‘‘. Once a subflow is set up, packets get demultiplexed using the 5-tuple, just like in traditional TCP. The 5-tuples get mapped to the local connection identifier (the token) ๐Ÿ’œ. Host A knows its own local token for the subflow even though it isn't sent on the wire. Only the responder's token is sent ๐Ÿงš.

3.3. MPTCP Operation and Data Transfer ๐Ÿš€

okay bestie, this is where the magic actually happens โœจ MPTCP takes one data stream from an app and splits it across one or more subflows ๐ŸŒŠ. It also attaches enough control info that the other side can put everything back together, reliably and in order, and hand it to the app ๐Ÿ’… it's giving main character energy. The subsections below break down how that works.

The Data Sequence Mapping and the Data ACK are both signaled in the DSS option ๐ŸŽ€ (Figure 9). You can signal either one or both in a single DSS, depending on which flags are set ๐Ÿฆ‹. The Data Sequence Mapping says how the subflow sequence space maps onto the connection level, and the Data ACK says the data has been received at the connection level ๐Ÿ’œ. We'll get into each of these in the next two subsections ๐Ÿซถ

                       1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +---------------+---------------+-------+----------------------+
  |     Kind      |    Length     |Subtype| (reserved) |F|m|M|a|A|
  +---------------+---------------+-------+----------------------+
  |           Data ACK (4 or 8 octets, depending on flags)       |
  +--------------------------------------------------------------+
  |   Data Sequence Number (4 or 8 octets, depending on flags)   |
  +--------------------------------------------------------------+
  |              Subflow Sequence Number (4 octets)              |
  +-------------------------------+------------------------------+
  |  Data-Level Length (2 octets) |      Checksum (2 octets)     |
  +-------------------------------+------------------------------+

Figure 9: Data Sequence Signal (DSS) Option ๐ŸŒธ

The flags, when set, define what's inside this option. Here's the tea ๐Ÿ“:

  • A = Data ACK present
  • a = Data ACK is 8 octets (if not set, Data ACK is 4 octets)
  • M = Data Sequence Number (DSN), Subflow Sequence Number (SSN), Data-Level Length, and Checksum (if negotiated) present
  • m = Data Sequence Number is 8 octets (if not set, DSN is 4 octets)

ngl the "a" and "m" flags only mean something if their "A" or "M" buddies are set ๐Ÿ™ˆ otherwise they get ignored. Also, the biggest this option can get, with every flag set, is 28 octets ๐Ÿ“

Now the "F" flag, which is giving "Data FIN" energy ๐Ÿ’€ If it's present, it means this mapping covers the final data from the sender. It's basically the connection-level version of the FIN flag in single-path TCP ๐Ÿงš A connection doesn't get closed unless one of three things happens: a Data FIN exchange, an MP_FASTCLOSE (Section 3.5) message, or an implementation-specific connection-level send timeout โฐ The purpose of the Data FIN, and how this flag, the subflow-level FIN flag, and the Data Sequence Mapping all work together, is covered in Section 3.3.3 ๐Ÿ”ฅ And the remaining reserved bits MUST be set to 0 by an implementation of this specification, no exceptions bestie ๐Ÿ™…โ€โ™€๏ธ

Okay, the checksum is a whole thing ๐Ÿง It only shows up in this option if MPTCP checksumming was negotiated during the MP_CAPABLE handshake (see Section 3.1) โœจ The receiver can tell whether there's a checksum just by looking at the option's length ๐Ÿ“ If a checksum is there but nobody negotiated it in the MP_CAPABLE handshake, the receiver MUST close the subflow with a RST, because the sender is not behaving as negotiated ๐Ÿ˜ค And if a checksum is missing even though it was negotiated, the receiver MUST close the subflow with a RST too, since that subflow is considered broken ๐Ÿ’” In both cases, this RST SHOULD come with an MP_TCPRST option (Section 3.6) carrying the reason code for an "MPTCP-specific error" ๐Ÿ’…

โš ๏ธ tbh, the checksum rules are where people trip up, so read that last bit twice ๐Ÿซถ

3.3.1. Data Sequence Mapping โœจ

okay so the whole data stream gets stitched back together using the Data Sequence Mapping parts of the DSS option (Figure 9) ๐Ÿงฉ these define the mapping from the subflow sequence number to the data sequence number. the receiver uses this to make sure everything reaches the application layer in order ๐Ÿ’… meanwhile, the subflow-level sequence numbers (the normal ones in the TCP header) only matter to that one subflow. SACK [RFC2018] is expected at the subflow level to make things more efficient, but it is not mandated ๐Ÿซถ

the mapping describes how the subflow sequence space maps onto the data sequence space ๐Ÿฆ‹ it's written as a starting sequence number for the subflow, a starting sequence number for the data level, and a length in bytes for which the mapping is valid. why map a whole range instead of signaling every packet? bestie, it's giving compatibility ๐Ÿ’– it still works when TCP/IP segmentation or coalescing happens outside the stack generating the data flow, like TCP segmentation offloading on network interface cards, or middleboxes such as Performance Enhancing Proxies (PEPs) [RFC3135]. one mapping can also cover loads of packets, which is super useful for bulk transfers ๐Ÿš€

a mapping is fixed: once it's processed, the subflow sequence number is bound to the data sequence number ๐Ÿ”’ a sender MUST NOT change this mapping after it has been declared ๐Ÿ™…โ€โ™€๏ธ but the same data sequence number can be mapped by different subflows for retransmission purposes (see Section 3.3.6) ๐Ÿ” this also lets the same data go out on multiple subflows at once for resilience or efficiency, especially on lossy links ๐ŸŒง๏ธ the detailed rules for that are outside the scope of this document, but an implementation SHOULD treat the first data it receives at a subflow for a given data sequence space as the data to deliver to the application, and any later data for that sequence space SHOULD be ignored ๐Ÿ‘‘

the data sequence number is absolute, whereas the subflow sequence numbering is relative (the SYN at the start of the subflow has a relative subflow sequence number of 0) ๐Ÿงš this is so middleboxes can change the Initial Sequence Number (ISN) of a subflow, like firewalls doing ISN randomization. โœจ

the Data Sequence Mapping also carries a checksum of the data it covers, but only if checksums were negotiated during the MP_CAPABLE exchange ๐ŸŽ€ checksums catch the payload being changed by a non-MPTCP-aware middlebox ๐Ÿ‘€ if the checksum fails, it triggers a failure of the subflow or a fallback to regular TCP, as covered in Section 3.7, because MPTCP can no longer reliably know the subflow sequence space at the receiver to build Data Sequence Mappings ๐Ÿ˜ญ without checksumming, corrupt data can reach the application if a middlebox changes segment boundaries, changes content, or doesn't deliver every segment covered by a Data Sequence Mapping ๐Ÿ’€ so checksumming is RECOMMENDED, unless it is known that the network path has no such devices ๐ŸŒธ

the checksum algorithm is the standard TCP checksum [RFC0793], run over the data covered by this mapping along with a pseudo-header, as shown in Figure 10 ๐Ÿฆ‹

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +--------------------------------------------------------------+
 |                                                              |
 |                Data Sequence Number (8 octets)               |
 |                                                              |
 +--------------------------------------------------------------+
 |              Subflow Sequence Number (4 octets)              |
 +-------------------------------+------------------------------+
 |  Data-Level Length (2 octets) |        Zeros (2 octets)      |
 +-------------------------------+------------------------------+

Figure 10: Pseudo-Header for DSS Checksum

the data sequence number in the pseudo-header is always the 64-bit value, no matter what length is used in the DSS option itself ๐Ÿ”ข the standard TCP checksum was picked because it gets calculated for the TCP subflow anyway ๐Ÿ™Œ if you compute it over the data first, before adding the pseudo-headers, you only have to do it once. and since the TCP checksum is additive, the checksum for a DSN_MAP can be built by just adding together the checksums for the data of each constituent TCP segment plus the checksum for the DSS pseudo-header ๐Ÿงฎ mathematically iconic, no cap ๐Ÿค“

checksumming relies on the TCP subflow containing contiguous data, so a TCP subflow MUST NOT use the Urgent Pointer to interrupt an existing mapping ๐Ÿšซ but if Urgent data is received on a subflow, it SHOULD be mapped to the data sequence space and delivered to the application, just like Urgent data in regular TCP ๐Ÿ’œ

to avoid possible deadlocks, subflow-level processing should be handled separately from connection-level processing ๐Ÿง  so even if there's no mapping from the subflow space to the data-level space, the data SHOULD still be ACKed at the subflow (if it's in-window) ๐Ÿซถ this data can't be acknowledged at the data level (Section 3.3.2) yet, because its data sequence numbers are unknown ๐Ÿ™ˆ implementations MAY hold onto that unmapped data for a short while, in the hope that a mapping shows up soon โณ unmapped data doesn't count toward the connection-level receive window, since that window is relative to the data sequence numbers, so if the receiver runs out of memory holding it, it has to be discarded ๐Ÿ—‘๏ธ if a mapping for that subflow-level sequence space doesn't arrive within a receive window's worth of data, that subflow SHOULD be treated as broken, closed with a RST, and any unmapped data silently discarded ๐Ÿ’€

data sequence numbers are always 64-bit quantities and MUST be maintained as such in implementations. that part is non-negotiable, bestie ๐Ÿ‘‘

if a connection is progressing slowly, so protection against wrapped sequence numbers isn't needed, an implementation MAY include just the lower 32 bits of the data sequence number in the Data Sequence Mapping and/or Data ACK as an optimization ๐ŸŒˆ and an implementation can make that choice independently for each packet โœจ every implementation MUST be able to receive and process both 64-bit and 32-bit sequence number values, but it doesn't have to be able to send both ๐Ÿงš

an implementation MUST send the full 64-bit data sequence number if it's transmitting fast enough that the 32-bit value could wrap within the Maximum Segment Lifetime (MSL) [RFC7323] ๐Ÿš€ the lengths of the DSNs used in these values (which may be different) are declared with flags in the DSS option ๐ŸŽ€ implementations MUST accept a 32-bit DSN and implicitly promote it to a 64-bit quantity by incrementing the upper 32 bits of the sequence number each time the lower 32 bits wrap ๐Ÿ” and a sanity check MUST be implemented to make sure a wrap happens at an expected time (for example, the sequence number jumps from a very high number to a very low one) and isn't triggered by out-of-order packets ๐Ÿ•ต๏ธโ€โ™€๏ธ no false wraps allowed, periodt ๐Ÿ’…

just like with the standard TCP sequence number, the data sequence number should not start at zero, but at a random value to make blind session hijacking harder ๐Ÿฆ‹ this specification requires setting the IDSN of each host to the least significant 64 bits of the SHA-256 hash of the host's key, as described in Section 3.1 ๐Ÿ” this is also required so the receiver knows what the expected IDSN is and can work out whether any initial connection-level packets are missing ๐Ÿ•Š๏ธ that's especially relevant if two subflows start transmitting at the same time ๐Ÿ‘ฏโ€โ™€๏ธ

the mapping provided by a Data Sequence Mapping MUST apply to some or all of the subflow sequence space in the TCP segment that carries the option ๐ŸŽฏ it doesn't need to be included in every MPTCP packet, as long as the subflow sequence space in that packet is covered by a mapping the receiver already knows about ๐Ÿงƒ that cuts down on overhead when the mapping is known in advance, like when there's a single subflow between the hosts, or when data gets scheduled in chunks bigger than one packet ๐Ÿ“

an "infinite" mapping can be used to fall back to regular TCP by mapping the subflow-level data to the connection-level data for the rest of the connection (see Section 3.7) ๐ŸŒ™ that's done by setting the Data-Level Length field of the DSS option to the reserved value of 0 ๐Ÿซง in that case, the checksum is also set to 0 ๐Ÿ’–

3.3.2. Data Acknowledgments โœจ

Okay so MPTCP has a connection-level acknowledgment, bestie ๐Ÿซถ, and it acts as a cumulative ACK for the whole connection, not just one subflow. You get it through the "Data ACK" field of the DSS option (Figure 9) ๐Ÿ’…. It's basically the same energy as a regular TCP cumulative ACK: it says how much data has arrived with no holes ๐ŸŒธ. The subflow-level ACK is more like TCP SACK, because the connection can still have holes even when a subflow looks totally fine ๐Ÿฆ‹. The Data ACK tells the other side the next data sequence number it expects to receive โœจ.

The Data ACK can be sent as the full 64-bit value or as the lower 32 bits, just like the DSN. If data comes in with a 64-bit DSN, it MUST be acknowledged with a 64-bit Data ACK ๐ŸŽ€. If the DSN is 32 bits, the implementation gets to choose between a 32-bit or 64-bit Data ACK, but it MUST accept either one in that situation ๐Ÿ’–.

The Data ACK proves that the data, and all the required MPTCP signaling, got received and accepted by the remote end ๐Ÿ‘‘. It's also how the left edge of the advertised receive window is marked (see Section 3.3.4). The receive window is shared by all subflows and is relative to the Data ACK, so an implementation MUST NOT use the RCV.WND field of a TCP segment at the connection level if that segment doesn't also carry a DSS option with a Data ACK field ๐Ÿ™ˆ. Splitting connection-level acknowledgments from subflow-level ones means each can be processed separately, and a receiver is free to drop segments after they've been acknowledged at the subflow level. An example would be memory constraints when lots of segments arrive out of order ๐Ÿงš.

An MPTCP sender MUST NOT free data from the send buffer until it has been acknowledged by both a Data ACK received on any subflow and, at the subflow level, by all subflows the data was sent on. The first condition keeps the connection alive, and the second keeps each subflow alive and self-consistent when data has to be retransmitted ๐Ÿ˜ญ. Heads up though: if some data needs retransmitting multiple times over one subflow, the send window can get blocked. When that happens, the MPTCP sender can decide to kill the misbehaving subflow by sending a RST with an appropriate MP_TCPRST (Section 3.6) error code ๐Ÿ’€.

The Data ACK MAY be included in all segments ๐ŸŒˆ. Advanced implementations SHOULD consider optimizations, though, where the Data ACK only shows up in segments when the Data ACK value actually moves forward, and this behavior MUST be treated as valid โœจ. That way the send buffer still gets freed, and the overhead drops when data is only flowing one way ๐Ÿ“.


3.3.3. Closing a Connection ๐Ÿฆ‹

In regular TCP, a FIN tells the receiver that the sender has no more data to send. MPTCP wants its subflows to operate independently and still look like TCP on the wire, so a FIN here only affects the subflow it was sent on ๐ŸŽ€. That gives nodes a lot of freedom over which paths are in use at any given time ๐Ÿ’…. The semantics of a FIN stay exactly the same as regular TCP, so the subflow isn't fully closed until both sides have ACKed each other's FINs ๐Ÿซถ.

When an application calls close() on a socket, it's saying it has no more data to send. In regular TCP that would produce a FIN on the connection. MPTCP needs an equivalent, and that's the DATA_FIN ๐ŸŒธ.

A DATA_FIN says the sender has no more data to send, so it can be used to check that all data was successfully received โœจ. Like the FIN on a regular TCP connection, a DATA_FIN is a unidirectional signal ๐Ÿ’œ.

The DATA_FIN is signaled by setting the "F" flag in the DSS option (Figure 9) to 1 ๐Ÿ”ฅ. A DATA_FIN takes up 1 octet (the final octet) of the connection-level sequence space. The DATA_FIN is counted in the Data-Level Length, but not at the subflow level. For example, a segment with a DSN value of 80 and a Data-Level Length of 11, with DATA_FIN set, would map 10 octets from the subflow into data sequence space 80-89. The DATA_FIN would then be DSN 90, so this segment, DATA_FIN included, gets acknowledged with a DATA_ACK of 91 ๐Ÿงš.

When the DATA_FIN isn't attached to a TCP segment that contains data, the DSS MUST have a subflow sequence number of 0, a Data-Level Length of 1, and the data sequence number that matches the DATA_FIN itself ๐Ÿ™ˆ. In this case the checksum only covers the pseudo-header ๐ŸŒˆ.

A DATA_FIN has the same semantics and behavior as a regular TCP FIN, just at the connection level โœจ. The big thing to know is that it only gets DATA_ACKed once all data has been successfully received at the connection level ๐Ÿ‘‘. A DATA_FIN is decoupled from a subflow FIN. You can only combine these signals on one subflow if there's no data outstanding on the other subflows, because otherwise data may need to be retransmitted on different subflows ๐Ÿ˜ญ. Basically, a host MUST NOT close all of its functioning subflows unless it's actually safe to do so. That means either all outstanding data has been DATA_ACKed, or the segment with the DATA_FIN flag set is the only one still outstanding ๐Ÿ’….

Once a DATA_FIN has been acknowledged, all the remaining subflows MUST be closed with standard FIN exchanges ๐ŸŽ€. Both hosts SHOULD send FINs on all subflows as a courtesy, so middleboxes can clean up their state even if one subflow has failed ๐Ÿซถ. Reducing the timeouts (MSL) on subflows at the end hosts after receiving a DATA_FIN is also encouraged ๐Ÿ“. Any subflow that still has outstanding data queued (data that was retransmitted on other subflows to get the DATA_FIN acknowledged) MAY be closed with a RST carrying an MP_TCPRST (Section 3.6) error code for "too much outstanding data" ๐Ÿ’€.

A connection counts as closed once both hosts' DATA_FINs have been acknowledged by DATA_ACKs ๐Ÿฆ‹.

As we said above, a standard TCP FIN on one subflow only shuts down that subflow ๐Ÿ’–. If every subflow has been closed with a FIN exchange but no DATA_FIN has been received and acknowledged, the MPTCP connection is only treated as closed after a timeout ๐ŸŒธ. So an implementation ends up with TIME_WAIT states at both the subflow level and the connection level (see Appendix D) โœจ. That's what allows "break-before-make" scenarios, where connectivity is lost on every subflow before a new one can be set up again ๐Ÿงš.


3.3.4. Receiver Considerations ๐Ÿ‘‘

Regular TCP advertises a receive window in every packet, which tells the sender how much data the receiver is willing to accept past the cumulative ACK. That's how flow control works: it slows down fast senders when receivers can't keep up ๐Ÿ’….

MPTCP has a unique receive window that's shared between the subflows ๐ŸŽ€. The idea is to let any subflow send data as long as the receiver is willing to accept it. The alternative, per-subflow receive windows, could end up stalling some subflows while others never use up their window ๐Ÿ˜ญ.

The receive window is relative to the DATA_ACK. Just like in TCP, a receiver MUST NOT shrink the right edge of the receive window (that's DATA_ACK + receive window) ๐Ÿ™ˆ. The receiver uses the data sequence number to decide whether a packet should be accepted at the connection level ๐Ÿซถ.

With regular TCP, the sequence number in a packet gets checked against the allowed receive window when deciding whether to accept it at the subflow level. With MPTCP, that check uses only the connection-level window ๐Ÿฆ‹. A sanity check SHOULD be performed at the subflow level to make sure the subflow and mapped sequence numbers pass this test: SSN - SUBFLOW_ACK <= DSN - DATA_ACK. Here SSN is the subflow sequence number of the received packet, and SUBFLOW_ACK is the RCV.NXT (next expected sequence number) of the subflow. The connection-level definitions for DSN and DATA_ACK are equivalent โœจ.

In regular TCP, once a segment is considered in-window, it goes into either the in-order receive queue or the out-of-order queue. Multipath TCP does the same thing, just at the connection level ๐ŸŒˆ. A segment goes into the connection-level in-order or out-of-order queue only if it's in-window at both the connection level and the subflow level ๐Ÿ’œ. The stack still has to remember, for each subflow, which segments were received successfully, so it can ACK them at the subflow level properly ๐ŸŒธ. This is typically done by keeping per-subflow out-of-order queues (holding only message headers, not the payloads) and remembering the value of the cumulative ACK ๐Ÿ“.

Implementers really need to get how big a receive buffer should be. The lower bound for full network utilization is the maximum bandwidth-delay product of any one of the paths. But that might not be enough when a packet gets lost on a slower subflow and has to be retransmitted (see Section 3.3.6) ๐Ÿ˜ญ. A tight upper bound is the maximum round-trip time (RTT) of any path multiplied by the total bandwidth available across all paths ๐Ÿ”ฅ. That lets all subflows keep going at full speed while a packet is fast-retransmitted on the maximum RTT path โœจ. Even that might not be enough to keep full performance if a retransmit timeout hits on the maximum RTT path ๐Ÿ’€. How retransmission strategies relate to receive buffer sizing is left for future study ๐Ÿงš.

3.3.5. Sender Considerations ๐ŸŽ€

okay bestie, sender side is where it gets SO juicy ๐Ÿ’… so the sender keeps a memory of the receive window advertisements it gets back from the receiver ๐Ÿงš. and it should ONLY bump its local receive window values when the largest sequence number allowed (that's DATA_ACK + receive window) actually goes up after a DATA_ACK comes in ๐ŸŒธ. this matters bigly because different paths have different RTTs, which means different feedback loops, and we need to let those paths do their own thing ๐Ÿฆ‹

ngl this part is so clever ๐Ÿ™ˆ MPTCP uses ONE receive window across ALL subflows. if that window was guaranteed to stay the same end to end, the host could always just read the newest value ๐Ÿ’– but nope, some middleboxes mess with the TCP-level receive window ๐Ÿ“ usually they SHRINK the offered window, but for short moments it could be bigger too (and it can't last forever, because the middlebox still has to keep up with delivering data to the receiver ๐Ÿ’€). so if the receive windows are different across subflows, MPTCP SHOULD take the LARGEST of the most recent window sizes when it's doing its calculations โœจ this rule is kinda hiding inside the requirement to never shrink the right edge of the window ๐Ÿ‘‘

the sender MUST also remember the receive windows advertised by EACH subflow ๐Ÿซถ the allowed window for subflow i is (ack_i, ack_i + rcv_wnd_i), where ack_i is the subflow-level cumulative ACK of subflow i ๐ŸŒˆ this is what keeps data from being sent to a middlebox unless there's actually enough buffering for it ๐Ÿ˜ญ

putting both rules together: a sender is allowed to send data segments with data-level sequence numbers between (DATA_ACK, DATA_ACK + receive_window) ๐Ÿ’œ each of those segments gets mapped onto subflows, as long as the subflow sequence numbers are inside the allowed windows for those subflows ๐Ÿฆ‹ and fyi subflow sequence numbers don't usually affect flow control when the same receive window is advertised everywhere. they only do flow control for the subflows with a SMALLER advertised receive window ๐Ÿ”ฅ

last one: the send buffer MUST, at a minimum, be as big as the receive buffer, so the sender can actually hit max throughput ๐Ÿš€โœจ


3.3.6. Reliability and Retransmissions ๐Ÿ”

okay this is the retransmission tea โ˜•๐Ÿ’… the Data Sequence Mapping lets a sender resend data with the SAME data sequence number on a DIFFERENT subflow ๐Ÿฆ‹ but when it does, a host MUST still retransmit the original data on the ORIGINAL subflow ๐Ÿซถ why? to keep the subflow's integrity (middleboxes could replay old data and/or reject holes in subflows, not cute ๐Ÿ’€). and the receiver just ignores those retransmissions ๐Ÿ™ˆ ngl it's clearly suboptimal, but for compatibility reasons it's the sensible move ๐Ÿ“ optimizations could be negotiated in future versions of this protocol ๐ŸŒธ also, this property means a sender could always send the SAME data with the SAME data sequence number on multiple subflows if it wants extra reliability ๐Ÿ’–

this spec does NOT mandate any retransmission mechanism ๐ŸŽ€ it's all local policy (more on that in Section 3.3.8 โœจ). you can imagine super aggressive connection-level policies where every packet lost at the subflow level gets resent on a different subflow (wasting bandwidth but maybe cutting app-to-app delays ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ), or conservative ones where connection-level retransmissions only kick in after a few subflow-level retransmission timeouts ๐Ÿง˜โ€โ™€๏ธ

it's envisaged that a standard connection-level retransmission mechanism would be built around a connection-level data queue ๐Ÿงš all segments that haven't been DATA_ACKed get stored there โญ a timer gets set when the head of the connection-level queue is ACKed at the subflow level but NOT DATA_ACKed at the data level ๐Ÿ”ฅ and that timer guards against retransmission failures caused by middleboxes that proactively ACK data ๐Ÿ‘‘

the sender MUST keep data in its send buffer as long as it hasn't been acknowledged BOTH (1) at the connection level AND (2) on all subflows it was sent on ๐Ÿซถ that way the sender can always retransmit when needed, on the same subflow or a different one ๐Ÿ’œ the special case is when a subflow fails: the sender will typically resend the data on other working subflows after a timeout, and it keeps trying on the failed subflow too ๐ŸŒˆ it declares the subflow failed after a predefined upper bound on retransmissions is hit (which MAY be LOWER than the usual TCP limits of the MSL) or when an ICMP error arrives, and ONLY THEN deletes the outstanding data segments ๐Ÿ˜ญ

if multiple retransmissions show a subflow is performing badly, this MAY lead to the host resetting that subflow with a RST ๐Ÿ™ˆ but honestly, more research is needed to figure out the heuristics for how and when to reset underperforming subflows ๐Ÿ”ฌ a highly asymmetric path could get misdiagnosed as underperforming ๐Ÿ™ƒ and a RST for this purpose SHOULD come with an "Unacceptable performance" MP_TCPRST option (Section 3.6) ๐ŸŽ€


3.3.7. Congestion Control Considerations ๐ŸŒŠ

different subflows in an MPTCP connection have different congestion windows ๐Ÿฆ‹ to get fairness at bottlenecks and resource pooling, you NEED to couple the congestion windows across subflows so most traffic gets pushed onto uncongested links ๐Ÿ’… one algorithm for this is presented in [RFC6356] ๐Ÿงš it doesn't achieve perfect resource pooling, but it's "safe" because it's readily deployable on the current Internet โœจ by safe we mean it doesn't take up more capacity on any one path than a single-path flow using only that route would, so it coexists fairly with single-path TCP at shared bottlenecks ๐Ÿซถ

it's foreseeable that different congestion controllers will be implemented for MPTCP ๐ŸŒˆ each one aims for different properties in the resource pooling / fairness / stability design space, plus different properties in quality of service, reliability, and resilience ๐Ÿ‘‘

regardless of the algorithm, the MPTCP design aims to give congestion control implementations enough information to make the right decisions ๐Ÿง  and that info includes, for each subflow, which packets were lost and when โญ


3.3.8. Subflow Policy ๐ŸŽฏ

within a local MPTCP implementation, a host can use ANY local policy it wants to decide how to share traffic across the available paths ๐Ÿ’–

in the typical use case, where the goal is maximizing throughput, all available paths get used at the same time for data transfer, using coupled congestion control as described in [RFC6356] ๐Ÿš€ other use cases are expected to show up though ๐Ÿฆ‹

for instance, one option is an "all-or-nothing" approach: have a second path ready in case the first one fails ๐Ÿ›Ÿ alternatively, you could fully saturate one path before using an additional one (the "overflow" case) ๐ŸŒŠ these choices would most likely be based on the monetary cost of links, but they could also depend on things like delay or jitter, where stability (of delay or bandwidth) matters more than raw throughput ๐Ÿ’… application requirements like these are discussed in detail in [RFC6897] ๐Ÿ“š

the catch is that making good choices at the sender needs full knowledge of the path "cost", and that's unlikely to be the case ๐Ÿ™ƒ it would be dreamy for a receiver to signal its own path preferences, since the receiver is often the multihomed party and may have to pay for metered incoming bandwidth ๐Ÿ’ธ๐Ÿซถ

to make this happen, the MP_JOIN option (see Section 3.2) has the "B" bit ๐ŸŽ€ it lets a host tell its peer that a path should be treated as a backup path, only used when other working subflows fail (so a subflow where the receiver set B=1 SHOULD NOT be used to send data unless there are no usable subflows where B=0) ๐Ÿ™ˆ

if the set of available paths changes, a host may want to signal a priority change to the peer ๐Ÿ”„ (like a subflow that used to be a backup should now take priority over all the remaining subflows ๐ŸŒŸ) so the MP_PRIO option, shown in Figure 11, can change the "B" flag of the subflow it's sent on ๐Ÿงš

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------+---------------+-------+-----+-+
 |     Kind      |     Length    |Subtype|(rsv)|B|
 +---------------+---------------+-------+-----+-+

        Figure 11: Change Subflow Priority (MP_PRIO) Option

another use of MP_PRIO is setting the "B" flag on a subflow to cleanly "retire" it before closing it and removing it with REMOVE_ADDR (Section 3.4.2) ๐ŸŒธ for example, to support make-before-break session continuity, where new subflows get added before the old ones are closed ๐Ÿ’–

one more thing: the backup flag is a request from a data receiver to a data sender ONLY, and the data sender SHOULD adhere to these requests ๐Ÿซถ but a host can't just assume the sender will, since local policies (or technical difficulties) may override MP_PRIO requests ๐Ÿ˜ฌ also, this signal only applies to one direction, so the sender of this option could keep using the subflow to send data even after it signaled B=1 to the other host ๐Ÿ™ƒโœจ

3.4. Address Knowledge Exchange (Path Management) โœจ

okay so "path management" is our fancy name for hosts swapping info about extra paths between each other ๐Ÿ—บ๏ธ, and in this design it's handled by giving each host multiple addresses ๐Ÿ . if you want the big-picture architecture thinking behind all this, check out [RFC6182] ๐Ÿ“š.

there are TWO ways hosts share this info, and both can be used on the same connection ๐Ÿ’…. method one is the direct setup of new subflows (see Section 3.2), where the initiator just has an extra address ๐ŸŽ€. method two (the subsections below) explicitly signals addresses to the other host so it can open new subflows ๐Ÿฆ‹. they're complementary: method one is implicit and simple, while method two is explicit and more complex but more robust ๐Ÿ’ช. together they let addresses change in flight (so NAT works, since the source address doesn't need to be known ๐ŸŒธ), they allow signaling of previously unknown addresses, and they allow addresses from other address families (e.g., both IPv4 and IPv6) ๐ŸŒˆ.

here's a typical run of the protocol ๐Ÿ‘‘:

  • an MPTCP connection is first set up between address/port A1 of Host A and address/port B1 of Host B. if Host A is multihomed and multiaddressed, it can start an additional subflow from its address A2 to B1 by sending a SYN with an MP_JOIN option from A2 to B1, using B's previously declared token for this connection. or, if B is multihomed, it can try to set up a new subflow from B2 to A1, using A's previously declared token. either way, the SYN gets sent to the port already in use for the original subflow on the receiving host ๐Ÿงš.
  • simultaneously (or after a timeout), an ADD_ADDR option (Section 3.4.1) is sent on an existing subflow, telling the receiver about the sender's alternative address(es) ๐Ÿ“ฃ. the recipient can use that info to open a new subflow to the sender's additional address(es). in our example, A sends the ADD_ADDR option telling B about address/port A2. the mix of the SYN-based option and the ADD_ADDR option (including timeouts) is implementation specific and can be tailored to agree with local policy โญ.
  • if subflow A2-B1 gets set up successfully, Host B can use the Address ID in the MP_JOIN option to match this source address with the ADD_ADDR option that will also arrive on an existing subflow. now B knows not to open A2-B1, and it ignores the ADD_ADDR ๐Ÿ™ˆ. otherwise, if B hasn't received the A2-B1 MP_JOIN SYN but did get the ADD_ADDR, it can try to start a new subflow from one or more of its addresses to address A2. this lets new sessions open even if one host is behind a NAT ๐Ÿ”ฅ.

other ways of using the two signaling mechanisms are possible too. for instance, signaling addresses in other address families can only be done explicitly, using the Add Address (ADD_ADDR) option ๐Ÿ’œ.


3.4.1. Address Advertisement ๐Ÿ“ฃ

the ADD_ADDR MPTCP option announces additional addresses (and, optionally, ports) where a host can be reached (Figure 12) ๐Ÿฆ‹. this option can be used at any time during a connection, depending on when the sender wants to enable multiple paths and/or when paths become available โœจ. as with all MPTCP signals, the receiver MUST undertake standard TCP validity checks, e.g., per [RFC5961], before acting upon it ๐Ÿ›ก๏ธ.

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------+---------------+-------+-------+---------------+
 |     Kind      |     Length    |Subtype|(rsv)|E|  Address ID   |
 +---------------+---------------+-------+-------+---------------+
 |           Address (IPv4: 4 octets / IPv6: 16 octets)          |
 +-------------------------------+-------------------------------+
 |   Port (2 octets, optional)   |                               |
 +-------------------------------+                               |
 |                Truncated HMAC (8 octets, if E=0)              |
 |                               +-------------------------------+
 |                               |
 +-------------------------------+

Figure 12: Add Address (ADD_ADDR) Option ๐ŸŽ€

every address has an Address ID ๐Ÿชช, which is used to uniquely identify the address within a connection (for address removal). the Address ID is also used to identify MP_JOIN options (see Section 3.2) that relate to the same address, even when address translators are in use. the Address ID MUST uniquely identify the address for the sender of the option (within the scope of the connection). the mechanism for allocating these IDs is implementation specific ๐Ÿ’….

all Address IDs learned via either MP_JOIN or ADD_ADDR SHOULD be stored by the receiver in a data structure that gathers all the Address-ID-to-address mappings for a connection (identified by a token pair) ๐Ÿ—‚๏ธ. that way there's a stored mapping between the Address ID, observed source address, and token pair for processing future control info on that connection. note that an implementation MAY discard incoming address advertisements at will ๐Ÿ—‘๏ธ, for example to avoid updating mapping state, or because the advertised addresses are of no use to it (like IPv6 addresses when it only has IPv4). so a host MUST treat address advertisements as soft state, and it MAY choose to refresh advertisements periodically ๐Ÿ”„. also, an implementation MAY choose to cache these advertisements even when they aren't relevant right now but might be later, like IPv4 addresses when IPv6 connectivity is available but IPv4 is waiting on DHCP ๐ŸงŠ.

this option is shown in Figure 12. the illustration is sized for IPv4 addresses. for IPv6, the address length will be 16 octets (instead of 4) ๐Ÿ“.

the 2 octets that specify the TCP port number to use are optional, and you can tell whether they're there from the option's length ๐Ÿ”ข. even though most use cases will use the same port pairs as the initial subflow (e.g., port 80 stays port 80 on all subflows, and so does the ephemeral port at the client), there may be cases (like port-based load balancing) where an explicit different port is required ๐ŸŽฏ. if no port is specified, MPTCP SHOULD attempt to connect to the specified address on the same port already in use by the subflow the ADD_ADDR signal was sent on. this is covered in more detail in Section 3.9 ๐Ÿซถ.

the Truncated HMAC parameter in this option is the rightmost 64 bits of an HMAC, negotiated and calculated the same way as for MP_JOIN (see Section 3.2) ๐Ÿ”. for this spec of MPTCP there's only one hash algorithm option, so this will be HMAC as defined in [RFC2104], using the SHA-256 hash algorithm [RFC6234]. just like with MP_JOIN, the HMAC key for a message sent by Host A is Key-A followed by Key-B, and for a message from Host B it's Key-B followed by Key-A. these are the keys exchanged in the original MP_CAPABLE handshake ๐Ÿค. the HMAC message is the Address ID, IP address, and port that come before the HMAC in the ADD_ADDR option. if the port isn't in the ADD_ADDR option, the HMAC message still includes 2 octets of value zero 0๏ธโƒฃ. the whole point of the HMAC is to stop unauthorized entities from injecting ADD_ADDR signals to hijack a connection ๐Ÿšซ. also, the HMAC stops the address from being changed in flight unless an intermediary knows the key ๐Ÿ•ต๏ธ. if a host receives an ADD_ADDR option whose HMAC it can't validate, it SHOULD silently ignore the option ๐Ÿคซ.

a set of four flags sits after the subtype and before the Address ID ๐Ÿณ๏ธ. only the rightmost bit, labeled "E", is assigned in this spec. the other bits are currently unassigned: they MUST be set to 0 by a sender and MUST be ignored by the receiver ๐Ÿ™…โ€โ™€๏ธ.

the "E" flag is there to add reliability for this option ๐Ÿ’–. since this option is often sent on pure ACKs, there's no guarantee it gets through. so a receiver that gets a fresh ADD_ADDR option (where E=0) sends the same option back to the sender, but without the HMAC and with E=1, to confirm receipt ๐Ÿ“ฌ. depending on local policy, if that "echo" doesn't come back, the initial ADD_ADDR sender can tell that the ADD_ADDR needs to be retransmitted ๐Ÿ”.

with NATs everywhere, it's reasonably likely that one host will try to advertise private addresses [RFC1918] ๐Ÿ˜๏ธ. it's not desirable to ban this, since both hosts might have extra interfaces on the same private network, so a host MAY advertise such addresses ๐Ÿก. the MP_JOIN handshake for creating a new subflow (Section 3.2) has mechanisms to minimize security risks ๐Ÿงฏ. the MP_JOIN message contains a 32-bit token that uniquely identifies the connection to the receiving host. if the token is unknown, the host responds with a RST ๐Ÿšซ. in the unlikely event that the token is valid at the receiving host, subflow setup continues, but the HMAC exchange has to happen for authentication. the HMAC exchange will fail and that's enough protection against two unconnected hosts accidentally setting up a new subflow because of a private address signal ๐Ÿ›‘. more security considerations around ADD_ADDR messages that accidentally misdirect, or maliciously direct, new MP_JOIN attempts are discussed in Section 5 ๐Ÿšจ.

a host that gets an ADD_ADDR but finds that a connection to that IP address and port number is unsuccessful SHOULD NOT try connecting to that address/port combination again for this connection ๐Ÿ™…. if a sender wants to trigger a new incoming connection attempt on a previously advertised address/port combination, it can refresh the ADD_ADDR info by sending the option again ๐Ÿ”„.

a host can send an ADD_ADDR message with an already-assigned Address ID, but the address MUST be the same as the address previously assigned to that Address ID ๐Ÿงท. a new ADD_ADDR may have the same port number or a different one. if the port number is different, the receiving host SHOULD try to set up a new subflow to this new address/port combination ๐Ÿ†•.

a host that wants to replace an existing Address ID MUST first remove the existing one (Section 3.4.2) ๐Ÿงน.

during normal MPTCP operation, it's unlikely there'll be enough TCP option space for ADD_ADDR to fit alongside the options for data sequence numbering (Section 3.3.1) ๐Ÿฅฒ. so an MPTCP implementation is expected to send the ADD_ADDR option on separate ACKs. as discussed earlier, though, an MPTCP implementation MUST NOT treat duplicate ACKs carrying any MPTCP option (except the DSS option) as indications of congestion [RFC5681] ๐Ÿšฆ. and an MPTCP implementation SHOULD NOT send more than two duplicate ACKs in a row for signaling purposes ๐Ÿ™Œ.

3.4.2. Remove Address โœจ

okay so bestie, if an address that was already announced stops being valid during an MPTCP connection (like the interface just disappears, or an IPv6 address stops being the preferred one), the affected host SHOULD tell its peer so the peer can remove the subflows tied to that address ๐Ÿซถ. Even if nobody is using that address right now, if it was announced before, an implementation SHOULD announce its removal too ๐Ÿงš. A host MAY also announce that a still-valid IP address shouldn't be used anymore, like for make-before-break session continuity ๐Ÿฆ‹.

This happens through the Remove Address (REMOVE_ADDR) option (Figure 13) ๐Ÿ’…. It removes a previously added address (or a list of them) from a connection and terminates any subflows currently using that address.

                     1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------+---------------+-------+-------+---------------+
|     Kind      |Length = 3 + n |Subtype|(resvd)|   Address ID  | ...
+---------------+---------------+-------+-------+---------------+
                          (followed by n-1 Address IDs, if required)

            Figure 13: Remove Address (REMOVE_ADDR) Option

Security time, and I need everyone listening ๐Ÿ™ˆ๐Ÿ’€. If a host receives a REMOVE_ADDR option, it must ensure that the affected path or paths are no longer in use before it starts closing anything. Getting a REMOVE_ADDR SHOULD first kick off a TCP keepalive [RFC1122] on the path ๐Ÿ”ฅ. If something answers, the path SHOULD NOT be removed. If the path turns out to still be alive, the receiving host SHOULD no longer use that address for future connections, BUT shutting down the subflow is the job of whoever sent the REMOVE_ADDR ๐Ÿ’….

Before the address is gone, the requesting host MAY also use MP_PRIO (Section 3.3.8) to ask that a path stop being used ๐ŸŒธ. The usual TCP validity tests on the subflow (like checking that the sequence and ACK numbers are correct) MUST also be run ๐Ÿงš. An implementation can use failures from these tests for intrusion detection or error logging ๐Ÿ‘€.

Sending this message SHOULD trigger RSTs from both hosts on the affected subflow(s) (if possible), as a courtesy ๐Ÿ’–. Receiving it does too, as long as no keepalive response came back. The point is to let middlebox state get cleaned up before any local state does ๐Ÿซง.

Address removal is handled by Address ID, which is what lets it work with NATs and other middleboxes that rewrite source addresses ๐Ÿฆ‹. If an Address ID is unknown, the receiver silently ignores the request ๐Ÿคซ.

A subflow that is still functioning MUST be closed with a FIN exchange as in regular TCP, rather than using this option. For more information, see Section 3.3.3. ๐Ÿšซ

3.5. Fast Close โœจ

regular TCP can send a RST to slam a connection shut abruptly ๐Ÿ’ฅ. With MPTCP though, a regular RST only covers its own subflow. It closes that one subflow and leaves the rest alone, because the MPTCP connection stays alive at the data level so that break-before-make handover between subflows is possible ๐Ÿฆ‹. So MPTCP needs its own connection-level "reset" for abruptly closing the whole thing, and that's what the MP_FASTCLOSE option is for ๐Ÿ‘‘.

MP_FASTCLOSE tells the peer that the connection is about to be abruptly closed and that no more data will be accepted ๐Ÿ™…โ€โ™€๏ธ. The reasons for sending one are implementation specific. Regular TCP doesn't let you send a RST while the connection is synchronized [RFC0793]. Implementations do allow it in that state if, say, the operating system is running out of resources. In those cases, MPTCP should send the MP_FASTCLOSE ๐ŸŒˆ. It looks like this (Figure 14):

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------+---------------+-------+-----------------------+
 |     Kind      |    Length     |Subtype|      (reserved)       |
 +---------------+---------------+-------+-----------------------+
 |                      Option Receiver's Key                    |
 |                            (64 bits)                          |
 |                                                               |
 +---------------------------------------------------------------+

            Figure 14: Fast Close (MP_FASTCLOSE) Option

If Host A wants to force the closure of an MPTCP connection, it has two options ๐Ÿ“:

  • Option A (ACK): Host A sends an ACK containing the MP_FASTCLOSE option on one subflow, containing the key of Host B as declared in the initial connection handshake. On all the other subflows, Host A sends a regular TCP RST to close these subflows and tears them down. Host A now enters FASTCLOSE_WAIT state.
  • Option R (RST): Host A sends a RST containing the MP_FASTCLOSE option on all subflows, containing the key of Host B as declared in the initial connection handshake. Host A can tear down the subflows and the connection immediately.

If Host A goes with Option A and sends an ACK with the MP_FASTCLOSE option, the connection shall proceed as follows ๐Ÿ’…:

  • Upon receipt of an ACK with MP_FASTCLOSE by Host B, containing the valid key, Host B answers on the same subflow with a TCP RST and tears down all subflows also through sending TCP RST signals. Host B can now close the whole MPTCP connection (it transitions directly to CLOSED state) ๐Ÿซถ.
  • As soon as Host A has received the TCP RST on the remaining subflow, it can close this subflow and tear down the whole connection (transition from FASTCLOSE_WAIT state to CLOSED state). If Host A receives an MP_FASTCLOSE instead of a TCP RST, both hosts attempted fast closure simultaneously. Host A should reply with a TCP RST and tear down the connection ๐Ÿ˜ญ.
  • If Host A does not receive a TCP RST in reply to its MP_FASTCLOSE after one retransmission timeout (RTO) (the RTO of the subflow where the MP_FASTCLOSE has been sent), it SHOULD retransmit the MP_FASTCLOSE. To keep this connection from being retained for a long time, the number of retransmissions SHOULD be limited; this limit is implementation specific. A RECOMMENDED number is 3. If no TCP RST is received in response, Host A SHOULD send a TCP RST with the MP_FASTCLOSE option itself when it releases state, in order to clear any remaining state at middleboxes ๐Ÿงน.

If Host A goes with Option R instead and sends a RST with the MP_FASTCLOSE option, Host B will act as follows ๐ŸŒธ: upon receipt of a RST with MP_FASTCLOSE, containing the valid key, Host B tears down all subflows by sending a TCP RST. Host B can now close the whole MPTCP connection (it transitions directly to CLOSED state) โœจ.


3.6. Subflow Reset โœจ

An MPTCP implementation may also need to send a regular TCP RST to force the closure of a subflow ๐ŸŽ€. A host sends a TCP RST to close a subflow, or to reject an attempt to open one (MP_JOIN). So the receiving host knows why a subflow is being closed or rejected, the TCP RST packet MAY include the MP_TCPRST option (Figure 15) ๐Ÿ”. The host MAY use that information to decide whether to re-establish the subflow immediately, later, or never ๐Ÿง .

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------+---------------+-------+-----------------------+
 |     Kind      |    Length     |Subtype|U|V|W|T|    Reason     |
 +---------------+---------------+-------+-----------------------+

            Figure 15: TCP RST Reason (MP_TCPRST) Option

MP_TCPRST carries a reason code so the sender can explain why the subflow ended ๐Ÿ’ฌ. It uses 12 bits of option space. The first 4 bits are flags (only one of them is defined right now), and the remaining octet is the reason code, which a receiver MAY use to infer information about the usability of this path ๐Ÿงฉ.

The T flag is how the sender says whether the error is Transient (T set to 1) or Permanent (T set to 0) ๐ŸŒฆ๏ธ. If the sender considers the error Transient, the recipient MAY try to re-establish a subflow for this connection over the failed path. When to try is implementation specific, but it SHOULD take into account the properties of the failure, as defined by the provided reason code โฐ. If the error is considered Permanent, the receiver of the RST segment SHOULD NOT try to re-establish a subflow for this connection over this path ๐Ÿšซ. The U, V, and W flags are not defined by this specification and are reserved for future use ๐Ÿ”’. An implementation of this specification MUST set these flags to 0, and a receiver MUST ignore them ๐Ÿ™ˆ.

Reason is an 8-bit field that holds the reason code for the termination of the subflow. The following codes are defined in this document ๐Ÿ“‹:

  • Unspecified error (code 0x00). This is the default error. It implies that the subflow is no longer available. Seeing this option tells you the RST was generated by an MPTCP-aware device ๐Ÿค–.
  • MPTCP-specific error (code 0x01). An error has been detected in the processing of MPTCP options. This is the usual reason code when a RST is sent to close a subflow because of an invalid response ๐Ÿ™ƒ.
  • Lack of resources (code 0x02). The sending host doesn't have enough resources to support the terminated subflow ๐Ÿชซ.
  • Administratively prohibited (code 0x03). The requested subflow is prohibited by the sending host's policies ๐Ÿšง.
  • Too much outstanding data (code 0x04). There's an excessive amount of data that needs to go over the terminated subflow, even though it has already been acknowledged over one or more other subflows. This can happen if a path was unavailable for a short while, and it's more efficient to reset and start again than to retransmit the queued data ๐Ÿ”.
  • Unacceptable performance (code 0x05). The performance of this subflow was too low compared to the other subflows of this Multipath TCP connection ๐Ÿข.
  • Middlebox interference (code 0x06). Middlebox interference has been detected over this subflow, which makes MPTCP signaling invalid. For example, this may be sent if the checksum does not validate ๐Ÿ‘พ.

3.7. Fallback ๐Ÿฆ‹

okay so bestie, this is the part where things can go sideways ๐Ÿ˜ญ. Middleboxes (those sneaky in-network boxes sitting on a path) can totally get in the way of MPTCP. MPTCP is built to handle plenty of middlebox shenanigans (see Section 6 ๐Ÿ’…), but there are still a couple of situations where a subflow just can't keep up with the MPTCP requirements. The two big ones are: MPTCP options getting dropped on a path, and payload data getting modified. When something like that happens, the connection has to "fall back" to the last known safe way of doing things. That could mean going back to plain regular TCP, or just ditching the problematic subflow. Iconic survival move ๐Ÿ›Ÿโœจ

At the very start of an MPTCP connection (aka the first subflow), it's super important to make sure the path is fully MPTCP capable and that the MPTCP options can actually reach each host ๐Ÿ”. The handshake from Section 3.1 SHOULD fall back to regular TCP if either of the SYN messages doesn't carry the MPTCP options. Honestly, this is the same behavior you want when a host isn't MPTCP capable or the path doesn't support the options, so it's giving consistency ๐Ÿ’–. When you're trying to join an existing MPTCP connection (Section 3.2) and the MPTCP options don't make it through on the SYNs, the subflow gets closed according to the MP_JOIN logic ๐Ÿšช.

Now here's the plot twist ๐Ÿ™ˆ: what if the MPTCP options DO get through on the SYN, but not on regular packets after that? If the subflow is the first one, so all the data in flight is contiguous, you can sort it out with these rules:

  • A sender MUST include a DSS option with Data Sequence Mapping in every segment until one of the sent segments gets acknowledged with a DSS option containing a Data ACK. Once that ACK comes back, the sender knows the DSS option survives in both directions and MAY send fewer DSS options than one per segment ๐ŸŽ€.
  • If, however, an ACK comes back for data (not just for the SYN) without a DSS option containing a Data ACK, the sender decides the path isn't MPTCP capable ๐Ÿ˜ฌ. If this happens on an additional subflow (one started with MP_JOIN), the host MUST close the subflow with a RST, which SHOULD contain an MP_TCPRST option (Section 3.6) with a "Middlebox interference" reason code.
  • If that ACK shows up on the first subflow (the one that started with MP_CAPABLE), before any additional subflows get added, the implementation MUST drop out of MPTCP mode and fall back to regular TCP. The sender sends one final Data Sequence Mapping, with the Data-Level Length value of 0 indicating an infinite mapping (so the other end knows in case the path drops options in only one direction), and then goes back to sending data on the single subflow without any MPTCP options ๐Ÿงš.
  • If a subflow breaks during operation, say it gets rerouted and MPTCP options aren't allowed anymore, then once that's detected (by the subflow-level receive buffer filling up, since there's no mapping left to DATA_ACK this data ๐Ÿ˜ต), the subflow SHOULD be treated as broken and closed with a RST. Why? Because no data can reach the application layer and no fallback signal can be reliably sent. This RST SHOULD include the MP_TCPRST option (Section 3.6) with a "Middlebox interference" reason code.

These rules should cover every case where this kind of failure could happen, whether it's on the forward or reverse path, and whether the server or the client sends data first ๐ŸŒˆ.

So far we've been talking about MPTCP options getting lost, either right at the start or partway through the connection. As Section 3.3 explains, every chunk of data that has a mapping is protected by a checksum, as long as checksums were negotiated ๐Ÿ”. That's how the stack detects whether a middlebox messed with the payload: adding, removing, or changing data. A checksum fails if the data changed in any way. The checksum also catches whether the length of the data on the subflow went up or down, and that means the Data Sequence Mapping isn't valid anymore ๐Ÿ™ƒ. The sender no longer knows which subflow-level sequence number the receiver is actually at (the middlebox is faking the ACKs), so it can't signal any more mappings. On top of that, payload changes that are valid at the application layer are possible, and those changes could be triggered across MPTCP segment boundaries and corrupt the data ๐Ÿ’€. So, all data from the start of the segment that failed the checksum onward is not trustworthy.

Quick note ๐Ÿ“: if checksum usage wasn't negotiated, this fallback mechanism cannot be used, unless some higher-layer or lower-layer signal tells the MPTCP implementation that the payload was tampered with.

When multiple subflows are in use, the data in flight on a subflow will probably include bits that aren't contiguous in the connection-level stream, since segments are spread across the subflows ๐Ÿงฉ. Because of the problems above, there's no way to figure out what adjustments were made to the data (especially changes to the subflow sequence numbering). So, it isn't possible to recover the subflow. The affected subflow MUST be closed immediately with a RST that includes an MP_FAIL option (Figure 16). That option carries the data sequence number at the start of the segment (as defined by the Data Sequence Mapping) that had the checksum failure. Also, the MP_FAIL option always uses the full 64-bit sequence number, even when 32-bit sequence numbers are normally used in the DSS signals on the path ๐Ÿ”ข.

                      1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +---------------+---------------+-------+----------------------+
 |     Kind      |   Length=12   |Subtype|      (reserved)      |
 +---------------+---------------+-------+----------------------+
 |                                                              |
 |                 Data Sequence Number (8 octets)              |
 |                                                              |
 +--------------------------------------------------------------+

                Figure 16: Fallback (MP_FAIL) Option

The receiver of this option MUST discard all data following the data sequence number specified. Failed data MUST NOT be DATA_ACKed, so it gets retransmitted on other subflows (Section 3.3.6) ๐Ÿ”.

There's a special case when there's a single subflow and it fails with a checksum error ๐Ÿฅบ. If it's known that all the unacknowledged data in flight is contiguous (which is usually true with a single subflow), an infinite mapping can be applied to the subflow without closing it first. That basically turns off all further MPTCP signaling โœจ. In this situation, if a receiver spots a checksum failure when there's only one path, it sends back an MP_FAIL option on the subflow-level ACK, pointing at the data-level sequence number of the start of the segment where the checksum error showed up. The sender gets that info and, if all the unacknowledged data in flight is contiguous, signals an infinite mapping. That infinite mapping is a DSS option (Section 3.3) on the first new packet, carrying a Data Sequence Mapping that works retroactively. It refers to the start of the subflow sequence number of the most recent segment known to be delivered intact (aka successfully DATA_ACKed) ๐Ÿ•ฐ๏ธ. From that point on, a middlebox can change the data without affecting MPTCP, because the stream is basically a regular, legacy TCP session. Paths might only be damaged in one direction, and the MP_FAIL signal only affects one direction of traffic. But for simplicity of implementation, the receiver of an MP_FAIL MUST also respond with an MP_FAIL in the reverse direction and fully revert to a regular TCP session ๐Ÿซถ.

Now, the rare case where the data isn't contiguous (which could happen with only one subflow if it's retransmitting data from a subflow that was recently closed uncleanly) ๐Ÿ˜ตโ€๐Ÿ’ซ: the receiver MUST close the subflow with a RST with MP_FAIL. The receiver MUST discard all data that follows the data sequence number specified. The sender MAY try to create a new subflow that belongs to the same connection, and if it does, it SHOULD immediately put the single subflow into single-path mode by setting an infinite Data Sequence Mapping. That mapping begins at the data-level sequence number that was declared in the MP_FAIL ๐ŸŽฏ.

After a sender signals an infinite mapping, it MUST only use subflow ACKs to clear its send buffer ๐Ÿงน. The reason is that Data ACKs can get out of line with the subflow ACKs when middleboxes insert or delete data. The receiver SHOULD stop generating Data ACKs after it receives an infinite mapping ๐Ÿ›‘.

When a connection has fallen back with an infinite mapping, only one subflow can send data. Otherwise the receiver wouldn't know how to reorder the data ๐ŸŒ€. In practice, that means all MPTCP subflows get terminated except one. Once MPTCP falls back to regular TCP, it MUST NOT go back to MPTCP later in the connection ๐Ÿšซ.

It's worth stressing ๐Ÿ“ฃ: MPTCP isn't trying to stop middleboxes that want to adjust the payload. An MPTCP-aware middlebox could provide that kind of functionality by also rewriting the checksums ๐Ÿง.


3.8. Error Handling ๐Ÿšจ

Besides the fallback mechanism we just covered, the standard classes of TCP errors may need handling in an MPTCP-specific way ๐Ÿ› ๏ธ. Changing semantics, like what a RST means, are covered in Section 4 ๐Ÿ’œ. Wherever possible, we don't want to deviate from regular TCP behavior ๐ŸŽ€.

Here's the list of possible errors and the right MPTCP behavior for each one ๐Ÿ“‹:

  • Unknown token in MP_JOIN (or HMAC failure in MP_JOIN ACK, or missing MP_JOIN in SYN/ACK response): send RST (analogous to TCP's behavior on an unknown port) ๐Ÿšซ
  • DSN out of window (during normal operation): drop the data; do not send Data ACKs ๐Ÿ—‘๏ธ
  • Remove request for unknown Address ID: silently ignore ๐Ÿคซ

3.9. Heuristics ๐Ÿง 

There are a bunch of heuristics that are needed for performance or deployment, but they aren't required for protocol correctness ๐Ÿ’…. In this section, we go over those heuristics. Also, discussions of buffering and certain sender and receiver window behaviors are in Sections 3.3.4 and 3.3.5, and retransmission is covered in Section 3.3.6 โœจ.

3.9.1. Port Usage ๐ŸŽ€

Okay so, under typical operation, an MPTCP implementation SHOULD use the same ports that are already in use ๐Ÿ’…. The destination port of a SYN carrying an MP_JOIN option SHOULD match the remote port of the first subflow in the connection. The local port for those SYNs SHOULD also match the first subflow's port, so an implementation SHOULD reserve ephemeral ports across all local IP addresses ๐Ÿงš. Is that always doable? Nope, there may be cases where it's infeasible ๐Ÿ™ˆ. The whole point is to maximize the odds that the SYN gets through a firewall or NAT at the recipient, and to avoid confusing any network-monitoring software โœจ.

There are also times when a host wants to signal that a specific port should be used, and that's what the ADD_ADDR option is for (see Section 3.4.1) ๐Ÿฆ‹. So it's totally possible to have multiple subflows between the same two addresses using different port pairs. That could even be used to load balance inside the network based on 5-tuples (e.g., some ECMP implementations [RFC2992]) ๐ŸŒˆ.

3.9.2. Delayed Subflow Start and Subflow Symmetry ๐ŸŒธ

Lowkey, lots of TCP connections are short-lived and only send a few segments, so the overhead of MPTCP just isn't worth it ๐Ÿ’€. That means a heuristic is required to decide when to start using additional subflows in an MPTCP connection. Experimental deployments have shown MPTCP works across a range of scenarios, so an implementation will likely need to think about stuff like the type of traffic and how long the session lasts. This information MAY be signaled by the application layer ๐Ÿซถ.

But for regular TCP traffic, here's a suggested general-purpose heuristic that an implementation MAY choose to use ๐Ÿ‘‘. If a host has data buffered for its peer (which implies the application has received a request for data), the host opens one subflow for each initial window's worth of data that is buffered.

Consideration should also be given to limiting how fast new subflows get added, plus capping the total number of subflows open for one connection ๐Ÿ“. A host may choose to vary these values based on its load or what it knows about traffic and path characteristics.

Heads up, this heuristic alone is probably not enough ๐Ÿ˜ญ. Traffic for common apps like downloads is highly asymmetric, and the multihomed host might be the client that never fills its buffers, so it never uses MPTCP under this heuristic. Advanced APIs that let an application signal its traffic requirements would really help with these decisions ๐Ÿ™Œ.

A time-based heuristic could be added too: open additional subflows after a given period of time has passed โฐ. That would fix the issue above and also give resilience to low-bandwidth but long-lived applications.

Another thing: both hosts might try to set up a subflow between the same pair of addresses at the same time, and that wastes resources ๐Ÿ˜ค.

If every subflow uses the same ports (like we recommended above), standard TCP simultaneous-open logic should handle it, and only one subflow ends up between the address pairs. But that only works if both end hosts use the same ports. If a host does not support TCP simultaneous open, it is RECOMMENDED that some element of randomization be applied to the time it waits before opening new subflows, so that only one subflow is created between a given address pair ๐ŸŽฒ. But if hosts signal additional ports to use (like for leveraging ECMP on-path), this heuristic is a no-go ๐Ÿšซ.

So that's some of the factors an implementer should consider when developing MPTCP heuristics. It's not meant to be prescriptive, though ๐Ÿ“.

3.9.3. Failure Handling ๐Ÿ› ๏ธ

The rules for handling unexpected signals live in Section 3.8, but there are other failure cases where hosts get to pick what's appropriate ๐Ÿง.

For example, Section 3.1 suggests that a host SHOULD fall back to trying regular TCP SYNs after one or more failures of MPTCP SYNs for a connection. A host may keep a system-wide cache of that info, so it can back off from MPTCP, first for that particular destination host and eventually across a whole interface if MPTCP connections keep failing ๐Ÿ”. How long that cache lasts is implementation specific.

Another failure: the MP_JOIN handshake can fail ๐Ÿšจ. Section 3.8 says an incorrect handshake MUST lead to the subflow being closed with a RST. A host running an active intrusion-detection system may choose to start blocking MP_JOIN packets from the source host if it sees multiple failed MP_JOIN attempts. From the connection initiator's side, if an MP_JOIN fails, it SHOULD NOT try to connect to that same IP address and port during the lifetime of the connection, unless the other host refreshes the info with another ADD_ADDR option. Just so you know, ADD_ADDR is informational only, so it doesn't guarantee the other host will actually attempt a connection ๐Ÿ’….

Also, over a bunch of connections, an implementation may pick up that certain interfaces or destination addresses keep failing, and it may default to skipping MPTCP for those ๐Ÿซ . The same goes for subflows that perform really badly or keep failing during use. Those behaviors can be learned too, so an implementation can temporarily choose not to use those paths ๐Ÿ›ค๏ธ.

4. Semantic Issues ๐Ÿง 

To make multipath work, some TCP components got new semantics ๐Ÿช„. So this section is a handy list of those changes for reference.

Sequence number: the in-header TCP sequence number is specific to the subflow ๐ŸŽฏ. So the receiver can reorder application data, there's an extra data-level sequence space. In that space, the initial SYN and the final DATA_FIN each take up 1 octet, so these signals get acknowledged at the connection level โœ…. There's an explicit mapping from data sequence space to subflow sequence space, and it's signaled through TCP options in data packets.

ACK: the ACK field in the TCP header only acknowledges the subflow sequence number, and not the data-level sequence space ๐Ÿ™…. Implementations SHOULD NOT attempt to infer a data-level acknowledgment from the subflow ACKs. This keeps subflow-level and connection-level processing separate at an end host.

Duplicate ACK: a duplicate ACK that carries any MPTCP signaling (except the DSS option) MUST NOT be treated as a sign of congestion ๐Ÿšซ. To keep non-MPTCP-aware entities from mistakenly reading duplicate ACKs as congestion, MPTCP SHOULD NOT send more than two duplicate ACKs containing (non-DSS) MPTCP signals in a row.

Receive Window: the receive window in the TCP header shows the free buffer space for the whole data-level connection (not just this subflow) at the receiver ๐ŸชŸ. The semantics are the same as regular TCP, but to keep them, the receive window must be interpreted at the sender as relative to the sequence number given in the DATA_ACK, rather than the subflow ACK in the TCP header. That's how the original role of flow control is preserved. Note that some middleboxes may change the receive window, so a host SHOULD use the maximum of the values recently seen on the constituent subflows for the connection-level receive window. It also needs to maintain a subflow-level window for subflow-level processing.

FIN: the FIN flag in the TCP header only applies to the subflow it's sent on, not the whole connection ๐Ÿ. For connection-level FIN semantics, the DATA_FIN option is used.

RST: the RST flag in the TCP header only applies to the subflow it's sent on, not the whole connection ๐Ÿ’ฅ. The MP_FASTCLOSE option gives you the Fast Close functionality of a RST at the MPTCP connection level.

Address List: managing the address list (i.e., knowing the local and remote hosts' lists of available IP addresses) happens per connection. It's not per subflow, per host, or per pair of communicating hosts ๐Ÿ—บ๏ธ. That means per-connection local policy can be applied. Adding an address to one connection (either explicitly with an ADD_ADDR message or implicitly through an MP_JOIN) has zero implications for other connections between the same pair of hosts ๐Ÿ’ซ.

5-tuple: the 5-tuple (protocol, local address, local port, remote address, remote port) that kernel APIs present to the application layer in a non-multipath-aware application is that of the first subflow ๐Ÿ”‘. That's true even if that subflow has since been closed and removed from the connection. This decision, plus other related API issues, gets a deeper dive in [RFC6897] ๐Ÿ“š.

5. Security Considerations ๐Ÿ”’โœจ

okay bestie, security is where things get serious ๐Ÿ˜ญ As identified in [RFC6181], adding multipath capability to TCP brings a whole new set of threat classes ๐Ÿ’€. To stop those threats, [RFC6182] lays out a set of requirements for an MPTCP security solution. The main goal is for MPTCP security to be "no worse" than regular TCP today ๐Ÿซถ. The key security requirements are:

  • Provide a mechanism to confirm that the parties in a subflow handshake are the same as the parties in the original connection setup.
  • Provide verification that the peer can receive traffic at a new address before using it as part of a connection.
  • Provide replay protection, i.e., ensure that a request to add/remove a subflow is "fresh".

To hit these goals, MPTCP uses a hash-based handshake algorithm, as documented in Sections 3.1 and 3.2 ๐Ÿ”.

The security of an MPTCP connection hangs on keys that get shared once at the start of the first subflow and are never sent over the network again (unless they're used in the Fast Close mechanism (Section 3.5)) ๐Ÿ—๏ธ. To keep demultiplexing easy without giving away any cryptographic material, future subflows use a truncated cryptographic hash of this key as the connection identification "token" ๐ŸŽ€. The keys are concatenated and used as keys for creating Hash-based Message Authentication Codes (HMACs) that get used on subflow setup, so the parties in the handshake can be verified as the same parties from the original connection setup. It also verifies that the peer can receive traffic at this new address ๐Ÿ“ฌ. Replay attacks would still be possible if only keys were used, so the handshakes use single-use random numbers (nonces) at both ends. This makes sure the HMAC will never be the same on two handshakes ๐Ÿ™…โ€โ™€๏ธ. Guidance on generating random numbers suitable for use as keys is given in [RFC4086] and discussed in Section 3.1. The nonces are valid for the lifetime of the TCP connection attempt. HMAC is also used to secure the ADD_ADDR option, because of the threats identified in [RFC7430] ๐Ÿ›ก๏ธ.

The use of crypto capability bits in the initial connection handshake lets you negotiate a particular algorithm, which means additional crypto mechanisms can be deployed in the future ๐Ÿ”ฎ. Ngl, this negotiation is still open to a bid-down attack, where an on-path active attacker modifies the crypto capability bits in the receiver's response to force a less secure crypto mechanism ๐Ÿ˜ฌ. So the security mechanism presented in this document should protect against all forms of flooding and hijacking attacks discussed in [RFC6181] ๐Ÿ’ช.

The version negotiation specified in Section 3.1 would, if differing MPTCP versions shared a common negotiation format, allow an on-path attacker to try a theoretical bid-down attack ๐Ÿ™ˆ. Since the v1 and v0 protocols have different handshakes, that attack would require the client to re-establish the connection using v0 and the server to support v0. Also, an on-path attacker would have access to the raw data, which negates any other TCP-level security mechanisms ๐Ÿ‘€. As also noted in Appendix E, this document specifies the removal of the AddrID field [RFC6824] from the MP_PRIO option (Section 3.3.8). That change kills the possibility of a theoretical attack where an attacker could place a subflow in "backup" mode ๐Ÿงฏ.

During normal operation, regular TCP protection mechanisms (like making sure sequence numbers are in-window) give the same level of protection against attacks on individual TCP subflows as regular TCP has today โœ…. Implementations will add extra buffers compared to regular TCP, so they can reassemble data at the connection level ๐Ÿงฉ. Applying window sizing will minimise the risk of denial-of-service attacks eating up resources ๐Ÿซง.

As discussed in Section 3.4.1, a host may advertise its private addresses, but those might point to different hosts in the receiver's network ๐Ÿ . The MP_JOIN handshake (Section 3.2) makes sure this doesn't succeed in setting up a subflow to the wrong host. It could still create unwanted TCP handshake traffic though ๐Ÿ˜ฉ. This feature of MPTCP could be a target for denial-of-service exploits, where malicious participants in MPTCP connections encourage the recipient to target other hosts in the network ๐Ÿšจ. So implementations should consider heuristics (Section 3.9) at both the sender and receiver to reduce the impact of this ๐Ÿง .

To protect further against malicious ADD_ADDR messages sent by an off-path attacker, the ADD_ADDR includes an HMAC using the keys negotiated during the handshake. This effectively stops an attacker from diverting an MPTCP connection through an off-path ADD_ADDR injection into the stream ๐Ÿšซ.

A small security risk could theoretically exist with key reuse. But to pull off a replay attack, both the sender and receiver keys, and the sender and receiver random numbers, in the MP_JOIN handshake (Section 3.2) would have to match ๐Ÿค.

While this specification defines a "medium" security solution that meets the criteria set out at the start of this section and in the threat analysis document [RFC6181], attacks only ever get worse ๐Ÿ“ˆ. So it's likely that a future version of MPTCP would need to support stronger security. There are several ways MPTCP security could be improved. Some of these would be compatible with MPTCP as defined in this document, while others may not be โœจ. For now, the best approach is to gain experience with the current approach, figure out what might work, and check that the threat analysis is still accurate ๐Ÿ”.

Possible ways of improving MPTCP security could include:

  • defining a new MPTCP cryptographic algorithm, as negotiated in MP_CAPABLE. If an implementation were deployed in a controlled environment where additional assumptions could be made, such as the ability for servers to store state during the TCP handshake, then it may be possible to use a stronger cryptographic algorithm than would otherwise be possible ๐Ÿฐ.
  • defining how to secure data transfer with MPTCP, while not changing the signaling part of the protocol ๐Ÿ“ฆ.
  • defining security that requires more option space, perhaps in conjunction with a "long options" proposal for extending the TCP option space (such as those surveyed in [TCPLO]), or perhaps building on the current approach with a second stage of security based on MPTCP options ๐Ÿงฑ.
  • revisiting the working group's decision to exclusively use TCP options for MPTCP signaling, and instead looking at the possibility of using TCP payloads as well ๐Ÿค”.

MPTCP has been designed with several methods available to indicate a new security mechanism, including:

  • available flags in MP_CAPABLE (Figure 4).
  • available subtypes in the MPTCP option (Figure 3).
  • the Version field in MP_CAPABLE (Figure 4).

6. Interactions with Middleboxes โœจ

Okay bestie, so Multipath TCP was designed to actually work in the messy world we have today ๐ŸŒ๐Ÿ’…. Its design takes into account "reasonable" existing middlebox behavior ๐Ÿงš. In this section we look at a few middlebox-related failure scenarios and how Multipath TCP handles them ๐Ÿซถ. After that we list the design decisions it made to accommodate the different middleboxes ๐Ÿ’œ.

A primary concern is our use of a new TCP option ๐Ÿ˜ญ. Middleboxes should forward packets with unknown options unchanged, yet some of them don't ๐Ÿ’€. We expect these middleboxes to strip options and pass the data, drop packets with new options, copy the same option into multiple segments (e.g., when doing segmentation), or drop options during segment coalescing ๐Ÿ™ˆ.

MPTCP uses a single new TCP option called "Kind", and all message types are defined by "subtype" values (see Section 7) ๐ŸŽ€. This should reduce the chances of only some types of MPTCP options getting through ๐Ÿฆ‹. Instead, the key differing characteristics are the different paths and the presence of the SYN flag ๐Ÿ›ค๏ธ.

MPTCP SYN packets on the first subflow of a connection contain the MP_CAPABLE option (Section 3.1) ๐ŸŒธ. If this is dropped, MPTCP SHOULD fall back to regular TCP ๐Ÿ”. If packets with the MP_JOIN option (Section 3.2) are dropped, the paths will simply not be used ๐Ÿšซ.

If a middlebox strips options but otherwise passes the packets unchanged, MPTCP will behave safely ๐Ÿ›ก๏ธ. If an MP_CAPABLE option is dropped on either the outgoing path or the return path, the initiating host can fall back to regular TCP, as illustrated in Figure 17 and discussed in Section 3.1 ๐Ÿ“.

                Host A                              Host B
                  |              Middlebox M            |
                  |                   |                 |
                  | SYN (MP_CAPABLE)  |        SYN      |
                  |-------------------|---------------->|
                  |                SYN/ACK              |
                  |<------------------------------------|
              a) MP_CAPABLE option stripped on outgoing path

                Host A                                Host B
                  |           SYN (MP_CAPABLE)            |
                  |-------------------------------------->|
                  |             Middlebox M               |
                  |                  |                    |
                  |    SYN/ACK       |SYN/ACK (MP_CAPABLE)|
                  |<-----------------|--------------------|
              b) MP_CAPABLE option stripped on return path

      Figure 17: Connection Setup with Middleboxes That Strip Options
                                from Packets

Subflow SYNs contain the MP_JOIN option ๐Ÿงฉ. If this option is stripped on the outgoing path, the SYN will look like a regular SYN to Host B ๐Ÿ‘€. Depending on whether there is a listening socket on the target port, Host B will reply with either a SYN/ACK or a RST (and the subflow connection fails) ๐Ÿ’”. When Host A receives the SYN/ACK, it sends a RST, because the SYN/ACK does not contain the MP_JOIN option and its token ๐Ÿ™…โ€โ™€๏ธ. Either way, the subflow setup fails, but it doesn't otherwise affect the MPTCP connection as a whole ๐Ÿ’–.

We now look at data flow with MPTCP, assuming the flow is correctly set up. That implies the options in the SYN packets were allowed through by the relevant middleboxes ๐Ÿšช. If options are allowed through and there is no resegmentation or coalescing into TCP segments, Multipath TCP flows can proceed without problems ๐Ÿ„โ€โ™€๏ธโœจ.

The case where options get stripped on data packets is discussed in Section 3.7 ๐Ÿ˜ฌ. If only some MPTCP options are stripped, behavior is not deterministic ๐ŸŽฒ. If some Data Sequence Mappings are lost, the connection can continue as long as mappings exist for the subflow-level data (e.g., if multiple maps have been sent that reinforce each other) ๐Ÿงต. If some subflow-level space is left unmapped, however, the subflow is treated as broken and is closed, using the process described in Section 3.7 ๐Ÿšจ. MPTCP should survive a loss of some Data ACKs, but performance will degrade as the fraction of stripped options increases ๐Ÿ“‰. We do not expect such cases to appear in practice, though: most middleboxes will either strip all options or let them all through ๐Ÿคทโ€โ™€๏ธ.

We end this section with a list of middlebox classes, their behavior, and the elements in the MPTCP design that allow operation through such middleboxes ๐Ÿฆ„. Issues surrounding dropping packets with options or stripping options were discussed above and are not included here ๐Ÿ™:

  • NATs (Network Address (and port) Translators) [RFC3022] change the source address (and often the source port) of packets ๐Ÿ . This means a host will not know its public-facing address for signaling in MPTCP ๐Ÿซฅ. Therefore, MPTCP permits implicit address addition via the MP_JOIN option, and the handshake mechanism ensures that connection attempts to private addresses [RFC1918], since they are authenticated, will only set up subflows to the correct hosts ๐Ÿ”. Explicit address removal is undertaken by an Address ID, so no knowledge of the source address is needed ๐Ÿ†”.
  • Performance Enhancing Proxies (PEPs) [RFC3135] might proactively ACK data to increase performance ๐ŸŽ๏ธ. MPTCP, however, relies on accurate congestion control signals from the end host, and non-MPTCP-aware PEPs will not be able to provide such signals ๐Ÿ“ก. MPTCP will, therefore, fall back to single-path TCP or close the problematic subflow (see Section 3.7) ๐Ÿ™ƒ.
  • Traffic normalizers [norm] may not allow holes in sequence numbers, and they may cache packets and retransmit the same data ๐Ÿงน. MPTCP looks like standard TCP on the wire and will not retransmit different data on the same subflow sequence number ๐Ÿงพ. In the event of a retransmission, the same data will be retransmitted on the original TCP subflow, even if it is additionally retransmitted at the connection level on a different subflow ๐Ÿ”„.
  • Firewalls [RFC2979] might perform Initial Sequence Number (ISN) randomization on TCP connections ๐ŸŽฒ. MPTCP uses relative sequence numbers in Data Sequence Mappings to cope with this ๐Ÿ’ช. Like NATs, firewalls will not permit many incoming connections, so MPTCP supports address signaling (ADD_ADDR) so that a multiaddressed host can invite its peer behind the firewall/NAT to connect out to its additional interface ๐Ÿ“ฃ.
  • Intrusion Detection Systems / Intrusion Prevention Systems (IDSs/IPSs) observe packet streams for patterns and content that could threaten a network ๐Ÿ‘ฎโ€โ™€๏ธ. MPTCP may require the instrumentation of additional paths, and an MPTCP-aware IDS or IPS would need to read MPTCP tokens to correlate data from multiple subflows, so it keeps comparable visibility into all of the traffic between devices ๐Ÿ”. Without such changes, an IDS would get an incomplete view of the traffic ๐Ÿ™ˆ. That increases the risk of missing traffic of interest (false negatives) and increases the chances of erroneously identifying a subflow as a risk due to only seeing partial data (false positives) ๐Ÿ˜ตโ€๐Ÿ’ซ.
  • Application-level middleboxes such as content-aware firewalls may alter the payload within a subflow, for example by rewriting URIs in HTTP traffic ๐ŸŒ. MPTCP will detect such changes using the checksum and close the affected subflow(s), if there are other subflows that can be used ๐Ÿง. If all subflows are affected, MPTCP will fall back to TCP, allowing such middleboxes to change the payload ๐Ÿซ . MPTCP-aware middleboxes should be able to adjust the payload and MPTCP metadata so they don't break the connection ๐Ÿ› ๏ธ.

In addition, all classes of middleboxes may affect TCP traffic in the following ways ๐ŸŒ€:

  • TCP options may be removed, or packets with unknown options dropped, by many classes of middleboxes ๐Ÿ—‘๏ธ. It is intended that the initial SYN exchange, with a TCP option, will be sufficient to identify the path's capabilities ๐Ÿชช. If such a packet does not get through, MPTCP will end up falling back to regular TCP ๐Ÿ”™.
  • Segmentation/coalescing (e.g., TCP segmentation offloading) might copy options between packets and might strip some options ๐Ÿงฉ. MPTCP's Data Sequence Mapping includes the relative subflow sequence number instead of using the sequence number in the segment ๐Ÿ”ข. In this way, the mapping is independent of the packets that carry it ๐ŸŽ.
  • The receive window may be shrunk by some middleboxes at the subflow level ๐ŸชŸ. MPTCP will use the maximum window at the data level but will also obey subflow-specific windows ๐Ÿ™‡โ€โ™€๏ธ.

7. IANA Considerations ๐Ÿ“‹

This document obsoletes [RFC6824] ๐Ÿ—‘๏ธโžก๏ธโœจ. As such, IANA has updated several registries to point to this document ๐Ÿ“Œ. In addition, this document creates one new registry ๐Ÿ†•. These topics are described in the following subsections ๐Ÿ‘‡.

7.1. TCP Option Kind Numbers ๐Ÿ”ข

IANA has updated the "TCP Option Kind Numbers" registry to point to this document for Multipath TCP, as shown in Table 1 ๐Ÿ’…:

        +------+--------+-----------------------+-----------+
        | Kind | Length |        Meaning        | Reference |
        +======+========+=======================+===========+
        |  30  |   N    | Multipath TCP (MPTCP) |  RFC 8684 |
        +------+--------+-----------------------+-----------+

                   Table 1: TCP Option Kind Numbers

7.2. MPTCP Option Subtypes ๐ŸŽ€

okay so the 4-bit MPTCP subtype lives in the "MPTCP Option Subtypes" subregistry under the "Transmission Control Protocol (TCP) Parameters" registry ๐Ÿ’… it was first defined in [RFC6824]. Since [RFC6824] is an Experimental RFC and not a Standards Track one, AND nobody has added anything beyond the entries pointing back to [RFC6824], IANA swapped the whole registry out for the contents of Table 2, plus the note below. no cap, very tidy ๐Ÿงš

Heads up: this registry covers the MPTCP Option Subtypes for MPTCP v1, which obsoletes the Experimental MPTCP v0. if you're after the v0 subtypes, go check [RFC6824] ๐Ÿ™ˆ
     +-------+-----------------+----------------------+-------------+
     | Value |      Symbol     |         Name         |  Reference  |
     +=======+=================+======================+=============+
     |  0x0  |    MP_CAPABLE   |  Multipath Capable   |  RFC 8684,  |
     |       |                 |                      | Section 3.1 |
     +-------+-----------------+----------------------+-------------+
     |  0x1  |     MP_JOIN     |   Join Connection    |  RFC 8684,  |
     |       |                 |                      | Section 3.2 |
     +-------+-----------------+----------------------+-------------+
     |  0x2  |       DSS       | Data Sequence Signal |  RFC 8684,  |
     |       |                 |  (Data ACK and Data  | Section 3.3 |
     |       |                 |  Sequence Mapping)   |             |
     +-------+-----------------+----------------------+-------------+
     |  0x3  |     ADD_ADDR    |     Add Address      |  RFC 8684,  |
     |       |                 |                      |   Section   |
     |       |                 |                      |    3.4.1    |
     +-------+-----------------+----------------------+-------------+
     |  0x4  |   REMOVE_ADDR   |    Remove Address    |  RFC 8684,  |
     |       |                 |                      |   Section   |
     |       |                 |                      |    3.4.2    |
     +-------+-----------------+----------------------+-------------+
     |  0x5  |     MP_PRIO     |    Change Subflow    |  RFC 8684,  |
     |       |                 |       Priority       |   Section   |
     |       |                 |                      |    3.3.8    |
     +-------+-----------------+----------------------+-------------+
     |  0x6  |     MP_FAIL     |       Fallback       |  RFC 8684,  |
     |       |                 |                      | Section 3.7 |
     +-------+-----------------+----------------------+-------------+
     |  0x7  |   MP_FASTCLOSE  |      Fast Close      |  RFC 8684,  |
     |       |                 |                      | Section 3.5 |
     +-------+-----------------+----------------------+-------------+
     |  0x8  |    MP_TCPRST    |    Subflow Reset     |  RFC 8684,  |
     |       |                 |                      | Section 3.6 |
     +-------+-----------------+----------------------+-------------+
     |  0xf  | MP_EXPERIMENTAL | Reserved for Private |             |
     |       |                 |         Use          |             |
     +-------+-----------------+----------------------+-------------+

                      Table 2: MPTCP Option Subtypes

values 0x9 through 0xe are unassigned right now ๐ŸŒธ and 0xf is reserved for private experiments. its use might get formalized in a future spec, so keep an eye out โœจ future assignments in this registry go through Standards Action as defined by [RFC8126]. every assignment is made up of the subtype's symbolic name, its value, and a reference to its spec. that's the whole vibe ๐Ÿ’œ

7.3. MPTCP Handshake Algorithms ๐Ÿฆ‹

the "MPTCP Handshake Algorithms" subregistry, also under the "Transmission Control Protocol (TCP) Parameters" registry, was defined in [RFC6824] too. same story as before: [RFC6824] is Experimental and not Standards Track, and no new entries have shown up beyond the ones pointing at it, so IANA replaced the registry with Table 3 and a note of its own ๐Ÿ“

Note: this registry covers the MPTCP Handshake Algorithms for MPTCP v1, which obsoletes the Experimental MPTCP v0. for the MPTCP v0 subtypes, check [RFC6824] ๐Ÿซถ
       +----------+---------------------------------+-------------+
       | Flag Bit |             Meaning             |  Reference  |
       +==========+=================================+=============+
       |    A     |        Checksum required        |  RFC 8684,  |
       |          |                                 | Section 3.1 |
       +----------+---------------------------------+-------------+
       |    B     |          Extensibility          |  RFC 8684,  |
       |          |                                 | Section 3.1 |
       +----------+---------------------------------+-------------+
       |    C     | Do not attempt to establish new |  RFC 8684,  |
       |          | subflows to the source address. | Section 3.1 |
       +----------+---------------------------------+-------------+
       |   D-G    |            Unassigned           |             |
       +----------+---------------------------------+-------------+
       |    H     |           HMAC-SHA256           |  RFC 8684,  |
       |          |                                 | Section 3.2 |
       +----------+---------------------------------+-------------+

                   Table 3: MPTCP Handshake Algorithms

the spicy bit: the meaning of bits "D" through "H" can depend on bit "B", depending on how the Extensibility parameter gets defined in future specs ๐Ÿ‘€ check Section 3.1 for the details. future assignments here are also going through Standards Action as defined by [RFC8126]. each one is made of the flags' value, a symbolic name for the algorithm, and a reference to its spec ๐Ÿ’…

7.4. MP_TCPRST Reason Codes ๐Ÿ’€

okay this one's fun ๐ŸŒˆ IANA made a whole new subregistry, "MPTCP MP_TCPRST Reason Codes", under the "Transmission Control Protocol (TCP) Parameters" registry. it's based on the reason code carried in the MP_TCPRST message (Section 3.6). the starter values are in Table 4. future assignments need Specification Required as defined by [RFC8126]. each assignment is the code's value, a short description of what it means, and a reference to its spec. the max value is 0xff, so there's room but not infinite room ๐Ÿ™ˆ

      +------+-----------------------------+-----------------------+
      | Code |           Meaning           |       Reference       |
      +======+=============================+=======================+
      | 0x00 |      Unspecified error      | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+
      | 0x01 |     MPTCP-specific error    | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+
      | 0x02 |      Lack of resources      | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+
      | 0x03 | Administratively prohibited | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+
      | 0x04 |  Too much outstanding data  | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+
      | 0x05 |   Unacceptable performance  | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+
      | 0x06 |    Middlebox interference   | RFC 8684, Section 3.6 |
      +------+-----------------------------+-----------------------+

                  Table 4: MPTCP MP_TCPRST Reason Codes

as guidance for the designated expert [RFC8126]: assignments should not normally be refused unless codepoint space is getting scarce, as long as there's a clear difference from codes that already exist, and as long as there's enough guidance for implementers on both the sending and receiving side. the expert's gotta be fair but also keep the space tidy ๐Ÿซถโœจ

8. References ๐Ÿ“š

8.1. Normative References ๐Ÿ”ฅ

the normative refs, all listed out so nothing gets lost ๐Ÿ’–

   [RFC0793]  Postel, J., "Transmission Control Protocol", STD 7,
              RFC 793, DOI 10.17487/RFC0793, September 1981,
              <https://www.rfc-editor.org/info/rfc793>.

   [RFC2104]  Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-
              Hashing for Message Authentication", RFC 2104,
              DOI 10.17487/RFC2104, February 1997,
              <https://www.rfc-editor.org/info/rfc2104>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/info/rfc2119>.

   [RFC5961]  Ramaiah, A., Stewart, R., and M. Dalal, "Improving TCP's
              Robustness to Blind In-Window Attacks", RFC 5961,
              DOI 10.17487/RFC5961, August 2010,
              <https://www.rfc-editor.org/info/rfc5961>.

   [RFC6234]  Eastlake 3rd, D. and T. Hansen, "US Secure Hash Algorithms
              (SHA and SHA-based HMAC and HKDF)", RFC 6234,
              DOI 10.17487/RFC6234, May 2011,
              <https://www.rfc-editor.org/info/rfc6234>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/info/rfc8174>.

8.2. Informative References โœจ

okay bestie, here are the receipts ๐Ÿ“š๐Ÿซถ these are the informative ones, so they back up everything in this doc, and they're SO worth a read ๐Ÿ’…


Appendix A. Notes on Use of TCP Options ๐ŸŽ€

okay so the TCP option space is seriously cramped ๐Ÿ˜ญ the Data Offset field is only 4 bits, and it counts the TCP header length in 32-bit words. a standard TCP header is 20 bytes, which leaves a max of 40 bytes for options, and a bunch of that is already taken by stuff like timestamp and SACK.

we did a quick study of the commonly used TCP options on SYN, data, and pure ACK packets, and guess what? there's enough room for every option in this doc ๐Ÿฆ‹๐Ÿ”ฅ

SYN packets usually carry Maximum Segment Size (MSS) (4 bytes), window scale (3 bytes), SACK permitted (2 bytes), and timestamp (10 bytes). together that's 19 bytes. some operating systems pad each option up to a word boundary, so it becomes 24 bytes (a brief survey suggests Windows XP and Mac OS X do this, while Linux doesn't). so optimistically we've got 21 bytes free, or 16 if options have to be word-aligned. either way, the SYN versions of MP_CAPABLE (12 bytes) and MP_JOIN (12 or 16 bytes) still fit in what's left ๐Ÿ’–

ok this next bit is kind of a big deal ๐Ÿ‘‘ because the connection uses a 64-bit data-level sequence space, it's feasible that MPTCP won't need the timestamp option for protection against wrapped sequence numbers (per the Protection Against Wrapped Sequences (PAWS) mechanism, as described in [RFC7323]). the data-level sequence space has way less chance of wrapping. confirming that this optimization is valid is left for further study ๐Ÿงš

TCP data packets usually carry a timestamp option in every packet, which takes 10 bytes (or 12 with padding). that leaves 30 bytes (or 28 if word-aligned). the DSS option changes size depending on (1) whether the Data Sequence Mapping, DATA_ACK, or both are included, (2) whether the sequence numbers in use are 4 or 8 octets, and (3) whether the checksum is present. the biggest DSS option is 28 bytes, so even that fits ๐Ÿ™ˆ but unless a connection is both bidirectional and high-bandwidth, it's unlikely that all that option space will be needed on every DSS option.

inside the DSS option, you don't have to include the Data Sequence Mapping and DATA_ACK in every packet. in lots of cases you can alternate their presence, as long as the mapping covers the data sent in the next packet. you could also alternate between 4-byte and 8-byte sequence numbers from one option to the next ๐Ÿ“

on subflow and connection setup, an MPTCP option also rides on the third packet (an ACK). those are 20 bytes (for MP_CAPABLE) and 24 bytes (for MP_JOIN), and both fit in the available option space ๐ŸŒธ

pure ACKs usually only have timestamps (10 bytes). here, Multipath TCP usually just needs to encode the DATA_ACK (max 12 bytes). every now and then ACKs carry SACK information, and depending on how many packets were lost, SACK can use up the entire option space. if a DATA_ACK has to be included too, it's probably necessary to cut down the number of SACK blocks to make room for it. ngl though, the DATA_ACK is unlikely to be needed when SACK is in use, since until at least some of the SACK blocks get retransmitted, the cumulative data-level ACK won't move forward. (or if it does, because of retransmissions on another path, that path can also carry the new DATA_ACK) ๐Ÿ’…

the ADD_ADDR option can be anywhere from 16 to 30 bytes, depending on (1) whether IPv4 or IPv6 is used and (2) whether the port number is present. it's unlikely that this signaling would fit in a data packet (though if there's space, it's totally fine to include it). it is recommended that duplicate ACKs not be used with any other payload or options, so these rare signals get sent on their own. this is also the reason duplicate ACKs with MPTCP options are not to be taken as a signal of congestion ๐Ÿ˜ญ๐Ÿซถ


Appendix B. TCP Fast Open and MPTCP ๐Ÿฆ‹

TCP Fast Open (TFO) is an experimental TCP extension, described in [RFC7413], that lets you send data one RTT earlier than regular TCP. it's considered a huge win, because super short connections are everywhere, especially for HTTP request/response stuff ๐Ÿš€ the trick is that the SYN segment gets sent together with the application's data, and the listener can reply with data right after the SYN/ACK. [RFC7413] keeps this secure by using a new TCP option that carries a cookie negotiated in an earlier connection ๐Ÿ”

when you combine TFO with MPTCP, there are two key points you need to keep in mind, and we'll go through them below ๐Ÿ’–

okay so, TFO (TCP Fast Open) has a rule that you can't just stuff data into the SYN right away, and that's for security reasons ๐Ÿ” [RFC7413]. So instead, the initiator asks the listener for a cookie first, and then uses that cookie in later connections ๐Ÿช. This happens with the TCP cookie request and response options, which are 2 bytes and 6-18 bytes respectively (depending on how long the cookie is) ๐Ÿ“.

TFO and MPTCP can totally be combined ๐Ÿ’…, as long as all the options together don't go over the max of 40 bytes that TCP allows:

  • In the SYN: MPTCP uses a 4-byte MP_CAPABLE option. The sum of the MPTCP and TFO options is 6 bytes. With typical TCP options using up to 19 bytes in the SYN (24 bytes if options are padded at a word boundary), there is enough space to combine the MP_CAPABLE with the TFO cookie request. ๐ŸŽ€
  • In the SYN + ACK: MPTCP uses a 12-byte MP_CAPABLE option, but now the TFO option can be as long as 18 bytes. Since the maximum option length might get exceeded, it's up to the listener to avoid this problem by using a shorter cookie ๐Ÿซถ. As an example, if we consider that 19 bytes are used for classical TCP options, the maximum possible cookie length would be 7 bytes. Note that, for the SYN packet, the same limitation applies to subsequent connections (because the initiator then echoes the cookie back to the listener) ๐Ÿ‘€. Finally, if the security impact of reducing the cookie size is not deemed acceptable, the listener can reduce the amount of space used by other TCP options by omitting the TCP timestamps (as outlined in Appendix A) โœจ.

B.2. Data Sequence Mapping under TFO

okay this one is giving main character energy ๐Ÿฆ‹. In the TCP establishment phase, MPTCP uses a key exchange that is used to generate the Initial Data Sequence Numbers (IDSNs). Specifically, the SYN with MP_CAPABLE occupies the first octet of data sequence space. With TFO, one way to handle the data sent together with the SYN would be to consider an implicit DSS mapping that covers that SYN segment (since there's not enough space in the SYN to include a DSS option). The problem with that approach is that if a middlebox modifies the TFO data, MPTCP won't notice because there's no DSS checksum ๐Ÿ˜ญ. For example, a TCP-aware (but not MPTCP-aware) middlebox could insert bytes at the beginning of the stream and adapt the TCP checksum and sequence numbers accordingly. With an implicit mapping, this information would give the initiator and listener a different view of the DSS mapping, and there would be no way to detect this inconsistency, because the DSS checksum isn't present ๐Ÿ’€.

To solve this issue, the TFO data must not be considered part of the data sequence number space ๐Ÿ™…โ€โ™€๏ธ: the SYN with MP_CAPABLE still occupies the first octet of data sequence space, but then the first non-TFO data byte occupies the second octet. This guarantees that, if the use of the DSS checksum is negotiated, all data in the data sequence number space is checksummed ๐Ÿ”ฅ. We also note that this does not entail a loss of functionality, because TFO data is always only sent on the initial subflow, before any attempt to create additional subflows ๐ŸŒˆ.

B.3. Connection Establishment Examples

here are a few examples of possible "TFO + MPTCP" establishment scenarios ๐Ÿงš.

Before an initiator can send data together with the SYN, it must request a cookie from the listener, as shown in Figure 18. (Note: The sequence number and length are annotated in Figure 18 as Seq(Length) (e.g., "S. 0(0)") and used as such in the subsequent figures (e.g., "S 0(20)" in Figure 19).) This is done by simply combining the TFO and MPTCP options ๐ŸŒธ.

initiator                                                    listener
    |                                                           |
    |   S Seq=0(Length=0) <MP_CAPABLE>, <TFO cookie request>    |
    | --------------------------------------------------------> |
    |                                                           |
    |   S. 0(0) ack 1 <MP_CAPABLE>, <TFO cookie>                |
    | <-------------------------------------------------------- |
    |                                                           |
    |   .  0(0) ack 1 <MP_CAPABLE>                              |
    | --------------------------------------------------------> |
    |                                                           |

                      Figure 18: Cookie Request

Once that's done, the received cookie can be used for TFO, as shown in Figure 19 ๐ŸŽ€. In this example, the initiator first sends 20 bytes in the SYN. The listener immediately replies with 100 bytes following the SYN-ACK, and then the initiator replies with 20 more bytes. Note that the last segment in the figure has a TCP sequence number of 21, while the DSS subflow sequence number is 1 (because the TFO data is not part of the data sequence number space, as explained in Appendix B.2.

initiator                                                    listener
    |                                                           |
    |    S  0(20) <MP_CAPABLE>, <TFO cookie>                    |
    | --------------------------------------------------------> |
    |                                                           |
    |    S. 0(0) ack 21 <MP_CAPABLE>                            |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  1(100) ack 21 <DSS ack=1 seq=1 ssn=1 dlen=100>      |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  21(0) ack 1 <MP_CAPABLE>                            |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  21(20) ack 101 <DSS ack=101 seq=1 ssn=1 dlen=20>    |
    | --------------------------------------------------------> |
    |                                                           |

                 Figure 19: The Listener Supports TFO

In Figure 20, the listener doesn't support TFO ๐Ÿ™ˆ. The initiator detects that no state is created in the listener (as no data is ACKed), so it now sends the MP_CAPABLE in the third packet, so the listener can build its MPTCP context at the end of the establishment. Now, the TFO data, when retransmitted, becomes part of the Data Sequence Mapping, because it's effectively sent (in fact re-sent) after the establishment ๐Ÿ’….

initiator                                                    listener
    |                                                           |
    |    S  0(20) <MP_CAPABLE>, <TFO cookie>                    |
    | --------------------------------------------------------> |
    |                                                           |
    |    S. 0(0) ack 1 <MP_CAPABLE>                             |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  1(0) ack 1 <MP_CAPABLE>                             |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  1(20) ack 1 <DSS ack=1 seq=1 ssn=1 dlen=20>         |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  0(0) ack 21 <DSS ack=21 seq=1 ssn=1 dlen=0>         |
    | <-------------------------------------------------------- |
    |                                                           |

            Figure 20: The Listener Does Not Support TFO

It's also possible that the listener only acknowledges part of the TFO data, as illustrated in Figure 21 ๐Ÿ“. The initiator will simply retransmit the missing data together with a DSS mapping.

initiator                                                    listener
    |                                                           |
    |    S  0(1000) <MP_CAPABLE>, <TFO cookie>                  |
    | --------------------------------------------------------> |
    |                                                           |
    |    S. 0(0) ack 501 <MP_CAPABLE>                           |
    | <-------------------------------------------------------- |
    |                                                           |
    |    .  501(0) ack 1 <MP_CAPABLE>                           |
    | --------------------------------------------------------> |
    |                                                           |
    |    .  501(500) ack 1 <DSS ack=1 seq=1 ssn=1 dlen=500>     |
    | --------------------------------------------------------> |
    |                                                           |

               Figure 21: Partial Data Acknowledgment

Appendix C. Control Blocks ๐Ÿง 

conceptually, an MPTCP connection can be represented as an MPTCP protocol control block (PCB). It holds several variables that track the progress and state of the MPTCP connection, plus a set of linked TCP control blocks that correspond to the subflows that have been established ๐Ÿฆ‹.

RFC 793 [RFC0793] specifies several state variables. Whenever possible, we reuse the same terminology as RFC 793 to describe the state variables that MPTCP maintains ๐Ÿ’œ.

C.1. MPTCP Control Block

the MPTCP control block contains the following variables per connection โœจ.

C.1.1. Authentication and Metadata

Local.Token (32 bits): This is the token chosen by the local host on this MPTCP connection. The token must be unique among all established MPTCP connections, and it's generated from the local key ๐Ÿ”‘.

Local.Key (64 bits): This is the key sent by the local host on this MPTCP connection.

Remote.Token (32 bits): This is the token chosen by the remote host on this MPTCP connection, generated from the remote key.

Remote.Key (64 bits): This is the key chosen by the remote host on this MPTCP connection.

MPTCP.Checksum (flag): This flag is set to true if at least one of the hosts has set the "A" bit in the MP_CAPABLE options exchanged during connection establishment; otherwise, it's set to false. If this flag is set, the checksum must be computed in all DSS options ๐Ÿ”ฅ.

C.1.2. Sending Side

SND.UNA (64 bits): This is the data sequence number of the next byte to be acknowledged, at the MPTCP connection level. This variable is updated upon reception of a DSS option containing a DATA_ACK ๐Ÿซถ.

SND.NXT (64 bits): This is the data sequence number of the next byte to be sent. SND.NXT is used to determine the value of the DSN in the DSS option ๐ŸŒˆ.

SND.WND (32 bits): This is the send window. It's 32 bits if the features in RFC 7323 are used, and 16 bits otherwise. MPTCP maintains the send window at the MPTCP connection level, and the same window is shared by all subflows. All subflows use the MPTCP connection-level SND.WND to compute the SEQ.WND value that is sent in each transmitted segment ๐Ÿ’–.

C.1.3. Receiving Side

okay so this is the receiving side of the MPTCP control block, and it's giving organized ๐Ÿ“ฆโœจ

  • RCV.NXT (64 bits): This is the data sequence number of the next byte that is expected on the MPTCP connection. It gets modified when in-order data shows up ๐Ÿ“ฅ. The value of RCV.NXT is used to specify the DATA_ACK that goes out in the DSS option on all subflows ๐Ÿซถ.
  • RCV.WND (32 bits): This is the connection-level receive window, which is the maximum of the RCV.WND on all the subflows ๐ŸŒธ. It's 32 bits if the features in RFC 7323 are used, and 16 bits otherwise ๐Ÿ’….

C.2. TCP Control Blocks

the MPTCP control block also keeps a list of the TCP control blocks that belong to the MPTCP connection ๐Ÿฆ‹

bestie, notice that the TCP control block on the TCP subflows does not have RCV.WND or SND.WND. ๐Ÿ™ˆ That's because those are tracked at the MPTCP connection level, not the subflow level. main character energy for the connection, lol ๐Ÿ‘‘

Inside each TCP control block, the following state variables are defined.

C.2.1. Sending Side

  • SND.UNA (32 bits): This is the sequence number of the next byte to be acknowledged on the subflow. It gets updated every time a TCP acknowledgment comes in on the subflow ๐Ÿ’œ.
  • SND.NXT (32 bits): This is the sequence number of the next byte to be sent on the subflow. SND.NXT is what sets the value of SEG.SEQ when the next segment goes out ๐Ÿš€.

C.2.2. Receiving Side

  • RCV.NXT (32 bits): This is the sequence number of the next byte that is expected on the subflow. It gets modified when in-order segments are received ๐Ÿ“ฌ. The value of RCV.NXT is copied into the SEG.ACK field of the next segments sent on the subflow ๐ŸŽ€.
  • RCV.WND (32 bits): This is the subflow-level receive window, and it's updated with the window field from the segments received on this subflow ๐Ÿ“. It's 32 bits if the features in RFC 7323 are used, and 16 bits otherwise ๐ŸŒˆ.

Appendix D. Finite State Machine โœจ

the diagram in Figure 22 shows the Finite State Machine for connection-level closure ๐Ÿงš it's a really good look at how the DATA_FIN connection-level signal (shown in the diagram as the DFIN flag on a DATA_ACK) (1) interacts with subflow-level FINs, and (2) allows break-before-make handover between subflows ๐Ÿ”ฅ

                                +---------+
                                | M_ESTAB |
                                +---------+
                       M_CLOSE    |     |    rcv DATA_FIN
                        -------   |     |    -------
   +---------+       snd DATA_FIN /       \ snd DATA_ACK[DFIN] +-------+
   |  M_FIN  |<-----------------           ------------------->|M_CLOSE|
   | WAIT-1  |---------------------------                      |  WAIT |
   +---------+               rcv DATA_FIN \                    +-------+
     | rcv DATA_ACK[DFIN]         ------- |                   M_CLOSE |
     | --------------        snd DATA_ACK |                   ------- |
     | CLOSE all subflows                 |              snd DATA_FIN |
     V                                    V                           V
   +-----------+              +-----------+                 +----------+
   |M_FINWAIT-2|              | M_CLOSING |                 |M_LAST-ACK|
   +-----------+              +-----------+                 +----------+
     |              rcv DATA_ACK[DFIN] |           rcv DATA_ACK[DFIN] |
     | rcv DATA_FIN     -------------- |               -------------- |
     |  -------     CLOSE all subflows |           CLOSE all subflows |
     | snd DATA_ACK[DFIN]              V            delete MPTCP PCB  V
     \                          +-----------+                 +--------+
       ------------------------>|M_TIME WAIT|---------------->|M_CLOSED|
                                +-----------+                 +--------+
                                           All subflows in CLOSED
                                               ------------
                                           delete MPTCP PCB

Figure 22: Finite State Machine for Connection Closure

Appendix E. Changes from RFC 6824 ๐Ÿฆ‹

this appendix is the tea โ˜• on the key technical changes between [RFC6824], which specifies MPTCP v0, and this document, which obsoletes [RFC6824] and specifies MPTCP v1. Note that this specification is not backward compatible with [RFC6824]. ๐Ÿ’€

  • This document incorporates lessons learned from the various implementations, deployments, and experiments gathered in the documents "Use Cases and Operational Experience with Multipath TCP" [RFC8041] and the IETF Journal article "Multipath TCP Deployments" [deployments]. ๐Ÿ“š
  • Connection initiation, through the exchange of the MP_CAPABLE MPTCP option, is different from [RFC6824]. The SYN no longer includes the initiator's key, to allow the MP_CAPABLE option on the SYN to be shorter in length and to avoid duplicating the sending of keying material. โœ‚๏ธ
  • This also ensures reliable delivery of the key on the MP_CAPABLE option by allowing its transmission to be combined with data and thus using TCP's built-in reliability mechanism. If the initiator does not immediately have data to send, the MP_CAPABLE option with the keys will be repeated on the first data packet. If the other end is the first to send, then the presence of the DSS option implicitly confirms the receipt of the MP_CAPABLE. ๐Ÿ”
  • In the Flags field of MP_CAPABLE, "C" is now assigned to mean that the sender of this option will not accept additional MPTCP subflows to the source address and port. This improves efficiency -- for example, in cases where the sender is behind a strict NAT. ๐Ÿ›ก๏ธ
  • In the Flags field of MP_CAPABLE, "H" now indicates the use of HMAC-SHA256 (rather than HMAC-SHA1). ๐Ÿ”
  • Connection initiation also defines the procedure for version negotiation, for implementations that support both v0 [RFC6824] and v1 (this document). ๐Ÿค
  • The HMAC-SHA256 (rather than HMAC-SHA1) algorithm is used, as it provides better security. It is used to generate the token in the MP_JOIN and ADD_ADDR messages and to set the IDSN. ๐Ÿ”’
  • A new subflow-level option exists to signal reasons for sending a RST on a subflow (MP_TCPRST (Section 3.6)); this can help an implementation decide whether to attempt later reconnection. ๐Ÿšจ
  • The MP_PRIO option (Section 3.3.8), which is used to signal a change of priority for a subflow, no longer includes the AddrID field. Its purpose was to allow the changed priority to be applied on a subflow other than the one it was sent on. However, it was determined that this could be used by a man-in-the-middle to divert all traffic onto its own path, and MP_PRIO does not include a token or other type of security mechanism. ๐Ÿ˜ค
  • The ADD_ADDR option (Section 3.4.1), which is used to inform the other host about another potential address, is different in several ways. It now includes an HMAC of the added address, for enhanced security. In addition, reliability for the ADD_ADDR option has been added: the IPVer field is replaced with a flag field, and one flag is assigned ("E") that is used as an "echo" so a host can indicate that it has received the option. ๐Ÿ“ฃ
  • This document describes an additional way of performing a Fast Close -- by sending an MP_FASTCLOSE option on a RST on all subflows. This allows the host to tear down the subflows and the connection immediately. โšก
  • IANA has reserved the MPTCP option subtype of value 0xf for Private Use (Section 7.2). This document doesn't define how to use that value. ๐Ÿคซ
  • This document adds a new appendix (Appendix B), which discusses the usage of both MPTCP options and TFO options on the same packet. ๐Ÿงš

Acknowledgments ๐Ÿซถ

The authors gratefully acknowledge significant input into this document from Sebastien Barre and Andrew McDonald. ๐Ÿ’–

The authors also wish to acknowledge reviews and contributions from Iljitsch van Beijnum, Lars Eggert, Marcelo Bagnulo, Robert Hancock, Pasi Sarolahti, Toby Moncaster, Philip Eardley, Sergio Lembo, Lawrence Conroy, Yoshifumi Nishida, Bob Briscoe, Stein Gjessing, Andrew McGregor, Georg Hampel, Anumita Biswas, Wes Eddy, Alexey Melnikov, Francis Dupont, Adrian Farrel, Barry Leiba, Robert Sparks, Sean Turner, Stephen Farrell, Martin Stiemerling, Gregory Detal, Fabien Duchene, Xavier de Foy, Rahul Jadhav, Klemens Schragel, Mirja Kรผhlewind, Sheng Jiang, Alissa Cooper, Ines Robles, Roman Danyliw, Adam Roach, Eric Vyncke, and Ben Kaduk. ๐ŸŒŸ

Authors' Addresses ๐Ÿ’Œ

Alan Ford
Pexip

Email: alan.ford@gmail.com
Costin Raiciu
University Politehnica of Bucharest
Splaiul Independentei 313
Bucharest
Romania

Email: costin.raiciu@cs.pub.ro
Mark Handley
University College London
Gower Street
London
WC1E 6BT
United Kingdom

Email: m.handley@cs.ucl.ac.uk
Olivier Bonaventure
Universitรฉ catholique de Louvain
Pl. Ste Barbe, 2
1348 Louvain-la-Neuve
Belgium

Email: olivier.bonaventure@uclouvain.be
Christoph Paasch
Apple, Inc.
Cupertino, CA
United States of America

Email: cpaasch@apple.com
๐Ÿ“ฆ where this page comes from