diff --git a/pkg/rtc/transport.go b/pkg/rtc/transport.go index dd27aa4b9..350fffe43 100644 --- a/pkg/rtc/transport.go +++ b/pkg/rtc/transport.go @@ -421,6 +421,9 @@ func newPeerConnection( // As Firefox does not support migration, ICE Lite can be disabled. se.SetLite(false) } + // ICE checks already prove the remote address, so the HelloVerifyRequest cookie only adds a round trip. + // pion applies this only when this side ends up as the DTLS server. + se.SetDTLSInsecureSkipHelloVerify(true) se.SetDTLSRetransmissionInterval(dtlsRetransmissionInterval) se.SetDTLSConnectContextMaker(func() (context.Context, func()) { return context.WithTimeout(context.Background(), dtlsHandshakeTimeout) diff --git a/pkg/rtc/transport_test.go b/pkg/rtc/transport_test.go index 3eeef2d5c..a2654ac09 100644 --- a/pkg/rtc/transport_test.go +++ b/pkg/rtc/transport_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/pion/dtls/v3/pkg/protocol/handshake" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v4" "github.com/stretchr/testify/require" @@ -251,6 +252,78 @@ func TestFirstNegotiationUsesFastDebounce(t *testing.T) { } } +func TestDTLSHandshakeSkipsHelloVerify(t *testing.T) { + // the SFU offers as on the subscriber connection, and a plain pion peer answers active like a browser, + // so the SFU is the DTLS server and only the peer sends ClientHello + handler := &transportfakes.FakeHandler{} + sfu, err := NewPCTransport(TransportParams{Config: &WebRTCConfig{}, IsOfferer: true, Handler: handler}) + require.NoError(t, err) + defer sfu.Close() + _, err = sfu.pc.CreateDataChannel(ReliableDataChannel, nil) + require.NoError(t, err) + + var clientHellos atomic.Int32 + se := webrtc.SettingEngine{} + require.NoError(t, se.SetAnsweringDTLSRole(webrtc.DTLSRoleClient)) + se.SetDTLSClientHelloMessageHook(func(m handshake.MessageClientHello) handshake.Message { + clientHellos.Inc() + return &m + }) + client, err := webrtc.NewAPI(webrtc.WithSettingEngine(se)).NewPeerConnection(webrtc.Configuration{}) + require.NoError(t, err) + defer client.Close() + + var lock sync.Mutex + var pending []webrtc.ICECandidateInit + hasOffer := false + handler.OnICECandidateCalls(func(c *webrtc.ICECandidate, _ livekit.SignalTarget) error { + if c == nil { + return nil + } + lock.Lock() + defer lock.Unlock() + if !hasOffer { + pending = append(pending, c.ToJSON()) + return nil + } + return client.AddICECandidate(c.ToJSON()) + }) + client.OnICECandidate(func(c *webrtc.ICECandidate) { + if c != nil { + sfu.AddICECandidate(c.ToJSON()) + } + }) + handler.OnOfferCalls(func(offer webrtc.SessionDescription, offerId uint32, _ map[string]string) error { + lock.Lock() + if err := client.SetRemoteDescription(offer); err != nil { + lock.Unlock() + return err + } + hasOffer = true + for _, c := range pending { + _ = client.AddICECandidate(c) + } + lock.Unlock() + + answer, err := client.CreateAnswer(nil) + if err != nil { + return err + } + if err := client.SetLocalDescription(answer); err != nil { + return err + } + return sfu.HandleRemoteDescription(answer, offerId) + }) + sfu.Negotiate(true) + + require.Eventually(t, func() bool { + return sfu.IsEstablished() && client.ConnectionState() == webrtc.PeerConnectionStateConnected + }, 10*time.Second, 10*time.Millisecond, "transports did not connect") + + // a HelloVerifyRequest from the SFU makes the peer send its ClientHello a second time, with the cookie + require.Equal(t, int32(1), clientHellos.Load()) +} + func TestFirstOfferMissedDuringICERestart(t *testing.T) { params := TransportParams{ Config: &WebRTCConfig{},