rtc: ICE gathers host candidates on tailscale0 too — stuck SYN-SENT to LiveKit :7881; add a client-side interface exclusion (rtc.interfaces twin of #91) #138
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
nether/nether-voicebridge#138
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Development note (filed 2026-09-28 at the operator's request). Not urgent; cosmetic today, but it is the client-side twin of #91.
Observation (prod, mautrix, v0.3.7)
During and shortly after calls the bridge process shows sockets stuck in
SYN-SENTtoward LiveKit's ICE-TCP port on nether-red (97.107.138.76:7881) — six at once on 2026-09-28 03:36, unchanged across ~10 min, gone once the calls ended. The port itself is reachable from mautrix (a plain/dev/tcpconnect succeeds), so the stuck SYNs are not a firewall problem on the server.The bridge's ICE-TCP listeners explain it.
ss -tlnpon the process shows passive ICE-TCP sockets bound to every non-loopback address on the box:libwebrtc gathers host candidates on all of them and pairs each with the SFU's public candidates. Pairs sourced from the tailscale addresses can never complete (no route from that interface to a public IP), so their TCP connectivity checks sit in SYN-SENT until they time out. Media is unaffected: the eth0 UDP pair wins. Costs are noise in the socket table, wasted connectivity checks and marginally slower ICE, and one more thing for anyone reading
ssto explain.Server side we already solved the mirror problem in #91 (
rtc.interfaces.includes: [eth0]on LiveKit, which had been advertising tailscale/incus candidates → ~2/3 zero-media). This is the same thing from the client's chair.What the SDK exposes today
livekit0.7.45 /libwebrtc0.3.36:RoomOptions.rtc_configcarries onlyice_servers,continual_gathering_policyandice_transport_type(All/Relay/NoHost). There is nonetwork_ignore_mask, interface allow-list or adapter-type filter.NoHostis not an option for us — we have no TURN and rely on the host candidate on eth0.Options
webrtc-sys) to surface libwebrtc'sPortAllocator::set_network_ignore_mask()(or anRTCConfiguration-level interface allow-list) throughRtcConfiguration; then addlivekit.ice_interfaces = ["eth0"]/ice_ignore_interfacesto our[bridge.livekit]config and thread it intoRoomOptions. Pin bump implications: constraint #7 (livekit is pinned) — do it as a deliberate bump.tailscale0(the systemd unit could useRestrictNetworkInterfaces=eth0 lo— worth a try first: it's one line in a drop-in and needs no code). Caveat: anything the bridge legitimately does over the tailnet would break; today it should not need the tailnet at all (homeserver, Discord, LiveKit are all public).ESTABLISHEDonly, so these never trip the connection envelope (#135-adjacent alert of 2026-09-28 was real sockets from concurrent calls, envelope raised to 90 in7e9fe17).Verification recipe
Cheapest first experiment: option 2 (
RestrictNetworkInterfaces=) on staging, confirm audio + empty SYN-SENT, then decide whether option 1 is worth pursuing.