package ice
import (
"net"
"time"
"github.com/pion/logging"
"github.com/pion/stun/v3"
)
type pairCandidateSelector interface {
Start()
ContactCandidates()
PingCandidate(local, remote Candidate )
HandleSuccessResponse(m *stun .Message , local, remote Candidate , remoteAddr net .Addr )
HandleBindingRequest(m *stun .Message , local, remote Candidate )
}
type controllingSelector struct {
startTime time .Time
agent *Agent
nominatedPair *CandidatePair
log logging .LeveledLogger
}
func (s *controllingSelector ) Start () {
s .startTime = time .Now ()
s .nominatedPair = nil
}
func (s *controllingSelector ) isNominatable (c Candidate ) bool {
switch {
case c .Type () == CandidateTypeHost :
return time .Since (s .startTime ).Nanoseconds () > s .agent .hostAcceptanceMinWait .Nanoseconds ()
case c .Type () == CandidateTypeServerReflexive :
return time .Since (s .startTime ).Nanoseconds () > s .agent .srflxAcceptanceMinWait .Nanoseconds ()
case c .Type () == CandidateTypePeerReflexive :
return time .Since (s .startTime ).Nanoseconds () > s .agent .prflxAcceptanceMinWait .Nanoseconds ()
case c .Type () == CandidateTypeRelay :
return time .Since (s .startTime ).Nanoseconds () > s .agent .relayAcceptanceMinWait .Nanoseconds ()
}
s .log .Errorf ("Invalid candidate type: %s" , c .Type ())
return false
}
func (s *controllingSelector ) ContactCandidates () {
switch {
case s .agent .getSelectedPair () != nil :
if s .agent .validateSelectedPair () {
s .log .Trace ("Checking keepalive" )
s .agent .checkKeepalive ()
}
case s .nominatedPair != nil :
s .nominatePair (s .nominatedPair )
default :
p := s .agent .getBestValidCandidatePair ()
if p != nil && s .isNominatable (p .Local ) && s .isNominatable (p .Remote ) {
s .log .Tracef ("Nominatable pair found, nominating (%s, %s)" , p .Local , p .Remote )
p .nominated = true
s .nominatedPair = p
s .nominatePair (p )
return
}
s .agent .pingAllCandidates ()
}
}
func (s *controllingSelector ) nominatePair (pair *CandidatePair ) {
msg , err := stun .Build (stun .BindingRequest , stun .TransactionID ,
stun .NewUsername (s .agent .remoteUfrag +":" +s .agent .localUfrag ),
UseCandidate (),
AttrControlling (s .agent .tieBreaker ),
PriorityAttr (pair .Local .Priority ()),
stun .NewShortTermIntegrity (s .agent .remotePwd ),
stun .Fingerprint ,
)
if err != nil {
s .log .Error (err .Error())
return
}
s .log .Tracef ("Ping STUN (nominate candidate pair) from %s to %s" , pair .Local , pair .Remote )
s .agent .sendBindingRequest (msg , pair .Local , pair .Remote )
}
func (s *controllingSelector ) HandleBindingRequest (message *stun .Message , local , remote Candidate ) {
s .agent .sendBindingSuccess (message , local , remote )
pair := s .agent .findPair (local , remote )
if pair == nil {
pair = s .agent .addPair (local , remote )
pair .UpdateRequestReceived ()
return
}
pair .UpdateRequestReceived ()
if pair .state == CandidatePairStateSucceeded && s .nominatedPair == nil && s .agent .getSelectedPair () == nil {
bestPair := s .agent .getBestAvailableCandidatePair ()
if bestPair == nil {
s .log .Tracef ("No best pair available" )
} else if bestPair .equal (pair ) && s .isNominatable (pair .Local ) && s .isNominatable (pair .Remote ) {
s .log .Tracef (
"The candidate (%s, %s) is the best candidate available, marking it as nominated" ,
pair .Local ,
pair .Remote ,
)
s .nominatedPair = pair
s .nominatePair (pair )
}
}
if s .agent .userBindingRequestHandler != nil {
if shouldSwitch := s .agent .userBindingRequestHandler (message , local , remote , pair ); shouldSwitch {
s .agent .setSelectedPair (pair )
}
}
}
func (s *controllingSelector ) HandleSuccessResponse (m *stun .Message , local , remote Candidate , remoteAddr net .Addr ) {
ok , pendingRequest , rtt := s .agent .handleInboundBindingSuccess (m .TransactionID )
if !ok {
s .log .Warnf ("Discard message from (%s), unknown TransactionID 0x%x" , remote , m .TransactionID )
return
}
transactionAddr := pendingRequest .destination
if !addrEqual (transactionAddr , remoteAddr ) {
s .log .Debugf (
"Discard message: transaction source and destination does not match expected(%s), actual(%s)" ,
transactionAddr ,
remote ,
)
return
}
s .log .Tracef ("Inbound STUN (SuccessResponse) from %s to %s" , remote , local )
pair := s .agent .findPair (local , remote )
if pair == nil {
s .log .Error ("Success response from invalid candidate pair" )
return
}
pair .state = CandidatePairStateSucceeded
s .log .Tracef ("Found valid candidate pair: %s" , pair )
if pendingRequest .isUseCandidate && s .agent .getSelectedPair () == nil {
s .agent .setSelectedPair (pair )
}
pair .UpdateRoundTripTime (rtt )
}
func (s *controllingSelector ) PingCandidate (local , remote Candidate ) {
msg , err := stun .Build (stun .BindingRequest , stun .TransactionID ,
stun .NewUsername (s .agent .remoteUfrag +":" +s .agent .localUfrag ),
AttrControlling (s .agent .tieBreaker ),
PriorityAttr (local .Priority ()),
stun .NewShortTermIntegrity (s .agent .remotePwd ),
stun .Fingerprint ,
)
if err != nil {
s .log .Error (err .Error())
return
}
s .agent .sendBindingRequest (msg , local , remote )
}
type controlledSelector struct {
agent *Agent
log logging .LeveledLogger
}
func (s *controlledSelector ) Start () {
}
func (s *controlledSelector ) ContactCandidates () {
if s .agent .getSelectedPair () != nil {
if s .agent .validateSelectedPair () {
s .log .Trace ("Checking keepalive" )
s .agent .checkKeepalive ()
}
} else {
s .agent .pingAllCandidates ()
}
}
func (s *controlledSelector ) PingCandidate (local , remote Candidate ) {
msg , err := stun .Build (stun .BindingRequest , stun .TransactionID ,
stun .NewUsername (s .agent .remoteUfrag +":" +s .agent .localUfrag ),
AttrControlled (s .agent .tieBreaker ),
PriorityAttr (local .Priority ()),
stun .NewShortTermIntegrity (s .agent .remotePwd ),
stun .Fingerprint ,
)
if err != nil {
s .log .Error (err .Error())
return
}
s .agent .sendBindingRequest (msg , local , remote )
}
func (s *controlledSelector ) HandleSuccessResponse (m *stun .Message , local , remote Candidate , remoteAddr net .Addr ) {
ok , pendingRequest , rtt := s .agent .handleInboundBindingSuccess (m .TransactionID )
if !ok {
s .log .Warnf ("Discard message from (%s), unknown TransactionID 0x%x" , remote , m .TransactionID )
return
}
transactionAddr := pendingRequest .destination
if !addrEqual (transactionAddr , remoteAddr ) {
s .log .Debugf (
"Discard message: transaction source and destination does not match expected(%s), actual(%s)" ,
transactionAddr ,
remote ,
)
return
}
s .log .Tracef ("Inbound STUN (SuccessResponse) from %s to %s" , remote , local )
pair := s .agent .findPair (local , remote )
if pair == nil {
s .log .Error ("Success response from invalid candidate pair" )
return
}
pair .state = CandidatePairStateSucceeded
s .log .Tracef ("Found valid candidate pair: %s" , pair )
if pair .nominateOnBindingSuccess {
if selectedPair := s .agent .getSelectedPair (); selectedPair == nil ||
(selectedPair != pair &&
(!s .agent .needsToCheckPriorityOnNominated () || selectedPair .priority () <= pair .priority ())) {
s .agent .setSelectedPair (pair )
} else if selectedPair != pair {
s .log .Tracef ("Ignore nominate new pair %s, already nominated pair %s" , pair , selectedPair )
}
}
pair .UpdateRoundTripTime (rtt )
}
func (s *controlledSelector ) HandleBindingRequest (message *stun .Message , local , remote Candidate ) {
pair := s .agent .findPair (local , remote )
if pair == nil {
pair = s .agent .addPair (local , remote )
}
pair .UpdateRequestReceived ()
if message .Contains (stun .AttrUseCandidate ) {
if pair .state == CandidatePairStateSucceeded {
selectedPair := s .agent .getSelectedPair ()
if selectedPair == nil ||
(selectedPair != pair &&
(!s .agent .needsToCheckPriorityOnNominated () ||
selectedPair .priority () <= pair .priority ())) {
s .agent .setSelectedPair (pair )
} else if selectedPair != pair {
s .log .Tracef ("Ignore nominate new pair %s, already nominated pair %s" , pair , selectedPair )
}
} else {
pair .nominateOnBindingSuccess = true
}
}
s .agent .sendBindingSuccess (message , local , remote )
s .PingCandidate (local , remote )
if s .agent .userBindingRequestHandler != nil {
if shouldSwitch := s .agent .userBindingRequestHandler (message , local , remote , pair ); shouldSwitch {
s .agent .setSelectedPair (pair )
}
}
}
type liteSelector struct {
pairCandidateSelector
}
func (s *liteSelector ) ContactCandidates () {
if _ , ok := s .pairCandidateSelector .(*controllingSelector ); ok {
s .pairCandidateSelector .ContactCandidates ()
} else if v , ok := s .pairCandidateSelector .(*controlledSelector ); ok {
v .agent .validateSelectedPair ()
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .