github.com/pion/webrtc/v4@v4.0.1/icegatherer.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 package webrtc 8 9 import ( 10 "fmt" 11 "sync" 12 "sync/atomic" 13 14 "github.com/pion/ice/v4" 15 "github.com/pion/logging" 16 "github.com/pion/stun/v3" 17 ) 18 19 // ICEGatherer gathers local host, server reflexive and relay 20 // candidates, as well as enabling the retrieval of local Interactive 21 // Connectivity Establishment (ICE) parameters which can be 22 // exchanged in signaling. 23 type ICEGatherer struct { 24 lock sync.RWMutex 25 log logging.LeveledLogger 26 state ICEGathererState 27 28 validatedServers []*stun.URI 29 gatherPolicy ICETransportPolicy 30 31 agent *ice.Agent 32 33 onLocalCandidateHandler atomic.Value // func(candidate *ICECandidate) 34 onStateChangeHandler atomic.Value // func(state ICEGathererState) 35 36 // Used for GatheringCompletePromise 37 onGatheringCompleteHandler atomic.Value // func() 38 39 api *API 40 } 41 42 // NewICEGatherer creates a new NewICEGatherer. 43 // This constructor is part of the ORTC API. It is not 44 // meant to be used together with the basic WebRTC API. 45 func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) { 46 var validatedServers []*stun.URI 47 if len(opts.ICEServers) > 0 { 48 for _, server := range opts.ICEServers { 49 url, err := server.urls() 50 if err != nil { 51 return nil, err 52 } 53 validatedServers = append(validatedServers, url...) 54 } 55 } 56 57 return &ICEGatherer{ 58 state: ICEGathererStateNew, 59 gatherPolicy: opts.ICEGatherPolicy, 60 validatedServers: validatedServers, 61 api: api, 62 log: api.settingEngine.LoggerFactory.NewLogger("ice"), 63 }, nil 64 } 65 66 func (g *ICEGatherer) createAgent() error { 67 g.lock.Lock() 68 defer g.lock.Unlock() 69 70 if g.agent != nil || g.State() != ICEGathererStateNew { 71 return nil 72 } 73 74 candidateTypes := []ice.CandidateType{} 75 if g.api.settingEngine.candidates.ICELite { 76 candidateTypes = append(candidateTypes, ice.CandidateTypeHost) 77 } else if g.gatherPolicy == ICETransportPolicyRelay { 78 candidateTypes = append(candidateTypes, ice.CandidateTypeRelay) 79 } 80 81 var nat1To1CandiTyp ice.CandidateType 82 switch g.api.settingEngine.candidates.NAT1To1IPCandidateType { 83 case ICECandidateTypeHost: 84 nat1To1CandiTyp = ice.CandidateTypeHost 85 case ICECandidateTypeSrflx: 86 nat1To1CandiTyp = ice.CandidateTypeServerReflexive 87 default: 88 nat1To1CandiTyp = ice.CandidateTypeUnspecified 89 } 90 91 mDNSMode := g.api.settingEngine.candidates.MulticastDNSMode 92 if mDNSMode != ice.MulticastDNSModeDisabled && mDNSMode != ice.MulticastDNSModeQueryAndGather { 93 // If enum is in state we don't recognized default to MulticastDNSModeQueryOnly 94 mDNSMode = ice.MulticastDNSModeQueryOnly 95 } 96 97 config := &ice.AgentConfig{ 98 Lite: g.api.settingEngine.candidates.ICELite, 99 Urls: g.validatedServers, 100 PortMin: g.api.settingEngine.ephemeralUDP.PortMin, 101 PortMax: g.api.settingEngine.ephemeralUDP.PortMax, 102 DisconnectedTimeout: g.api.settingEngine.timeout.ICEDisconnectedTimeout, 103 FailedTimeout: g.api.settingEngine.timeout.ICEFailedTimeout, 104 KeepaliveInterval: g.api.settingEngine.timeout.ICEKeepaliveInterval, 105 LoggerFactory: g.api.settingEngine.LoggerFactory, 106 CandidateTypes: candidateTypes, 107 HostAcceptanceMinWait: g.api.settingEngine.timeout.ICEHostAcceptanceMinWait, 108 SrflxAcceptanceMinWait: g.api.settingEngine.timeout.ICESrflxAcceptanceMinWait, 109 PrflxAcceptanceMinWait: g.api.settingEngine.timeout.ICEPrflxAcceptanceMinWait, 110 RelayAcceptanceMinWait: g.api.settingEngine.timeout.ICERelayAcceptanceMinWait, 111 STUNGatherTimeout: g.api.settingEngine.timeout.ICESTUNGatherTimeout, 112 InterfaceFilter: g.api.settingEngine.candidates.InterfaceFilter, 113 IPFilter: g.api.settingEngine.candidates.IPFilter, 114 NAT1To1IPs: g.api.settingEngine.candidates.NAT1To1IPs, 115 NAT1To1IPCandidateType: nat1To1CandiTyp, 116 IncludeLoopback: g.api.settingEngine.candidates.IncludeLoopbackCandidate, 117 Net: g.api.settingEngine.net, 118 MulticastDNSMode: mDNSMode, 119 MulticastDNSHostName: g.api.settingEngine.candidates.MulticastDNSHostName, 120 LocalUfrag: g.api.settingEngine.candidates.UsernameFragment, 121 LocalPwd: g.api.settingEngine.candidates.Password, 122 TCPMux: g.api.settingEngine.iceTCPMux, 123 UDPMux: g.api.settingEngine.iceUDPMux, 124 ProxyDialer: g.api.settingEngine.iceProxyDialer, 125 DisableActiveTCP: g.api.settingEngine.iceDisableActiveTCP, 126 MaxBindingRequests: g.api.settingEngine.iceMaxBindingRequests, 127 BindingRequestHandler: g.api.settingEngine.iceBindingRequestHandler, 128 } 129 130 requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes 131 if len(requestedNetworkTypes) == 0 { 132 requestedNetworkTypes = supportedNetworkTypes() 133 } 134 135 for _, typ := range requestedNetworkTypes { 136 config.NetworkTypes = append(config.NetworkTypes, ice.NetworkType(typ)) 137 } 138 139 agent, err := ice.NewAgent(config) 140 if err != nil { 141 return err 142 } 143 144 g.agent = agent 145 return nil 146 } 147 148 // Gather ICE candidates. 149 func (g *ICEGatherer) Gather() error { 150 if err := g.createAgent(); err != nil { 151 return err 152 } 153 154 agent := g.getAgent() 155 // it is possible agent had just been closed 156 if agent == nil { 157 return fmt.Errorf("%w: unable to gather", errICEAgentNotExist) 158 } 159 160 g.setState(ICEGathererStateGathering) 161 if err := agent.OnCandidate(func(candidate ice.Candidate) { 162 onLocalCandidateHandler := func(*ICECandidate) {} 163 if handler, ok := g.onLocalCandidateHandler.Load().(func(candidate *ICECandidate)); ok && handler != nil { 164 onLocalCandidateHandler = handler 165 } 166 167 onGatheringCompleteHandler := func() {} 168 if handler, ok := g.onGatheringCompleteHandler.Load().(func()); ok && handler != nil { 169 onGatheringCompleteHandler = handler 170 } 171 172 if candidate != nil { 173 c, err := newICECandidateFromICE(candidate) 174 if err != nil { 175 g.log.Warnf("Failed to convert ice.Candidate: %s", err) 176 return 177 } 178 onLocalCandidateHandler(&c) 179 } else { 180 g.setState(ICEGathererStateComplete) 181 182 onGatheringCompleteHandler() 183 onLocalCandidateHandler(nil) 184 } 185 }); err != nil { 186 return err 187 } 188 return agent.GatherCandidates() 189 } 190 191 // Close prunes all local candidates, and closes the ports. 192 func (g *ICEGatherer) Close() error { 193 return g.close(false /* shouldGracefullyClose */) 194 } 195 196 // GracefulClose prunes all local candidates, and closes the ports. It also waits 197 // for any goroutines it started to complete. This is only safe to call outside of 198 // ICEGatherer callbacks or if in a callback, in its own goroutine. 199 func (g *ICEGatherer) GracefulClose() error { 200 return g.close(true /* shouldGracefullyClose */) 201 } 202 203 func (g *ICEGatherer) close(shouldGracefullyClose bool) error { 204 g.lock.Lock() 205 defer g.lock.Unlock() 206 207 if g.agent == nil { 208 return nil 209 } 210 if shouldGracefullyClose { 211 if err := g.agent.GracefulClose(); err != nil { 212 return err 213 } 214 } else { 215 if err := g.agent.Close(); err != nil { 216 return err 217 } 218 } 219 220 g.agent = nil 221 g.setState(ICEGathererStateClosed) 222 223 return nil 224 } 225 226 // GetLocalParameters returns the ICE parameters of the ICEGatherer. 227 func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) { 228 if err := g.createAgent(); err != nil { 229 return ICEParameters{}, err 230 } 231 232 agent := g.getAgent() 233 // it is possible agent had just been closed 234 if agent == nil { 235 return ICEParameters{}, fmt.Errorf("%w: unable to get local parameters", errICEAgentNotExist) 236 } 237 238 frag, pwd, err := agent.GetLocalUserCredentials() 239 if err != nil { 240 return ICEParameters{}, err 241 } 242 243 return ICEParameters{ 244 UsernameFragment: frag, 245 Password: pwd, 246 ICELite: false, 247 }, nil 248 } 249 250 // GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer. 251 func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) { 252 if err := g.createAgent(); err != nil { 253 return nil, err 254 } 255 256 agent := g.getAgent() 257 // it is possible agent had just been closed 258 if agent == nil { 259 return nil, fmt.Errorf("%w: unable to get local candidates", errICEAgentNotExist) 260 } 261 262 iceCandidates, err := agent.GetLocalCandidates() 263 if err != nil { 264 return nil, err 265 } 266 267 return newICECandidatesFromICE(iceCandidates) 268 } 269 270 // OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available 271 // Take note that the handler will be called with a nil pointer when gathering is finished. 272 func (g *ICEGatherer) OnLocalCandidate(f func(*ICECandidate)) { 273 g.onLocalCandidateHandler.Store(f) 274 } 275 276 // OnStateChange fires any time the ICEGatherer changes 277 func (g *ICEGatherer) OnStateChange(f func(ICEGathererState)) { 278 g.onStateChangeHandler.Store(f) 279 } 280 281 // State indicates the current state of the ICE gatherer. 282 func (g *ICEGatherer) State() ICEGathererState { 283 return atomicLoadICEGathererState(&g.state) 284 } 285 286 func (g *ICEGatherer) setState(s ICEGathererState) { 287 atomicStoreICEGathererState(&g.state, s) 288 289 if handler, ok := g.onStateChangeHandler.Load().(func(state ICEGathererState)); ok && handler != nil { 290 handler(s) 291 } 292 } 293 294 func (g *ICEGatherer) getAgent() *ice.Agent { 295 g.lock.RLock() 296 defer g.lock.RUnlock() 297 return g.agent 298 } 299 300 func (g *ICEGatherer) collectStats(collector *statsReportCollector) { 301 agent := g.getAgent() 302 if agent == nil { 303 return 304 } 305 306 collector.Collecting() 307 go func(collector *statsReportCollector, agent *ice.Agent) { 308 for _, candidatePairStats := range agent.GetCandidatePairsStats() { 309 collector.Collecting() 310 311 stats, err := toICECandidatePairStats(candidatePairStats) 312 if err != nil { 313 g.log.Error(err.Error()) 314 continue 315 } 316 317 collector.Collect(stats.ID, stats) 318 } 319 320 for _, candidateStats := range agent.GetLocalCandidatesStats() { 321 collector.Collecting() 322 323 networkType, err := getNetworkType(candidateStats.NetworkType) 324 if err != nil { 325 g.log.Error(err.Error()) 326 } 327 328 candidateType, err := getCandidateType(candidateStats.CandidateType) 329 if err != nil { 330 g.log.Error(err.Error()) 331 } 332 333 stats := ICECandidateStats{ 334 Timestamp: statsTimestampFrom(candidateStats.Timestamp), 335 ID: candidateStats.ID, 336 Type: StatsTypeLocalCandidate, 337 IP: candidateStats.IP, 338 Port: int32(candidateStats.Port), 339 Protocol: networkType.Protocol(), 340 CandidateType: candidateType, 341 Priority: int32(candidateStats.Priority), 342 URL: candidateStats.URL, 343 RelayProtocol: candidateStats.RelayProtocol, 344 Deleted: candidateStats.Deleted, 345 } 346 collector.Collect(stats.ID, stats) 347 } 348 349 for _, candidateStats := range agent.GetRemoteCandidatesStats() { 350 collector.Collecting() 351 networkType, err := getNetworkType(candidateStats.NetworkType) 352 if err != nil { 353 g.log.Error(err.Error()) 354 } 355 356 candidateType, err := getCandidateType(candidateStats.CandidateType) 357 if err != nil { 358 g.log.Error(err.Error()) 359 } 360 361 stats := ICECandidateStats{ 362 Timestamp: statsTimestampFrom(candidateStats.Timestamp), 363 ID: candidateStats.ID, 364 Type: StatsTypeRemoteCandidate, 365 IP: candidateStats.IP, 366 Port: int32(candidateStats.Port), 367 Protocol: networkType.Protocol(), 368 CandidateType: candidateType, 369 Priority: int32(candidateStats.Priority), 370 URL: candidateStats.URL, 371 RelayProtocol: candidateStats.RelayProtocol, 372 } 373 collector.Collect(stats.ID, stats) 374 } 375 collector.Done() 376 }(collector, agent) 377 } 378 379 func (g *ICEGatherer) getSelectedCandidatePairStats() (ICECandidatePairStats, bool) { 380 agent := g.getAgent() 381 if agent == nil { 382 return ICECandidatePairStats{}, false 383 } 384 385 selectedCandidatePairStats, isAvailable := agent.GetSelectedCandidatePairStats() 386 if !isAvailable { 387 return ICECandidatePairStats{}, false 388 } 389 390 stats, err := toICECandidatePairStats(selectedCandidatePairStats) 391 if err != nil { 392 g.log.Error(err.Error()) 393 return ICECandidatePairStats{}, false 394 } 395 396 return stats, true 397 }