Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
de134ed
style: apply cargo fmt formatting
datagutt Mar 15, 2026
a8d8a37
feat: gate window recovery on RTT velocity
datagutt Mar 15, 2026
57525c7
feat: add velocity penalty to EDPF predicted arrival
datagutt Mar 15, 2026
d53d8bc
feat: add BDP hard-cap to EDPF scheduler
datagutt Mar 15, 2026
ba34eb7
feat: add link lifecycle state machine
datagutt Mar 15, 2026
870b4e0
feat: add AsymmetricEwma for fast-down/slow-up smoothing
datagutt Mar 15, 2026
14a0398
feat: add TOML config with --config CLI arg
datagutt Mar 15, 2026
d744c4a
chore: update Cargo.lock for toml dependency
datagutt Mar 15, 2026
b419ef6
feat: add heuristic keyframe detection for priority scheduling
datagutt Mar 15, 2026
cfa41cb
feat: add shared bottleneck detection across links (RFC 8382)
datagutt Mar 15, 2026
e64e0b5
refactor: replace #[allow(dead_code)] with #[cfg(test)] for test-only…
datagutt Mar 15, 2026
20d081c
ci: add 'feat/improvements' branch to build triggers
datagutt Mar 22, 2026
4fc707f
feat(config): add mark-critical hint command for encoder-driven priority
datagutt Apr 20, 2026
809423e
feat(control)!: replace text control protocol with JSON-RPC 2.0
datagutt Apr 20, 2026
15052b1
feat(priority)!: replace mark_critical RPC with UDP sidecar windows
datagutt Apr 20, 2026
9e3b4ff
feat(metrics): add Prometheus /metrics endpoint
datagutt Apr 20, 2026
03ed91f
feat(control)!: add JSON-RPC subscriptions on the async Unix socket
datagutt Apr 20, 2026
548ef85
refactor(srtla_send): cull unproven selection modes
datagutt May 4, 2026
dd83f55
feat(srtla_send): weak-link classifier in shadow mode
datagutt May 4, 2026
19a73b7
feat(srtla_send): per-link CC soft-cap in shadow mode
datagutt May 4, 2026
bcfd0f8
feat(srtla_send): gate weak + cc-backing-off links in enhanced mode
datagutt May 5, 2026
6286e0f
feat(srtla_send): link_cc HAI + FastRecovery + Drain
datagutt May 5, 2026
1def164
feat(srtla_send): adaptive batch-send regimes
datagutt May 5, 2026
feb2b95
feat(srtla_send): pluggable uplink binder for egress steering
datagutt May 27, 2026
d61d965
style(srtla_send): clear clippy across all targets
datagutt May 27, 2026
274c971
style(srtla_send): cargo fmt
datagutt May 27, 2026
a229f8f
docs(srtla_send): sync docs with current scheduling modes
datagutt Jun 1, 2026
0a03fef
feat(srtla_send): wire NAK deltas into link_cc loss path
datagutt Jun 1, 2026
79e727b
feat(srtla_send): cc_target_bps as soft cap on enhanced score
datagutt Jun 1, 2026
f35f395
feat(srtla_send): surface batch_regime in per-link stats
datagutt Jun 1, 2026
379affe
feat(srtla_send): in-flight cap soft admission gate
datagutt Jun 1, 2026
5add55a
style(srtla_send): drop unused BatchRegime re-export and clippy nits
datagutt Jun 2, 2026
35c4d3b
fix(srtla_send): demote lossy links continuously, never hard-kill
datagutt Jun 3, 2026
616047e
fix(srtla_send): keep quality-gated links rankable, not excluded
datagutt Jun 3, 2026
72eac3c
feat(srtla_send): jitter-immune queue-building detector
datagutt Jun 3, 2026
73a410e
fix(srtla_send): make the in-flight cap bandwidth-delay-relative
datagutt Jun 3, 2026
1bb18a0
fix(srtla_send): reject outlier throughput samples on the cc soft cap
datagutt Jun 3, 2026
3c5da0c
fix(srtla_send): window the cc rtt-min so handovers don't pin inflation
datagutt Jun 3, 2026
ad70b4c
test(srtla_send): add deterministic fake-clock seam for timing tests
datagutt Jun 14, 2026
f61e53e
fix(srtla_send): refuse zero-valid-ip sighup reload
datagutt Jun 14, 2026
f870bda
ci(srtla_send): add cargo-deny supply-chain gate
datagutt Jun 14, 2026
81250c5
test(srtla_send): add protocol, ack/nak, and registration conformance…
datagutt Jun 15, 2026
403a442
fix(srtla_send): de-twitch the link admission gate with sustained sig…
datagutt Jun 15, 2026
261d0e0
fix(srtla_send): measure elapsed-since-failure for all-links-failed t…
datagutt Jun 20, 2026
fcd9d7c
fix(srtla_send): restart silently-dead uplink reader tasks proactively
datagutt Jun 20, 2026
22c6966
fix(srtla_send): reject zero-rtt keepalive samples and clamp smoothed…
datagutt Jun 20, 2026
3919115
fix(srtla_send): saturate the in-flight window-growth multiplication
datagutt Jun 20, 2026
1866c6c
fix(srtla_send): retry recvmmsg on EINTR and clamp msg_len to MTU
datagutt Jun 20, 2026
3b6cf8e
fix(srtla_send): correct cc target-update logic bugs
datagutt Jun 23, 2026
cc5e6ed
fix(srtla_send): break weak-link starvation latch with probation re-test
datagutt Jun 23, 2026
afecf99
fix(srtla_send): drop the size-based keyframe heuristic, trust the hint
datagutt Jun 23, 2026
2009a0a
feat(srtla_send): warn when a sidecar binds a non-loopback address
datagutt Jun 23, 2026
4714d95
test(srtla_send): property-fuzz the srt/srtla packet parsers
datagutt Jun 26, 2026
38646f7
test(srtla_send): srtla wire-conformance + keepalive interop coverage
datagutt Jun 26, 2026
195fe78
test(srtla_send): unit coverage for kalman filter and bitrate tracker
datagutt Jun 26, 2026
d041e3f
test(srtla_send): gate netns tests on observed readiness, not fixed s…
datagutt Jun 26, 2026
1763a9e
test(srtla_send): satisfy clippy --tests on ported test code
datagutt Jun 26, 2026
598706b
feat(srtla_send): stalled-link deselect guard (on by default)
datagutt Jul 6, 2026
c1946f8
style(srtla_send): apply nightly rustfmt import wrapping
datagutt Jul 13, 2026
e3f936f
ci(srtla_send): add miri lane over batch_recv unsafe pointer logic
datagutt Jul 13, 2026
673138d
feat(srtla_send): flush batches with sendmmsg
datagutt Jul 13, 2026
0cc0c6d
refactor(srtla_send): drop the batch flush on connection switch
datagutt Jul 13, 2026
24b5f64
fix(srtla_send): remove the switch cooldown that was costing 15 points
datagutt Jul 14, 2026
3b9f0a2
feat(srtla_send): probe starved links instead of the second-best one
datagutt Jul 14, 2026
f5de6c0
fix(srtla_send): make LinkPhase a scheduling weight, not an admission…
datagutt Jul 14, 2026
9b14dd7
fix(srtla_send): restore the windows build
datagutt Jul 14, 2026
417df1c
refactor(srtla_send): remove connection exploration
datagutt Jul 14, 2026
b686ecc
style(srtla_send): apply rustfmt line wrapping
datagutt Jul 14, 2026
ace481b
fix(srtla_send): only back off for loss we caused
datagutt Jul 14, 2026
a1b4037
fix(srtla_send): name the stats bitrate field for its actual unit
datagutt Jul 14, 2026
87d8564
test(srtla_send): netem coverage for wire loss on a bonded link
datagutt Jul 14, 2026
cf7144a
fix(srtla_send): floor the loss backoff at delivered throughput
datagutt Jul 14, 2026
b909220
fix(network-sim): drain child pipes so the process under test cannot …
datagutt Jul 14, 2026
eec33b6
test(network-sim): make the netns topology actually bond
datagutt Jul 14, 2026
6e0d57d
test(network-sim): keep the bonded netem run out of congestion collapse
datagutt Jul 14, 2026
a99f672
test(srtla_send): assert the CC invariant, not retransmit-noise goodput
datagutt Jul 14, 2026
7ccbfd0
test(network-sim): adaptive SRT sender to load the bond realistically
datagutt Jul 15, 2026
bd6fad8
fix(srtla_send): make now_ms monotonic to survive wall-clock steps
datagutt Jul 15, 2026
2958108
refactor(srtla_send): inject clock into BitrateTracker
datagutt Jul 15, 2026
32e3cff
refactor(srtla_send): inject clock into RttTracker
datagutt Jul 15, 2026
62d2e3c
refactor(srtla_send): inject clock into CongestionControl
datagutt Jul 15, 2026
5a8e7c9
refactor(srtla_send): inject clock into ReconnectionState
datagutt Jul 15, 2026
c40eeb8
refactor(srtla_send): inject clock into connection phase transitions
datagutt Jul 15, 2026
9f4c8c7
refactor(srtla_send): collapse the dual clock onto now_ms
datagutt Jul 15, 2026
fcada23
refactor(srtla_send): inject clock into needs_keepalive and perform_w…
datagutt Jul 15, 2026
8d830d7
refactor(srtla_send): inject clock into time_since_last_nak_ms
datagutt Jul 15, 2026
92c2548
refactor(srtla_send): inject clock into is_timed_out
datagutt Jul 15, 2026
e117016
refactor(srtla_send): inject clock into the ack/nak handlers
datagutt Jul 15, 2026
aafc966
refactor(srtla_send): inject clock into registration packet handlers
datagutt Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-debian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- feat/improvements
release:
types: [released]

Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ jobs:
- name: Run security audit
run: cargo audit

