Skip to content

classic: per-link weights (Moblin connection priorities) via the IPs file - #24

Merged
datagutt merged 1 commit into
irlserver:mainfrom
ziggy6792:link-weights
Oct 1, 2026
Merged

datagutt merged 1 commit into
irlserver:mainfrom
ziggy6792:link-weights

Conversation

@ziggy6792

@ziggy6792 ziggy6792 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

On a bonded setup the links rarely cost the same: free venue Wi-Fi or Ethernet next to a metered phone tether. Classic mode balances purely on capacity (window / (in_flight + 1)), so an expensive link carries traffic even when the cheap one has room. This PR lets the user say "prefer this link": the preferred link carries what it can while it is healthy, and the others take over as it congests or dies, exactly as they do today.

It is a copy of Moblin's "connection priorities", the only open per-packet bonding scheduler with priorities we could find (BELABOX srtla and belaUI have none), so it behaves the same as what users already know from Moblin.

Semantics (as Moblin)

  • A weight per link, integer 1..10 (Moblin's slider range). Missing = 1.
  • Normalised on every load so the lowest link is 1: w - lowest + 1 (it subtracts, it does not divide), as in SrtlaClient.swift updateConnectionPriorities.
  • In classic selection the link's get_score() is multiplied by the weight while its window is above 20 000; between 20 000 and 10 000 the multiplier fades linearly from the weight to 1; at or below 10 000 it is 1. This is RemoteConnection.swift score(). The thresholds carry over 1:1 because WINDOW_DEF/WINDOW_MAX/WINDOW_MULT are the same constants.
  • So a congested preferred link loses its preference before it drops packets, and a dead one is skipped as before (timed-out and stall-gated links are still excluded).

How to use it

The weight goes in the IPs file, one link per line:

192.168.0.15 10
192.168.0.2

That is the file the sender already reloads on SIGHUP, so weights are hot-reloadable with no new flag or RPC. Rules:

  • values above 10 are clamped to 10;
  • a 0 or an unparsable weight is logged as a warning and read as 1. The IP still counts, so the reload guard behaves as before.

Scope

  • Classic mode only. get_score() itself is unchanged, and enhanced mode ignores weights (a test covers this).
  • Weight 1 is exactly neutral, so an IPs file without weights schedules as it does today. Bare-IP lines are fully backward compatible.

Observability

  • New gauge srtla_send_link_weight{ip=…}.
  • The weight is in the stats topic, in the added uplink … (weight N) line, and in the reload / SIGHUP log lines.

Tests

14 new tests in src/tests/link_weight_tests.rs:

  • the three multiplier bands;
  • weight 1 is neutral in every band;
  • normalisation;
  • unweighted links still share evenly;
  • classic prefers the weighted link while it is healthy, the preference fades as its window shrinks, and a small window hands traffic to the other link;
  • timed-out and stall-gated weighted links are still skipped;
  • enhanced mode ignores weights;
  • reload re-weights existing links;
  • the metrics export.

cargo +nightly test --workspace passes (549 tests), and cargo +nightly fmt --check and clippy --all-targets -D warnings are clean on this branch (rebased onto c86a3e1; the only conflict was the src/tests/mod.rs module list).

Bench evidence

Two-uplink LAN bench: Ethernet en7 + Wi-Fi en0, a 1080p30 file source through an adaptive encoder, v4.1.0 + this patch, classic mode, receiver irlserver/srtla 7fa6985.

  • Link A capped to ~1.7 Mbit/s for 60 s, B uncapped (preferred A = weight 10, other link 1). B's share of the bytes:

    before the cap during the cap last 30 s
    weights 1/1 4.2 % 96.4 % 5.6 %
    weight 10 on A 4.6 % 93.4 % 0.3 %

    Traffic spilled to B within 0.9 s of the cap and came back to A 4.7 s after it ended (5.5 s with weights 1/1). No stream damage in either mode.

  • Preferred link unplugged 2 / 8 / 12 / 8 s, then the other link for 8 s: hitless with and without weights. There was no SRT session END and no corrupt packets, and the survivor carried 5.1–7.2 Mbit/s.

  • Both links capped below the stream: it still aggregates with weights on, splitting 49.5 % / 50.5 % for 600 s with no damage.

On this Ethernet+Wi-Fi bench, classic mode already sends most traffic to the wired link. With a real metered tether, the idle trickle on the expensive link is where the saving shows up.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added optional link weights (1–10) to IP list entries. In classic mode, weights influence link selection based on the link’s available window; enhanced mode ignores them.
    • Added per-link weight reporting in metrics. When weights are omitted, they default to 1; configured weights are normalized so the lowest is 1.

…priorities)

