fix(media-server): skip DTLS HelloVerify for inbound to accept Meta's first ClientHello

Inbound calls started failing DTLS again with state=failed ~2.5s after
ICE connected. Keeping DTLSRoleServer (so Meta is the DTLS client and
only starts ClientHello after it has our fingerprint) but adding
SetDTLSInsecureSkipHelloVerify(true) — Meta's WhatsApp calling stack
loses cookies between retransmits on occasion, and skipping HelloVerify
lets pion accept the first ClientHello in one round-trip rather than
rejecting the retry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tanmay Deep Sharma
2026-04-21 20:06:53 +07:00
co-authored by Claude Opus 4.7
parent 14869693d2
commit 07e8c8381c
@@ -48,14 +48,13 @@ func NewMetaPeer(cfg *config.Config, sdpOffer string, iceServers []webrtc.ICESer
return nil, "", fmt.Errorf("set UDP port range: %w", err)
}
// For inbound WhatsApp calls we answer Meta's offer. Meta doesn't know our
// DTLS fingerprint until Rails delivers the answer via pre_accept_call /
// accept_call, which happens ~1 second *after* pion is ready to handshake.
// If we're the DTLS client (pion default when remote is actpass) we blast
// ClientHello at Meta before Meta is listening for us; Meta responds with a
// fatal alert and pion closes the PC. Flip the roles so *we* are the DTLS
// server: Meta becomes the client and only starts ClientHello after it has
// our fingerprint, so the handshake happens in the right order.
// For inbound calls Meta doesn't know our DTLS fingerprint until Rails
// delivers the SDP answer via pre_accept_call / accept_call — which runs
// *after* create_session returns. If we're DTLS client (pion default when
// remote is actpass) we'd send ClientHello before Meta is listening for
// us and Meta would respond with a fatal alert. Answer as server so Meta
// becomes the client and only starts ClientHello after it has our
// fingerprint.
if err := se.SetAnsweringDTLSRole(webrtc.DTLSRoleServer); err != nil {
return nil, "", fmt.Errorf("set answering DTLS role: %w", err)
}
@@ -63,6 +62,11 @@ func NewMetaPeer(cfg *config.Config, sdpOffer string, iceServers []webrtc.ICESer
return context.WithTimeout(context.Background(), 30*time.Second)
})
se.SetDTLSRetransmissionInterval(200 * time.Millisecond)
// Skip the HelloVerify round-trip on the server side. Meta's WhatsApp
// calling stack sometimes uses stateless retry which loses the pion
// cookie between retransmits; skipping it makes the server accept the
// ClientHello in one go.
se.SetDTLSInsecureSkipHelloVerify(true)
// If a public IP is configured, use NAT1To1 so ICE candidates advertise
// the correct address instead of a private Docker/container IP.