ICE: surface bot's local candidates + offer host-network compose mode - #15
Merged
Merged
Conversation
Live trace shows ICE failing because the bot only ever advertised one
local candidate - its docker bridge IP `172.18.0.5`. No srflx, no
relay. Without an externally-reachable candidate the viewer's TS6
client has nowhere to NAT-punch back to:
Connection(0) Check CandidatePair(('172.18.0.5', 49510) ->
('192.168.178.29', 52891)) FAILED
Connection(0) Check CandidatePair(('172.18.0.5', 49510) ->
('87.79.86.36', 52891)) FAILED
STUN/TURN responses are getting eaten by the docker bridge NAT.
Two changes:
1. New `stream_publisher.local_ice_candidates` log right before
sending the offer. Lists what aiortc actually produced after
gathering completed (kind = host / srflx / relay, ip+port). Makes
"STUN didn't help" diagnosable in one line instead of inferring it
from absence-of-evidence in the connection-check trace.
2. docker-compose: split into two profiles.
- "bridge" (default, profile="" too so existing `docker compose up`
still works unchanged): original ts6-net bridge networking.
- "host": same image, but `network_mode: host`. Bot sees the host's
real LAN/WAN interface, STUN actually returns a usable srflx,
viewers can reach back. Operator picks which one fits their
deployment via `docker compose --profile host up -d`.
ruff + mypy strict + 219 tests pass.
https://claude.ai/code/session_016DuCjRJK995Tj9aDhhB9at
queueeee
pushed a commit
that referenced
this pull request
May 1, 2026
PR #15's compose-profiles approach broke `docker compose up -d`: putting profiles=["", "bridge"] on the default service made the empty-string profile NOT match no-profile invocations, so the user got no logs after running their normal commands. Cleaner pattern: docker-compose.yml stays single-service (default bridge, identical to pre-#15 behaviour), and docker-compose.host.yml is an override layered on top via -f when the operator needs host networking for ICE. docker compose up -d # bridge docker compose -f docker-compose.yml \ -f docker-compose.host.yml up -d # host The overlay uses Compose v2's `!reset null` to drop the bridge attachment, the ts6-net network section, and the port mapping; then `network_mode: host` puts the bot in the host namespace. ruff + 219 tests still green. https://claude.ai/code/session_016DuCjRJK995Tj9aDhhB9at
2 of 4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Diagnose aus dem letzten Live-Lauf
ICE-Race-Fix von PR #14 ist drin und hat gefruchtet — keine "addIceCandidate without remote description" Warnings mehr. Aber ICE schlägt trotzdem fehl, jetzt offen sichtbar:
Der Bot hat nur EINE lokale Candidate-IP:
172.18.0.5. Das ist die Docker-Bridge-IP. Kein srflx-Candidate vom STUN, keine Relay-Candidate vom TURN. Ohne externe Adresse kann der Viewer nicht zurückreichen, NAT-Punch ist mangels öffentlicher Bot-Adresse nicht möglich.Root cause: Die Docker-Bridge-NAT-Schicht frisst STUN-Responses oder verbiegt sie so, dass aiortc keine srflx baut.
Was dieser PR macht
Zwei Sachen:
stream_publisher.local_ice_candidatesLog vor dem Senden der Offer-SDP — listet alle Bot-Candidates die aiortc nach Gathering produziert hat. Zeile zeigtkinds=['host']versuskinds=['host', 'srflx']versus mit Relay → in einem Log sehen ob STUN/TURN was beigetragen haben.docker-compose Profiles — bridge (default, alles wie vorher) vs. host:
Beide nutzen das gleiche Image. Der
host-Modus eliminiert die Docker-Bridge-NAT — Bot sieht die echte Host-LAN/WAN-IP, STUN funktioniert, Viewer können den Bot direkt erreichen.Trade-off
hostvsbridgets6-netDocker-Netzwerk127.0.0.1oder LAN-IPOperator commands
Wechsel auf host-mode:
Wenn der host-Mode aus irgendeinem Grund nicht geht (z.B. Host-Port 8080 belegt, TS6-Server nicht via 127.0.0.1 erreichbar): dann TURN aktivieren statt host-mode. In
.env:Dann
docker compose down && docker compose up -d. Im neuen Lauf solltelocal_ice_candidates kinds=['host', 'relay']erscheinen und das Relay durchpeitschen.Test plan
ruff check src tests— cleanmypy --strict— cleanpytest— 219 passedlocal_ice_candidates kinds=['host', 'srflx'](mind. host+srflx).Wenn nur
kinds=['host']weiterhin → TURN nötig.https://claude.ai/code/session_016DuCjRJK995Tj9aDhhB9at
Generated by Claude Code