diff --git a/agent.go b/agent.go index 571cd5b..a268382 100644 --- a/agent.go +++ b/agent.go @@ -15,8 +15,6 @@ import ( "sync/atomic" "time" - atomicx "github.com/pion/ice/v3/internal/atomic" - stunx "github.com/pion/ice/v3/internal/stun" "github.com/pion/logging" "github.com/pion/mdns" "github.com/pion/stun/v2" @@ -25,6 +23,9 @@ import ( "github.com/pion/transport/v3/stdnet" "github.com/pion/transport/v3/vnet" "golang.org/x/net/proxy" + + atomicx "github.com/pion/ice/v3/internal/atomic" + stunx "github.com/pion/ice/v3/internal/stun" ) type bindingRequest struct { @@ -43,6 +44,7 @@ type Agent struct { onConnectionStateChangeHdlr atomic.Value // func(ConnectionState) onSelectedCandidatePairChangeHdlr atomic.Value // func(Candidate, Candidate) onCandidateHdlr atomic.Value // func(Candidate) + onSuccessfulBindingResponseHdlr atomic.Value // func(Candidate) // State owned by the taskLoop onConnected chan struct{} @@ -648,7 +650,8 @@ func (a *Agent) checkKeepalive() { if (a.keepaliveInterval != 0) && ((time.Since(selectedPair.Local.LastSent()) > a.keepaliveInterval) || - (time.Since(selectedPair.Remote.LastReceived()) > a.keepaliveInterval)) { + (time.Since(selectedPair.Remote.LastReceived()) > a.keepaliveInterval) || + (time.Since(selectedPair.lastBindingRequest) > a.keepaliveInterval)) { // We use binding request instead of indication to support refresh consent schemas // see https://tools.ietf.org/html/rfc7675 a.selector.PingCandidate(selectedPair.Local, selectedPair.Remote) @@ -991,6 +994,9 @@ func (a *Agent) sendBindingRequest(m *stun.Message, local, remote Candidate) { isUseCandidate: m.Contains(stun.AttrUseCandidate), }) + p := a.findPair(local, remote) + p.markBindingRequest(m.TransactionID) + a.sendSTUN(m, local, remote) } @@ -1174,7 +1180,7 @@ func (a *Agent) GetSelectedCandidatePair() (*CandidatePair, error) { return nil, err } - return &CandidatePair{Local: local, Remote: remote}, nil + return &CandidatePair{Local: local, Remote: remote, latency: selectedPair.Latency()}, nil } func (a *Agent) getSelectedPair() *CandidatePair { diff --git a/agent_handlers.go b/agent_handlers.go index c5a5ec0..427b438 100644 --- a/agent_handlers.go +++ b/agent_handlers.go @@ -23,6 +23,12 @@ func (a *Agent) OnCandidate(f func(Candidate)) error { return nil } +// OnSuccessfulBindingResponse sets a handler that is fired when a successful binding response is received +func (a *Agent) OnSuccessfulBindingResponse(f func(*CandidatePair)) error { + a.onSuccessfulBindingResponseHdlr.Store(f) + return nil +} + func (a *Agent) onSelectedCandidatePairChange(p *CandidatePair) { if h, ok := a.onSelectedCandidatePairChangeHdlr.Load().(func(Candidate, Candidate)); ok { h(p.Local, p.Remote) @@ -41,6 +47,12 @@ func (a *Agent) onConnectionStateChange(s ConnectionState) { } } +func (a *Agent) onSuccessfulBindingResponse(p *CandidatePair) { + if h, ok := a.onSuccessfulBindingResponseHdlr.Load().(func(*CandidatePair)); ok { + h(p) + } +} + func (a *Agent) candidatePairRoutine() { for p := range a.chanCandidatePair { a.onSelectedCandidatePairChange(p) diff --git a/candidatepair.go b/candidatepair.go index 93470fe..4ebd56c 100644 --- a/candidatepair.go +++ b/candidatepair.go @@ -5,6 +5,7 @@ package ice import ( "fmt" + "time" "github.com/pion/stun/v2" ) @@ -24,6 +25,9 @@ type CandidatePair struct { iceRoleControlling bool Remote Candidate Local Candidate + latency time.Duration + lastBindingRequest time.Time + lastBindingTransactionID [12]byte bindingRequestCount uint16 state CandidatePairState nominated bool @@ -100,3 +104,20 @@ func (a *Agent) sendSTUN(msg *stun.Message, local, remote Candidate) { a.log.Tracef("Failed to send STUN message: %s", err) } } + +func (p *CandidatePair) markBindingRequest(transactionID [12]byte) { + p.lastBindingRequest = time.Now() + p.lastBindingTransactionID = transactionID +} + +func (p *CandidatePair) markBindingResponse(transactionID [12]byte) { + if p.lastBindingRequest.IsZero() || transactionID != p.lastBindingTransactionID { + return + } + + p.latency = time.Since(p.lastBindingRequest) +} + +func (p *CandidatePair) Latency() time.Duration { + return p.latency +} diff --git a/selection.go b/selection.go index e6e1fac..53b4b36 100644 --- a/selection.go +++ b/selection.go @@ -139,6 +139,9 @@ func (s *controllingSelector) HandleSuccessResponse(m *stun.Message, local, remo return } + p.markBindingResponse(m.TransactionID) + s.agent.onSuccessfulBindingResponse(p) + p.state = CandidatePairStateSucceeded s.log.Tracef("Found valid candidate pair: %s", p) if pendingRequest.isUseCandidate && s.agent.getSelectedPair() == nil { @@ -230,6 +233,9 @@ func (s *controlledSelector) HandleSuccessResponse(m *stun.Message, local, remot return } + p.markBindingResponse(m.TransactionID) + s.agent.onSuccessfulBindingResponse(p) + p.state = CandidatePairStateSucceeded s.log.Tracef("Found valid candidate pair: %s", p) if p.nominateOnBindingSuccess {