This document explains the SRTLA protocol, architecture, and internal algorithms.
For quick-start usage, command reference, and build instructions, see the README.
- Overview
- Architecture
- Protocol
- Extended Keepalive
- Congestion Control
- Connection Recovery
- Observability
- Timeouts & Limits
SRTLA (SRT Link Aggregation) is a protocol that sits between an SRT encoder and SRT server, bonding multiple network connections to increase bandwidth and reliability.
This implementation is a fork of BELABOX/srtla with contributions from IRLToolkit, IRLServer, OpenIRL, and CeraLive. The irlserver/main upstream was merged in full (commit aa66a88), bringing upstream-aligned connection handling, ACK-throttling removal, and quality evaluation enhancements.
Key capabilities:
- Bandwidth aggregation: Combine multiple connections for higher total throughput
- Redundancy: If one link fails, others continue
- Adaptive load balancing: Better links get more traffic automatically
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Encoder │────▶│ srtla_send │═══════════════▶│ srtla_rec │────▶│ SRT Server │
│ (SRT) │ │ │ Multiple IPs │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ Sender Side Receiver Side │
│ │ │ │
│ ┌───────────────┼───────────────┐ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ │ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │usb0 │ │usb1 │ │wlan0│ │ │
│ │ LTE │ │ LTE │ │WiFi │ │ │
│ └─────┘ └─────┘ └─────┘ │ │
│ │ │ │ │ │
│ └─────────────┼─────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ Internet (multiple paths) │ │
│ │ │ │
│ └───────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────┘
End-to-end SRT connection
The sender:
- Listens on a local port for SRT connections from the encoder
- Creates one UDP socket per source IP (each bound to a different network)
- Distributes outgoing SRT packets across all links
- Tracks packet delivery via SRTLA ACKs and SRT NAKs
- Adjusts which links get more traffic based on performance
The receiver:
- Listens for incoming SRTLA connections
- Groups connections from the same sender (via registration handshake)
- Receives packets across all connections via
recvmmsgbatches and forwards to downstream SRT server - Broadcasts SRT ACK/NAK control packets to every connection in the group via
sendmmsg, so a single bad link cannot stall retransmits - Sends SRTLA ACKs to help sender with load distribution
- Pads small control packets to 32 bytes (
pad_sendto) to keep cellular NAT mappings warm
When the sender starts, it must register all its links with the receiver:
Sender Receiver
│ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Link 1 (usb0) │ │
│──┼── REG1 [client_id] ──────────┼────▶│ "I want to start a session"
│ │ │ │
│◀─┼── REG2 [full_id] ────────────┼─────│ "OK, here's the full session ID"
│ │ │ │
│──┼── REG2 [full_id] ────────────┼────▶│ Link 1 joins the session
│◀─┼── REG3 ──────────────────────┼─────│ "Link 1 confirmed"
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Link 2 (usb1) │ │
│──┼── REG2 [full_id] ────────────┼────▶│ Link 2 joins the session
│◀─┼── REG3 ──────────────────────┼─────│ "Link 2 confirmed"
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ │
│ │ Link 3 (wlan0) │ │
│──┼── REG2 [full_id] ────────────┼────▶│ Link 3 joins the session
│◀─┼── REG3 ──────────────────────┼─────│ "Link 3 confirmed"
│ └──────────────────────────────┘ │
│ │
│═══════ SRT Data (distributed) ═══════▶│
│◀══════ SRT Data + SRTLA ACKs ════════│
│ │
| Type | Value | Length | Description |
|---|---|---|---|
SRTLA_TYPE_KEEPALIVE |
0x9000 | 2 bytes | NAT keepalive, echoed back by receiver |
SRTLA_TYPE_ACK |
0x9100 | variable | Batch ACK for congestion control |
SRTLA_TYPE_REG1 |
0x9200 | 258 bytes | Initial group registration (client_id) |
SRTLA_TYPE_REG2 |
0x9201 | 258 bytes | Registration response/confirmation (full_id) |
SRTLA_TYPE_REG3 |
0x9202 | 2 bytes | Connection established |
SRTLA_TYPE_REG_ERR |
0x9210 | 2 bytes | Registration error |
SRTLA_TYPE_REG_NGP |
0x9211 | 2 bytes | No group found (triggers re-registration) |
The session ID is 256 bytes:
- First 128 bytes: Generated by sender (client_id)
- Last 128 bytes: Generated by receiver (server_id)
This ensures:
- Sender can identify its own sessions
- Receiver can verify sessions it created
- Hard to spoof connections
Mobile networks use NAT with short timeouts. SRTLA sends keepalive packets every ~1 second of idle time:
Sender NAT Receiver
│ │ │
│ ─── KEEPALIVE ────────▶│ ─── KEEPALIVE ──────────▶│
│ │ │
│◀─── KEEPALIVE ─────────│◀─── KEEPALIVE ───────────│
│ │ │
│ (NAT mapping refreshed) │
CeraLive senders emit an extended keepalive (38-byte 0x9000 packet, magic 0xC01F at bytes 10-11, version 0x0001) that carries connection_info_t — per-link RTT, NAK count, congestion window, in-flight packets, and bitrate. The receiver detects this capability via sender_supports_extended_keepalives in src/receiver_config.h and uses the data to populate has_valid_sender_telemetry.
Stock senders (BELABOX, Moblin, upstream binaries) emit only the bare 2-byte keepalive. The receiver falls back gracefully: it still tracks per-connection quality from its own observations, just without the sender-side view. The ext-ka-probe helper in tests/compat/ sends a real extended keepalive so the receiver's telemetry path can be exercised in the compat harness without requiring our srtla_send.
For the full ecosystem picture — which implementations support extended keepalive, which don't, and what that means for interop — see COMPATIBILITY.md.
SRTLA uses a window-based algorithm on the sender to distribute packets across links.
Each link has a "window" that represents its capacity. The sender selects links using:
score = window / (in_flight_packets + 1)
The link with the highest score is selected for each packet.
| Event | Action |
|---|---|
| SRTLA ACK received | window += 1 (slow increase) |
| In-flight < window | window += 30 (faster increase) |
| SRT NAK received | window -= 100 (fast decrease) |
| Parameter | Value | Meaning |
|---|---|---|
WINDOW_MIN |
1,000 | Minimum window (link nearly disabled) |
WINDOW_DEF |
20,000 | Starting window for new links |
WINDOW_MAX |
60,000 | Maximum window (best performing link) |
A link with a large window and few in-flight packets scores highest and receives the most traffic. A link with a small window (due to packet loss) scores lower and gets fewer packets. This naturally:
- Sends more through fast/reliable links
- Avoids overloading slow/lossy links
- Recovers slowly after packet loss (prevents oscillation)
SRTLA ACKs are sent unconditionally every RECV_ACK_INT (10) packets. Earlier builds delayed ACKs as a back-pressure mechanism, but that created a feedback loop with senders that tie SRT window growth to ACK timing: throttled ACKs slowed window growth, the link looked worse, more throttling kicked in, and audio glitches followed. The irlserver upstream removed this throttling; CeraLive aligned with that decision at the merge.
Connections with temporary problems are not disabled outright. The receiver tracks per-connection quality and applies a graduated response:
The receiver assigns each connection a weight level that influences how much traffic it receives. Levels range from WEIGHT_FULL (optimal) down through WEIGHT_EXCELLENT, WEIGHT_DEGRADED, WEIGHT_FAIR, WEIGHT_POOR, and WEIGHT_CRITICAL (critically impaired). See src/receiver_config.h for the exact weight values.
Quality is assessed by bandwidth performance, packet loss, and dynamic bandwidth evaluation against median/minimum thresholds. New connections have a grace period (CONNECTION_GRACE_PERIOD) before penalties apply.
Connections showing signs of recovery enter a recovery mode and receive more frequent keepalive packets for RECOVERY_CHANCE_PERIOD. After successful recovery they are fully reactivated; if recovery fails within that window, the connection is abandoned.
srtla_rec emits structured lifecycle events to its log at key points in a group's lifetime. These are machine-parseable lines intended for operator tracing and automated monitoring.
| Event | When emitted |
|---|---|
group_registered |
A new group is created after a successful REG1/REG2/REG3 handshake |
conn_added |
A connection joins an existing group (additional link registered) |
conn_removed reason=… |
A connection is removed from a group (timeout, explicit teardown, or error) |
group_reaped reason=… |
A group is destroyed after GROUP_TIMEOUT idle or all connections gone |
quality_path=… |
Per-connection quality level transition (e.g. WEIGHT_FULL → WEIGHT_DEGRADED) |
See docs/TROUBLESHOOTING.md → Structured Lifecycle Events for log format details and example output.
srtla_send can publish per-uplink telemetry to a JSON stats file via --stats-file <path>. The file is rewritten atomically every 1000 ms. See ADR-001 for the full schema and transport rationale.
| Parameter | Description |
|---|---|
CONN_TIMEOUT |
Link considered dead if no response |
REG2_TIMEOUT |
Wait for REG2 after sending REG1 |
REG3_TIMEOUT |
Wait for REG3 after sending REG2 |
GLOBAL_TIMEOUT |
Exit if no links connect within this window |
IDLE_TIME |
Send keepalive after this idle time |
HOUSEKEEPING_INT |
Check connection health interval |
Post-merge timeouts are tuned for cellular resilience. See src/receiver_config.h for current values.
| Parameter | Description |
|---|---|
MAX_CONNS_PER_GROUP |
Maximum links per streaming session |
MAX_GROUPS |
Maximum concurrent streaming sessions |
CONN_TIMEOUT |
Per-connection inactivity timeout (cellular-resilient) |
GROUP_TIMEOUT |
Idle group reap timeout (cellular-resilient; longer than pre-merge) |
RECV_ACK_INT |
Send SRTLA ACK every N data packets |
KEEPALIVE_PERIOD |
Keepalive interval during connection recovery |
RECOVERY_CHANCE_PERIOD |
Window for a connection to recover before abandonment |
CONN_QUALITY_EVAL_PERIOD |
Interval for evaluating per-connection quality |
CONNECTION_GRACE_PERIOD |
Grace period before new connections accumulate penalties |
| Buffer | Why |
|---|---|
SEND_BUF_SIZE |
Handle bursts without dropping |
RECV_BUF_SIZE |
Buffer incoming packets during processing |