13 KiB
MPTCP — User Guide
luci-app-mptcp is the LuCI front end for the kernel's Multipath TCP stack
— the layer that actually bonds your WAN links together. It has seven pages
under Network → MPTCP: MPTCP (settings), Diagnostics,
DSCP / Weight Routing, Bandwidth, Established connections,
MPTCP Fullmesh, and MPTCP monitoring.
Screenshots below were taken on a test router (v0.64-snapshot, kernel
6.18, 3 WAN links). Peer/remote IPs that would reveal the test VPS were
replaced with a placeholder (203.0.113.10) before capture — everything
else is unmodified live output.
MPTCP (settings)
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp
The form's contents adapt to your kernel version — the screenshot is from
a 6.18 kernel, which exposes the modern in-kernel/userspace path-manager
options; older kernels (<6) show a different, longer set of legacy fields
(scheduler-specific tuning like ndiffports/round-robin subflow counts,
mptcp_version, etc.) instead.
GLOBALS
| Field | Meaning |
|---|---|
| Multipath TCP | Read-only indicator of whether MPTCP is enabled network-wide. |
| Multipath TCP checksum | Enables MPTCP-level checksums (extra integrity check, minor overhead). |
| Multipath TCP path-manager | default or fullmesh (OMR's normal choice — creates subflows between every local/remote address pair). |
| Multipath TCP scheduler | Which subflow the kernel picks to send data on. default, or a BPF scheduler (bpf_burst, bpf_red, bpf_first, bpf_rr, plus any custom .o dropped into /usr/share/bpf/scheduler — auto-discovered and listed here). On kernels <6, classic in-tree schedulers (round-robin, redundant, BLEST, ECF) are offered instead. |
| Congestion Control | Populated live from sysctl net.ipv4.tcp_available_congestion_control — pick any congestion control algorithm your kernel has compiled in. Default is cubic; this bench uses bbr. |
| Path Manager type (kernel ≥6 only) | In-kernel (simpler, default) vs. userspace (delegates subflow decisions to mptcpd). |
| Max subflows | Cap on additional subflows per MPTCP connection — usually matches (or exceeds) your WAN count. |
| Retranmission intervals | How many MPTCP-level retransmit intervals with no traffic/no ack before a subflow is declared stale. Lower = faster active-backup switchover; higher = better utilization on lossy/high-BER links. |
| Max add address | Cap on ADD_ADDR suboptions accepted per connection. |
| Control message timeout | Resend delay for an unacknowledged ADD_ADDR message. |
| Blackhole timeout (kernel ≥6.18) | Initial cooldown before re-enabling MPTCP on a socket after a middlebox appears to be blackholing it; grows exponentially on repeat failures. |
| Close timeout (kernel ≥6.18) | Make-after-break grace period: how long a socket holds state after its last subflow drops, before moving to TCP_CLOSE. |
| Control message timeout (second one, kernel ≥6.18) | SYN+MP_CAPABLE retransmit count before falling back to plain TCP. |
When the userspace path manager is selected, extra fields appear
(mptcpd enable/disable, its plugin and path-manager .so lists —
auto-discovered from /usr/lib/mptcpd — and address announcement/
notification flags); not shown here since this bench uses the in-kernel
manager.
Interfaces Settings
One block per real network interface (LAN + every WAN — VPN-internal
interfaces like omrvpn/omr6in4 are filtered out here since they aren't
independent WAN paths):
- Multipath TCP —
disabled,enabled,master, orbackup. Exactly one interface must bemaster(the interface MPTCP's initial subflow uses); the rest areenabled(orbackup, only cut in when masters/enabled links fail). - Weight — only used when a
*weightscheduler/path-manager is active; >100 makes a link more attractive, <100 less, up to 256.
Diagnostics
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_diagnostics
Answers why isn't MPTCP aggregating my WANs correctly? without requiring
you to read raw nstat/ip mptcp output by hand. Combines the kernel's
MPTCP MIB counters, the live endpoint/subflow state, and a per-WAN sanity
check into a plain-English, color-coded issue list (fallback to plain TCP,
blackholed/stale subflows, resets, checksum errors, JOIN handshake
failures, dropped ADD_ADDR/RM_ADDR signalling), refreshed every 15 seconds.
On this bench, the issue list actually fired: an orange banner flagging "2644 MPTCP-level reset/fastclose event(s) sent by this router (~119/hour since boot)" — with its own plain-English caveat underneath explaining that a high count sent by the router itself (as opposed to received) is usually just abortive closes from its own health-check/keepalive probes opening a connection, sending one request, then closing it, and is only worth escalating if paired with rising Blackhole/SubflowStale counters — which is exactly the kind of self-diagnosis this page exists to save you from having to work out by hand.
A Per-WAN MPTCP endpoint status table cross-checks every
multipath-enabled interface against ip mptcp endpoint show: a WAN can
look perfectly healthy in OpenMPTCProuter's Status page (IP, gateway, ping
all fine) and still never carry a subflow if its endpoint is missing —
this is usually the fastest way to catch that.
A Live MPTCP subflows table goes one level deeper than that: a live
read of every established subflow (preferring omr-sockdiag, a small tool
that queries the kernel directly over NETLINK_SOCK_DIAG — the same
INET_DIAG protocol ss uses internally — and falling back to parsing
ss -tin text if that tool isn't installed), one row per actual established
subflow, attributed to its WAN by matching the connection's local address
against the endpoint list above. Each row shows the local/remote
address:port, whether the kernel is
currently using it as an active or backup path, congestion window, RTT
(smoothed/variance), retransmissions (current/total), and pacing/delivery
rate — the same live TCP-level numbers ss -i would show for that
connection, without needing to run it by hand over SSH. A WAN with a
correctly-registered endpoint but no row here means the path manager
just hasn't opened a subflow on it yet (or it's currently down/unreachable),
which the endpoint table alone can't tell you.
A Kernel MPTCP
configuration block shows the live scheduler/path-manager/checksum/
timeout values straight from /proc/sys/net/mptcp/*, and a sortable
counters table plus collapsible raw multipath -k/-f/-c sections are
available for anyone who wants the unprocessed data (the raw -k/-f/-c
sections mirror MPTCP monitoring's kernel-info line, MPTCP
Fullmesh, and Established connections below).
The counters table is genuinely cumulative since boot, unlike MPTCP
monitoring's raw multipath -m output below: that wraps nstat -z,
which (without -a) reports the delta since the previous nstat call by
anyone on the router — confirmed live, two calls a couple seconds apart
came back ~0. A page auto-polling every 15s through that would show
"since last poll" numbers while looking like a lifetime total, and would
race with anything else on the box that also calls nstat. Diagnostics
reads the kernel's own procfs SNMP-style counters directly instead
(/proc/net/netstat's MPTcpExt: block, or /proc/net/mptcp_net/snmp on
older kernels), which has no such history-file/shared-state gotcha.
Backed by a new method on the same luci.mptcp rpcd object as the other
pages: ubus call luci.mptcp diagnose '{}' (no arguments).
DSCP / Weight Routing
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_dscp_routing
A per-WAN pin/weight table shared between two independent consumers —
MPTCP's bpf_dscp/bpf_weight* schedulers (see the MPTCP settings page
above) and mqvpn's equivalent scheduler modes — so both can be
configured from one place instead of duplicating the same WAN list twice.
This page only edits the pins/weights themselves; which scheduler is
actually active is still chosen on the MPTCP or MQVPN settings pages.
Its content is entirely conditional: it shows the DSCP class pins
table only if MPTCP's scheduler is bpf_dscp or mqvpn is enabled at
all (mqvpn pushes its DSCP mask unconditionally whenever it's on, unlike
weight below), and the WAN weight section only if MPTCP's scheduler
is one of the bpf_weight/bpf_weight_rr/bpf_burstweight family or
mqvpn's scheduler is wrtt/wrr. With neither condition met, the page
just says so and renders nothing else.
On this bench, MPTCP's scheduler is blest (neither DSCP nor weight) but
mqvpn is enabled, so only the DSCP half shows:
| Section | Fields |
|---|---|
| Gateway sync | Mirror DSCP/weight pins to gateway — also push each pin/weight to the VPS so it holds for download traffic too (the gateway's own send direction), not just what the router sends. Disabling it only stops future syncs; pins already pushed stay pushed. Its saved value (not the live checkbox) decides whether the Download column below even renders — toggling it needs Save & Apply and a reload before that column appears/disappears. |
| DSCP class pins | One row per DSCP class → Upload interface (router → internet: pins the class to a WAN for bpf_dscp's local map, and for mqvpn announces it on that path's DSCP mask via the control API) and, only while Gateway sync is on, Download interface (gateway → router; bpf_dscp-only, pins the VPS's own send-side choice — leave empty to just mirror the Upload interface instead). mqvpn has no separate download pin at all — its downlink always mirrors upload. |
| WAN weight (not shown here — no weight scheduler active) | One row per multipath-enabled WAN, a single Weight value (default 100, max 256) used by both upload and download, for both MPTCP's *weight schedulers and mqvpn's Weighted RTT/Weighted Round Robin. |
Bandwidth
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/bandwidth
Live, polled-every-second SVG traffic graphs. A tab per multipath-enabled interface, plus an All interfaces overview.
The combined view overlays every WAN's inbound (and, separately, outbound) throughput plus a bold total line, with a live current/average/peak table underneath per interface and for the total.
Selecting a WAN tab (e.g. wan1) narrows this to that link alone —
inbound and outbound charts with their own current/average/peak stats,
useful for judging one link's real-world throughput independent of the
others.
Established connections
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_connections
Raw, auto-refreshing (every 10s) output of multipath -c — every ESTAB
MPTCP connection with local and peer address:port. Useful for confirming
traffic is actually using multiple subflows/paths, or for spotting
connections stuck on a single WAN.
MPTCP Fullmesh
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_fullmesh
Raw output of multipath -f: the fullmesh path-manager's current subflow
map — one line per local address, its subflow ID, and which device it
rides on. This is the direct evidence that the fullmesh path-manager
picked up every WAN address correctly; a WAN missing from this list isn't
being used as an MPTCP subflow.
MPTCP monitoring
https://<router-ip>/cgi-bin/luci/admin/network/mptcp/mptcp_monitor
Raw output of multipath -m: the kernel's full MPTcpExt* counter dump
(from nstat/netstat -s-style MIB counters) — join attempts/failures,
retransmissions, checksum errors, address add/remove events, fallbacks to
plain TCP, etc. All zero on a healthy, idle test link; non-zero values in
categories like MPTcpExtMPCapableFallbackSYNACK or MPTcpExtMPFailTx
are what you'd check first when MPTCP negotiation is suspected to be
failing (e.g. a middlebox stripping MPTCP TCP options).
Not reachable from the menu: MPTCP Support Check
The package also ships mptcp/mptcp_check.js — a page that runs a
tracebox-based trace toward the configured VPS over a chosen interface,
to show whether MPTCP options survive the path (look for
TCPOptionMPTCPCapable [...] Sender's Key in the output: present means
supported, a leading - means something on the path is stripping it).
It calls the same luci.mptcp rpcd backend as the other pages
(mptcp_check_trace, wrapping tracebox -s omr-mptcp-trace.lua).
However, luci-app-mptcp.json has no menu entry for it, and LuCI's
dispatcher falls back to the MPTCP settings page for its URL
(admin/network/mptcp/mptcp_check) rather than routing to it — so as
shipped, this page isn't actually reachable through the web UI. Worth
knowing if you're debugging why MPTCP isn't negotiating: the check logic
exists and works via the same rpcd method, it just currently has no way in
from the menu.







