Introduction

Every time you open a webpage your phone does something quietly heroic: it picks one network path — Wi-Fi or cellular — and pins your entire connection to it. If that path degrades or vanishes, the connection stalls, times out, and you start over.

Multipath TCP (MPTCP) rethinks that bargain. A single TCP connection is split into several subflows, each travelling a different physical path — say, Wi-Fi on one network interface and LTE on another. The two streams carry different pieces of the same data and are reassembled at the far end, invisibly to the application.

The gain is twofold. First, throughput: if each path delivers BB bytes per second, two paths can deliver up to 2B2B. Second, resilience: when a path fails, the surviving subflows absorb the load without the application ever knowing a link went down. There is no reconnect, no dropped session, no lost state — just a momentary dip in throughput that heals itself.

MPTCP is standardised as RFC 6824 (2013) and updated in RFC 8684 (2020). Apple has used it since iOS 7 for Siri, and since iOS 16 it is available to all apps. Linux added MPTCP support in kernel 5.6 (2020).

Try It: Drop a Path

The simulation below models two subflows — Wi-Fi and Cellular — carrying packets for a single MPTCP connection. Each subflow has its own bandwidth. The receiver reassembles packets in sequence-number order.

<!-- {{c_html_intro}} -->
<div class="panel">
  <div class="path-row">
    <div class="path-card" id="card-wifi">
      <div class="path-label">{{label_wifi}}</div>
      <div class="bw-bar-wrap"><div class="bw-bar" id="bar-wifi"></div></div>
      <div class="path-bw" id="bw-wifi"></div>
      <div class="path-status" id="status-wifi">{{state_active}}</div>
      <button id="btn-wifi" type="button">{{btn_drop_wifi}}</button>
    </div>
    <div class="path-card" id="card-cell">
      <div class="path-label">{{label_cell}}</div>
      <div class="bw-bar-wrap"><div class="bw-bar" id="bar-cell"></div></div>
      <div class="path-bw" id="bw-cell"></div>
      <div class="path-status" id="status-cell">{{state_active}}</div>
      <button id="btn-cell" type="button">{{btn_drop_cell}}</button>
    </div>
  </div>
  <div class="total-row">
    <span class="total-label">{{label_total_bw}}</span>
    <span class="total-val" id="total-bw">0 Mbps</span>
  </div>
  <div class="stream-box">
    <div class="stream-label">{{label_byte_stream}}</div>
    <div class="stream-track" id="stream-track"></div>
    <div class="seq-row">
      <span class="seq-label">{{label_seq}}</span>
      <span class="seq-val" id="seq-val">0</span>
    </div>
  </div>
  <div class="log-box" id="log-box"></div>
</div>
/* {{c_css_intro}} */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; color: #222; background: #f4f6f8; }
.panel { padding: .8rem; display: flex; flex-direction: column; gap: .7rem; }
.path-row { display: flex; gap: .6rem; }
.path-card { flex: 1; background: #fff; border: 1px solid #dde3ea; border-radius: 10px;
             padding: .65rem .75rem; display: flex; flex-direction: column; gap: .3rem; }
