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  }