Skip to content

docs: name the RFC deviation, correct the Kubernetes Service claim - #14

Merged
srperens merged 1 commit into
mainfrom
docs/rfc-deviation-and-scaling
Sep 17, 2026
Merged

srperens merged 1 commit into
mainfrom
docs/rfc-deviation-and-scaling

Conversation

@srperens

Copy link
Copy Markdown
Owner

What

Two docs corrections prompted by pion/turn#284 — the still-open request for exactly the single-port mode uTURN implements. Both claims verified against primary sources before writing.

1. The shared relay address is a spec deviation, not a missing feature

ARCHITECTURE.md listed "per-allocation relay addresses" under deliberately out of scope, which reads like an unimplemented nicety. It is a deviation from a MUST:

Both the relayed transport address and the 5-tuple MUST be unique across all allocations, so either one can be used to uniquely identify the allocation.
RFC 5766 §5

RFC 8656 §6 carries the requirement forward for the relayed transport address.

uTURN honours the 5-tuple half — a client is still identified by its source address — and gives up the other half. That is worth stating plainly because everything else in the docs follows from it: one relayed transport address shared by every allocation is precisely why a packet cannot be attributed by its destination, why the pairing must come from the payload's ICE ufrag, and why both parties must be ICE agents. Naming the cause makes the constraints read as consequences instead of a list of quirks.

The pion thread is cited because it contains both the objection (renandincer's analysis of what a single 2-tuple cannot disambiguate) and the sketch of the way out that uTURN took: "keep track of the ICE connectivity check STUN packets over the TURN server and somehow tie the third party peer into the mix."

2. Several replicas behind one Service do not work — the README claimed they do

Introduced by me in #13: the STUNner section said the exposed surface is "a single 3478/udp Service". That is true for one replica and wrong beyond it.

  • All allocation state is in-process — AllocationTable's DashMaps, no shared store — and client-to-client relaying resolves both parties inside one instance. Every participant of a call must therefore reach the same process.
  • Kubernetes Services do not provide that: kube-proxy "select[s] a backend Pod at random" by default (sessionAffinity defaults to None).
  • sessionAffinity: ClientIP is a partial fix only. It pins each client to a pod, which keeps one client's allocation intact — but two clients have two source IPs and can still be pinned to two different pods, and neither would see the other's ufrag registration. This is the harder half and affinity cannot solve it.

Documented as: one replica per Service, scale out by adding instances and steering a whole call to one of them.

Worth noting which way this cuts: it bounds uTURN, not STUNner. STUNner scales its dataplane out fine, because its pods relay to a backend any of them can reach over pod networking — and STUNner does set sessionAffinity: ClientIP on the Services exposing its Gateways, for the client-pinning half. So rg0now's remark in the thread ("You cannot expose a media server over a Kubernetes Service anyway") is too absolute as stated, but the underlying point applies squarely to uTURN.

Verification

  • RFC 5766 §5 and RFC 8656 §6 read directly from rfc-editor.org; both quoted verbatim.
  • kube-proxy random-backend behaviour and sessionAffinity semantics (default None, clientIP.timeoutSeconds default 10800) from the Kubernetes docs source.
  • STUNner's sessionAffinity: ClientIP usage from its own docs/GATEWAY.md.
  • In-process-only state confirmed in src/lookup/table.rs (no redis/etcd/cluster dependency anywhere in src or Cargo.toml).

cargo fmt --check, cargo clippy --all-targets -- -D warnings, cargo test (65 passed) clean. Docs only; no behaviour change.

Note on release

This lands after v0.3.3 was tagged and pushed. Nothing in the release is wrong as shipped — the code is unchanged — but the README as tagged carries the replica claim. Worth a v0.3.4 docs release if you want the published docs to match, otherwise it rides along with the next release.

🤖 Generated with Claude Code

Two corrections prompted by pion/turn#284, the still-open request for the
single-port mode uTURN implements. Both verified against the sources.

The shared relay address is a spec deviation, not a missing feature. RFC
5766 Section 5: "Both the relayed transport address and the 5-tuple MUST be
unique across all allocations, so either one can be used to uniquely
identify the allocation." RFC 8656 Section 6 keeps the requirement for the
relayed transport address. uTURN honours the 5-tuple half and gives up the
other, and everything else in the docs follows from that - it is why pairing
must be inferred from ICE ufrags at all. Listing "no per-allocation relay
address" under out-of-scope made a design decision look like a gap. The
pion thread also sketches the workaround uTURN took, so it is cited.

Several replicas behind one address do not work, and the README said the
opposite. It claimed the exposed surface is "a single 3478/udp Service".
All allocation state is in-process (AllocationTable's DashMaps, no shared
store) and client-to-client resolves both parties inside one instance, so
every participant of a call must reach the same process. Kubernetes Services
"select a backend Pod at random" by default, and sessionAffinity: ClientIP -
what STUNner sets on its Gateway Services - only pins each client
individually: two clients have two source IPs and can land on two pods,
where neither sees the other's ufrag registration. Documented as one replica
per Service, scale out by instance.

Note this bounds the STUNner comparison rather than the reverse: STUNner
scales out fine here, because its dataplane relays to a backend reachable
from any pod.

Docs only; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@srperens
srperens merged commit a2e477d into main Sep 17, 2026
3 checks passed
@srperens
srperens deleted the docs/rfc-deviation-and-scaling branch September 17, 2026 07:04
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.

1 participant