- name: Install cargo-deny
run: cargo install cargo-deny

- name: Run supply-chain checks (advisories + sources)
run: cargo deny check advisories sources

# Miri lane over the only unsafe FFI in the tree: the recvmmsg batch-receive
# path in src/connection/batch_recv.rs. Miri interprets the pure pointer logic
# (self-referential iovec/mmsghdr setup, msg_len -> MTU clamp, sockaddr_storage
# decode) and fails the PR on any UB regression there. Hard limit: miri cannot
# execute the real recvmmsg syscall, so the live-syscall path is not covered.
# The `batch_recv` filter scopes the run to that module's pure tests; the
# mimalloc test allocator is cfg'd out under miri (C FFI miri cannot run).
miri:
name: Miri (batch_recv unsafe pointer logic)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly
components: miri

- name: Run batch_recv tests under miri
run: cargo miri test --lib batch_recv

test-stable:
name: Test (Rust stable)
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ target/
# but don't want them in the repo
*.c
*.h
# ...except the harness's own C source, which is a real tracked file that
# the network-sim crate embeds via include_str! and compiles on demand.
!crates/network-sim/src/adaptive_srt_send.c
bond-bunny-main/
moblin/
/.claude
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Changelog

Changes on the `exp` branch since the `v3.0.0` release (https://github.com/irlserver/srtla_send/releases).

This is an overview of the **current** state of `exp`, not a replay of every commit. Several ideas were prototyped and then removed before they reached this branch tip; those are listed under "Explored and removed" so the commit history makes sense. The version in `Cargo.toml` is still `3.0.0`, so this work is unreleased.

## Scheduling and link selection

The runtime still has exactly two scheduling modes: `classic` (capacity based, matches the original C behaviour) and `enhanced` (quality aware, the default). Enhanced selection gained an admission gate on top of its quality scoring:

* **Weak-link admission gate.** A classifier (`sender/selection/classifier.rs`) marks a link weak when its RTT busts the chosen delay tier or its throughput share falls below an entering threshold, with enter/leave hysteresis. Enhanced selection excludes weak links from ranking when at least one healthy link can carry the packet, and falls back to the full pool when every link is weak (better to send on a weak link than drop the packet).
* **Congestion-aware skip.** Each connection carries a `cc_backing_off` flag set by the per-link CC controller. Enhanced selection treats a backing-off link as an extra weak signal and skips it under the same fallback rule.
* **Critical-packet priority.** During a keyframe (IDR / SPS / PPS) the scheduler routes packets to the highest-quality link. Two triggers feed this: a heuristic burst detector (`sender/keyframe.rs`, which declares a burst after 5 consecutive max-MTU 1316-byte packets), and an explicit critical window opened by an upstream encoder (see the priority sidecar below).

## RTT estimation

* **Kalman filter as the primary RTT estimator.** `RttTracker` now uses a 2-state Kalman filter (value, velocity) as its smooth RTT source, replacing the old smooth/fast EWMA pair. The filter tracks trends naturally, so its velocity term doubles as spike detection. A new `kalman.rs` module holds the filter and its RTT preset.
* **Dual-window minimum tracking and a sample filter.** RTT keeps a fast and a slow minimum window plus a small min-sample filter for stability, exposed through `LinkStats`.
* EWMA is retained only for the symmetric `rtt_avg_delta`. The asymmetric (fast-down, slow-up) EWMA variant exists in the tree but is `#[cfg(test)]` only, so it does not compile into the production binary.

## Per-link congestion control

* **`link_cc` controller** (`sender/selection/link_cc.rs`). A per-connection state machine (`CcState`: Bootstrap, Climbing, Holding, BackingOff, Drain) with a `ClimbMode` sub-state (Hai, FastRecovery, Normal) that produces a `target_bps` soft cap from age-bucketed RTT EWMA, RTT variance, a sliding-window loss permille, and observed bitrate.
* **Current wiring.** Only the `BackingOff` state influences selection today (via the `cc_backing_off` flag described above). The `target_bps` soft cap is computed and exported for telemetry but is not yet applied as a rate cap in the data path.

## Transport and egress

* **Pluggable uplink binder.** A new `UplinkBinder` trait makes egress steering injectable. `SourceIpBinder` keeps the existing source-IP bind (Linux source routing); a callback binder lets a library consumer steer the raw fd (for example Android `Network.bindSocket`) while keeping `IpAddr` as the uplink identity. The binder is threaded through sender startup, connection creation, and reconnect so the same binding is re-applied on reconnect.
* **Adaptive batch-send regimes.** `batch_send.rs` picks a batch threshold per connection from observed bitrate: LowActivity (under 500 kbps, threshold 4), Normal (500 kbps to 5 Mbps, threshold 16), HighLoad (above 5 Mbps, threshold 32). The 15ms flush interval is unchanged. The regime is recomputed once per housekeeping tick.

## Configuration, control, and observability

* **TOML config file.** A `--config` flag loads tunable constants from TOML (`toml_config.rs`), falling back to defaults on error, reloaded on SIGHUP.
* **Dynamic runtime config.** `DynamicConfig` replaces the old `DynamicToggles`, holding the scheduling mode and toggles behind atomics for thread-safe runtime changes over stdin and a control socket.
* **JSON-RPC control socket.** A Unix-socket control plane (`control.rs`, `control_socket.rs`, `--control-socket`) supports `set_mode` (classic or enhanced), `set_quality`, `get_status`, `get_stats`, and `subscribe` / `unsubscribe` to the `stats` and `priority.window` topics. Documented in `docs/CONTROL_PROTOCOL.md`.
* **Critical-packet priority sidecar.** A dedicated UDP socket (`priority.rs`, `--priority-bind`) takes a 5-byte datagram from an encoder to open a critical window of N milliseconds. Loopback UDP shares the data path's network stack, so the hint stays tightly ordered against the packets it describes (tighter than the out-of-band JSON-RPC channel). Overlapping windows extend the deadline monotonically. Documented in `docs/KEYFRAME_PRIORITY.md`.
* **Prometheus metrics endpoint.** A hand-rolled `/metrics` HTTP server (`metrics.rs`, `--metrics-bind`, no axum/hyper dependency) exports per-link and aggregate gauges plus the current mode. A shared stats layer (`stats.rs`, `subscriptions.rs`) backs both the metrics endpoint and the control socket subscriptions.

## Testing and tooling

* **`network-sim` crate.** A new workspace crate under `crates/network-sim` providing an integration-test harness, impairment models, scenario definitions, and topology helpers.
* **Network-namespace integration tests.** `tests/netns_basic.rs` (registration and forwarding), `tests/netns_failure.rs` (link failure and recovery), `tests/netns_impairment.rs` (adaptation to impairments), and `tests/netns_scenario.rs` (stability under evolving conditions).
* **CodeRabbit** review config added (`.coderabbit.yaml`).
* Test-only items moved from `#[allow(dead_code)]` to `#[cfg(test)]` gating, with new unit coverage for the CC state machine, batch regimes, weak-link gating, and the TOML config.

## Housekeeping

* Removed the bundled `receiver` symlink and the `.serena` memory files.
* Selection-strategy modules (`classic`, `enhanced`) made private.
* Connection-rotation tuning: `MIN_SWITCH_INTERVAL_MS` to 15, `STARTUP_GRACE_MS` to 5000.
* Dependency refreshes in `Cargo.lock`, plus `cargo fmt` and clippy cleanups across all targets.

## Explored and removed (not in the current branch)

These appear in the commit history between `v3.0.0` and `exp` but are **not present in the current tree**. They were prototyped, then dropped or superseded:

* **EDPF scheduler (Earliest Delivery Path First), BLEST head-of-line guard, and IoDS reordering prevention.** The whole arrival-time-prediction scheduling pipeline was removed. No `edpf` / `blest` / `iods` modules exist; `congestion/` holds only `classic`, `enhanced`, and `mod`.
* **Shared bottleneck detection (RFC 8382).** Removed along with the EDPF pipeline it fed.
* **Connection exploration (`--exploration`, `set_exploration`).** Removed entirely: the flag, the JSON-RPC method, `sender/selection/exploration.rs`, and the `enable_explore` plumbing. The original version probed the *second-best* link (usually a healthy link already earning its own ACKs), fired every 30s whether or not anything was wrong, and had no budget. It diverted roughly half of all traffic for as long as its trigger held, surviving only because the switch cooldown happened to rate-limit it. It was rewritten as a bounded probe of *starved* links (one packet per link per 200ms) and then measured on the netem testbed against the scenario it exists for: a link gated to 0.00 Mbps, silently healed, then needed when the healthy link collapsed. It moved delivery 0.76 pts (Welch t=0.75, n=15), which is to say not at all. `GATED_LINK_PENALTY` already keeps a gated link *rankable* rather than excluded, so it retains a 0.2 to 0.5 Mbps trickle and re-adopts itself about 7s after healing, and the classifier's probation re-test covers the share-starvation latch on top of that. A mechanism that cannot be shown to help is debt, so it is gone rather than kept off by default.
* **RTT-threshold scheduling mode and the `edpf` mode.** `SchedulingMode` now has only `Classic` and `Enhanced`; the parser explicitly rejects `rtt-threshold` and `edpf`, and there is no `--rtt-delta-ms` flag in the CLI. `README.md` was updated to drop these modes (along with the stale `set_rtt_delta` and `mark_critical` control-socket examples), and `docs/RTT_THRESHOLD_SCHEDULING.md` was removed.
163 changes: 161 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading