Skip to content

feat(receiver): upstream hardening cherry-picks + test coverage (7855012 + 39e324a) - #18

Merged
andrescera merged 6 commits into
mainfrom
feat/upstream-receiver-hardening
Jun 12, 2026
Merged

feat(receiver): upstream hardening cherry-picks + test coverage (7855012 + 39e324a)#18
andrescera merged 6 commits into
mainfrom
feat/upstream-receiver-hardening

Conversation

@andrescera

Copy link
Copy Markdown
Member

Affected repo & language: srtla (C/C++)

What

Two upstream receiver-hardening cherry-picks (commits 7855012 + 39e324a from irlserver/main) plus comprehensive test coverage upstream never wrote. The first fix prevents pre-auth group table exhaustion DoS; the second throttles source IPs that repeatedly fail SRT authentication. Both are critical for production receiver stability.

Why

Upstream currency before hardening work. The receiver is exposed to two DoS vectors: (1) an attacker flooding REG1 packets from distinct ports fills the group table with ghost groups, blocking legitimate registrations; (2) an attacker sending malformed SRT handshakes from a single IP exhausts auth attempts, blocking that IP. These fixes close both vectors and are load-bearing for the device's multi-link bonding reliability.

How to verify

  • Full gate: cmake -B build -DCMAKE_BUILD_TYPE=Release -DSRTLA_BUILD_TESTS=ON -DBUILD_COMPAT_TESTS=ON && cmake --build build -j$(nproc) && ctest --test-dir build --output-on-failure && tests/compat/run-matrix.sh --tier blocking
  • Result: 152/152 ctest cases pass (6 new ghost-group eviction cases + 10 auth rate limiter cases, all green)
  • Compat matrix: 5/5 blocking scenarios pass (belabox-sender, irlserver-send, moblin-mock, oursxbelabox, oursxopenirl)
  • Rule D: no .omo references in tracked files

Risks

Receiver lifecycle changes: group eviction timing and auth failure handling are now stricter. Mitigations: new test suites (16 cases) cover both the happy path and edge cases; oursxours compat smoke test passes (loopback, no netem). The cherry-picks are upstream-authored and tested in irlserver; we adapted the test harness to our fork's architecture (separate commit 962e920).


Checklist

  • Docs updated if behavior or structure changed (Rule A: AGENTS.md, README, docs/, ARCHITECTURE.md, versions.yaml)
  • Started from updated main; branch rebased on latest canonical branch (Rule B)
  • git grep -n '.omo' -- ':!.gitignore' returns nothing (Rule D)
  • Tests pass; QA evidence attached or linked

andrescera and others added 6 commits June 11, 2026 23:40
unauthenticated reg1 packets create connection groups before any srt
handshake, and a global cap of MAX_GROUPS held for GROUP_TIMEOUT (30s)
lets an attacker flood ghost groups and lock out the real broadcaster
with reg_err.

track whether a group has forwarded real srt data (has_seen_data).
groups that registered but never streamed are now reaped at
PENDING_GROUP_TIMEOUT (5s) instead of 30s, and when the table is full a
new registration evicts the oldest ghost group before rejecting. once a
group forwards real srt traffic it is promoted and no longer evictable,
so active streams and cellular reconnects are unaffected.
srtla_rec relays the srt handshake but never authenticates. detect a
rejected connection on the relayed stream and count it per source ip;
once an ip crosses AUTH_FAIL_THRESHOLD within AUTH_FAIL_WINDOW its new
reg1 registrations are refused for AUTH_FAIL_COOLDOWN.

srt-live-server (irl-srt-server) does not send a libsrt handshake
rejection: it accepts the handshake, then reads the streamid, runs auth,
and on failure closes the socket. so the wire signal for a failed auth
is an srt shutdown that arrives before the session is established. mark a
group established once the server acks media, and treat a shutdown before
that as a failure. the libsrt-native handshake reject (type >= failure
base) is also handled as a fallback for other servers.

a failed-auth group is torn down immediately after relaying the
rejection to the client, reclaiming its slot instead of letting it linger
for GROUP_TIMEOUT. safe to remove inside handle_srt_data: the group is
held by the by-value shared_ptr param and the main loop stops using stale
epoll pointers once the group count shrinks.

keys are ip-only so port rotation does not evade. thresholds are lenient
(5 fails / 60s, 60s cooldown) so a mistyped key or several broadcasters
behind one nat are not locked out. complements ghost group eviction by
also covering attackers that complete the handshake before failing auth.

also define is_srt_shutdown, which was declared but never implemented.
The two cherry-picked upstream commits (7855012 ghost-group eviction,
39e324a per-IP auth-fail rate limiter) change two receiver behaviors that
our fork's pre-existing tests pinned. No upstream logic is altered; only
the test harness/fixtures/setup are adapted to the new model:

- SRTHandler / SRTLAHandler now take an AuthRateLimiter&. Thread an owned
  limiter through handler_harness.h and the inline fixture in
  test_registration_handshake.cpp so they construct under the new signature.

- An empty group that never forwarded SRT data is now an evictable "ghost"
  reaped at PENDING_GROUP_TIMEOUT (5s) rather than GROUP_TIMEOUT (30s), and
  the (MAX_GROUPS+1)-th REG1 evicts the oldest ghost before returning
  REG_ERR. The MAX_GROUPS filler (test_group_limits) and the timeout-cleanup
  groups (test_timeout_cleanup) model established streams, so they are marked
  data-seen — restoring the "table genuinely full" / GROUP_TIMEOUT semantics
  those tests were authored against. The ghost-flood/eviction path itself is
  covered by test_ghost_group_eviction.cpp (unchanged).

Full suite: 142/142 green.
Add tests/test_auth_rate_limiter.cpp pinning the per-IP SRT auth-fail throttle from upstream pick 39e324a and the is_srt_shutdown classifier: per-IP failure counting, the 5-in-60s block trip, sliding-window misses, cooldown expiry, IP-only keying (port rotation cannot evade), per-IP independence, stale-entry cleanup reclamation, and SHUTDOWN classification vs other control/data packets.

No clock-injection refactor was needed: AuthRateLimiter already takes time_t now on record_failure/is_blocked/cleanup, so the suite drives time by passing values (no real waits; 0 sleep/usleep). The only production change is an additive read-only AuthRateLimiter::tracked_entry_count() so the cleanup-reclamation case is observable; throttling behavior is unchanged.

Registered inline with TEST_PREFIX "auth_rate_limiter." so `ctest -R auth_rate_limiter` selects exactly this suite. Full suite 152/152 green.
@andrescera
andrescera merged commit 529f307 into main Jun 12, 2026
9 checks passed
@andrescera
andrescera deleted the feat/upstream-receiver-hardening branch June 12, 2026 09:45
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