Skip to content

TURN over TCP, client side: REQUESTED-TRANSPORT split, stream framing, no retransmits over TCP - #254

Open
mnaza wants to merge 3 commits into
webrtc-rs:masterfrom
mnaza:turn-over-tcp
Open

mnaza wants to merge 3 commits into
webrtc-rs:masterfrom
mnaza:turn-over-tcp

Conversation

@mnaza

@mnaza mnaza commented Sep 22, 2026

Copy link
Copy Markdown

Part of webrtc-rs/webrtc#848 — the rtc half. The webrtc half (the relayer, RTCTcpTransport, the driver) follows as a draft PR that bumps this submodule.

As agreed on the issue: TCP first, TLS as a follow-up.

What changes

feat(turn): request the relay transport independently of the server transport. allocate() and its authenticated retry derived REQUESTED-TRANSPORT from transport_protocol, so a client reaching its server over TCP asked for an RFC 6062 TCP allocation. RFC 8656 §7.1 keeps the two apart. ClientConfig gains requested_transport, defaulting to UDP.

This is a break for callers that build ClientConfig as a struct literal. The published baseline is 0.21.0 and master is 1.0.0-alpha.1, so the semver job allows it. The in-tree examples and tests set the new field. One question for you: should ClientConfig become #[non_exhaustive] before 1.0? TLS will probably want a field of its own, and that would be the same break again.

feat(shared): TurnStreamDecoder. Over a stream, TURN's STUN and ChannelData messages arrive back to back with no framing of their own (RFC 8656 §12.5). tcp_framing is RFC 4571, which is ICE-TCP's framing, so there was nothing to split a TURN stream with. The new decoder has TcpFrameDecoder's shape — push bytes, pop whole messages — so a host can hold either one per stream.

fix(turn): do not retransmit requests over TCP. Transactions retransmitted on every transport. RFC 8489 §6.2.2 says to send once over a reliable transport and wait Ti = 39.5 s; transactions over anything that is not UDP now do that. It's a separate commit, and TCP works without it, so drop it if you'd rather take it separately.

Tests

  • rtc-turn: an Allocate over TCP carries REQUESTED-TRANSPORT UDP, and the default is unchanged. A request over TCP is sent once and times out at Ti, while UDP still retransmits.
  • rtc-shared: the decoder handles STUN, ChannelData with and without padding, coalesced and byte-by-byte input, and invalid first bits. There is a doctest too.
  • All three were seen failing before their change. rtc-turn passes 47 tests plus its doctest.

One unrelated note: rtc-sctp's test_assoc_reset_close_one_way fails for me under a full cargo test --workspace run, and passes on its own. It does the same on the untouched base (784e464), so it isn't this change — I mention it in case it's news.

Refs webrtc-rs/webrtc#848.

…ransport

Allocate derived REQUESTED-TRANSPORT from the transport used to reach the
server, so a client talking to its server over TCP asked for an RFC 6062
TCP allocation. RFC 8656 §7.1 keeps the two apart: REQUESTED-TRANSPORT
names what the allocation relays, and a client on TCP normally still wants
a UDP relay.

ClientConfig gains `requested_transport`, defaulting to UDP, used by the
Allocate and by its authenticated retry. Callers that set nothing keep
what they had; callers that build ClientConfig as a struct literal need the
new field, which the in-tree examples and tests now set.

Refs webrtc-rs/webrtc#848.
…ages

Over TCP, TURN sends its messages back to back with no framing of its own
(RFC 8656 §12.5): a STUN message is 20 bytes plus its length field, a
ChannelData message 4 bytes plus its length rounded up to a multiple of 4.
tcp_framing is RFC 4571's length prefix, which is ICE-TCP's framing and not
TURN's, so a host reading a TURN stream had nothing to split it with.

TurnStreamDecoder has TcpFrameDecoder's shape — push bytes, pop whole
messages — so a host can hold either per stream. Messages come out as they
were on the wire, padding included; a first byte that starts neither kind
is an error, since a stream has nowhere to resynchronise.

Refs webrtc-rs/webrtc#848.
Transactions retransmitted on every transport: 200 ms doubling to 1.6 s,
seven requests, a timeout after about eight seconds. Over a reliable
transport RFC 8489 §6.2.2 has the client send once and wait Ti, 39.5 s —
TCP is already retransmitting underneath, and repeating the request on top
only sends the server duplicates of it.

A transaction over anything that is not UDP now has one deadline at Ti and
no retransmission; passing it reports TransactionTimeout as before. UDP is
unchanged.

Kept as its own commit: it is not needed for TURN over TCP to work, only
for it to behave as the RFC asks.

Refs webrtc-rs/webrtc#848.
mnaza added a commit to mnaza/relaysight that referenced this pull request Sep 22, 2026
…, install gateways from signed releases

Everything since the last sync, in one commit; this clone keeps its own
history.

A revoked gateway's cameras can be retired from the roster: a tombstone
rather than a delete, so recordings still play and a gateway reporting the
camera again brings it back with its history. The action refuses while
the gateway is still in service.

Each camera can have its own credentials, encrypted on the gateway under
the same key as its identity and entered with `vms-gateway credentials`,
which reads the password from stdin. The shared CAMERA_USERNAME and
CAMERA_PASSWORD stay as the fallback.

Gateways install on a site box from a signed release: install.sh verifies
the Ed25519-signed manifest before touching anything and sets up a
hardened systemd service with a daily self-update. An update keeps a new
binary only once the API has accepted a heartbeat from it, and rolls back
otherwise. make check-installer runs all of it on a real systemd.
Retina now comes from a pinned fork carrying the fix for decimal SSRCs,
until upstream releases it. docs/INSTALL-GATEWAY.md says what is checked
and what is not; no release has been cut yet, because the release key
does not exist yet.

Plus the upstream design for TURN over TCP in webrtc-rs, which is open as
webrtc-rs/rtc#254 and webrtc-rs/webrtc#907.
@codecov

codecov Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.24060% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.81%. Comparing base (784e464) to head (5d14a40).

Files with missing lines Patch % Lines
rtc-turn/src/client/client_test.rs 95.74% 4 Missing ⚠️
rtc-turn/examples/turn_client_udp.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #254      +/-   ##
==========================================
- Coverage   87.85%   87.81%   -0.04%     
==========================================
  Files         499      500       +1     
  Lines       72540    72670     +130     
==========================================
+ Hits        63728    63816      +88     
- Misses       8812     8854      +42     
Flag Coverage Δ
aws-lc-rs 87.65% <96.24%> (-0.04%) ⬇️
ring 87.80% <96.24%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rainliu rainliu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't add the public field

Adding requested_transport to ClientConfig, whose fields are all public, breaks anyone who builds it as a struct literal. The field doesn't earn that break:

  • The only other value is useless. The field's own doc says asking for TCP "requests an RFC 6062 TCP allocation instead, which this client does not implement." I checked: rtc-turn has no Connect or ConnectionBind; only the constants exist in rtc-stun. So requested_transport: TCP just gets an allocation the client can't use.
  • Hard-coding UDP gives the same fix with no API change. Both Allocate requests send PROTO_UDP, with a comment citing RFC 8656 §7.1 and noting that RFC 6062 isn't implemented. I applied that on top of the PR:
    • rtc-turn passes 46 tests (the default-value test goes with the field);
    • the examples build;
    • ClientConfig, the examples and the doc example are unchanged from master.

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