selection.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. package ice
  2. import (
  3. "net"
  4. "time"
  5. "github.com/pion/logging"
  6. "github.com/pion/stun"
  7. )
  8. type pairCandidateSelector interface {
  9. Start()
  10. ContactCandidates()
  11. PingCandidate(local, remote Candidate)
  12. HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr)
  13. HandleBindingRequest(m *stun.Message, local, remote Candidate)
  14. }
  15. type controllingSelector struct {
  16. startTime time.Time
  17. agent *Agent
  18. nominatedPair *CandidatePair
  19. log logging.LeveledLogger
  20. }
  21. func (s *controllingSelector) Start() {
  22. s.startTime = time.Now()
  23. s.nominatedPair = nil
  24. }
  25. func (s *controllingSelector) isNominatable(c Candidate) bool {
  26. switch {
  27. case c.Type() == CandidateTypeHost:
  28. return time.Since(s.startTime).Nanoseconds() > s.agent.hostAcceptanceMinWait.Nanoseconds()
  29. case c.Type() == CandidateTypeServerReflexive:
  30. return time.Since(s.startTime).Nanoseconds() > s.agent.srflxAcceptanceMinWait.Nanoseconds()
  31. case c.Type() == CandidateTypePeerReflexive:
  32. return time.Since(s.startTime).Nanoseconds() > s.agent.prflxAcceptanceMinWait.Nanoseconds()
  33. case c.Type() == CandidateTypeRelay:
  34. return time.Since(s.startTime).Nanoseconds() > s.agent.relayAcceptanceMinWait.Nanoseconds()
  35. }
  36. s.log.Errorf("isNominatable invalid candidate type %s", c.Type().String())
  37. return false
  38. }
  39. func (s *controllingSelector) ContactCandidates() {
  40. switch {
  41. case s.agent.getSelectedPair() != nil:
  42. if s.agent.validateSelectedPair() {
  43. s.log.Trace("checking keepalive")
  44. s.agent.checkKeepalive()
  45. }
  46. case s.nominatedPair != nil:
  47. s.nominatePair(s.nominatedPair)
  48. default:
  49. p := s.agent.getBestValidCandidatePair()
  50. if p != nil && s.isNominatable(p.Local) && s.isNominatable(p.Remote) {
  51. s.log.Tracef("Nominatable pair found, nominating (%s, %s)", p.Local.String(), p.Remote.String())
  52. p.nominated = true
  53. s.nominatedPair = p
  54. s.nominatePair(p)
  55. return
  56. }
  57. s.agent.pingAllCandidates()
  58. }
  59. }
  60. func (s *controllingSelector) nominatePair(pair *CandidatePair) {
  61. // The controlling agent MUST include the USE-CANDIDATE attribute in
  62. // order to nominate a candidate pair (Section 8.1.1). The controlled
  63. // agent MUST NOT include the USE-CANDIDATE attribute in a Binding
  64. // request.
  65. msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
  66. stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag),
  67. UseCandidate(),
  68. AttrControlling(s.agent.tieBreaker),
  69. PriorityAttr(pair.Local.Priority()),
  70. stun.NewShortTermIntegrity(s.agent.remotePwd),
  71. stun.Fingerprint,
  72. )
  73. if err != nil {
  74. s.log.Error(err.Error())
  75. return
  76. }
  77. s.log.Tracef("ping STUN (nominate candidate pair) from %s to %s", pair.Local.String(), pair.Remote.String())
  78. s.agent.sendBindingRequest(msg, pair.Local, pair.Remote)
  79. }
  80. func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) {
  81. s.agent.sendBindingSuccess(m, local, remote)
  82. p := s.agent.findPair(local, remote)
  83. if p == nil {
  84. s.agent.addPair(local, remote)
  85. return
  86. }
  87. if p.state == CandidatePairStateSucceeded && s.nominatedPair == nil && s.agent.getSelectedPair() == nil {
  88. bestPair := s.agent.getBestAvailableCandidatePair()
  89. if bestPair == nil {
  90. s.log.Tracef("No best pair available")
  91. } else if bestPair.equal(p) && s.isNominatable(p.Local) && s.isNominatable(p.Remote) {
  92. s.log.Tracef("The candidate (%s, %s) is the best candidate available, marking it as nominated",
  93. p.Local.String(), p.Remote.String())
  94. s.nominatedPair = p
  95. s.nominatePair(p)
  96. }
  97. }
  98. }
  99. func (s *controllingSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) {
  100. ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID)
  101. if !ok {
  102. s.log.Warnf("discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID)
  103. return
  104. }
  105. transactionAddr := pendingRequest.destination
  106. // Assert that NAT is not symmetric
  107. // https://tools.ietf.org/html/rfc8445#section-7.2.5.2.1
  108. if !addrEqual(transactionAddr, remoteAddr) {
  109. s.log.Debugf("discard message: transaction source and destination does not match expected(%s), actual(%s)", transactionAddr, remote)
  110. return
  111. }
  112. s.log.Tracef("inbound STUN (SuccessResponse) from %s to %s", remote.String(), local.String())
  113. p := s.agent.findPair(local, remote)
  114. if p == nil {
  115. // This shouldn't happen
  116. s.log.Error("Success response from invalid candidate pair")
  117. return
  118. }
  119. p.state = CandidatePairStateSucceeded
  120. s.log.Tracef("Found valid candidate pair: %s", p)
  121. if pendingRequest.isUseCandidate && s.agent.getSelectedPair() == nil {
  122. s.agent.setSelectedPair(p)
  123. }
  124. }
  125. func (s *controllingSelector) PingCandidate(local, remote Candidate) {
  126. msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
  127. stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag),
  128. AttrControlling(s.agent.tieBreaker),
  129. PriorityAttr(local.Priority()),
  130. stun.NewShortTermIntegrity(s.agent.remotePwd),
  131. stun.Fingerprint,
  132. )
  133. if err != nil {
  134. s.log.Error(err.Error())
  135. return
  136. }
  137. s.agent.sendBindingRequest(msg, local, remote)
  138. }
  139. type controlledSelector struct {
  140. agent *Agent
  141. log logging.LeveledLogger
  142. }
  143. func (s *controlledSelector) Start() {
  144. }
  145. func (s *controlledSelector) ContactCandidates() {
  146. if s.agent.getSelectedPair() != nil {
  147. if s.agent.validateSelectedPair() {
  148. s.log.Trace("checking keepalive")
  149. s.agent.checkKeepalive()
  150. }
  151. } else {
  152. s.agent.pingAllCandidates()
  153. }
  154. }
  155. func (s *controlledSelector) PingCandidate(local, remote Candidate) {
  156. msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
  157. stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag),
  158. AttrControlled(s.agent.tieBreaker),
  159. PriorityAttr(local.Priority()),
  160. stun.NewShortTermIntegrity(s.agent.remotePwd),
  161. stun.Fingerprint,
  162. )
  163. if err != nil {
  164. s.log.Error(err.Error())
  165. return
  166. }
  167. s.agent.sendBindingRequest(msg, local, remote)
  168. }
  169. func (s *controlledSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) {
  170. // nolint:godox
  171. // TODO according to the standard we should specifically answer a failed nomination:
  172. // https://tools.ietf.org/html/rfc8445#section-7.3.1.5
  173. // If the controlled agent does not accept the request from the
  174. // controlling agent, the controlled agent MUST reject the nomination
  175. // request with an appropriate error code response (e.g., 400)
  176. // [RFC5389].
  177. ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID)
  178. if !ok {
  179. s.log.Warnf("discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID)
  180. return
  181. }
  182. transactionAddr := pendingRequest.destination
  183. // Assert that NAT is not symmetric
  184. // https://tools.ietf.org/html/rfc8445#section-7.2.5.2.1
  185. if !addrEqual(transactionAddr, remoteAddr) {
  186. s.log.Debugf("discard message: transaction source and destination does not match expected(%s), actual(%s)", transactionAddr, remote)
  187. return
  188. }
  189. s.log.Tracef("inbound STUN (SuccessResponse) from %s to %s", remote.String(), local.String())
  190. p := s.agent.findPair(local, remote)
  191. if p == nil {
  192. // This shouldn't happen
  193. s.log.Error("Success response from invalid candidate pair")
  194. return
  195. }
  196. p.state = CandidatePairStateSucceeded
  197. s.log.Tracef("Found valid candidate pair: %s", p)
  198. if p.nominateOnBindingSuccess {
  199. if selectedPair := s.agent.getSelectedPair(); selectedPair == nil ||
  200. (selectedPair != p && selectedPair.priority() <= p.priority()) {
  201. s.agent.setSelectedPair(p)
  202. } else if selectedPair != p {
  203. s.log.Tracef("ignore nominate new pair %s, already nominated pair %s", p, selectedPair)
  204. }
  205. }
  206. }
  207. func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) {
  208. useCandidate := m.Contains(stun.AttrUseCandidate)
  209. p := s.agent.findPair(local, remote)
  210. if p == nil {
  211. p = s.agent.addPair(local, remote)
  212. }
  213. if useCandidate {
  214. // https://tools.ietf.org/html/rfc8445#section-7.3.1.5
  215. if p.state == CandidatePairStateSucceeded {
  216. // If the state of this pair is Succeeded, it means that the check
  217. // previously sent by this pair produced a successful response and
  218. // generated a valid pair (Section 7.2.5.3.2). The agent sets the
  219. // nominated flag value of the valid pair to true.
  220. if selectedPair := s.agent.getSelectedPair(); selectedPair == nil ||
  221. (selectedPair != p && selectedPair.priority() <= p.priority()) {
  222. s.agent.setSelectedPair(p)
  223. } else if selectedPair != p {
  224. s.log.Tracef("ignore nominate new pair %s, already nominated pair %s", p, selectedPair)
  225. }
  226. } else {
  227. // If the received Binding request triggered a new check to be
  228. // enqueued in the triggered-check queue (Section 7.3.1.4), once the
  229. // check is sent and if it generates a successful response, and
  230. // generates a valid pair, the agent sets the nominated flag of the
  231. // pair to true. If the request fails (Section 7.2.5.2), the agent
  232. // MUST remove the candidate pair from the valid list, set the
  233. // candidate pair state to Failed, and set the checklist state to
  234. // Failed.
  235. p.nominateOnBindingSuccess = true
  236. }
  237. }
  238. s.agent.sendBindingSuccess(m, local, remote)
  239. s.PingCandidate(local, remote)
  240. }
  241. type liteSelector struct {
  242. pairCandidateSelector
  243. }
  244. // A lite selector should not contact candidates
  245. func (s *liteSelector) ContactCandidates() {
  246. if _, ok := s.pairCandidateSelector.(*controllingSelector); ok {
  247. // nolint:godox
  248. // pion/ice#96
  249. // TODO: implement lite controlling agent. For now falling back to full agent.
  250. // This only happens if both peers are lite. See RFC 8445 S6.1.1 and S6.2
  251. s.pairCandidateSelector.ContactCandidates()
  252. } else if v, ok := s.pairCandidateSelector.(*controlledSelector); ok {
  253. v.agent.validateSelectedPair()
  254. }
  255. }