selection.go 9.8 KB

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