fix(dev): run the dev stack on the host network so inbound SIP arrives - #374
Merged
Conversation
Ein registrierendes SIP-Konto kommt durch NAT nach draußen: der REGISTER geht raus, die Antwort kommt über dieselbe Bindung zurück, das Konto steht auf Up. Ein eingehender INVITE ist aber eine neue Transaktion, und dafür müsste die Gegenstelle die Adresse erreichen, die wir ihr als Contact genannt haben — im Bridge-Netz eine Container-IP, die es außerhalb von Docker nicht gibt. Es klingelt nie, und im Log steht nichts, weil nie etwas ankommt. Ports zu mappen reicht nicht: RTP wählt seine Ports dynamisch, ein durchgereichter INVITE ergäbe ein stummes Gespräch. Coturn steht aus demselben Grund im Host-Netz. Was daran hängt: Ohne Portmapping bindet der Host direkt auf DEV_CALLORA_PORT, und der Compose-Name "postgres" löst nicht mehr auf — die Datenbank wird über localhost erreicht. host.docker.internal gibt es im Host-Netz auch nicht mehr, also nutzen beide ICE-Agenten dieselbe Adresse, die Coturn als Relay-IP ausgibt. Die Frontdoor bleibt im Bridge-Netz, damit ihr Portmapping auf 8080 weiter das tut, wofür es da ist — sie erreicht den Host jetzt über das Gateway statt über den Compose-Namen, der von dort nicht mehr auflöst. Ohne das wäre die optionale Frontdoor ab dieser Änderung kaputt gewesen, und zwar erst beim Benutzen. DEV_MEDIA_IP ist neu und hat bewusst 0.0.0.0 als Default: das funktioniert überall, kostet aber Zeit. Der Agent sammelt dann einen Host-Kandidaten je Interface — hier vier Docker-Bridges plus LAN plus Loopback —, der Browser bringt seine eigenen mit, Chrome staffelt die Prüfungen mit ~50 ms Abstand, und aus mehreren Dutzend Paaren werden Sekunden bis zur Nominierung (gemessen: 2 s bis ICE, 2 s bis DTLS, bei 265 ms für die gesamte Signalisierung davor). Die LAN-Adresse gehört deshalb in die .env und nicht als Default ins Repository: dort wäre sie die Adresse genau eines Rechners und bei jedem anderen ein Mediensocket, der an nichts bindet. Linux only — auf macOS und Windows gibt es kein echtes Host-Netz. Beide Zusammenstellungen validiert: docker compose config, mit und ohne Frontdoor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011iSX3geDv7PNRmiusRr1xd
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.
A registering SIP account gets out through NAT: the REGISTER leaves, the reply returns over the same binding, the account shows Up. An inbound INVITE is a new transaction, and for that the far end has to reach the address we gave it as Contact — on the bridge network a container IP that does not exist outside Docker. So it never rings, and nothing appears in the log, because nothing ever arrives.
Mapping ports does not help: RTP picks its ports dynamically, so even a forwarded INVITE would produce a silent call. Coturn already sits on the host network for the same reason.
What hangs off that
DEV_CALLORA_PORT(ASPNETCORE_URLS)postgresno longer resolves — the database is reached overlocalhosthost.docker.internalTwo things this PR fixes beyond the switch itself
The frontdoor would have broken — and only when used. It proxies to the compose service name
callora-backend:5000, which does not resolve from a bridge-network container once the backend is on the host network. It stays on the bridge (so its8080port mapping keeps doing its job) and reaches the host through the gateway instead.DEV_MEDIA_IPdefaults to0.0.0.0, not to a LAN address. Binding to all interfaces works everywhere and costs time: the agent then gathers one host candidate per interface — four Docker bridges plus LAN plus loopback here — the browser brings its own, Chrome paces its checks ~50 ms apart, and several dozen pairs turn into seconds before nomination. Measured: 2 s to ICE, 2 s to DTLS, against 265 ms for all the signalling before it. So the fast path is a.enventry, documented in.env.example; a LAN address checked into the repository would be one machine's address and, on every other, a media socket that binds to nothing.Verification
docker compose configpasses with and without the frontdoor overlay, and the nested${BACKEND_UPSTREAM:-host.docker.internal:${DEV_CALLORA_PORT:-5000}}resolves as intended.