.path-card.down { background: #fff5f5; border-color: #f8c8c8; }
.path-label { font-weight: 700; font-size: .95rem; }
.bw-bar-wrap { background: #e8eef3; border-radius: 4px; height: 6px; overflow: hidden; }
.bw-bar { height: 100%; border-radius: 4px; background: #2a7de1; transition: width .3s; }
.path-bw { font-size: .8rem; color: #555; }
.path-status { font-size: .8rem; font-weight: 600; }
.path-status.active { color: #0a7d33; }
.path-status.down { color: #c92f3c; }
button { font: 600 13px system-ui; padding: .35rem .7rem; border-radius: 7px; cursor: pointer;
         border: 1px solid #2a7de1; background: #2a7de1; color: #fff; margin-top: .1rem; }
button.restore { background: #0a7d33; border-color: #0a7d33; }
.total-row { display: flex; align-items: center; gap: .5rem; background: #fff;
             border: 1px solid #dde3ea; border-radius: 8px; padding: .45rem .75rem; }
.total-label { font-size: .85rem; color: #555; }
.total-val { font-weight: 700; font-size: 1rem; color: #1d3557; }
.stream-box { background: #fff; border: 1px solid #dde3ea; border-radius: 10px; padding: .65rem .75rem; }
.stream-label { font-size: .8rem; color: #666; margin-bottom: .3rem; }
.stream-track { display: flex; gap: 3px; flex-wrap: nowrap; overflow: hidden; height: 26px; align-items: center; }
.pkt { width: 18px; height: 18px; border-radius: 3px; flex-shrink: 0; font-size: 9px;
       display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; }
.pkt.wifi { background: #2a7de1; }
.pkt.cell { background: #e07b00; }
.seq-row { display: flex; align-items: center; gap: .4rem; margin-top: .4rem; }
.seq-label { font-size: .8rem; color: #666; }
.seq-val { font-weight: 700; font-size: .95rem; color: #1d3557; }
.log-box { background: #fff; border: 1px solid #dde3ea; border-radius: 8px;
           padding: .5rem .7rem; font-size: .78rem; color: #444; max-height: 80px;
           overflow-y: auto; line-height: 1.5; }
.log-entry { }
.log-entry.ev { color: #c92f3c; font-weight: 600; }
// Code not found

Click Drop Wi-Fi or Drop Cellular to simulate a link failure. The surviving path absorbs all traffic with no interruption to the byte stream. Click Restore to bring the path back. Notice that total throughput equals the sum of active-path bandwidths, and that the sequence number counter never resets — the connection stays alive.

How It Really Works

MPTCP looks simple from the outside, but several hard problems lurk beneath the surface.

The sequence-number problem. Standard TCP uses a single sequence number to guarantee ordered, reliable delivery. MPTCP runs kk independent TCP subflows, each with its own per-subflow sequence space. A second, data-level sequence number is stamped on every segment, so the receiver can reorder bytes from different paths into a single coherent stream. Two sequence spaces — one per subflow, one global — must stay consistent at all times.

Head-of-line blocking across paths. Paths have different latencies. A packet sent on a slow path may arrive after packets that were sent later on a fast path. MPTCP must buffer out-of-order arrivals and deliver bytes to the application only when all earlier sequence numbers are accounted for. The buffer can grow large if path latencies diverge.

Coupled congestion control. Naive MPTCP would be unfair: it would grab kk times the bandwidth of a single-path flow sharing the same bottleneck. RFC 6356 defines Coupled Congestion Control — the congestion windows of the subflows are linked so that the aggregate MPTCP flow is no more aggressive than a single TCP flow would be on the best available path. This is a solved problem, but it requires careful tuning when paths have very different round-trip times.

Path management. Subflows are opened and closed dynamically using TCP options added during the handshake. A middlebox (firewall, NAT) that strips unknown TCP options can silently break MPTCP, causing a fallback to regular TCP — a graceful degradation built into the spec.

The result is a protocol that is transparent to applications, backward-compatible with the internet, and genuinely resilient — at the cost of non-trivial state in the kernel.

Where It Matters

Multipath TCP is quietly reshaping several corners of networking:

  • Mobile devices: Apple routes Siri requests over both Wi-Fi and cellular simultaneously so voice latency does not spike when one link is congested. Since iOS 16, any app can opt in. Android support is growing via user-space implementations.
  • Data centres: servers with multiple network interface cards bond them into a single high-bandwidth stream without complex link-aggregation protocols; MPTCP handles the split automatically.
  • Satellite + terrestrial backup: a router can keep a primary fibre path and a satellite backup as two subflows. Traffic silently shifts to satellite when the fibre cut happens — no BGP failover delay, no session teardown.
  • Wi-Fi to cellular handoff: walking out of a building while on a video call, MPTCP migrates the stream to cellular before Wi-Fi drops; the call never freezes.
  • Research and measurement: because MPTCP exposes path-level statistics (per-subflow RTT, loss, throughput), it is a natural tool for studying path diversity in the real internet.

These applications all share the same core promise: treat the network as a set of interchangeable paths rather than a single sacred pipe. That idea connects naturally to broader topics in distributed systems and the robustness of the internet's routing layer.

Conclusion

Classical TCP was designed when machines had one network interface and paths were assumed stable. Multipath TCP acknowledges the world as it is: mobile devices switch between Wi-Fi and cellular, servers have multiple NICs, and links fail without warning.

By splitting one logical connection across several physical paths, MPTCP delivers both higher throughput when all paths are healthy and zero-interruption failover when one drops. The application sees neither the extra bandwidth juggling nor the path death — just a steady byte stream.

The engineering beneath that simplicity — dual sequence spaces, coupled congestion control, dynamic path management — is the kind of quiet complexity that makes the internet reliable without anyone noticing. Next time your phone switches from Wi-Fi to cellular mid-call without a hiccup, there is a good chance Multipath TCP is why.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/multipath-tcp/Content licensed under CC BY-NC 4.0.