Each IPs-file line is `<ip>[ <weight>]`: integer 1..10, missing = 1, larger
values clamped to 10, a 0 or unparsable weight is warned about and read as 1
(the IP still counts, so the SIGHUP reload guard is unchanged). Weights are
normalised on every load so the lowest link is 1 (priority - lowest + 1,
as Moblin's updateConnectionPriorities).

Classic selection multiplies get_score() by the link's weight while its
window is above 20 000, fades the multiplier linearly to 1 between 20 000
and 10 000, and ignores it below (Moblin's RemoteConnection.score()). A
weight of 1 is exactly neutral, so an unweighted file schedules as before.
get_score() and enhanced mode are untouched.

Observability: srtla_send_link_weight gauge per link, weight in the stats
topic, in the 'added uplink' line, on reload re-weights and in the SIGHUP
queued line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Repository guideline files applied to this review (2)
RELEASING.md — configured
AGENTS.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: irlserver/srtla_send/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 09342244-bb92-421d-beeb-b95aba6ba097

📥 Commits

Reviewing files that changed from the base of the PR and between c86a3e1 and ca2d033.

📒 Files selected for processing (12)
  • README.md
  • crates/srtla-core/src/connection/mod.rs
  • crates/srtla-core/src/selection/classic.rs
  • crates/srtla-core/src/test_helpers.rs
  • src/metrics.rs
  • src/sender/connections.rs
  • src/sender/mod.rs
  • src/sender/reload.rs
  • src/stats.rs
  • src/tests/link_weight_tests.rs
  • src/tests/mod.rs
  • src/tests/sender_tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

BIND_IPS_FILE entries can include optional weights. The sender normalizes and assigns these weights to links. Classic selection applies a window-dependent weight factor, while enhanced selection ignores weights. Per-link weights are also exposed in metrics.

Changes

Weighted link selection

Layer / File(s) Summary
Weight model and IP parsing
crates/srtla-core/src/connection/mod.rs, src/sender/reload.rs, README.md
Connection weights are bounded and normalized. IP entries accept optional weights, with missing or invalid weights defaulting to 1 and values above 10 clamped. Reload parsing returns weights aligned with valid IPs.
Weight assignment and reload wiring
src/sender/connections.rs, src/sender/mod.rs, src/tests/sender_tests.rs
Startup, queued updates, and SIGHUP reloads pass weights alongside IPs. Existing and new connections receive the weight for their IP; missing weights default to 1.
Classic selection and weight reporting
crates/srtla-core/src/selection/classic.rs, crates/srtla-core/src/test_helpers.rs, src/stats.rs, src/metrics.rs, src/tests/*
Classic selection uses weighted scores. Stats and metrics expose each link’s weight. Tests cover normalization, selection, reload assignment, enhanced-mode behavior, and metrics.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant IpFile as BIND_IPS_FILE
  participant Sender as Sender
  participant Connections as Connection manager
  participant Selector as Classic selection
  IpFile->>Sender: provide IP entries and optional weights
  Sender->>Connections: pass parsed IPs and normalized weights
  Connections->>Connections: assign weights to existing and new links
  Selector->>Connections: read link scores and weights
Loading

Suggested reviewers: datagutt

Merge Risk: ⚪ Minimal · up to ca2d0

No actionable merge-blocking issue is established for the weighted-link behavior. The change is mergeable subject to normal build and test checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to ca2d0

The preferences are bounded and do not override link eligibility or grant additional authority. No introduced security weakness was established. Live reload retains existing partial-failure behavior, while configuration permissions and recovery after external process interruption remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated effect is redistribution of a sender’s traffic among its configured, eligible uplinks to the configured receiver. Weight assignment does not itself choose a new receiver, create an additional authority, or admit an otherwise ineligible link.

Trust Boundaries and Controls

  • observed — The inspected weight source is the local IP configuration file, not peer feedback. Parsing supplies bounded preferences, and core scoring independently clamps them. Configuration-file access permissions were not supplied, so this establishes the control flow rather than proving who can modify the file in production.

Resilience and Maintainability Implications

  • observed — The event loop consumes a pending update once and awaits its application serially. Housekeeping retries existing connection objects; it does not reconstruct configured IPs whose connection creation failed. A subsequent accepted reload can attempt those absent IPs again. No automatic restoration of the consumed configuration snapshot was found in the inspected lifecycle.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: per-link weights for classic-mode scheduling configured through the IPs file.
Docstring Coverage ✅ Passed Docstring coverage is 90.20% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 11 files. (1 skipped: 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Autopilot could not be updated. Open Coding to check access and billing.

@datagutt
datagutt merged commit 5f2e081 into irlserver:main Oct 1, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants