From a08444862b5492cec239cdca82f9cf32fa3da765 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Tue, 21 Apr 2026 18:12:32 +0700 Subject: [PATCH] fix(media-server): give inbound DTLS enough time for Meta to catch up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inbound calls consistently died at ~2s with the Meta PC going straight from connecting → closed (never reaching connected). Root cause: pion starts the DTLS handshake the moment we SetLocalDescription during create_session, but Rails only delivers our SDP answer to Meta via pre_accept_call / accept_call afterwards. During the ~1s gap Meta has no fingerprint for us and drops our ClientHello. pion's default DTLS retransmission window lapsed before Meta was ready to respond. - Bump the DTLS connect context to a 30s timeout so retries keep going until Meta has our credentials. - Shorten the retransmission interval to 200ms so a retry lands quickly once Meta is listening. - Log DTLS transport state and the stack trace for explicit pc.Close() so the next regression is easy to diagnose. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../media-server/internal/peer/meta_peer.go | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/enterprise/media-server/internal/peer/meta_peer.go b/enterprise/media-server/internal/peer/meta_peer.go index 7544b12da..5592ad4b3 100644 --- a/enterprise/media-server/internal/peer/meta_peer.go +++ b/enterprise/media-server/internal/peer/meta_peer.go @@ -3,9 +3,12 @@ package peer import ( + "context" "fmt" "log/slog" + "runtime/debug" "sync" + "time" "github.com/pion/interceptor" "github.com/pion/webrtc/v4" @@ -45,6 +48,17 @@ func NewMetaPeer(cfg *config.Config, sdpOffer string, iceServers []webrtc.ICESer return nil, "", fmt.Errorf("set UDP port range: %w", err) } + // Meta's side finishes setting up our DTLS credentials only *after* Rails + // calls pre_accept_call / accept_call on the Graph API. For inbound calls + // that happens ~1 second after we've already begun the DTLS handshake, so + // our first ClientHello is dropped. Extend the handshake window to 30s and + // tighten the retransmission interval so a later retry lands once Meta is + // ready. + se.SetDTLSConnectContextMaker(func() (context.Context, func()) { + return context.WithTimeout(context.Background(), 30*time.Second) + }) + se.SetDTLSRetransmissionInterval(200 * time.Millisecond) + // If a public IP is configured, use NAT1To1 so ICE candidates advertise // the correct address instead of a private Docker/container IP. // NOTE: In pion/webrtc v4.2+, migrate to SetICEAddressRewriteRules. @@ -154,6 +168,14 @@ func NewMetaPeer(cfg *config.Config, sdpOffer string, iceServers []webrtc.ICESer slog.Debug("meta peer: signaling state changed", "state", state.String()) }) + // Also log DTLS transport state — failure here is the likely cause of the + // inbound "closed immediately after ICE connected" symptom. + if t := pc.SCTP().Transport(); t != nil { + t.OnStateChange(func(state webrtc.DTLSTransportState) { + slog.Info("meta peer: DTLS state changed", "state", state.String()) + }) + } + // Perform SDP negotiation based on call direction. var sdpResult string if sdpOffer != "" { @@ -261,6 +283,6 @@ func (mp *MetaPeer) Close() error { } mp.closed = true - slog.Info("meta peer: closing peer connection") + slog.Info("meta peer: closing peer connection (explicit)", "stack", string(debug.Stack())) return mp.pc.Close() }