PARTS is a Go library implementing UMCC (Uncoupled Multipath Congestion Control), a multipath transport protocol built on top of the SCION Internet architecture. It establishes end-to-end connections across multiple network paths simultaneously and includes a Shared Bottleneck Detection (SBD) algorithm that detects when multiple paths are suffering from the same congestion point and reroutes traffic around it.
This is a research prototype developed at the NetSys Lab.
SCION is a next-generation Internet architecture that provides explicit, policy-based path control. Unlike the traditional Internet, SCION allows endpoints to enumerate multiple paths to the same destination and switch between them. Each path is described as a sequence of border router interfaces, identified as ISD-AS-Interface (e.g., 1-ff00:0:1-2). PARTS exploits this to send traffic across several paths at once.
You need a running SCION environment (local topology or connected to a testbed) to use this library.
Application
│
▼
Scheduler ──── selects which path(s) to use for each write
│
▼
Dataplane ──── encapsulates and transmits packets over SCION paths
│
▼
Underlay ──── raw SCION UDP socket (optimized posix impl)
│ ▲
│ └── Congestion Control (metrics collected per path)
▼
Control Plane ── aggregates per-path metrics, fires CongestionEvents
Data flows down; metrics flow up. The control plane runs two background loops:
RunCCLoop— handles per-path congestion control and retransmitsRunMetricLoop— receives metric packets from the remote peer and forwards them to the scheduler asCongestionEvents
A listening socket. Created with Listen(). Accepts incoming connections via AcceptConn(), which performs the handshake and path discovery.
A connection between two SCION endpoints. Each connection has:
- A Scheduler (pluggable — see below)
- A Dataplane for sending/receiving packets
- A ControlPlane for metrics and congestion signalling
- One or more Streams
A logical data stream within a connection — similar to a TCP stream or a QUIC stream. Data is written and read through streams.
A wrapper around a snet.Path that adds:
- A list of interface IDs in
ISD-AS-Interfaceformat (used by SBD) - A numeric
Idfor cheap per-packet path labelling - A
Sorterstring for deterministic ordering
All packets share a common header structure. Packet type is encoded in the Flags field.
| Type | Flags value | Key fields |
|---|---|---|
| Data | PARTS_MSG_DATA = 1 |
Flags, StreamId (8B), SequenceNo (8B), PathId (4B), PartId (8B), PartSize (4B), payload |
| ACK | PARTS_MSG_ACK = 3 |
Flags, StreamId, PathId, LastAckedNo (8B), NumNacks (4B), Nack ranges |
| Metric | (control plane) | Flags, StreamId, PathId, Throughput (4B, bytes/s), PacketLoss (4B, %) |
| Handshake | PARTS_MSG_HS = 2 |
Gob-encoded address negotiation |
Packing and unpacking is handled by PartsPacketPacker in packets.go.
The scheduler is a plugin selected at runtime. All schedulers implement SchedulerPlugin:
type SchedulerPlugin interface {
ScheduleWrite(data []byte, stream *PartsStream, state *NetworkState) SchedulingDecision
OnCongestionEvent(event *CongestionEvent) error
}| Scheduler | File | Description |
|---|---|---|
SinglePath |
scheduler_singlepath.go | Default. Sends everything over one path. |
RoundRobin |
scheduler_roundrobin.go | Distributes packets across paths in rotation. |
ECMP |
scheduler_ecmp.go | Equal-cost multipath distribution. |
SBD |
scheduler_sbd.go | Multipath with shared bottleneck detection (see below). |
To activate a scheduler, call conn.scheduler.ActivatePlugin(NewSchedulerSbd()) after creating the connection.
Configured via the SCHEDULER_STRATEGY environment variable:
| Value | Behaviour |
|---|---|
shortest |
Fewest hops — homogeneous paths, higher shared-bottleneck risk |
random |
Random selection — diverse paths, lower shared-bottleneck risk |
least_disjoint |
Maximum path overlap — higher shared-bottleneck risk |
most_disjoint |
Maximum path diversity — best shared-bottleneck avoidance |
The SBD algorithm detects when congestion on multiple paths is caused by the same network link and reroutes away from it.
- Metric collection — The receiver sends periodic metric packets reporting per-path throughput and packet loss back to the sender.
- Similarity check —
OnCongestionEventbuffers the last 8 events and compares all pairs from different paths. Two paths are considered to share a bottleneck if:- Their packet loss rates are both ≥ 5% and within ±15 percentage points of each other, or
- Their throughput values are within 20% of each other (relative difference).
- Interface intersection — For all affected paths, the algorithm builds the intersection of their border-router interface IDs. This set represents candidate shared-bottleneck links.
- Refinement — Any interface that also appears on a healthy (unaffected) path is removed — it cannot be the shared bottleneck.
- Rerouting — Paths that traverse the identified bottleneck interfaces are excluded; the path selection strategy picks replacements from the remaining paths.
- Repeat — The loop runs continuously, re-evaluating on every new congestion event.
Each border router interface is identified as ISD-AS-Interface, for example 1-ff00:0:110-42. This format guarantees global uniqueness across the SCION topology. Interface ID strings are built in paths.go:74 (InterfacesToIds).
Tuning constants (in scheduler_sbd.go)
| Constant | Default | Meaning |
|---|---|---|
lossEpsilon |
15 |
Max absolute difference in loss % for two paths to be "similar" |
minLossToCompare |
5 |
Minimum loss % before a path is considered degraded |
throughputPct |
20 |
Max relative throughput difference (%) for two paths to be "similar" |
import "github.com/netsys-lab/parts"
// --- Server side ---
socket, err := parts.Listen("1-ff00:0:1,[127.0.0.1]:8080")
conn, err := socket.AcceptConn()
stream, err := conn.AcceptStream()
buf := make([]byte, 4096)
n, err := stream.Read(buf)
// --- Client side ---
conn, err := parts.Dial(
"1-ff00:0:1,[127.0.0.1]:9000", // local SCION address
"1-ff00:0:1,[127.0.0.1]:8080", // remote SCION address
)
stream, err := conn.OpenStream()
_, err = stream.Write([]byte("hello"))Addresses follow SCION format: ISD-AS,[IP]:port.
| Variable | Description |
|---|---|
NUM_PATHS |
Number of paths the SBD scheduler will use simultaneously |
SCHEDULER_STRATEGY |
Path selection strategy: shortest, random, least_disjoint, most_disjoint |
NO_SBD |
Set to any non-empty value to disable the SBD algorithm (metrics still collected) |
BOTTLENECK |
Label written to the metric log — useful for experiment tagging |
Requires Go 1.22+. The scion-optimized-connection dependency is a local replace directive — clone it as a sibling directory first:
git clone https://github.com/netsys-lab/scion-optimized-connection ../scion-optimized-connection
go build ./...Structured logging is available via the Log global (defined in logging.go). Per-path metrics and SBD results are appended to /opt/sbd.log in CSV-like format by the control plane and the SBD scheduler.
| File | Purpose |
|---|---|
| lib.go | Public API: Listen and Dial |
| socket.go | PartsSocket — connection setup and handshake |
| conn.go | PartsConn — wires together dataplane, control plane, scheduler |
| stream.go | PartsStream — read/write interface for application data |
| dataplane.go | Packet transmission and reception |
| controlplane.go | Metric collection and congestion event dispatch |
| scheduler.go | Scheduler interface and plugin registry |
| scheduler_sbd.go | Shared bottleneck detection scheduler |
| paths.go | SCION path querying and interface ID generation |
| packets.go | Packet serialisation / deserialisation |
| underlay.go | SCION socket abstraction |
| state.go | Global NetworkState (known remotes, active paths) |
- Stream reliability / retransmission is partially implemented
- Congestion control integration is in progress
- MTU discovery is not yet dynamic (hardcoded to 1472)
- Crypto / authentication layer not yet present
- Read/WriteLarge helpers not yet implemented
See TODOS.md for the full list.