Skip to content

fix(relay): force dual-stack relay socket for wildcard listeners#226

Open
ti wants to merge 2 commits into
l7mp:mainfrom
ti:fix-relay-dualstack-network
Open

fix(relay): force dual-stack relay socket for wildcard listeners#226
ti wants to merge 2 commits into
l7mp:mainfrom
ti:fix-relay-dualstack-network

Conversation

@ti

@ti ti commented May 28, 2026

Copy link
Copy Markdown
Contributor

What

Override pion/turn's family-pinned AllocateListenerConfig.Network hint
(udp4 / udp6) with the family-neutral udp when the listener uses
the wildcard Address, so the relay socket binds [::] on Linux
dual-stack hosts and reaches both IPv4 and IPv6 peers from a single
allocation.

A no-op when r.Address is family-pinned (RFC 6156 strict behavior is
preserved).

Why

Browsers' TURN clients omit REQUESTED-ADDRESS-FAMILY in their
Allocate request, so pion/turn falls back to the RFC 6156 default IPv4
family. The Allocation Manager then asks the relay generator to listen
on udp4. On an IPv6-only Kubernetes cluster, a pion-based WebRTC
media-server pod only has an IPv6 address, so a single-family IPv4
relay socket can never reach it.

The result is what looks like a successful TURN allocation with no
working media path: Allocate succeeds, CreatePermission for an
IPv6 peer is silently dropped by pion/turn's strict ipMatchesFamily,
Send-indication fails with "no permission added", and the WebRTC
peer-connection times out.

Stack

This builds on #225 (Address: "0.0.0.0"""). Together they make
the listener and relay sockets uniformly dual-stack on dual-stack hosts.

Pairing pion/turn fix

End-to-end cross-family forwarding additionally requires pion/turn's
internal/server/turn.go to accept IPv6 peers on a default-IPv4
allocation (ipMatchesFamily). Tracked at pion/turn#566 and
proposed in pion/turn#567. Until that lands, deployments need a
vendored pion/turn (a replace directive in go.mod is enough).

Verified

On an IPv6-only Linux host with a custom build of stunnerd from this
branch:

  • Listener socket-pool bound on [::]:<turn-port> (verified via
    /proc/net/udp6).
  • Relay socket bound on [::]:<random> after Allocate (was previously
    IPv4-only).
  • Pure IPv6 backend pod reachable through TURN end-to-end with the
    paired pion/turn patch above; ICE candidate pairs forced to relay
    (iceTransportPolicy: "relay") succeed, browser reports media
    flowing through the relay candidate.

No regression on dual-stack and IPv4-only hosts.

Refs: #225, pion/turn#566, pion/turn#567

ti added 2 commits May 26, 2026 14:45
Listener and relay sockets hardcoded "0.0.0.0" + the IPv4-only network
strings "udp4"/"tcp4". On IPv4-only hosts that's fine, but on IPv6-only
hosts (e.g. IPv6-only EKS) the socket cannot be reached at all, so
TURN allocations silently fail. On dual-stack hosts (Linux default with
net.ipv6.bindv6only=0) it still works for IPv4 peers but breaks for
IPv6-only peers, including pod-to-pod traffic on IPv6-only Kubernetes.

Switch to the unspecified address (":port" / "") plus the family-neutral
network strings ("udp" / "tcp"). The kernel then opens an IPv6 wildcard
socket that accepts IPv4 (mapped) and IPv6 peers on dual-stack hosts and
the available family on single-family hosts, matching the behavior of
Go's net.Listen with no host. Verified end-to-end on IPv6-only EKS:

  - 16-thread UDP listener now bound on [::]:7006 (was 0.0.0.0:7006)
  - TURN allocate succeeds from an IPv6-only client
  - relay address returned to the client is on the listener's IPv6 addr

No semantic change for IPv4-only deployments: ":port" continues to bind
to the IPv4 wildcard when no IPv6 stack is present.
…ener

Stunner's RelayGen.AllocatePacketConn forwards pion/turn's hint
`AllocateListenerConfig.Network` ("udp4"/"udp6") straight to
ListenPacket. On Linux dual-stack hosts that hint forces a
single-family socket regardless of how the listener was bound, so a
relay allocated from an IPv6-only Kubernetes cluster is unreachable
when pion/turn defaults to the RFC 6156 IPv4 allocation family
(default, since browser TURN clients omit REQUESTED-ADDRESS-FAMILY).

Build on top of l7mp#225 (Address: "" wildcard fix): when the listener uses
the wildcard, replace the family-pinned network with the family-neutral
"udp" so the relay socket binds [::] on dual-stack hosts and reaches
both IPv4 and IPv6 peers from one allocation. No-op for explicit
family-pinned listeners.

End-to-end cross-family forwarding additionally requires pion/turn's
`ipMatchesFamily` permission check to accept v6 peers on v4
allocations; tracked in a separate pion/turn upstream issue. With both
fixes in place, browser-issued Allocate (default IPv4 family) on an
IPv6-only EKS cluster successfully relays to IPv6 backend pods --
verified end-to-end with LiveKit (connectionType=turn).

Refs: l7mp#225
@rg0now

rg0now commented May 29, 2026

Copy link
Copy Markdown
Member

See pion/turn#566 (comment).

TL;DR The current policy is compliant with the RFC. We can decide to deviate from the standard, but only for a good reason.

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