diff --git a/gather.go b/gather.go index 73359de8..d5315d7a 100644 --- a/gather.go +++ b/gather.go @@ -255,6 +255,7 @@ func (a *Agent) gatherCandidatesLocalUDPMux(ctx context.Context) error { //nolin } localAddresses := a.udpMux.GetListenAddresses() + configs := make(map[CandidateHostConfig]struct{}) for _, addr := range localAddresses { udpAddr, ok := addr.(*net.UDPAddr) @@ -272,10 +273,6 @@ func (a *Agent) gatherCandidatesLocalUDPMux(ctx context.Context) error { //nolin candidateIP = mappedIP } - conn, err := a.udpMux.GetConn(a.localUfrag, udpAddr) - if err != nil { - return err - } hostConfig := CandidateHostConfig{ Network: udp, Address: candidateIP.String(), @@ -283,6 +280,20 @@ func (a *Agent) gatherCandidatesLocalUDPMux(ctx context.Context) error { //nolin Component: ComponentRTP, } + // Detect a duplicate candidate before calling addCandidate(). + // otherwise, addCandidate() detects the duplicate candidate + // and close its connection, invalidating all candidates + // that share the same connection. + if _, ok := configs[hostConfig]; ok { + continue + } + configs[hostConfig] = struct{}{} + + conn, err := a.udpMux.GetConn(a.localUfrag, udpAddr) + if err != nil { + return err + } + c, err := NewCandidateHost(&hostConfig) if err != nil { closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create host mux candidate: %s %d: %v", candidateIP, udpAddr.Port, err)) diff --git a/gather_test.go b/gather_test.go index 448a34ed..8cbef5d7 100644 --- a/gather_test.go +++ b/gather_test.go @@ -599,6 +599,51 @@ func TestTURNProxyDialer(t *testing.T) { assert.NoError(t, a.Close()) } +// TestUDPMuxDefaultWithNAT1To1IPsUsage asserts that candidates +// are given and connections are valid when using UDPMuxDefault and NAT1To1IPs. +func TestUDPMuxDefaultWithNAT1To1IPsUsage(t *testing.T) { + report := test.CheckRoutines(t) + defer report() + + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + conn, err := net.ListenPacket("udp4", ":0") + assert.NoError(t, err) + defer func() { + _ = conn.Close() + }() + + mux := NewUDPMuxDefault(UDPMuxParams{ + UDPConn: conn, + }) + defer func() { + _ = mux.Close() + }() + + a, err := NewAgent(&AgentConfig{ + NAT1To1IPs: []string{"1.2.3.4"}, + NAT1To1IPCandidateType: CandidateTypeHost, + UDPMux: mux, + }) + assert.NoError(t, err) + + gatherCandidateDone := make(chan struct{}) + assert.NoError(t, a.OnCandidate(func(c Candidate) { + if c == nil { + close(gatherCandidateDone) + } else { + assert.Equal(t, "1.2.3.4", c.Address()) + } + })) + assert.NoError(t, a.GatherCandidates()) + <-gatherCandidateDone + + assert.NotEqual(t, 0, len(mux.connsIPv4)) + + assert.NoError(t, a.Close()) +} + // Assert that candidates are given for each mux in a MultiUDPMux func TestMultiUDPMuxUsage(t *testing.T) { report := test.CheckRoutines(t)