fix(media-server): give inbound DTLS enough time for Meta to catch up

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) <noreply@anthropic.com>
This commit is contained in:
Tanmay Deep Sharma
2026-04-21 18:12:32 +07:00
co-authored by Claude Opus 4.7
parent a723fbe455
commit a08444862b
@@ -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()
}