github.com/pion/webrtc/v3@v3.2.24/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/v2" 15 "github.com/pion/logging" 16 "github.com/pion/stun" 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 InterfaceFilter: g.api.settingEngine.candidates.InterfaceFilter, 112 IPFilter: g.api.settingEngine.candidates.IPFilter, 113 NAT1To1IPs: g.api.settingEngine.candidates.NAT1To1IPs, 114 NAT1To1IPCandidateType: nat1To1CandiTyp, 115 IncludeLoopback: g.api.settingEngine.candidates.IncludeLoopbackCandidate, 116 Net: g.api.settingEngine.net, 117 MulticastDNSMode: mDNSMode, 118 MulticastDNSHostName: g.api.settingEngine.candidates.MulticastDNSHostName, 119 LocalUfrag: g.api.settingEngine.candidates.UsernameFragment, 120 LocalPwd: g.api.settingEngine.candidates.Password, 121 TCPMux: g.api.settingEngine.iceTCPMux, 122 UDPMux: g.api.settingEngine.iceUDPMux, 123 ProxyDialer: g.api.settingEngine.iceProxyDialer, 124 DisableActiveTCP: g.api.settingEngine.iceDisableActiveTCP, 125 } 126 127 requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes 128 if len(requestedNetworkTypes) == 0 { 129 requestedNetworkTypes = supportedNetworkTypes() 130 } 131 132 for _, typ := range requestedNetworkTypes { 133 config.NetworkTypes = append(config.NetworkTypes, ice.NetworkType(typ)) 134 } 135 136 agent, err := ice.NewAgent(config) 137 if err != nil { 138 return err 139 } 140 141 g.agent = agent 142 return nil 143 } 144 145 // Gather ICE candidates. 146 func (g *ICEGatherer) Gather() error { 147 if err := g.createAgent(); err != nil { 148 return err 149 } 150 151 agent := g.getAgent() 152 // it is possible agent had just been closed 153 if agent == nil { 154 return fmt.Errorf("%w: unable to gather", errICEAgentNotExist) 155 } 156 157 g.setState(ICEGathererStateGathering) 158 if err := agent.OnCandidate(func(candidate ice.Candidate) { 159 onLocalCandidateHandler := func(*ICECandidate) {} 160 if handler, ok := g.onLocalCandidateHandler.Load().(func(candidate *ICECandidate)); ok && handler != nil { 161 onLocalCandidateHandler = handler 162 } 163 164 onGatheringCompleteHandler := func() {} 165 if handler, ok := g.onGatheringCompleteHandler.Load().(func()); ok && handler != nil { 166 onGatheringCompleteHandler = handler 167 } 168 169 if candidate != nil { 170 c, err := newICECandidateFromICE(candidate) 171 if err != nil { 172 g.log.Warnf("Failed to convert ice.Candidate: %s", err) 173 return 174 } 175 onLocalCandidateHandler(&c) 176 } else { 177 g.setState(ICEGathererStateComplete) 178 179 onGatheringCompleteHandler() 180 onLocalCandidateHandler(nil) 181 } 182 }); err != nil { 183 return err 184 } 185 return agent.GatherCandidates() 186 } 187 188 // Close prunes all local candidates, and closes the ports. 189 func (g *ICEGatherer) Close() error { 190 g.lock.Lock() 191 defer g.lock.Unlock() 192 193 if g.agent == nil { 194 return nil 195 } else if err := g.agent.Close(); err != nil { 196 return err 197 } 198 199 g.agent = nil 200 g.setState(ICEGathererStateClosed) 201 202 return nil 203 } 204 205 // GetLocalParameters returns the ICE parameters of the ICEGatherer. 206 func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) { 207 if err := g.createAgent(); err != nil { 208 return ICEParameters{}, err 209 } 210 211 agent := g.getAgent() 212 // it is possible agent had just been closed 213 if agent == nil { 214 return ICEParameters{}, fmt.Errorf("%w: unable to get local parameters", errICEAgentNotExist) 215 } 216 217 frag, pwd, err := agent.GetLocalUserCredentials() 218 if err != nil { 219 return ICEParameters{}, err 220 } 221 222 return ICEParameters{ 223 UsernameFragment: frag, 224 Password: pwd, 225 ICELite: false, 226 }, nil 227 } 228 229 // GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer. 230 func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) { 231 if err := g.createAgent(); err != nil { 232 return nil, err 233 } 234 235 agent := g.getAgent() 236 // it is possible agent had just been closed 237 if agent == nil { 238 return nil, fmt.Errorf("%w: unable to get local candidates", errICEAgentNotExist) 239 } 240 241 iceCandidates, err := agent.GetLocalCandidates() 242 if err != nil { 243 return nil, err 244 } 245 246 return newICECandidatesFromICE(iceCandidates) 247 } 248 249 // OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available 250 // Take note that the handler will be called with a nil pointer when gathering is finished. 251 func (g *ICEGatherer) OnLocalCandidate(f func(*ICECandidate)) { 252 g.onLocalCandidateHandler.Store(f) 253 } 254 255 // OnStateChange fires any time the ICEGatherer changes 256 func (g *ICEGatherer) OnStateChange(f func(ICEGathererState)) { 257 g.onStateChangeHandler.Store(f) 258 } 259 260 // State indicates the current state of the ICE gatherer. 261 func (g *ICEGatherer) State() ICEGathererState { 262 return atomicLoadICEGathererState(&g.state) 263 } 264 265 func (g *ICEGatherer) setState(s ICEGathererState) { 266 atomicStoreICEGathererState(&g.state, s) 267 268 if handler, ok := g.onStateChangeHandler.Load().(func(state ICEGathererState)); ok && handler != nil { 269 handler(s) 270 } 271 } 272 273 func (g *ICEGatherer) getAgent() *ice.Agent { 274 g.lock.RLock() 275 defer g.lock.RUnlock() 276 return g.agent 277 } 278 279 func (g *ICEGatherer) collectStats(collector *statsReportCollector) { 280 agent := g.getAgent() 281 if agent == nil { 282 return 283 } 284 285 collector.Collecting() 286 go func(collector *statsReportCollector, agent *ice.Agent) { 287 for _, candidatePairStats := range agent.GetCandidatePairsStats() { 288 collector.Collecting() 289 290 state, err := toStatsICECandidatePairState(candidatePairStats.State) 291 if err != nil { 292 g.log.Error(err.Error()) 293 } 294 295 pairID := newICECandidatePairStatsID(candidatePairStats.LocalCandidateID, 296 candidatePairStats.RemoteCandidateID) 297 298 stats := ICECandidatePairStats{ 299 Timestamp: statsTimestampFrom(candidatePairStats.Timestamp), 300 Type: StatsTypeCandidatePair, 301 ID: pairID, 302 // TransportID: 303 LocalCandidateID: candidatePairStats.LocalCandidateID, 304 RemoteCandidateID: candidatePairStats.RemoteCandidateID, 305 State: state, 306 Nominated: candidatePairStats.Nominated, 307 PacketsSent: candidatePairStats.PacketsSent, 308 PacketsReceived: candidatePairStats.PacketsReceived, 309 BytesSent: candidatePairStats.BytesSent, 310 BytesReceived: candidatePairStats.BytesReceived, 311 LastPacketSentTimestamp: statsTimestampFrom(candidatePairStats.LastPacketSentTimestamp), 312 LastPacketReceivedTimestamp: statsTimestampFrom(candidatePairStats.LastPacketReceivedTimestamp), 313 FirstRequestTimestamp: statsTimestampFrom(candidatePairStats.FirstRequestTimestamp), 314 LastRequestTimestamp: statsTimestampFrom(candidatePairStats.LastRequestTimestamp), 315 LastResponseTimestamp: statsTimestampFrom(candidatePairStats.LastResponseTimestamp), 316 TotalRoundTripTime: candidatePairStats.TotalRoundTripTime, 317 CurrentRoundTripTime: candidatePairStats.CurrentRoundTripTime, 318 AvailableOutgoingBitrate: candidatePairStats.AvailableOutgoingBitrate, 319 AvailableIncomingBitrate: candidatePairStats.AvailableIncomingBitrate, 320 CircuitBreakerTriggerCount: candidatePairStats.CircuitBreakerTriggerCount, 321 RequestsReceived: candidatePairStats.RequestsReceived, 322 RequestsSent: candidatePairStats.RequestsSent, 323 ResponsesReceived: candidatePairStats.ResponsesReceived, 324 ResponsesSent: candidatePairStats.ResponsesSent, 325 RetransmissionsReceived: candidatePairStats.RetransmissionsReceived, 326 RetransmissionsSent: candidatePairStats.RetransmissionsSent, 327 ConsentRequestsSent: candidatePairStats.ConsentRequestsSent, 328 ConsentExpiredTimestamp: statsTimestampFrom(candidatePairStats.ConsentExpiredTimestamp), 329 } 330 collector.Collect(stats.ID, stats) 331 } 332 333 for _, candidateStats := range agent.GetLocalCandidatesStats() { 334 collector.Collecting() 335 336 networkType, err := getNetworkType(candidateStats.NetworkType) 337 if err != nil { 338 g.log.Error(err.Error()) 339 } 340 341 candidateType, err := getCandidateType(candidateStats.CandidateType) 342 if err != nil { 343 g.log.Error(err.Error()) 344 } 345 346 stats := ICECandidateStats{ 347 Timestamp: statsTimestampFrom(candidateStats.Timestamp), 348 ID: candidateStats.ID, 349 Type: StatsTypeLocalCandidate, 350 IP: candidateStats.IP, 351 Port: int32(candidateStats.Port), 352 Protocol: networkType.Protocol(), 353 CandidateType: candidateType, 354 Priority: int32(candidateStats.Priority), 355 URL: candidateStats.URL, 356 RelayProtocol: candidateStats.RelayProtocol, 357 Deleted: candidateStats.Deleted, 358 } 359 collector.Collect(stats.ID, stats) 360 } 361 362 for _, candidateStats := range agent.GetRemoteCandidatesStats() { 363 collector.Collecting() 364 networkType, err := getNetworkType(candidateStats.NetworkType) 365 if err != nil { 366 g.log.Error(err.Error()) 367 } 368 369 candidateType, err := getCandidateType(candidateStats.CandidateType) 370 if err != nil { 371 g.log.Error(err.Error()) 372 } 373 374 stats := ICECandidateStats{ 375 Timestamp: statsTimestampFrom(candidateStats.Timestamp), 376 ID: candidateStats.ID, 377 Type: StatsTypeRemoteCandidate, 378 IP: candidateStats.IP, 379 Port: int32(candidateStats.Port), 380 Protocol: networkType.Protocol(), 381 CandidateType: candidateType, 382 Priority: int32(candidateStats.Priority), 383 URL: candidateStats.URL, 384 RelayProtocol: candidateStats.RelayProtocol, 385 } 386 collector.Collect(stats.ID, stats) 387 } 388 collector.Done() 389 }(collector, agent) 390 }