summaryrefslogtreecommitdiff
path: root/vendor/github.com/pion/ice
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/pion/ice')
-rw-r--r--vendor/github.com/pion/ice/v2/.gitignore24
-rw-r--r--vendor/github.com/pion/ice/v2/.golangci.yml89
-rw-r--r--vendor/github.com/pion/ice/v2/LICENSE21
-rw-r--r--vendor/github.com/pion/ice/v2/README.md67
-rw-r--r--vendor/github.com/pion/ice/v2/agent.go1233
-rw-r--r--vendor/github.com/pion/ice/v2/agent_config.go252
-rw-r--r--vendor/github.com/pion/ice/v2/agent_stats.go113
-rw-r--r--vendor/github.com/pion/ice/v2/candidate.go68
-rw-r--r--vendor/github.com/pion/ice/v2/candidate_base.go496
-rw-r--r--vendor/github.com/pion/ice/v2/candidate_host.go76
-rw-r--r--vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go60
-rw-r--r--vendor/github.com/pion/ice/v2/candidate_relay.go73
-rw-r--r--vendor/github.com/pion/ice/v2/candidate_server_reflexive.go59
-rw-r--r--vendor/github.com/pion/ice/v2/candidatepair.go98
-rw-r--r--vendor/github.com/pion/ice/v2/candidatepair_state.go37
-rw-r--r--vendor/github.com/pion/ice/v2/candidaterelatedaddress.go30
-rw-r--r--vendor/github.com/pion/ice/v2/candidatetype.go62
-rw-r--r--vendor/github.com/pion/ice/v2/codecov.yml20
-rw-r--r--vendor/github.com/pion/ice/v2/context.go37
-rw-r--r--vendor/github.com/pion/ice/v2/errors.go132
-rw-r--r--vendor/github.com/pion/ice/v2/external_ip_mapper.go143
-rw-r--r--vendor/github.com/pion/ice/v2/gather.go497
-rw-r--r--vendor/github.com/pion/ice/v2/go.mod16
-rw-r--r--vendor/github.com/pion/ice/v2/go.sum60
-rw-r--r--vendor/github.com/pion/ice/v2/ice.go76
-rw-r--r--vendor/github.com/pion/ice/v2/icecontrol.go87
-rw-r--r--vendor/github.com/pion/ice/v2/mdns.go63
-rw-r--r--vendor/github.com/pion/ice/v2/networktype.go130
-rw-r--r--vendor/github.com/pion/ice/v2/priority.go33
-rw-r--r--vendor/github.com/pion/ice/v2/rand.go53
-rw-r--r--vendor/github.com/pion/ice/v2/renovate.json15
-rw-r--r--vendor/github.com/pion/ice/v2/role.go43
-rw-r--r--vendor/github.com/pion/ice/v2/selection.go287
-rw-r--r--vendor/github.com/pion/ice/v2/stats.go177
-rw-r--r--vendor/github.com/pion/ice/v2/stun.go24
-rw-r--r--vendor/github.com/pion/ice/v2/tcp_mux.go295
-rw-r--r--vendor/github.com/pion/ice/v2/tcp_packet_conn.go240
-rw-r--r--vendor/github.com/pion/ice/v2/tcptype.go48
-rw-r--r--vendor/github.com/pion/ice/v2/transport.go145
-rw-r--r--vendor/github.com/pion/ice/v2/url.go225
-rw-r--r--vendor/github.com/pion/ice/v2/usecandidate.go23
-rw-r--r--vendor/github.com/pion/ice/v2/util.go233
42 files changed, 5960 insertions, 0 deletions
diff --git a/vendor/github.com/pion/ice/v2/.gitignore b/vendor/github.com/pion/ice/v2/.gitignore
new file mode 100644
index 0000000..83db74b
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/.gitignore
@@ -0,0 +1,24 @@
+### JetBrains IDE ###
+#####################
+.idea/
+
+### Emacs Temporary Files ###
+#############################
+*~
+
+### Folders ###
+###############
+bin/
+vendor/
+node_modules/
+
+### Files ###
+#############
+*.ivf
+*.ogg
+tags
+cover.out
+*.sw[poe]
+*.wasm
+examples/sfu-ws/cert.pem
+examples/sfu-ws/key.pem
diff --git a/vendor/github.com/pion/ice/v2/.golangci.yml b/vendor/github.com/pion/ice/v2/.golangci.yml
new file mode 100644
index 0000000..d6162c9
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/.golangci.yml
@@ -0,0 +1,89 @@
+linters-settings:
+ govet:
+ check-shadowing: true
+ misspell:
+ locale: US
+ exhaustive:
+ default-signifies-exhaustive: true
+ gomodguard:
+ blocked:
+ modules:
+ - github.com/pkg/errors:
+ recommendations:
+ - errors
+
+linters:
+ enable:
+ - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
+ - bodyclose # checks whether HTTP response body is closed successfully
+ - deadcode # Finds unused code
+ - depguard # Go linter that checks if package imports are in a list of acceptable packages
+ - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
+ - dupl # Tool for code clone detection
+ - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
+ - exhaustive # check exhaustiveness of enum switch statements
+ - exportloopref # checks for pointers to enclosing loop variables
+ - gci # Gci control golang package import order and make it always deterministic.
+ - gochecknoglobals # Checks that no globals are present in Go code
+ - gochecknoinits # Checks that no init functions are present in Go code
+ - gocognit # Computes and checks the cognitive complexity of functions
+ - goconst # Finds repeated strings that could be replaced by a constant
+ - gocritic # The most opinionated Go source code linter
+ - godox # Tool for detection of FIXME, TODO and other comment keywords
+ - goerr113 # Golang linter to check the errors handling expressions
+ - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
+ - gofumpt # Gofumpt checks whether code was gofumpt-ed.
+ - goheader # Checks is file header matches to pattern
+ - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
+ - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
+ - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
+ - goprintffuncname # Checks that printf-like functions are named with `f` at the end
+ - gosec # Inspects source code for security problems
+ - gosimple # Linter for Go source code that specializes in simplifying a code
+ - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
+ - ineffassign # Detects when assignments to existing variables are not used
+ - misspell # Finds commonly misspelled English words in comments
+ - nakedret # Finds naked returns in functions greater than a specified function length
+ - noctx # noctx finds sending http request without context.Context
+ - scopelint # Scopelint checks for unpinned variables in go programs
+ - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
+ - structcheck # Finds unused struct fields
+ - stylecheck # Stylecheck is a replacement for golint
+ - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
+ - unconvert # Remove unnecessary type conversions
+ - unparam # Reports unused function parameters
+ - unused # Checks Go code for unused constants, variables, functions and types
+ - varcheck # Finds unused global variables and constants
+ - whitespace # Tool for detection of leading and trailing whitespace
+ disable:
+ - funlen # Tool for detection of long functions
+ - gocyclo # Computes and checks the cyclomatic complexity of functions
+ - godot # Check if comments end in a period
+ - gomnd # An analyzer to detect magic numbers.
+ - lll # Reports long lines
+ - maligned # Tool to detect Go structs that would take less memory if their fields were sorted
+ - nestif # Reports deeply nested if statements
+ - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
+ - nolintlint # Reports ill-formed or insufficient nolint directives
+ - prealloc # Finds slice declarations that could potentially be preallocated
+ - rowserrcheck # checks whether Err of rows is checked successfully
+ - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
+ - testpackage # linter that makes you use a separate _test package
+ - wsl # Whitespace Linter - Forces you to use empty lines!
+
+issues:
+ exclude-use-default: false
+ exclude-rules:
+ # Allow complex tests, better to be self contained
+ - path: _test\.go
+ linters:
+ - gocognit
+
+ # Allow complex main function in examples
+ - path: examples
+ text: "of func `main` is high"
+ linters:
+ - gocognit
+
+run:
+ skip-dirs-use-default: false
diff --git a/vendor/github.com/pion/ice/v2/LICENSE b/vendor/github.com/pion/ice/v2/LICENSE
new file mode 100644
index 0000000..ab60297
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/pion/ice/v2/README.md b/vendor/github.com/pion/ice/v2/README.md
new file mode 100644
index 0000000..cbb55fd
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/README.md
@@ -0,0 +1,67 @@
+<h1 align="center">
+ <br>
+ Pion ICE
+ <br>
+</h1>
+<h4 align="center">A Go implementation of ICE</h4>
+<p align="center">
+ <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-ice-gray.svg?longCache=true&colorB=brightgreen" alt="Pion transport"></a>
+ <a href="http://gophers.slack.com/messages/pion"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
+ <br>
+ <a href="https://travis-ci.org/pion/ice"><img src="https://travis-ci.org/pion/ice.svg?branch=master" alt="Build Status"></a>
+ <a href="https://pkg.go.dev/github.com/pion/ice"><img src="https://godoc.org/github.com/pion/ice?status.svg" alt="GoDoc"></a>
+ <a href="https://codecov.io/gh/pion/ice"><img src="https://codecov.io/gh/pion/ice/branch/master/graph/badge.svg" alt="Coverage Status"></a>
+ <a href="https://goreportcard.com/report/github.com/pion/ice"><img src="https://goreportcard.com/badge/github.com/pion/ice" alt="Go Report Card"></a>
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
+</p>
+<br>
+
+### Roadmap
+The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
+
+### Community
+Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion).
+
+We are always looking to support **your projects**. Please reach out if you have something to build!
+
+If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
+
+### Contributing
+Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
+
+* [John Bradley](https://github.com/kc5nra) - *Original Author*
+* [Sean DuBois](https://github.com/Sean-Der) - *Original Author*
+* [Michael MacDonald](https://github.com/mjmac) - *Original Author*
+* [Michiel De Backker](https://github.com/backkem) - *Original Author*
+* [Konstantin Itskov](https://github.com/trivigy) - *Original Author*
+* [Luke Curley](https://github.com/kixelated)
+* [Hugo Arregui](https://github.com/hugoArregui)
+* [Adam Kiss](https://github.com/masterada)
+* [Aleksandr Razumov](https://github.com/ernado)
+* [Yutaka Takeda](https://github.com/enobufs)
+* [Atsushi Watanabe](https://github.com/at-wat)
+* [Robert Eperjesi](https://github.com/epes)
+* [Sebastian Waisbrot](https://github.com/seppo0010)
+* [Zizheng Tai](https://github.com/ZizhengTai)
+* [Aaron France](https://github.com/AeroNotix)
+* [Chao Yuan](https://github.com/yuanchao0310)
+* [Jason Maldonis](https://github.com/jjmaldonis)
+* [Nevio Vesic](https://github.com/0x19)
+* [David Hamilton](https://github.com/dihamilton)
+* [adwpc](https://github.com/adwpc)
+* [Ori Bernstein](https://eigenstate.org)
+* [Sam Lancia](https://github.com/nerd2)
+* [Lander Noterman](https://github.com/LanderN)
+* [BUPTCZQ](https://github.com/buptczq)
+* [Henry](https://github.com/cryptix)
+* [Jerko Steiner](https://github.com/jeremija)
+* [Sidney San Martín](https://github.com/s4y)
+* [JooYoung Lim](https://github.com/DevRockstarZ)
+* [Kory Miller](https://github.com/korymiller1489)
+* [ZHENK](https://github.com/scorpionknifes)
+* [Assad Obaid](https://github.com/assadobaid)
+* [Antoine Baché](https://github.com/Antonito)
+* [Will Forcey](https://github.com/wawesomeNOGUI)
+
+### License
+MIT License - see [LICENSE](LICENSE) for full text
diff --git a/vendor/github.com/pion/ice/v2/agent.go b/vendor/github.com/pion/ice/v2/agent.go
new file mode 100644
index 0000000..9f1ac6a
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/agent.go
@@ -0,0 +1,1233 @@
+// Package ice implements the Interactive Connectivity Establishment (ICE)
+// protocol defined in rfc5245.
+package ice
+
+import (
+ "context"
+ "net"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/pion/logging"
+ "github.com/pion/mdns"
+ "github.com/pion/stun"
+ "github.com/pion/transport/packetio"
+ "github.com/pion/transport/vnet"
+ "golang.org/x/net/proxy"
+)
+
+type bindingRequest struct {
+ timestamp time.Time
+ transactionID [stun.TransactionIDSize]byte
+ destination net.Addr
+ isUseCandidate bool
+}
+
+// Agent represents the ICE agent
+type Agent struct {
+ chanTask chan task
+ afterRunFn []func(ctx context.Context)
+ muAfterRun sync.Mutex
+
+ onConnectionStateChangeHdlr atomic.Value // func(ConnectionState)
+ onSelectedCandidatePairChangeHdlr atomic.Value // func(Candidate, Candidate)
+ onCandidateHdlr atomic.Value // func(Candidate)
+
+ // State owned by the taskLoop
+ onConnected chan struct{}
+ onConnectedOnce sync.Once
+
+ // force candidate to be contacted immediately (instead of waiting for task ticker)
+ forceCandidateContact chan bool
+
+ tieBreaker uint64
+ lite bool
+
+ connectionState ConnectionState
+ gatheringState GatheringState
+
+ mDNSMode MulticastDNSMode
+ mDNSName string
+ mDNSConn *mdns.Conn
+
+ muHaveStarted sync.Mutex
+ startedCh <-chan struct{}
+ startedFn func()
+ isControlling bool
+
+ maxBindingRequests uint16
+
+ hostAcceptanceMinWait time.Duration
+ srflxAcceptanceMinWait time.Duration
+ prflxAcceptanceMinWait time.Duration
+ relayAcceptanceMinWait time.Duration
+
+ portmin uint16
+ portmax uint16
+
+ candidateTypes []CandidateType
+
+ // How long connectivity checks can fail before the ICE Agent
+ // goes to disconnected
+ disconnectedTimeout time.Duration
+
+ // How long connectivity checks can fail before the ICE Agent
+ // goes to failed
+ failedTimeout time.Duration
+
+ // How often should we send keepalive packets?
+ // 0 means never
+ keepaliveInterval time.Duration
+
+ // How often should we run our internal taskLoop to check for state changes when connecting
+ checkInterval time.Duration
+
+ localUfrag string
+ localPwd string
+ localCandidates map[NetworkType][]Candidate
+
+ remoteUfrag string
+ remotePwd string
+ remoteCandidates map[NetworkType][]Candidate
+
+ checklist []*candidatePair
+ selector pairCandidateSelector
+
+ selectedPair atomic.Value // *candidatePair
+
+ urls []*URL
+ networkTypes []NetworkType
+
+ buffer *packetio.Buffer
+
+ // LRU of outbound Binding request Transaction IDs
+ pendingBindingRequests []bindingRequest
+
+ // 1:1 D-NAT IP address mapping
+ extIPMapper *externalIPMapper
+
+ // State for closing
+ done chan struct{}
+ err atomicError
+
+ gatherCandidateCancel func()
+
+ chanCandidate chan Candidate
+ chanCandidatePair chan *candidatePair
+ chanState chan ConnectionState
+
+ loggerFactory logging.LoggerFactory
+ log logging.LeveledLogger
+
+ net *vnet.Net
+ tcpMux TCPMux
+
+ interfaceFilter func(string) bool
+
+ insecureSkipVerify bool
+
+ proxyDialer proxy.Dialer
+}
+
+type task struct {
+ fn func(context.Context, *Agent)
+ done chan struct{}
+}
+
+// afterRun registers function to be run after the task.
+func (a *Agent) afterRun(f func(context.Context)) {
+ a.muAfterRun.Lock()
+ a.afterRunFn = append(a.afterRunFn, f)
+ a.muAfterRun.Unlock()
+}
+
+func (a *Agent) getAfterRunFn() []func(context.Context) {
+ a.muAfterRun.Lock()
+ defer a.muAfterRun.Unlock()
+ fns := a.afterRunFn
+ a.afterRunFn = nil
+ return fns
+}
+
+func (a *Agent) ok() error {
+ select {
+ case <-a.done:
+ return a.getErr()
+ default:
+ }
+ return nil
+}
+
+func (a *Agent) getErr() error {
+ if err := a.err.Load(); err != nil {
+ return err
+ }
+ return ErrClosed
+}
+
+// Run task in serial. Blocking tasks must be cancelable by context.
+func (a *Agent) run(ctx context.Context, t func(context.Context, *Agent)) error {
+ if err := a.ok(); err != nil {
+ return err
+ }
+ done := make(chan struct{})
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case a.chanTask <- task{t, done}:
+ <-done
+ return nil
+ }
+}
+
+// taskLoop handles registered tasks and agent close.
+func (a *Agent) taskLoop() {
+ after := func() {
+ for {
+ // Get and run func registered by afterRun().
+ fns := a.getAfterRunFn()
+ if len(fns) == 0 {
+ break
+ }
+ for _, fn := range fns {
+ fn(a.context())
+ }
+ }
+ }
+ defer func() {
+ a.deleteAllCandidates()
+ a.startedFn()
+
+ if err := a.buffer.Close(); err != nil {
+ a.log.Warnf("failed to close buffer: %v", err)
+ }
+
+ a.closeMulticastConn()
+ a.updateConnectionState(ConnectionStateClosed)
+
+ after()
+
+ close(a.chanState)
+ close(a.chanCandidate)
+ close(a.chanCandidatePair)
+ }()
+
+ for {
+ select {
+ case <-a.done:
+ return
+ case t := <-a.chanTask:
+ t.fn(a.context(), a)
+ close(t.done)
+ after()
+ }
+ }
+}
+
+// NewAgent creates a new Agent
+func NewAgent(config *AgentConfig) (*Agent, error) { //nolint:gocognit
+ var err error
+ if config.PortMax < config.PortMin {
+ return nil, ErrPort
+ }
+
+ mDNSName := config.MulticastDNSHostName
+ if mDNSName == "" {
+ if mDNSName, err = generateMulticastDNSName(); err != nil {
+ return nil, err
+ }
+ }
+
+ if !strings.HasSuffix(mDNSName, ".local") || len(strings.Split(mDNSName, ".")) != 2 {
+ return nil, ErrInvalidMulticastDNSHostName
+ }
+
+ mDNSMode := config.MulticastDNSMode
+ if mDNSMode == 0 {
+ mDNSMode = MulticastDNSModeQueryOnly
+ }
+
+ loggerFactory := config.LoggerFactory
+ if loggerFactory == nil {
+ loggerFactory = logging.NewDefaultLoggerFactory()
+ }
+ log := loggerFactory.NewLogger("ice")
+
+ var mDNSConn *mdns.Conn
+ mDNSConn, mDNSMode, err = createMulticastDNS(mDNSMode, mDNSName, log)
+ // Opportunistic mDNS: If we can't open the connection, that's ok: we
+ // can continue without it.
+ if err != nil {
+ log.Warnf("Failed to initialize mDNS %s: %v", mDNSName, err)
+ }
+ closeMDNSConn := func() {
+ if mDNSConn != nil {
+ if mdnsCloseErr := mDNSConn.Close(); mdnsCloseErr != nil {
+ log.Warnf("Failed to close mDNS: %v", mdnsCloseErr)
+ }
+ }
+ }
+
+ startedCtx, startedFn := context.WithCancel(context.Background())
+
+ a := &Agent{
+ chanTask: make(chan task),
+ chanState: make(chan ConnectionState),
+ chanCandidate: make(chan Candidate),
+ chanCandidatePair: make(chan *candidatePair),
+ tieBreaker: globalMathRandomGenerator.Uint64(),
+ lite: config.Lite,
+ gatheringState: GatheringStateNew,
+ connectionState: ConnectionStateNew,
+ localCandidates: make(map[NetworkType][]Candidate),
+ remoteCandidates: make(map[NetworkType][]Candidate),
+ urls: config.Urls,
+ networkTypes: config.NetworkTypes,
+ onConnected: make(chan struct{}),
+ buffer: packetio.NewBuffer(),
+ done: make(chan struct{}),
+ startedCh: startedCtx.Done(),
+ startedFn: startedFn,
+ portmin: config.PortMin,
+ portmax: config.PortMax,
+ loggerFactory: loggerFactory,
+ log: log,
+ net: config.Net,
+ proxyDialer: config.ProxyDialer,
+
+ mDNSMode: mDNSMode,
+ mDNSName: mDNSName,
+ mDNSConn: mDNSConn,
+
+ gatherCandidateCancel: func() {},
+
+ forceCandidateContact: make(chan bool, 1),
+
+ interfaceFilter: config.InterfaceFilter,
+
+ insecureSkipVerify: config.InsecureSkipVerify,
+ }
+
+ a.tcpMux = config.TCPMux
+ if a.tcpMux == nil {
+ a.tcpMux = newInvalidTCPMux()
+ }
+
+ if a.net == nil {
+ a.net = vnet.NewNet(nil)
+ } else if a.net.IsVirtual() {
+ a.log.Warn("vnet is enabled")
+ if a.mDNSMode != MulticastDNSModeDisabled {
+ a.log.Warn("vnet does not support mDNS yet")
+ }
+ }
+
+ config.initWithDefaults(a)
+
+ // Make sure the buffer doesn't grow indefinitely.
+ // NOTE: We actually won't get anywhere close to this limit.
+ // SRTP will constantly read from the endpoint and drop packets if it's full.
+ a.buffer.SetLimitSize(maxBufferSize)
+
+ if a.lite && (len(a.candidateTypes) != 1 || a.candidateTypes[0] != CandidateTypeHost) {
+ closeMDNSConn()
+ return nil, ErrLiteUsingNonHostCandidates
+ }
+
+ if config.Urls != nil && len(config.Urls) > 0 && !containsCandidateType(CandidateTypeServerReflexive, a.candidateTypes) && !containsCandidateType(CandidateTypeRelay, a.candidateTypes) {
+ closeMDNSConn()
+ return nil, ErrUselessUrlsProvided
+ }
+
+ if err = config.initExtIPMapping(a); err != nil {
+ closeMDNSConn()
+ return nil, err
+ }
+
+ go a.taskLoop()
+ a.startOnConnectionStateChangeRoutine()
+
+ // Restart is also used to initialize the agent for the first time
+ if err := a.Restart(config.LocalUfrag, config.LocalPwd); err != nil {
+ closeMDNSConn()
+ _ = a.Close()
+ return nil, err
+ }
+
+ return a, nil
+}
+
+// OnConnectionStateChange sets a handler that is fired when the connection state changes
+func (a *Agent) OnConnectionStateChange(f func(ConnectionState)) error {
+ a.onConnectionStateChangeHdlr.Store(f)
+ return nil
+}
+
+// OnSelectedCandidatePairChange sets a handler that is fired when the final candidate
+// pair is selected
+func (a *Agent) OnSelectedCandidatePairChange(f func(Candidate, Candidate)) error {
+ a.onSelectedCandidatePairChangeHdlr.Store(f)
+ return nil
+}
+
+// OnCandidate sets a handler that is fired when new candidates gathered. When
+// the gathering process complete the last candidate is nil.
+func (a *Agent) OnCandidate(f func(Candidate)) error {
+ a.onCandidateHdlr.Store(f)
+ return nil
+}
+
+func (a *Agent) onSelectedCandidatePairChange(p *candidatePair) {
+ if h, ok := a.onSelectedCandidatePairChangeHdlr.Load().(func(Candidate, Candidate)); ok {
+ h(p.local, p.remote)
+ }
+}
+
+func (a *Agent) onCandidate(c Candidate) {
+ if onCandidateHdlr, ok := a.onCandidateHdlr.Load().(func(Candidate)); ok {
+ onCandidateHdlr(c)
+ }
+}
+
+func (a *Agent) onConnectionStateChange(s ConnectionState) {
+ if hdlr, ok := a.onConnectionStateChangeHdlr.Load().(func(ConnectionState)); ok {
+ hdlr(s)
+ }
+}
+
+func (a *Agent) startOnConnectionStateChangeRoutine() {
+ go func() {
+ for {
+ // CandidatePair and ConnectionState are usually changed at once.
+ // Blocking one by the other one causes deadlock.
+ p, isOpen := <-a.chanCandidatePair
+ if !isOpen {
+ return
+ }
+ a.onSelectedCandidatePairChange(p)
+ }
+ }()
+ go func() {
+ for {
+ select {
+ case s, isOpen := <-a.chanState:
+ if !isOpen {
+ for c := range a.chanCandidate {
+ a.onCandidate(c)
+ }
+ return
+ }
+ a.onConnectionStateChange(s)
+
+ case c, isOpen := <-a.chanCandidate:
+ if !isOpen {
+ for s := range a.chanState {
+ a.onConnectionStateChange(s)
+ }
+ return
+ }
+ a.onCandidate(c)
+ }
+ }
+ }()
+}
+
+func (a *Agent) startConnectivityChecks(isControlling bool, remoteUfrag, remotePwd string) error {
+ a.muHaveStarted.Lock()
+ defer a.muHaveStarted.Unlock()
+ select {
+ case <-a.startedCh:
+ return ErrMultipleStart
+ default:
+ }
+ if err := a.SetRemoteCredentials(remoteUfrag, remotePwd); err != nil {
+ return err
+ }
+
+ a.log.Debugf("Started agent: isControlling? %t, remoteUfrag: %q, remotePwd: %q", isControlling, remoteUfrag, remotePwd)
+
+ return a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ agent.isControlling = isControlling
+ agent.remoteUfrag = remoteUfrag
+ agent.remotePwd = remotePwd
+
+ if isControlling {
+ a.selector = &controllingSelector{agent: a, log: a.log}
+ } else {
+ a.selector = &controlledSelector{agent: a, log: a.log}
+ }
+
+ if a.lite {
+ a.selector = &liteSelector{pairCandidateSelector: a.selector}
+ }
+
+ a.selector.Start()
+ a.startedFn()
+
+ agent.updateConnectionState(ConnectionStateChecking)
+
+ a.requestConnectivityCheck()
+ go a.connectivityChecks()
+ })
+}
+
+func (a *Agent) connectivityChecks() {
+ lastConnectionState := ConnectionState(0)
+ checkingDuration := time.Time{}
+
+ contact := func() {
+ if err := a.run(a.context(), func(ctx context.Context, a *Agent) {
+ defer func() {
+ lastConnectionState = a.connectionState
+ }()
+
+ switch a.connectionState {
+ case ConnectionStateFailed:
+ // The connection is currently failed so don't send any checks
+ // In the future it may be restarted though
+ return
+ case ConnectionStateChecking:
+ // We have just entered checking for the first time so update our checking timer
+ if lastConnectionState != a.connectionState {
+ checkingDuration = time.Now()
+ }
+
+ // We have been in checking longer then Disconnect+Failed timeout, set the connection to Failed
+ if time.Since(checkingDuration) > a.disconnectedTimeout+a.failedTimeout {
+ a.updateConnectionState(ConnectionStateFailed)
+ return
+ }
+ }
+
+ a.selector.ContactCandidates()
+ }); err != nil {
+ a.log.Warnf("taskLoop failed: %v", err)
+ }
+ }
+
+ for {
+ interval := defaultKeepaliveInterval
+
+ updateInterval := func(x time.Duration) {
+ if x != 0 && (interval == 0 || interval > x) {
+ interval = x
+ }
+ }
+
+ switch lastConnectionState {
+ case ConnectionStateNew, ConnectionStateChecking: // While connecting, check candidates more frequently
+ updateInterval(a.checkInterval)
+ case ConnectionStateConnected, ConnectionStateDisconnected:
+ updateInterval(a.keepaliveInterval)
+ default:
+ }
+ // Ensure we run our task loop as quickly as the minimum of our various configured timeouts
+ updateInterval(a.disconnectedTimeout)
+ updateInterval(a.failedTimeout)
+
+ t := time.NewTimer(interval)
+ select {
+ case <-a.forceCandidateContact:
+ t.Stop()
+ contact()
+ case <-t.C:
+ contact()
+ case <-a.done:
+ t.Stop()
+ return
+ }
+ }
+}
+
+func (a *Agent) updateConnectionState(newState ConnectionState) {
+ if a.connectionState != newState {
+ // Connection has gone to failed, release all gathered candidates
+ if newState == ConnectionStateFailed {
+ a.deleteAllCandidates()
+ }
+
+ a.log.Infof("Setting new connection state: %s", newState)
+ a.connectionState = newState
+
+ // Call handler after finishing current task since we may be holding the agent lock
+ // and the handler may also require it
+ a.afterRun(func(ctx context.Context) {
+ a.chanState <- newState
+ })
+ }
+}
+
+func (a *Agent) setSelectedPair(p *candidatePair) {
+ a.log.Tracef("Set selected candidate pair: %s", p)
+
+ if p == nil {
+ var nilPair *candidatePair
+ a.selectedPair.Store(nilPair)
+ return
+ }
+
+ p.nominated = true
+ a.selectedPair.Store(p)
+
+ a.updateConnectionState(ConnectionStateConnected)
+
+ // Notify when the selected pair changes
+ if p != nil {
+ a.afterRun(func(ctx context.Context) {
+ select {
+ case a.chanCandidatePair <- p:
+ case <-ctx.Done():
+ }
+ })
+ }
+
+ // Signal connected
+ a.onConnectedOnce.Do(func() { close(a.onConnected) })
+}
+
+func (a *Agent) pingAllCandidates() {
+ a.log.Trace("pinging all candidates")
+
+ if len(a.checklist) == 0 {
+ a.log.Warn("pingAllCandidates called with no candidate pairs. Connection is not possible yet.")
+ }
+
+ for _, p := range a.checklist {
+ if p.state == CandidatePairStateWaiting {
+ p.state = CandidatePairStateInProgress
+ } else if p.state != CandidatePairStateInProgress {
+ continue
+ }
+
+ if p.bindingRequestCount > a.maxBindingRequests {
+ a.log.Tracef("max requests reached for pair %s, marking it as failed\n", p)
+ p.state = CandidatePairStateFailed
+ } else {
+ a.selector.PingCandidate(p.local, p.remote)
+ p.bindingRequestCount++
+ }
+ }
+}
+
+func (a *Agent) getBestAvailableCandidatePair() *candidatePair {
+ var best *candidatePair
+ for _, p := range a.checklist {
+ if p.state == CandidatePairStateFailed {
+ continue
+ }
+
+ if best == nil {
+ best = p
+ } else if best.Priority() < p.Priority() {
+ best = p
+ }
+ }
+ return best
+}
+
+func (a *Agent) getBestValidCandidatePair() *candidatePair {
+ var best *candidatePair
+ for _, p := range a.checklist {
+ if p.state != CandidatePairStateSucceeded {
+ continue
+ }
+
+ if best == nil {
+ best = p
+ } else if best.Priority() < p.Priority() {
+ best = p
+ }
+ }
+ return best
+}
+
+func (a *Agent) addPair(local, remote Candidate) *candidatePair {
+ p := newCandidatePair(local, remote, a.isControlling)
+ a.checklist = append(a.checklist, p)
+ return p
+}
+
+func (a *Agent) findPair(local, remote Candidate) *candidatePair {
+ for _, p := range a.checklist {
+ if p.local.Equal(local) && p.remote.Equal(remote) {
+ return p
+ }
+ }
+ return nil
+}
+
+// validateSelectedPair checks if the selected pair is (still) valid
+// Note: the caller should hold the agent lock.
+func (a *Agent) validateSelectedPair() bool {
+ selectedPair := a.getSelectedPair()
+ if selectedPair == nil {
+ return false
+ }
+
+ disconnectedTime := time.Since(selectedPair.remote.LastReceived())
+
+ // Only allow transitions to failed if a.failedTimeout is non-zero
+ totalTimeToFailure := a.failedTimeout
+ if totalTimeToFailure != 0 {
+ totalTimeToFailure += a.disconnectedTimeout
+ }
+
+ switch {
+ case totalTimeToFailure != 0 && disconnectedTime > totalTimeToFailure:
+ a.updateConnectionState(ConnectionStateFailed)
+ case a.disconnectedTimeout != 0 && disconnectedTime > a.disconnectedTimeout:
+ a.updateConnectionState(ConnectionStateDisconnected)
+ default:
+ a.updateConnectionState(ConnectionStateConnected)
+ }
+
+ return true
+}
+
+// checkKeepalive sends STUN Binding Indications to the selected pair
+// if no packet has been sent on that pair in the last keepaliveInterval
+// Note: the caller should hold the agent lock.
+func (a *Agent) checkKeepalive() {
+ selectedPair := a.getSelectedPair()
+ if selectedPair == nil {
+ return
+ }
+
+ if (a.keepaliveInterval != 0) &&
+ ((time.Since(selectedPair.local.LastSent()) > a.keepaliveInterval) ||
+ (time.Since(selectedPair.remote.LastReceived()) > a.keepaliveInterval)) {
+ // we use binding request instead of indication to support refresh consent schemas
+ // see https://tools.ietf.org/html/rfc7675
+ a.selector.PingCandidate(selectedPair.local, selectedPair.remote)
+ }
+}
+
+// AddRemoteCandidate adds a new remote candidate
+func (a *Agent) AddRemoteCandidate(c Candidate) error {
+ if c == nil {
+ return nil
+ }
+
+ // cannot check for network yet because it might not be applied
+ // when mDNS hostame is used.
+ if c.TCPType() == TCPTypeActive {
+ // TCP Candidates with tcptype active will probe server passive ones, so
+ // no need to do anything with them.
+ a.log.Infof("Ignoring remote candidate with tcpType active: %s", c)
+ return nil
+ }
+
+ // If we have a mDNS Candidate lets fully resolve it before adding it locally
+ if c.Type() == CandidateTypeHost && strings.HasSuffix(c.Address(), ".local") {
+ if a.mDNSMode == MulticastDNSModeDisabled {
+ a.log.Warnf("remote mDNS candidate added, but mDNS is disabled: (%s)", c.Address())
+ return nil
+ }
+
+ hostCandidate, ok := c.(*CandidateHost)
+ if !ok {
+ return ErrAddressParseFailed
+ }
+
+ go a.resolveAndAddMulticastCandidate(hostCandidate)
+ return nil
+ }
+
+ go func() {
+ if err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ agent.addRemoteCandidate(c)
+ }); err != nil {
+ a.log.Warnf("Failed to add remote candidate %s: %v", c.Address(), err)
+ return
+ }
+ }()
+ return nil
+}
+
+func (a *Agent) resolveAndAddMulticastCandidate(c *CandidateHost) {
+ if a.mDNSConn == nil {
+ return
+ }
+ _, src, err := a.mDNSConn.Query(c.context(), c.Address())
+ if err != nil {
+ a.log.Warnf("Failed to discover mDNS candidate %s: %v", c.Address(), err)
+ return
+ }
+
+ ip, _, _, _ := parseAddr(src) //nolint:dogsled
+ if ip == nil {
+ a.log.Warnf("Failed to discover mDNS candidate %s: failed to parse IP", c.Address())
+ return
+ }
+
+ if err = c.setIP(ip); err != nil {
+ a.log.Warnf("Failed to discover mDNS candidate %s: %v", c.Address(), err)
+ return
+ }
+
+ if err = a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ agent.addRemoteCandidate(c)
+ }); err != nil {
+ a.log.Warnf("Failed to add mDNS candidate %s: %v", c.Address(), err)
+ return
+ }
+}
+
+func (a *Agent) requestConnectivityCheck() {
+ select {
+ case a.forceCandidateContact <- true:
+ default:
+ }
+}
+
+// addRemoteCandidate assumes you are holding the lock (must be execute using a.run)
+func (a *Agent) addRemoteCandidate(c Candidate) {
+ set := a.remoteCandidates[c.NetworkType()]
+
+ for _, candidate := range set {
+ if candidate.Equal(c) {
+ return
+ }
+ }
+
+ set = append(set, c)
+ a.remoteCandidates[c.NetworkType()] = set
+
+ if localCandidates, ok := a.localCandidates[c.NetworkType()]; ok {
+ for _, localCandidate := range localCandidates {
+ a.addPair(localCandidate, c)
+ }
+ }
+
+ a.requestConnectivityCheck()
+}
+
+func (a *Agent) addCandidate(ctx context.Context, c Candidate, candidateConn net.PacketConn) error {
+ return a.run(ctx, func(ctx context.Context, agent *Agent) {
+ c.start(a, candidateConn, a.startedCh)
+
+ set := a.localCandidates[c.NetworkType()]
+ for _, candidate := range set {
+ if candidate.Equal(c) {
+ if err := c.close(); err != nil {
+ a.log.Warnf("Failed to close duplicate candidate: %v", err)
+ }
+ return
+ }
+ }
+
+ set = append(set, c)
+ a.localCandidates[c.NetworkType()] = set
+
+ if remoteCandidates, ok := a.remoteCandidates[c.NetworkType()]; ok {
+ for _, remoteCandidate := range remoteCandidates {
+ a.addPair(c, remoteCandidate)
+ }
+ }
+
+ a.requestConnectivityCheck()
+
+ a.chanCandidate <- c
+ })
+}
+
+// GetLocalCandidates returns the local candidates
+func (a *Agent) GetLocalCandidates() ([]Candidate, error) {
+ var res []Candidate
+
+ err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ var candidates []Candidate
+ for _, set := range agent.localCandidates {
+ candidates = append(candidates, set...)
+ }
+ res = candidates
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
+
+// GetLocalUserCredentials returns the local user credentials
+func (a *Agent) GetLocalUserCredentials() (frag string, pwd string, err error) {
+ valSet := make(chan struct{})
+ err = a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ frag = agent.localUfrag
+ pwd = agent.localPwd
+ close(valSet)
+ })
+
+ if err == nil {
+ <-valSet
+ }
+ return
+}
+
+// GetRemoteUserCredentials returns the remote user credentials
+func (a *Agent) GetRemoteUserCredentials() (frag string, pwd string, err error) {
+ valSet := make(chan struct{})
+ err = a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ frag = agent.remoteUfrag
+ pwd = agent.remotePwd
+ close(valSet)
+ })
+
+ if err == nil {
+ <-valSet
+ }
+ return
+}
+
+// Close cleans up the Agent
+func (a *Agent) Close() error {
+ if err := a.ok(); err != nil {
+ return err
+ }
+
+ done := make(chan struct{})
+
+ a.afterRun(func(context.Context) {
+ close(done)
+ })
+
+ a.gatherCandidateCancel()
+ a.err.Store(ErrClosed)
+
+ a.tcpMux.RemoveConnByUfrag(a.localUfrag)
+
+ close(a.done)
+
+ <-done
+ return nil
+}
+
+// Remove all candidates. This closes any listening sockets
+// and removes both the local and remote candidate lists.
+//
+// This is used for restarts, failures and on close
+func (a *Agent) deleteAllCandidates() {
+ for net, cs := range a.localCandidates {
+ for _, c := range cs {
+ if err := c.close(); err != nil {
+ a.log.Warnf("Failed to close candidate %s: %v", c, err)
+ }
+ }
+ delete(a.localCandidates, net)
+ }
+ for net, cs := range a.remoteCandidates {
+ for _, c := range cs {
+ if err := c.close(); err != nil {
+ a.log.Warnf("Failed to close candidate %s: %v", c, err)
+ }
+ }
+ delete(a.remoteCandidates, net)
+ }
+}
+
+func (a *Agent) findRemoteCandidate(networkType NetworkType, addr net.Addr) Candidate {
+ ip, port, _, ok := parseAddr(addr)
+ if !ok {
+ a.log.Warnf("Error parsing addr: %s", addr)
+ return nil
+ }
+
+ set := a.remoteCandidates[networkType]
+ for _, c := range set {
+ if c.Address() == ip.String() && c.Port() == port {
+ return c
+ }
+ }
+ return nil
+}
+
+func (a *Agent) sendBindingRequest(m *stun.Message, local, remote Candidate) {
+ a.log.Tracef("ping STUN from %s to %s\n", local.String(), remote.String())
+
+ a.invalidatePendingBindingRequests(time.Now())
+ a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{
+ timestamp: time.Now(),
+ transactionID: m.TransactionID,
+ destination: remote.addr(),
+ isUseCandidate: m.Contains(stun.AttrUseCandidate),
+ })
+
+ a.sendSTUN(m, local, remote)
+}
+
+func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote Candidate) {
+ base := remote
+
+ ip, port, _, ok := parseAddr(base.addr())
+ if !ok {
+ a.log.Warnf("Error parsing addr: %s", base.addr())
+ return
+ }
+
+ if out, err := stun.Build(m, stun.BindingSuccess,
+ &stun.XORMappedAddress{
+ IP: ip,
+ Port: port,
+ },
+ stun.NewShortTermIntegrity(a.localPwd),
+ stun.Fingerprint,
+ ); err != nil {
+ a.log.Warnf("Failed to handle inbound ICE from: %s to: %s error: %s", local, remote, err)
+ } else {
+ a.sendSTUN(out, local, remote)
+ }
+}
+
+/* Removes pending binding requests that are over maxBindingRequestTimeout old
+
+ Let HTO be the transaction timeout, which SHOULD be 2*RTT if
+ RTT is known or 500 ms otherwise.
+ https://tools.ietf.org/html/rfc8445#appendix-B.1
+*/
+func (a *Agent) invalidatePendingBindingRequests(filterTime time.Time) {
+ initialSize := len(a.pendingBindingRequests)
+
+ temp := a.pendingBindingRequests[:0]
+ for _, bindingRequest := range a.pendingBindingRequests {
+ if filterTime.Sub(bindingRequest.timestamp) < maxBindingRequestTimeout {
+ temp = append(temp, bindingRequest)
+ }
+ }
+
+ a.pendingBindingRequests = temp
+ if bindRequestsRemoved := initialSize - len(a.pendingBindingRequests); bindRequestsRemoved > 0 {
+ a.log.Tracef("Discarded %d binding requests because they expired", bindRequestsRemoved)
+ }
+}
+
+// Assert that the passed TransactionID is in our pendingBindingRequests and returns the destination
+// If the bindingRequest was valid remove it from our pending cache
+func (a *Agent) handleInboundBindingSuccess(id [stun.TransactionIDSize]byte) (bool, *bindingRequest) {
+ a.invalidatePendingBindingRequests(time.Now())
+ for i := range a.pendingBindingRequests {
+ if a.pendingBindingRequests[i].transactionID == id {
+ validBindingRequest := a.pendingBindingRequests[i]
+ a.pendingBindingRequests = append(a.pendingBindingRequests[:i], a.pendingBindingRequests[i+1:]...)
+ return true, &validBindingRequest
+ }
+ }
+ return false, nil
+}
+
+// handleInbound processes STUN traffic from a remote candidate
+func (a *Agent) handleInbound(m *stun.Message, local Candidate, remote net.Addr) { //nolint:gocognit
+ var err error
+ if m == nil || local == nil {
+ return
+ }
+
+ if m.Type.Method != stun.MethodBinding ||
+ !(m.Type.Class == stun.ClassSuccessResponse ||
+ m.Type.Class == stun.ClassRequest ||
+ m.Type.Class == stun.ClassIndication) {
+ a.log.Tracef("unhandled STUN from %s to %s class(%s) method(%s)", remote, local, m.Type.Class, m.Type.Method)
+ return
+ }
+
+ if a.isControlling {
+ if m.Contains(stun.AttrICEControlling) {
+ a.log.Debug("inbound isControlling && a.isControlling == true")
+ return
+ } else if m.Contains(stun.AttrUseCandidate) {
+ a.log.Debug("useCandidate && a.isControlling == true")
+ return
+ }
+ } else {
+ if m.Contains(stun.AttrICEControlled) {
+ a.log.Debug("inbound isControlled && a.isControlling == false")
+ return
+ }
+ }
+
+ remoteCandidate := a.findRemoteCandidate(local.NetworkType(), remote)
+ if m.Type.Class == stun.ClassSuccessResponse {
+ if err = assertInboundMessageIntegrity(m, []byte(a.remotePwd)); err != nil {
+ a.log.Warnf("discard message from (%s), %v", remote, err)
+ return
+ }
+
+ if remoteCandidate == nil {
+ a.log.Warnf("discard success message from (%s), no such remote", remote)
+ return
+ }
+
+ a.selector.HandleSuccessResponse(m, local, remoteCandidate, remote)
+ } else if m.Type.Class == stun.ClassRequest {
+ if err = assertInboundUsername(m, a.localUfrag+":"+a.remoteUfrag); err != nil {
+ a.log.Warnf("discard message from (%s), %v", remote, err)
+ return
+ } else if err = assertInboundMessageIntegrity(m, []byte(a.localPwd)); err != nil {
+ a.log.Warnf("discard message from (%s), %v", remote, err)
+ return
+ }
+
+ if remoteCandidate == nil {
+ ip, port, networkType, ok := parseAddr(remote)
+ if !ok {
+ a.log.Errorf("Failed to create parse remote net.Addr when creating remote prflx candidate")
+ return
+ }
+
+ prflxCandidateConfig := CandidatePeerReflexiveConfig{
+ Network: networkType.String(),
+ Address: ip.String(),
+ Port: port,
+ Component: local.Component(),
+ RelAddr: "",
+ RelPort: 0,
+ }
+
+ prflxCandidate, err := NewCandidatePeerReflexive(&prflxCandidateConfig)
+ if err != nil {
+ a.log.Errorf("Failed to create new remote prflx candidate (%s)", err)
+ return
+ }
+ remoteCandidate = prflxCandidate
+
+ a.log.Debugf("adding a new peer-reflexive candidate: %s ", remote)
+ a.addRemoteCandidate(remoteCandidate)
+ }
+
+ a.log.Tracef("inbound STUN (Request) from %s to %s", remote.String(), local.String())
+
+ a.selector.HandleBindingRequest(m, local, remoteCandidate)
+ }
+
+ if remoteCandidate != nil {
+ remoteCandidate.seen(false)
+ }
+}
+
+// validateNonSTUNTraffic processes non STUN traffic from a remote candidate,
+// and returns true if it is an actual remote candidate
+func (a *Agent) validateNonSTUNTraffic(local Candidate, remote net.Addr) bool {
+ var isValidCandidate uint64
+ if err := a.run(local.context(), func(ctx context.Context, agent *Agent) {
+ remoteCandidate := a.findRemoteCandidate(local.NetworkType(), remote)
+ if remoteCandidate != nil {
+ remoteCandidate.seen(false)
+ atomic.AddUint64(&isValidCandidate, 1)
+ }
+ }); err != nil {
+ a.log.Warnf("failed to validate remote candidate: %v", err)
+ }
+
+ return atomic.LoadUint64(&isValidCandidate) == 1
+}
+
+func (a *Agent) getSelectedPair() *candidatePair {
+ selectedPair := a.selectedPair.Load()
+
+ if selectedPair == nil {
+ return nil
+ }
+
+ return selectedPair.(*candidatePair)
+}
+
+func (a *Agent) closeMulticastConn() {
+ if a.mDNSConn != nil {
+ if err := a.mDNSConn.Close(); err != nil {
+ a.log.Warnf("failed to close mDNS Conn: %v", err)
+ }
+ }
+}
+
+// SetRemoteCredentials sets the credentials of the remote agent
+func (a *Agent) SetRemoteCredentials(remoteUfrag, remotePwd string) error {
+ switch {
+ case remoteUfrag == "":
+ return ErrRemoteUfragEmpty
+ case remotePwd == "":
+ return ErrRemotePwdEmpty
+ }
+
+ return a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ agent.remoteUfrag = remoteUfrag
+ agent.remotePwd = remotePwd
+ })
+}
+
+// Restart restarts the ICE Agent with the provided ufrag/pwd
+// If no ufrag/pwd is provided the Agent will generate one itself
+//
+// Restart must only be called when GatheringState is GatheringStateComplete
+// a user must then call GatherCandidates explicitly to start generating new ones
+func (a *Agent) Restart(ufrag, pwd string) error {
+ if ufrag == "" {
+ var err error
+ ufrag, err = generateUFrag()
+ if err != nil {
+ return err
+ }
+ }
+ if pwd == "" {
+ var err error
+ pwd, err = generatePwd()
+ if err != nil {
+ return err
+ }
+ }
+
+ if len([]rune(ufrag))*8 < 24 {
+ return ErrLocalUfragInsufficientBits
+ }
+ if len([]rune(pwd))*8 < 128 {
+ return ErrLocalPwdInsufficientBits
+ }
+
+ var err error
+ if runErr := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ if agent.gatheringState == GatheringStateGathering {
+ err = ErrRestartWhenGathering
+ return
+ }
+
+ // Clear all agent needed to take back to fresh state
+ agent.localUfrag = ufrag
+ agent.localPwd = pwd
+ agent.remoteUfrag = ""
+ agent.remotePwd = ""
+ a.gatheringState = GatheringStateNew
+ a.checklist = make([]*candidatePair, 0)
+ a.pendingBindingRequests = make([]bindingRequest, 0)
+ a.setSelectedPair(nil)
+ a.deleteAllCandidates()
+ if a.selector != nil {
+ a.selector.Start()
+ }
+
+ // Restart is used by NewAgent. Accept/Connect should be used to move to checking
+ // for new Agents
+ if a.connectionState != ConnectionStateNew {
+ a.updateConnectionState(ConnectionStateChecking)
+ }
+ }); runErr != nil {
+ return runErr
+ }
+ return err
+}
+
+func (a *Agent) setGatheringState(newState GatheringState) error {
+ done := make(chan struct{})
+ if err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ if a.gatheringState != newState && newState == GatheringStateComplete {
+ a.chanCandidate <- nil
+ }
+
+ a.gatheringState = newState
+ close(done)
+ }); err != nil {
+ return err
+ }
+
+ <-done
+ return nil
+}
diff --git a/vendor/github.com/pion/ice/v2/agent_config.go b/vendor/github.com/pion/ice/v2/agent_config.go
new file mode 100644
index 0000000..e09ad76
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/agent_config.go
@@ -0,0 +1,252 @@
+package ice
+
+import (
+ "time"
+
+ "github.com/pion/logging"
+ "github.com/pion/transport/vnet"
+ "golang.org/x/net/proxy"
+)
+
+const (
+ // defaultCheckInterval is the interval at which the agent performs candidate checks in the connecting phase
+ defaultCheckInterval = 200 * time.Millisecond
+
+ // keepaliveInterval used to keep candidates alive
+ defaultKeepaliveInterval = 2 * time.Second
+
+ // defaultDisconnectedTimeout is the default time till an Agent transitions disconnected
+ defaultDisconnectedTimeout = 5 * time.Second
+
+ // defaultFailedTimeout is the default time till an Agent transitions to failed after disconnected
+ defaultFailedTimeout = 25 * time.Second
+
+ // wait time before nominating a host candidate
+ defaultHostAcceptanceMinWait = 0
+
+ // wait time before nominating a srflx candidate
+ defaultSrflxAcceptanceMinWait = 500 * time.Millisecond
+
+ // wait time before nominating a prflx candidate
+ defaultPrflxAcceptanceMinWait = 1000 * time.Millisecond
+
+ // wait time before nominating a relay candidate
+ defaultRelayAcceptanceMinWait = 2000 * time.Millisecond
+
+ // max binding request before considering a pair failed
+ defaultMaxBindingRequests = 7
+
+ // the number of bytes that can be buffered before we start to error
+ maxBufferSize = 1000 * 1000 // 1MB
+
+ // wait time before binding requests can be deleted
+ maxBindingRequestTimeout = 4000 * time.Millisecond
+)
+
+func defaultCandidateTypes() []CandidateType {
+ return []CandidateType{CandidateTypeHost, CandidateTypeServerReflexive, CandidateTypeRelay}
+}
+
+// AgentConfig collects the arguments to ice.Agent construction into
+// a single structure, for future-proofness of the interface
+type AgentConfig struct {
+ Urls []*URL
+
+ // PortMin and PortMax are optional. Leave them 0 for the default UDP port allocation strategy.
+ PortMin uint16
+ PortMax uint16
+
+ // LocalUfrag and LocalPwd values used to perform connectivity
+ // checks. The values MUST be unguessable, with at least 128 bits of
+ // random number generator output used to generate the password, and
+ // at least 24 bits of output to generate the username fragment.
+ LocalUfrag string
+ LocalPwd string
+
+ // MulticastDNSMode controls mDNS behavior for the ICE agent
+ MulticastDNSMode MulticastDNSMode
+
+ // MulticastDNSHostName controls the hostname for this agent. If none is specified a random one will be generated
+ MulticastDNSHostName string
+
+ // DisconnectedTimeout defaults to 5 seconds when this property is nil.
+ // If the duration is 0, the ICE Agent will never go to disconnected
+ DisconnectedTimeout *time.Duration
+
+ // FailedTimeout defaults to 25 seconds when this property is nil.
+ // If the duration is 0, we will never go to failed.
+ FailedTimeout *time.Duration
+
+ // KeepaliveInterval determines how often should we send ICE
+ // keepalives (should be less then connectiontimeout above)
+ // when this is nil, it defaults to 10 seconds.
+ // A keepalive interval of 0 means we never send keepalive packets
+ KeepaliveInterval *time.Duration
+
+ // NetworkTypes is an optional configuration for disabling or enabling
+ // support for specific network types.
+ NetworkTypes []NetworkType
+
+ // CandidateTypes is an optional configuration for disabling or enabling
+ // support for specific candidate types.
+ CandidateTypes []CandidateType
+
+ LoggerFactory logging.LoggerFactory
+
+ // checkInterval controls how often our internal task loop runs when
+ // in the connecting state. Only useful for testing.
+ checkInterval time.Duration
+
+ // MaxBindingRequests is the max amount of binding requests the agent will send
+ // over a candidate pair for validation or nomination, if after MaxBindingRequests
+ // the candidate is yet to answer a binding request or a nomination we set the pair as failed
+ MaxBindingRequests *uint16
+
+ // Lite agents do not perform connectivity check and only provide host candidates.
+ Lite bool
+
+ // NAT1To1IPCandidateType is used along with NAT1To1IPs to specify which candidate type
+ // the 1:1 NAT IP addresses should be mapped to.
+ // If unspecified or CandidateTypeHost, NAT1To1IPs are used to replace host candidate IPs.
+ // If CandidateTypeServerReflexive, it will insert a srflx candidate (as if it was dervied
+ // from a STUN server) with its port number being the one for the actual host candidate.
+ // Other values will result in an error.
+ NAT1To1IPCandidateType CandidateType
+
+ // NAT1To1IPs contains a list of public IP addresses that are to be used as a host
+ // candidate or srflx candidate. This is used typically for servers that are behind
+ // 1:1 D-NAT (e.g. AWS EC2 instances) and to eliminate the need of server reflexisive
+ // candidate gathering.
+ NAT1To1IPs []string
+
+ // HostAcceptanceMinWait specify a minimum wait time before selecting host candidates
+ HostAcceptanceMinWait *time.Duration
+ // HostAcceptanceMinWait specify a minimum wait time before selecting srflx candidates
+ SrflxAcceptanceMinWait *time.Duration
+ // HostAcceptanceMinWait specify a minimum wait time before selecting prflx candidates
+ PrflxAcceptanceMinWait *time.Duration
+ // HostAcceptanceMinWait specify a minimum wait time before selecting relay candidates
+ RelayAcceptanceMinWait *time.Duration
+
+ // Net is the our abstracted network interface for internal development purpose only
+ // (see github.com/pion/transport/vnet)
+ Net *vnet.Net
+
+ // InterfaceFilter is a function that you can use in order to whitelist or blacklist
+ // the interfaces which are used to gather ICE candidates.
+ InterfaceFilter func(string) bool
+
+ // InsecureSkipVerify controls if self-signed certificates are accepted when connecting
+ // to TURN servers via TLS or DTLS
+ InsecureSkipVerify bool
+
+ // TCPMux will be used for multiplexing incoming TCP connections for ICE TCP.
+ // Currently only passive candidates are supported. This functionality is
+ // experimental and the API might change in the future.
+ TCPMux TCPMux
+
+ // Proxy Dialer is a dialer that should be implemented by the user based on golang.org/x/net/proxy
+ // dial interface in order to support corporate proxies
+ ProxyDialer proxy.Dialer
+}
+
+// initWithDefaults populates an agent and falls back to defaults if fields are unset
+func (config *AgentConfig) initWithDefaults(a *Agent) {
+ if config.MaxBindingRequests == nil {
+ a.maxBindingRequests = defaultMaxBindingRequests
+ } else {
+ a.maxBindingRequests = *config.MaxBindingRequests
+ }
+
+ if config.HostAcceptanceMinWait == nil {
+ a.hostAcceptanceMinWait = defaultHostAcceptanceMinWait
+ } else {
+ a.hostAcceptanceMinWait = *config.HostAcceptanceMinWait
+ }
+
+ if config.SrflxAcceptanceMinWait == nil {
+ a.srflxAcceptanceMinWait = defaultSrflxAcceptanceMinWait
+ } else {
+ a.srflxAcceptanceMinWait = *config.SrflxAcceptanceMinWait
+ }
+
+ if config.PrflxAcceptanceMinWait == nil {
+ a.prflxAcceptanceMinWait = defaultPrflxAcceptanceMinWait
+ } else {
+ a.prflxAcceptanceMinWait = *config.PrflxAcceptanceMinWait
+ }
+
+ if config.RelayAcceptanceMinWait == nil {
+ a.relayAcceptanceMinWait = defaultRelayAcceptanceMinWait
+ } else {
+ a.relayAcceptanceMinWait = *config.RelayAcceptanceMinWait
+ }
+
+ if config.DisconnectedTimeout == nil {
+ a.disconnectedTimeout = defaultDisconnectedTimeout
+ } else {
+ a.disconnectedTimeout = *config.DisconnectedTimeout
+ }
+
+ if config.FailedTimeout == nil {
+ a.failedTimeout = defaultFailedTimeout
+ } else {
+ a.failedTimeout = *config.FailedTimeout
+ }
+
+ if config.KeepaliveInterval == nil {
+ a.keepaliveInterval = defaultKeepaliveInterval
+ } else {
+ a.keepaliveInterval = *config.KeepaliveInterval
+ }
+
+ if config.checkInterval == 0 {
+ a.checkInterval = defaultCheckInterval
+ } else {
+ a.checkInterval = config.checkInterval
+ }
+
+ if config.CandidateTypes == nil || len(config.CandidateTypes) == 0 {
+ a.candidateTypes = defaultCandidateTypes()
+ } else {
+ a.candidateTypes = config.CandidateTypes
+ }
+}
+
+func (config *AgentConfig) initExtIPMapping(a *Agent) error {
+ var err error
+ a.extIPMapper, err = newExternalIPMapper(config.NAT1To1IPCandidateType, config.NAT1To1IPs)
+ if err != nil {
+ return err
+ }
+ if a.extIPMapper == nil {
+ return nil // this may happen when config.NAT1To1IPs is an empty array
+ }
+ if a.extIPMapper.candidateType == CandidateTypeHost {
+ if a.mDNSMode == MulticastDNSModeQueryAndGather {
+ return ErrMulticastDNSWithNAT1To1IPMapping
+ }
+ candiHostEnabled := false
+ for _, candiType := range a.candidateTypes {
+ if candiType == CandidateTypeHost {
+ candiHostEnabled = true
+ break
+ }
+ }
+ if !candiHostEnabled {
+ return ErrIneffectiveNAT1To1IPMappingHost
+ }
+ } else if a.extIPMapper.candidateType == CandidateTypeServerReflexive {
+ candiSrflxEnabled := false
+ for _, candiType := range a.candidateTypes {
+ if candiType == CandidateTypeServerReflexive {
+ candiSrflxEnabled = true
+ break
+ }
+ }
+ if !candiSrflxEnabled {
+ return ErrIneffectiveNAT1To1IPMappingSrflx
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/pion/ice/v2/agent_stats.go b/vendor/github.com/pion/ice/v2/agent_stats.go
new file mode 100644
index 0000000..18d9ed8
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/agent_stats.go
@@ -0,0 +1,113 @@
+package ice
+
+import (
+ "context"
+ "time"
+)
+
+// GetCandidatePairsStats returns a list of candidate pair stats
+func (a *Agent) GetCandidatePairsStats() []CandidatePairStats {
+ var res []CandidatePairStats
+ err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ result := make([]CandidatePairStats, 0, len(agent.checklist))
+ for _, cp := range agent.checklist {
+ stat := CandidatePairStats{
+ Timestamp: time.Now(),
+ LocalCandidateID: cp.local.ID(),
+ RemoteCandidateID: cp.remote.ID(),
+ State: cp.state,
+ Nominated: cp.nominated,
+ // PacketsSent uint32
+ // PacketsReceived uint32
+ // BytesSent uint64
+ // BytesReceived uint64
+ // LastPacketSentTimestamp time.Time
+ // LastPacketReceivedTimestamp time.Time
+ // FirstRequestTimestamp time.Time
+ // LastRequestTimestamp time.Time
+ // LastResponseTimestamp time.Time
+ // TotalRoundTripTime float64
+ // CurrentRoundTripTime float64
+ // AvailableOutgoingBitrate float64
+ // AvailableIncomingBitrate float64
+ // CircuitBreakerTriggerCount uint32
+ // RequestsReceived uint64
+ // RequestsSent uint64
+ // ResponsesReceived uint64
+ // ResponsesSent uint64
+ // RetransmissionsReceived uint64
+ // RetransmissionsSent uint64
+ // ConsentRequestsSent uint64
+ // ConsentExpiredTimestamp time.Time
+ }
+ result = append(result, stat)
+ }
+ res = result
+ })
+ if err != nil {
+ a.log.Errorf("error getting candidate pairs stats %v", err)
+ return []CandidatePairStats{}
+ }
+ return res
+}
+
+// GetLocalCandidatesStats returns a list of local candidates stats
+func (a *Agent) GetLocalCandidatesStats() []CandidateStats {
+ var res []CandidateStats
+ err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ result := make([]CandidateStats, 0, len(agent.localCandidates))
+ for networkType, localCandidates := range agent.localCandidates {
+ for _, c := range localCandidates {
+ stat := CandidateStats{
+ Timestamp: time.Now(),
+ ID: c.ID(),
+ NetworkType: networkType,
+ IP: c.Address(),
+ Port: c.Port(),
+ CandidateType: c.Type(),
+ Priority: c.Priority(),
+ // URL string
+ RelayProtocol: "udp",
+ // Deleted bool
+ }
+ result = append(result, stat)
+ }
+ }
+ res = result
+ })
+ if err != nil {
+ a.log.Errorf("error getting candidate pairs stats %v", err)
+ return []CandidateStats{}
+ }
+ return res
+}
+
+// GetRemoteCandidatesStats returns a list of remote candidates stats
+func (a *Agent) GetRemoteCandidatesStats() []CandidateStats {
+ var res []CandidateStats
+ err := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ result := make([]CandidateStats, 0, len(agent.remoteCandidates))
+ for networkType, localCandidates := range agent.remoteCandidates {
+ for _, c := range localCandidates {
+ stat := CandidateStats{
+ Timestamp: time.Now(),
+ ID: c.ID(),
+ NetworkType: networkType,
+ IP: c.Address(),
+ Port: c.Port(),
+ CandidateType: c.Type(),
+ Priority: c.Priority(),
+ // URL string
+ RelayProtocol: "udp",
+ }
+ result = append(result, stat)
+ }
+ }
+ res = result
+ })
+ if err != nil {
+ a.log.Errorf("error getting candidate pairs stats %v", err)
+ return []CandidateStats{}
+ }
+ return res
+}
diff --git a/vendor/github.com/pion/ice/v2/candidate.go b/vendor/github.com/pion/ice/v2/candidate.go
new file mode 100644
index 0000000..701f8f8
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidate.go
@@ -0,0 +1,68 @@
+package ice
+
+import (
+ "context"
+ "net"
+ "time"
+)
+
+const (
+ receiveMTU = 8192
+ defaultLocalPreference = 65535
+
+ // ComponentRTP indicates that the candidate is used for RTP
+ ComponentRTP uint16 = 1
+ // ComponentRTCP indicates that the candidate is used for RTCP
+ ComponentRTCP
+)
+
+// Candidate represents an ICE candidate
+type Candidate interface {
+ // An arbitrary string used in the freezing algorithm to
+ // group similar candidates. It is the same for two candidates that
+ // have the same type, base IP address, protocol (UDP, TCP, etc.),
+ // and STUN or TURN server.
+ Foundation() string
+
+ // ID is a unique identifier for just this candidate
+ // Unlike the foundation this is different for each candidate
+ ID() string
+
+ // A component is a piece of a data stream.
+ // An example is one for RTP, and one for RTCP
+ Component() uint16
+ SetComponent(uint16)
+
+ // The last time this candidate received traffic
+ LastReceived() time.Time
+
+ // The last time this candidate sent traffic
+ LastSent() time.Time
+
+ NetworkType() NetworkType
+ Address() string
+ Port() int
+
+ Priority() uint32
+
+ // A transport address related to a
+ // candidate, which is useful for diagnostics and other purposes
+ RelatedAddress() *CandidateRelatedAddress
+
+ String() string
+ Type() CandidateType
+ TCPType() TCPType
+
+ Equal(other Candidate) bool
+
+ Marshal() string
+
+ addr() net.Addr
+ agent() *Agent
+ context() context.Context
+
+ close() error
+ seen(outbound bool)
+ start(a *Agent, conn net.PacketConn, initializedCh <-chan struct{})
+ writeTo(raw []byte, dst Candidate) (int, error)
+}
diff --git a/vendor/github.com/pion/ice/v2/candidate_base.go b/vendor/github.com/pion/ice/v2/candidate_base.go
new file mode 100644
index 0000000..2f16a8a
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidate_base.go
@@ -0,0 +1,496 @@
+package ice
+
+import (
+ "context"
+ "fmt"
+ "hash/crc32"
+ "net"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/pion/logging"
+ "github.com/pion/stun"
+)
+
+type candidateBase struct {
+ id string
+ networkType NetworkType
+ candidateType CandidateType
+
+ component uint16
+ address string
+ port int
+ relatedAddress *CandidateRelatedAddress
+ tcpType TCPType
+
+ resolvedAddr net.Addr
+
+ lastSent atomic.Value
+ lastReceived atomic.Value
+ conn net.PacketConn
+
+ currAgent *Agent
+ closeCh chan struct{}
+ closedCh chan struct{}
+
+ foundationOverride string
+ priorityOverride uint32
+}
+
+// Done implements context.Context
+func (c *candidateBase) Done() <-chan struct{} {
+ return c.closeCh
+}
+
+// Err implements context.Context
+func (c *candidateBase) Err() error {
+ select {
+ case <-c.closedCh:
+ return ErrRunCanceled
+ default:
+ return nil
+ }
+}
+
+// Deadline implements context.Context
+func (c *candidateBase) Deadline() (deadline time.Time, ok bool) {
+ return time.Time{}, false
+}
+
+// Value implements context.Context
+func (c *candidateBase) Value(key interface{}) interface{} {
+ return nil
+}
+
+// ID returns Candidate ID
+func (c *candidateBase) ID() string {
+ return c.id
+}
+
+func (c *candidateBase) Foundation() string {
+ if c.foundationOverride != "" {
+ return c.foundationOverride
+ }
+
+ return fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(c.Type().String()+c.address+c.networkType.String())))
+}
+
+// Address returns Candidate Address
+func (c *candidateBase) Address() string {
+ return c.address
+}
+
+// Port returns Candidate Port
+func (c *candidateBase) Port() int {
+ return c.port
+}
+
+// Type returns candidate type
+func (c *candidateBase) Type() CandidateType {
+ return c.candidateType
+}
+
+// NetworkType returns candidate NetworkType
+func (c *candidateBase) NetworkType() NetworkType {
+ return c.networkType
+}
+
+// Component returns candidate component
+func (c *candidateBase) Component() uint16 {
+ return c.component
+}
+
+func (c *candidateBase) SetComponent(component uint16) {
+ c.component = component
+}
+
+// LocalPreference returns the local preference for this candidate
+func (c *candidateBase) LocalPreference() uint16 {
+ if c.NetworkType().IsTCP() {
+ // RFC 6544, section 4.2
+ //
+ // In Section 4.1.2.1 of [RFC5245], a recommended formula for UDP ICE
+ // candidate prioritization is defined. For TCP candidates, the same
+ // formula and candidate type preferences SHOULD be used, and the
+ // RECOMMENDED type preferences for the new candidate types defined in
+ // this document (see Section 5) are 105 for NAT-assisted candidates and
+ // 75 for UDP-tunneled candidates.
+ //
+ // (...)
+ //
+ // With TCP candidates, the local preference part of the recommended
+ // priority formula is updated to also include the directionality
+ // (active, passive, or simultaneous-open) of the TCP connection. The
+ // RECOMMENDED local preference is then defined as:
+ //
+ // local preference = (2^13) * direction-pref + other-pref
+ //
+ // The direction-pref MUST be between 0 and 7 (both inclusive), with 7
+ // being the most preferred. The other-pref MUST be between 0 and 8191
+ // (both inclusive), with 8191 being the most preferred. It is
+ // RECOMMENDED that the host, UDP-tunneled, and relayed TCP candidates
+ // have the direction-pref assigned as follows: 6 for active, 4 for
+ // passive, and 2 for S-O. For the NAT-assisted and server reflexive
+ // candidates, the RECOMMENDED values are: 6 for S-O, 4 for active, and
+ // 2 for passive.
+ //
+ // (...)
+ //
+ // If any two candidates have the same type-preference and direction-
+ // pref, they MUST have a unique other-pref. With this specification,
+ // this usually only happens with multi-homed hosts, in which case
+ // other-pref is the preference for the particular IP address from which
+ // the candidate was obtained. When there is only a single IP address,
+ // this value SHOULD be set to the maximum allowed value (8191).
+ var otherPref uint16 = 8191
+
+ directionPref := func() uint16 {
+ switch c.Type() {
+ case CandidateTypeHost, CandidateTypeRelay:
+ switch c.tcpType {
+ case TCPTypeActive:
+ return 6
+ case TCPTypePassive:
+ return 4
+ case TCPTypeSimultaneousOpen:
+ return 2
+ case TCPTypeUnspecified:
+ return 0
+ }
+ case CandidateTypePeerReflexive, CandidateTypeServerReflexive:
+ switch c.tcpType {
+ case TCPTypeSimultaneousOpen:
+ return 6
+ case TCPTypeActive:
+ return 4
+ case TCPTypePassive:
+ return 2
+ case TCPTypeUnspecified:
+ return 0
+ }
+ case CandidateTypeUnspecified:
+ return 0
+ }
+ return 0
+ }()
+
+ return (1<<13)*directionPref + otherPref
+ }
+
+ return defaultLocalPreference
+}
+
+// RelatedAddress returns *CandidateRelatedAddress
+func (c *candidateBase) RelatedAddress() *CandidateRelatedAddress {
+ return c.relatedAddress
+}
+
+func (c *candidateBase) TCPType() TCPType {
+ return c.tcpType
+}
+
+// start runs the candidate using the provided connection
+func (c *candidateBase) start(a *Agent, conn net.PacketConn, initializedCh <-chan struct{}) {
+ if c.conn != nil {
+ c.agent().log.Warn("Can't start already started candidateBase")
+ return
+ }
+ c.currAgent = a
+ c.conn = conn
+ c.closeCh = make(chan struct{})
+ c.closedCh = make(chan struct{})
+
+ go c.recvLoop(initializedCh)
+}
+
+func (c *candidateBase) recvLoop(initializedCh <-chan struct{}) {
+ defer func() {
+ close(c.closedCh)
+ }()
+
+ select {
+ case <-initializedCh:
+ case <-c.closeCh:
+ return
+ }
+
+ log := c.agent().log
+ buffer := make([]byte, receiveMTU)
+ for {
+ n, srcAddr, err := c.conn.ReadFrom(buffer)
+ if err != nil {
+ return
+ }
+
+ handleInboundCandidateMsg(c, c, buffer[:n], srcAddr, log)
+ }
+}
+
+func handleInboundCandidateMsg(ctx context.Context, c Candidate, buffer []byte, srcAddr net.Addr, log logging.LeveledLogger) {
+ if stun.IsMessage(buffer) {
+ m := &stun.Message{
+ Raw: make([]byte, len(buffer)),
+ }
+ // Explicitly copy raw buffer so Message can own the memory.
+ copy(m.Raw, buffer)
+ if err := m.Decode(); err != nil {
+ log.Warnf("Failed to handle decode ICE from %s to %s: %v", c.addr(), srcAddr, err)
+ return
+ }
+ err := c.agent().run(ctx, func(ctx context.Context, agent *Agent) {
+ agent.handleInbound(m, c, srcAddr)
+ })
+ if err != nil {
+ log.Warnf("Failed to handle message: %v", err)
+ }
+
+ return
+ }
+
+ if !c.agent().validateNonSTUNTraffic(c, srcAddr) {
+ log.Warnf("Discarded message from %s, not a valid remote candidate", c.addr())
+ return
+ }
+
+ // NOTE This will return packetio.ErrFull if the buffer ever manages to fill up.
+ if _, err := c.agent().buffer.Write(buffer); err != nil {
+ log.Warnf("failed to write packet")
+ }
+}
+
+// close stops the recvLoop
+func (c *candidateBase) close() error {
+ // If conn has never been started will be nil
+ if c.Done() == nil {
+ return nil
+ }
+
+ // Assert that conn has not already been closed
+ select {
+ case <-c.Done():
+ return nil
+ default:
+ }
+
+ var firstErr error
+
+ // Unblock recvLoop
+ close(c.closeCh)
+ if err := c.conn.SetDeadline(time.Now()); err != nil {
+ firstErr = err
+ }
+
+ // Close the conn
+ if err := c.conn.Close(); err != nil && firstErr == nil {
+ firstErr = err
+ }
+
+ if firstErr != nil {
+ return firstErr
+ }
+
+ // Wait until the recvLoop is closed
+ <-c.closedCh
+
+ return nil
+}
+
+func (c *candidateBase) writeTo(raw []byte, dst Candidate) (int, error) {
+ n, err := c.conn.WriteTo(raw, dst.addr())
+ if err != nil {
+ c.agent().log.Warnf("%s: %v", errSendPacket, err)
+ return n, nil
+ }
+ c.seen(true)
+ return n, nil
+}
+
+// Priority computes the priority for this ICE Candidate
+func (c *candidateBase) Priority() uint32 {
+ if c.priorityOverride != 0 {
+ return c.priorityOverride
+ }
+
+ // The local preference MUST be an integer from 0 (lowest preference) to
+ // 65535 (highest preference) inclusive. When there is only a single IP
+ // address, this value SHOULD be set to 65535. If there are multiple
+ // candidates for a particular component for a particular data stream
+ // that have the same type, the local preference MUST be unique for each
+ // one.
+ return (1<<24)*uint32(c.Type().Preference()) +
+ (1<<8)*uint32(c.LocalPreference()) +
+ uint32(256-c.Component())
+}
+
+// Equal is used to compare two candidateBases
+func (c *candidateBase) Equal(other Candidate) bool {
+ return c.NetworkType() == other.NetworkType() &&
+ c.Type() == other.Type() &&
+ c.Address() == other.Address() &&
+ c.Port() == other.Port() &&
+ c.TCPType() == other.TCPType() &&
+ c.RelatedAddress().Equal(other.RelatedAddress())
+}
+
+// String makes the candidateBase printable
+func (c *candidateBase) String() string {
+ return fmt.Sprintf("%s %s %s:%d%s", c.NetworkType(), c.Type(), c.Address(), c.Port(), c.relatedAddress)
+}
+
+// LastReceived returns a time.Time indicating the last time
+// this candidate was received
+func (c *candidateBase) LastReceived() time.Time {
+ lastReceived := c.lastReceived.Load()
+ if lastReceived == nil {
+ return time.Time{}
+ }
+ return lastReceived.(time.Time)
+}
+
+func (c *candidateBase) setLastReceived(t time.Time) {
+ c.lastReceived.Store(t)
+}
+
+// LastSent returns a time.Time indicating the last time
+// this candidate was sent
+func (c *candidateBase) LastSent() time.Time {
+ lastSent := c.lastSent.Load()
+ if lastSent == nil {
+ return time.Time{}
+ }
+ return lastSent.(time.Time)
+}
+
+func (c *candidateBase) setLastSent(t time.Time) {
+ c.lastSent.Store(t)
+}
+
+func (c *candidateBase) seen(outbound bool) {
+ if outbound {
+ c.setLastSent(time.Now())
+ } else {
+ c.setLastReceived(time.Now())
+ }
+}
+
+func (c *candidateBase) addr() net.Addr {
+ return c.resolvedAddr
+}
+
+func (c *candidateBase) agent() *Agent {
+ return c.currAgent
+}
+
+func (c *candidateBase) context() context.Context {
+ return c
+}
+
+// Marshal returns the string representation of the ICECandidate
+func (c *candidateBase) Marshal() string {
+ val := fmt.Sprintf("%s %d %s %d %s %d typ %s",
+ c.Foundation(),
+ c.Component(),
+ c.NetworkType().NetworkShort(),
+ c.Priority(),
+ c.Address(),
+ c.Port(),
+ c.Type())
+
+ if c.tcpType != TCPTypeUnspecified {
+ val += fmt.Sprintf(" tcptype %s", c.tcpType.String())
+ }
+
+ if c.RelatedAddress() != nil {
+ val = fmt.Sprintf("%s raddr %s rport %d",
+ val,
+ c.RelatedAddress().Address,
+ c.RelatedAddress().Port)
+ }
+
+ return val
+}
+
+// UnmarshalCandidate creates a Candidate from its string representation
+func UnmarshalCandidate(raw string) (Candidate, error) {
+ split := strings.Fields(raw)
+ if len(split) < 8 {
+ return nil, fmt.Errorf("%w (%d)", errAttributeTooShortICECandidate, len(split))
+ }
+
+ // Foundation
+ foundation := split[0]
+
+ // Component
+ rawComponent, err := strconv.ParseUint(split[1], 10, 16)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", errParseComponent, err)
+ }
+ component := uint16(rawComponent)
+
+ // Protocol
+ protocol := split[2]
+
+ // Priority
+ priorityRaw, err := strconv.ParseUint(split[3], 10, 32)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", errParsePriority, err)
+ }
+ priority := uint32(priorityRaw)
+
+ // Address
+ address := split[4]
+
+ // Port
+ rawPort, err := strconv.ParseUint(split[5], 10, 16)
+ if err != nil {
+ return nil, fmt.Errorf("%w: %v", errParsePort, err)
+ }
+ port := int(rawPort)
+ typ := split[7]
+
+ relatedAddress := ""
+ relatedPort := 0
+ tcpType := TCPTypeUnspecified
+
+ if len(split) > 8 {
+ split = split[8:]
+
+ if split[0] == "raddr" {
+ if len(split) < 4 {
+ return nil, fmt.Errorf("%w: incorrect length", errParseRelatedAddr)
+ }
+
+ // RelatedAddress
+ relatedAddress = split[1]
+
+ // RelatedPort
+ rawRelatedPort, parseErr := strconv.ParseUint(split[3], 10, 16)
+ if parseErr != nil {
+ return nil, fmt.Errorf("%w: %v", errParsePort, parseErr)
+ }
+ relatedPort = int(rawRelatedPort)
+ } else if split[0] == "tcptype" {
+ if len(split) < 2 {
+ return nil, fmt.Errorf("%w: incorrect length", errParseTypType)
+ }
+
+ tcpType = NewTCPType(split[1])
+ }
+ }
+
+ switch typ {
+ case "host":
+ return NewCandidateHost(&CandidateHostConfig{"", protocol, address, port, component, priority, foundation, tcpType})
+ case "srflx":
+ return NewCandidateServerReflexive(&CandidateServerReflexiveConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort})
+ case "prflx":
+ return NewCandidatePeerReflexive(&CandidatePeerReflexiveConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort})
+ case "relay":
+ return NewCandidateRelay(&CandidateRelayConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort, nil})
+ default:
+ }
+
+ return nil, fmt.Errorf("%w (%s)", errUnknownCandidateTyp, typ)
+}
diff --git a/vendor/github.com/pion/ice/v2/candidate_host.go b/vendor/github.com/pion/ice/v2/candidate_host.go
new file mode 100644
index 0000000..b03dbdb
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidate_host.go
@@ -0,0 +1,76 @@
+package ice
+
+import (
+ "net"
+ "strings"
+)
+
+// CandidateHost is a candidate of type host
+type CandidateHost struct {
+ candidateBase
+
+ network string
+}
+
+// CandidateHostConfig is the config required to create a new CandidateHost
+type CandidateHostConfig struct {
+ CandidateID string
+ Network string
+ Address string
+ Port int
+ Component uint16
+ Priority uint32
+ Foundation string
+ TCPType TCPType
+}
+
+// NewCandidateHost creates a new host candidate
+func NewCandidateHost(config *CandidateHostConfig) (*CandidateHost, error) {
+ candidateID := config.CandidateID
+
+ if candidateID == "" {
+ candidateID = globalCandidateIDGenerator.Generate()
+ }
+
+ c := &CandidateHost{
+ candidateBase: candidateBase{
+ id: candidateID,
+ address: config.Address,
+ candidateType: CandidateTypeHost,
+ component: config.Component,
+ port: config.Port,
+ tcpType: config.TCPType,
+ foundationOverride: config.Foundation,
+ priorityOverride: config.Priority,
+ },
+ network: config.Network,
+ }
+
+ if !strings.HasSuffix(config.Address, ".local") {
+ ip := net.ParseIP(config.Address)
+ if ip == nil {
+ return nil, ErrAddressParseFailed
+ }
+
+ if err := c.setIP(ip); err != nil {
+ return nil, err
+ }
+ } else {
+ // Until mDNS candidate is resolved assume it is UDPv4
+ c.candidateBase.networkType = NetworkTypeUDP4
+ }
+
+ return c, nil
+}
+
+func (c *CandidateHost) setIP(ip net.IP) error {
+ networkType, err := determineNetworkType(c.network, ip)
+ if err != nil {
+ return err
+ }
+
+ c.candidateBase.networkType = networkType
+ c.candidateBase.resolvedAddr = createAddr(networkType, ip, c.port)
+
+ return nil
+}
diff --git a/vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go b/vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go
new file mode 100644
index 0000000..0b330d1
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go
@@ -0,0 +1,60 @@
+// Package ice ...
+//nolint:dupl
+package ice
+
+import "net"
+
+// CandidatePeerReflexive ...
+type CandidatePeerReflexive struct {
+ candidateBase
+}
+
+// CandidatePeerReflexiveConfig is the config required to create a new CandidatePeerReflexive
+type CandidatePeerReflexiveConfig struct {
+ CandidateID string
+ Network string
+ Address string
+ Port int
+ Component uint16
+ Priority uint32
+ Foundation string
+ RelAddr string
+ RelPort int
+}
+
+// NewCandidatePeerReflexive creates a new peer reflective candidate
+func NewCandidatePeerReflexive(config *CandidatePeerReflexiveConfig) (*CandidatePeerReflexive, error) {
+ ip := net.ParseIP(config.Address)
+ if ip == nil {
+ return nil, ErrAddressParseFailed
+ }
+
+ networkType, err := determineNetworkType(config.Network, ip)
+ if err != nil {
+ return nil, err
+ }
+
+ candidateID := config.CandidateID
+ candidateIDGenerator := newCandidateIDGenerator()
+ if candidateID == "" {
+ candidateID = candidateIDGenerator.Generate()
+ }
+
+ return &CandidatePeerReflexive{
+ candidateBase: candidateBase{
+ id: candidateID,
+ networkType: networkType,
+ candidateType: CandidateTypePeerReflexive,
+ address: config.Address,
+ port: config.Port,
+ resolvedAddr: createAddr(networkType, ip, config.Port),
+ component: config.Component,
+ foundationOverride: config.Foundation,
+ priorityOverride: config.Priority,
+ relatedAddress: &CandidateRelatedAddress{
+ Address: config.RelAddr,
+ Port: config.RelPort,
+ },
+ },
+ }, nil
+}
diff --git a/vendor/github.com/pion/ice/v2/candidate_relay.go b/vendor/github.com/pion/ice/v2/candidate_relay.go
new file mode 100644
index 0000000..44762f7
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidate_relay.go
@@ -0,0 +1,73 @@
+package ice
+
+import (
+ "net"
+)
+
+// CandidateRelay ...
+type CandidateRelay struct {
+ candidateBase
+
+ onClose func() error
+}
+
+// CandidateRelayConfig is the config required to create a new CandidateRelay
+type CandidateRelayConfig struct {
+ CandidateID string
+ Network string
+ Address string
+ Port int
+ Component uint16
+ Priority uint32
+ Foundation string
+ RelAddr string
+ RelPort int
+ OnClose func() error
+}
+
+// NewCandidateRelay creates a new relay candidate
+func NewCandidateRelay(config *CandidateRelayConfig) (*CandidateRelay, error) {
+ candidateID := config.CandidateID
+
+ if candidateID == "" {
+ candidateID = globalCandidateIDGenerator.Generate()
+ }
+
+ ip := net.ParseIP(config.Address)
+ if ip == nil {
+ return nil, ErrAddressParseFailed
+ }
+
+ networkType, err := determineNetworkType(config.Network, ip)
+ if err != nil {
+ return nil, err
+ }
+
+ return &CandidateRelay{
+ candidateBase: candidateBase{
+ id: candidateID,
+ networkType: networkType,
+ candidateType: CandidateTypeRelay,
+ address: config.Address,
+ port: config.Port,
+ resolvedAddr: &net.UDPAddr{IP: ip, Port: config.Port},
+ component: config.Component,
+ foundationOverride: config.Foundation,
+ priorityOverride: config.Priority,
+ relatedAddress: &CandidateRelatedAddress{
+ Address: config.RelAddr,
+ Port: config.RelPort,
+ },
+ },
+ onClose: config.OnClose,
+ }, nil
+}
+
+func (c *CandidateRelay) close() error {
+ err := c.candidateBase.close()
+ if c.onClose != nil {
+ err = c.onClose()
+ c.onClose = nil
+ }
+ return err
+}
diff --git a/vendor/github.com/pion/ice/v2/candidate_server_reflexive.go b/vendor/github.com/pion/ice/v2/candidate_server_reflexive.go
new file mode 100644
index 0000000..125a537
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidate_server_reflexive.go
@@ -0,0 +1,59 @@
+// Package ice ...
+//nolint:dupl
+package ice
+
+import "net"
+
+// CandidateServerReflexive ...
+type CandidateServerReflexive struct {
+ candidateBase
+}
+
+// CandidateServerReflexiveConfig is the config required to create a new CandidateServerReflexive
+type CandidateServerReflexiveConfig struct {
+ CandidateID string
+ Network string
+ Address string
+ Port int
+ Component uint16
+ Priority uint32
+ Foundation string
+ RelAddr string
+ RelPort int
+}
+
+// NewCandidateServerReflexive creates a new server reflective candidate
+func NewCandidateServerReflexive(config *CandidateServerReflexiveConfig) (*CandidateServerReflexive, error) {
+ ip := net.ParseIP(config.Address)
+ if ip == nil {
+ return nil, ErrAddressParseFailed
+ }
+
+ networkType, err := determineNetworkType(config.Network, ip)
+ if err != nil {
+ return nil, err
+ }
+
+ candidateID := config.CandidateID
+ if candidateID == "" {
+ candidateID = globalCandidateIDGenerator.Generate()
+ }
+
+ return &CandidateServerReflexive{
+ candidateBase: candidateBase{
+ id: candidateID,
+ networkType: networkType,
+ candidateType: CandidateTypeServerReflexive,
+ address: config.Address,
+ port: config.Port,
+ resolvedAddr: &net.UDPAddr{IP: ip, Port: config.Port},
+ component: config.Component,
+ foundationOverride: config.Foundation,
+ priorityOverride: config.Priority,
+ relatedAddress: &CandidateRelatedAddress{
+ Address: config.RelAddr,
+ Port: config.RelPort,
+ },
+ },
+ }, nil
+}
diff --git a/vendor/github.com/pion/ice/v2/candidatepair.go b/vendor/github.com/pion/ice/v2/candidatepair.go
new file mode 100644
index 0000000..49dec37
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidatepair.go
@@ -0,0 +1,98 @@
+package ice
+
+import (
+ "fmt"
+
+ "github.com/pion/stun"
+)
+
+func newCandidatePair(local, remote Candidate, controlling bool) *candidatePair {
+ return &candidatePair{
+ iceRoleControlling: controlling,
+ remote: remote,
+ local: local,
+ state: CandidatePairStateWaiting,
+ }
+}
+
+// candidatePair represents a combination of a local and remote candidate
+type candidatePair struct {
+ iceRoleControlling bool
+ remote Candidate
+ local Candidate
+ bindingRequestCount uint16
+ state CandidatePairState
+ nominated bool
+}
+
+func (p *candidatePair) String() string {
+ if p == nil {
+ return ""
+ }
+
+ return fmt.Sprintf("prio %d (local, prio %d) %s <-> %s (remote, prio %d)",
+ p.Priority(), p.local.Priority(), p.local, p.remote, p.remote.Priority())
+}
+
+func (p *candidatePair) Equal(other *candidatePair) bool {
+ if p == nil && other == nil {
+ return true
+ }
+ if p == nil || other == nil {
+ return false
+ }
+ return p.local.Equal(other.local) && p.remote.Equal(other.remote)
+}
+
+// RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs
+// Let G be the priority for the candidate provided by the controlling
+// agent. Let D be the priority for the candidate provided by the
+// controlled agent.
+// pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+func (p *candidatePair) Priority() uint64 {
+ var g uint32
+ var d uint32
+ if p.iceRoleControlling {
+ g = p.local.Priority()
+ d = p.remote.Priority()
+ } else {
+ g = p.remote.Priority()
+ d = p.local.Priority()
+ }
+
+ // Just implement these here rather
+ // than fooling around with the math package
+ min := func(x, y uint32) uint64 {
+ if x < y {
+ return uint64(x)
+ }
+ return uint64(y)
+ }
+ max := func(x, y uint32) uint64 {
+ if x > y {
+ return uint64(x)
+ }
+ return uint64(y)
+ }
+ cmp := func(x, y uint32) uint64 {
+ if x > y {
+ return uint64(1)
+ }
+ return uint64(0)
+ }
+
+ // 1<<32 overflows uint32; and if both g && d are
+ // maxUint32, this result would overflow uint64
+ return (1<<32-1)*min(g, d) + 2*max(g, d) + cmp(g, d)
+}
+
+func (p *candidatePair) Write(b []byte) (int, error) {
+ return p.local.writeTo(b, p.remote)
+}
+
+func (a *Agent) sendSTUN(msg *stun.Message, local, remote Candidate) {
+ _, err := local.writeTo(msg.Raw, remote)
+ if err != nil {
+ a.log.Tracef("failed to send STUN message: %s", err)
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/candidatepair_state.go b/vendor/github.com/pion/ice/v2/candidatepair_state.go
new file mode 100644
index 0000000..28c7187
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidatepair_state.go
@@ -0,0 +1,37 @@
+package ice
+
+// CandidatePairState represent the ICE candidate pair state
+type CandidatePairState int
+
+const (
+ // CandidatePairStateWaiting means a check has not been performed for
+ // this pair
+ CandidatePairStateWaiting = iota + 1
+
+ // CandidatePairStateInProgress means a check has been sent for this pair,
+ // but the transaction is in progress.
+ CandidatePairStateInProgress
+
+ // CandidatePairStateFailed means a check for this pair was already done
+ // and failed, either never producing any response or producing an unrecoverable
+ // failure response.
+ CandidatePairStateFailed
+
+ // CandidatePairStateSucceeded means a check for this pair was already
+ // done and produced a successful result.
+ CandidatePairStateSucceeded
+)
+
+func (c CandidatePairState) String() string {
+ switch c {
+ case CandidatePairStateWaiting:
+ return "waiting"
+ case CandidatePairStateInProgress:
+ return "in-progress"
+ case CandidatePairStateFailed:
+ return "failed"
+ case CandidatePairStateSucceeded:
+ return "succeeded"
+ }
+ return "Unknown candidate pair state"
+}
diff --git a/vendor/github.com/pion/ice/v2/candidaterelatedaddress.go b/vendor/github.com/pion/ice/v2/candidaterelatedaddress.go
new file mode 100644
index 0000000..18cf318
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidaterelatedaddress.go
@@ -0,0 +1,30 @@
+package ice
+
+import "fmt"
+
+// CandidateRelatedAddress convey transport addresses related to the
+// candidate, useful for diagnostics and other purposes.
+type CandidateRelatedAddress struct {
+ Address string
+ Port int
+}
+
+// String makes CandidateRelatedAddress printable
+func (c *CandidateRelatedAddress) String() string {
+ if c == nil {
+ return ""
+ }
+
+ return fmt.Sprintf(" related %s:%d", c.Address, c.Port)
+}
+
+// Equal allows comparing two CandidateRelatedAddresses.
+// The CandidateRelatedAddress are allowed to be nil.
+func (c *CandidateRelatedAddress) Equal(other *CandidateRelatedAddress) bool {
+ if c == nil && other == nil {
+ return true
+ }
+ return c != nil && other != nil &&
+ c.Address == other.Address &&
+ c.Port == other.Port
+}
diff --git a/vendor/github.com/pion/ice/v2/candidatetype.go b/vendor/github.com/pion/ice/v2/candidatetype.go
new file mode 100644
index 0000000..376c408
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/candidatetype.go
@@ -0,0 +1,62 @@
+package ice
+
+// CandidateType represents the type of candidate
+type CandidateType byte
+
+// CandidateType enum
+const (
+ CandidateTypeUnspecified CandidateType = iota
+ CandidateTypeHost
+ CandidateTypeServerReflexive
+ CandidateTypePeerReflexive
+ CandidateTypeRelay
+)
+
+// String makes CandidateType printable
+func (c CandidateType) String() string {
+ switch c {
+ case CandidateTypeHost:
+ return "host"
+ case CandidateTypeServerReflexive:
+ return "srflx"
+ case CandidateTypePeerReflexive:
+ return "prflx"
+ case CandidateTypeRelay:
+ return "relay"
+ case CandidateTypeUnspecified:
+ return "Unknown candidate type"
+ }
+ return "Unknown candidate type"
+}
+
+// Preference returns the preference weight of a CandidateType
+//
+// 4.1.2.2. Guidelines for Choosing Type and Local Preferences
+// The RECOMMENDED values are 126 for host candidates, 100
+// for server reflexive candidates, 110 for peer reflexive candidates,
+// and 0 for relayed candidates.
+func (c CandidateType) Preference() uint16 {
+ switch c {
+ case CandidateTypeHost:
+ return 126
+ case CandidateTypePeerReflexive:
+ return 110
+ case CandidateTypeServerReflexive:
+ return 100
+ case CandidateTypeRelay, CandidateTypeUnspecified:
+ return 0
+ }
+ return 0
+}
+
+func containsCandidateType(candidateType CandidateType, candidateTypeList []CandidateType) bool {
+ if candidateTypeList == nil {
+ return false
+ }
+ for _, ct := range candidateTypeList {
+ if ct == candidateType {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/pion/ice/v2/codecov.yml b/vendor/github.com/pion/ice/v2/codecov.yml
new file mode 100644
index 0000000..085200a
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/codecov.yml
@@ -0,0 +1,20 @@
+#
+# DO NOT EDIT THIS FILE
+#
+# It is automatically copied from https://github.com/pion/.goassets repository.
+#
+
+coverage:
+ status:
+ project:
+ default:
+ # Allow decreasing 2% of total coverage to avoid noise.
+ threshold: 2%
+ patch:
+ default:
+ target: 70%
+ only_pulls: true
+
+ignore:
+ - "examples/*"
+ - "examples/**/*"
diff --git a/vendor/github.com/pion/ice/v2/context.go b/vendor/github.com/pion/ice/v2/context.go
new file mode 100644
index 0000000..627d81e
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/context.go
@@ -0,0 +1,37 @@
+package ice
+
+import (
+ "context"
+ "time"
+)
+
+func (a *Agent) context() context.Context {
+ return agentContext(a.done)
+}
+
+type agentContext chan struct{}
+
+// Done implements context.Context
+func (a agentContext) Done() <-chan struct{} {
+ return (chan struct{})(a)
+}
+
+// Err implements context.Context
+func (a agentContext) Err() error {
+ select {
+ case <-(chan struct{})(a):
+ return ErrRunCanceled
+ default:
+ return nil
+ }
+}
+
+// Deadline implements context.Context
+func (a agentContext) Deadline() (deadline time.Time, ok bool) {
+ return time.Time{}, false
+}
+
+// Value implements context.Context
+func (a agentContext) Value(key interface{}) interface{} {
+ return nil
+}
diff --git a/vendor/github.com/pion/ice/v2/errors.go b/vendor/github.com/pion/ice/v2/errors.go
new file mode 100644
index 0000000..e7dd625
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/errors.go
@@ -0,0 +1,132 @@
+package ice
+
+import "errors"
+
+var (
+ // ErrUnknownType indicates an error with Unknown info.
+ ErrUnknownType = errors.New("Unknown")
+
+ // ErrSchemeType indicates the scheme type could not be parsed.
+ ErrSchemeType = errors.New("unknown scheme type")
+
+ // ErrSTUNQuery indicates query arguments are provided in a STUN URL.
+ ErrSTUNQuery = errors.New("queries not supported in stun address")
+
+ // ErrInvalidQuery indicates an malformed query is provided.
+ ErrInvalidQuery = errors.New("invalid query")
+
+ // ErrHost indicates malformed hostname is provided.
+ ErrHost = errors.New("invalid hostname")
+
+ // ErrPort indicates malformed port is provided.
+ ErrPort = errors.New("invalid port")
+
+ // ErrLocalUfragInsufficientBits indicates local username fragment insufficient bits are provided.
+ // Have to be at least 24 bits long
+ ErrLocalUfragInsufficientBits = errors.New("local username fragment is less than 24 bits long")
+
+ // ErrLocalPwdInsufficientBits indicates local passoword insufficient bits are provided.
+ // Have to be at least 128 bits long
+ ErrLocalPwdInsufficientBits = errors.New("local password is less than 128 bits long")
+
+ // ErrProtoType indicates an unsupported transport type was provided.
+ ErrProtoType = errors.New("invalid transport protocol type")
+
+ // ErrClosed indicates the agent is closed
+ ErrClosed = errors.New("the agent is closed")
+
+ // ErrNoCandidatePairs indicates agent does not have a valid candidate pair
+ ErrNoCandidatePairs = errors.New("no candidate pairs available")
+
+ // ErrCanceledByCaller indicates agent connection was canceled by the caller
+ ErrCanceledByCaller = errors.New("connecting canceled by caller")
+
+ // ErrMultipleStart indicates agent was started twice
+ ErrMultipleStart = errors.New("attempted to start agent twice")
+
+ // ErrRemoteUfragEmpty indicates agent was started with an empty remote ufrag
+ ErrRemoteUfragEmpty = errors.New("remote ufrag is empty")
+
+ // ErrRemotePwdEmpty indicates agent was started with an empty remote pwd
+ ErrRemotePwdEmpty = errors.New("remote pwd is empty")
+
+ // ErrNoOnCandidateHandler indicates agent was started without OnCandidate
+ ErrNoOnCandidateHandler = errors.New("no OnCandidate provided")
+
+ // ErrMultipleGatherAttempted indicates GatherCandidates has been called multiple times
+ ErrMultipleGatherAttempted = errors.New("attempting to gather candidates during gathering state")
+
+ // ErrUsernameEmpty indicates agent was give TURN URL with an empty Username
+ ErrUsernameEmpty = errors.New("username is empty")
+
+ // ErrPasswordEmpty indicates agent was give TURN URL with an empty Password
+ ErrPasswordEmpty = errors.New("password is empty")
+
+ // ErrAddressParseFailed indicates we were unable to parse a candidate address
+ ErrAddressParseFailed = errors.New("failed to parse address")
+
+ // ErrLiteUsingNonHostCandidates indicates non host candidates were selected for a lite agent
+ ErrLiteUsingNonHostCandidates = errors.New("lite agents must only use host candidates")
+
+ // ErrUselessUrlsProvided indicates that one or more URL was provided to the agent but no host
+ // candidate required them
+ ErrUselessUrlsProvided = errors.New("agent does not need URL with selected candidate types")
+
+ // ErrUnsupportedNAT1To1IPCandidateType indicates that the specified NAT1To1IPCandidateType is
+ // unsupported
+ ErrUnsupportedNAT1To1IPCandidateType = errors.New("unsupported 1:1 NAT IP candidate type")
+
+ // ErrInvalidNAT1To1IPMapping indicates that the given 1:1 NAT IP mapping is invalid
+ ErrInvalidNAT1To1IPMapping = errors.New("invalid 1:1 NAT IP mapping")
+
+ // ErrExternalMappedIPNotFound in NAT1To1IPMapping
+ ErrExternalMappedIPNotFound = errors.New("external mapped IP not found")
+
+ // ErrMulticastDNSWithNAT1To1IPMapping indicates that the mDNS gathering cannot be used along
+ // with 1:1 NAT IP mapping for host candidate.
+ ErrMulticastDNSWithNAT1To1IPMapping = errors.New("mDNS gathering cannot be used with 1:1 NAT IP mapping for host candidate")
+
+ // ErrIneffectiveNAT1To1IPMappingHost indicates that 1:1 NAT IP mapping for host candidate is
+ // requested, but the host candidate type is disabled.
+ ErrIneffectiveNAT1To1IPMappingHost = errors.New("1:1 NAT IP mapping for host candidate ineffective")
+
+ // ErrIneffectiveNAT1To1IPMappingSrflx indicates that 1:1 NAT IP mapping for srflx candidate is
+ // requested, but the srflx candidate type is disabled.
+ ErrIneffectiveNAT1To1IPMappingSrflx = errors.New("1:1 NAT IP mapping for srflx candidate ineffective")
+
+ // ErrInvalidMulticastDNSHostName indicates an invalid MulticastDNSHostName
+ ErrInvalidMulticastDNSHostName = errors.New("invalid mDNS HostName, must end with .local and can only contain a single '.'")
+
+ // ErrRestartWhenGathering indicates Restart was called when Agent is in GatheringStateGathering
+ ErrRestartWhenGathering = errors.New("ICE Agent can not be restarted when gathering")
+
+ // ErrRunCanceled indicates a run operation was canceled by its individual done
+ ErrRunCanceled = errors.New("run was canceled by done")
+
+ // ErrTCPMuxNotInitialized indicates TCPMux is not initialized and that invalidTCPMux is used.
+ ErrTCPMuxNotInitialized = errors.New("TCPMux is not initialized")
+
+ // ErrTCPRemoteAddrAlreadyExists indicates we already have the connection with same remote addr.
+ ErrTCPRemoteAddrAlreadyExists = errors.New("conn with same remote addr already exists")
+
+ errSendPacket = errors.New("failed to send packet")
+ errAttributeTooShortICECandidate = errors.New("attribute not long enough to be ICE candidate")
+ errParseComponent = errors.New("could not parse component")
+ errParsePriority = errors.New("could not parse priority")
+ errParsePort = errors.New("could not parse port")
+ errParseRelatedAddr = errors.New("could not parse related addresses")
+ errParseTypType = errors.New("could not parse typtype")
+ errUnknownCandidateTyp = errors.New("unknown candidate typ")
+ errGetXorMappedAddrResponse = errors.New("failed to get XOR-MAPPED-ADDRESS response")
+ errConnectionAddrAlreadyExist = errors.New("connection with same remote address already exists")
+ errReadingStreamingPacket = errors.New("error reading streaming packet")
+ errWriting = errors.New("error writing to")
+ errClosingConnection = errors.New("error closing connection")
+ errDetermineNetworkType = errors.New("unable to determine networkType")
+ errMissingProtocolScheme = errors.New("missing protocol scheme")
+ errTooManyColonsAddr = errors.New("too many colons in address")
+ errRead = errors.New("unexpected error trying to read")
+ errUnknownRole = errors.New("unknown role")
+ errMismatchUsername = errors.New("username mismatch")
+ errICEWriteSTUNMessage = errors.New("the ICE conn can't write STUN messages")
+)
diff --git a/vendor/github.com/pion/ice/v2/external_ip_mapper.go b/vendor/github.com/pion/ice/v2/external_ip_mapper.go
new file mode 100644
index 0000000..5310cc0
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/external_ip_mapper.go
@@ -0,0 +1,143 @@
+package ice
+
+import (
+ "net"
+ "strings"
+)
+
+func validateIPString(ipStr string) (net.IP, bool, error) {
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ return nil, false, ErrInvalidNAT1To1IPMapping
+ }
+ return ip, (ip.To4() != nil), nil
+}
+
+// ipMapping holds the mapping of local and external IP address for a particular IP family
+type ipMapping struct {
+ ipSole net.IP // when non-nil, this is the sole external IP for one local IP assumed
+ ipMap map[string]net.IP // local-to-external IP mapping (k: local, v: external)
+}
+
+func (m *ipMapping) setSoleIP(ip net.IP) error {
+ if m.ipSole != nil || len(m.ipMap) > 0 {
+ return ErrInvalidNAT1To1IPMapping
+ }
+
+ m.ipSole = ip
+
+ return nil
+}
+
+func (m *ipMapping) addIPMapping(locIP, extIP net.IP) error {
+ if m.ipSole != nil {
+ return ErrInvalidNAT1To1IPMapping
+ }
+
+ locIPStr := locIP.String()
+
+ // check if dup of local IP
+ if _, ok := m.ipMap[locIPStr]; ok {
+ return ErrInvalidNAT1To1IPMapping
+ }
+
+ m.ipMap[locIPStr] = extIP
+
+ return nil
+}
+
+func (m *ipMapping) findExternalIP(locIP net.IP) (net.IP, error) {
+ if m.ipSole != nil {
+ return m.ipSole, nil
+ }
+
+ extIP, ok := m.ipMap[locIP.String()]
+ if !ok {
+ return nil, ErrExternalMappedIPNotFound
+ }
+
+ return extIP, nil
+}
+
+type externalIPMapper struct {
+ ipv4Mapping ipMapping
+ ipv6Mapping ipMapping
+ candidateType CandidateType
+}
+
+func newExternalIPMapper(candidateType CandidateType, ips []string) (*externalIPMapper, error) { //nolint:gocognit
+ if len(ips) == 0 {
+ return nil, nil
+ }
+ if candidateType == CandidateTypeUnspecified {
+ candidateType = CandidateTypeHost // defaults to host
+ } else if candidateType != CandidateTypeHost && candidateType != CandidateTypeServerReflexive {
+ return nil, ErrUnsupportedNAT1To1IPCandidateType
+ }
+
+ m := &externalIPMapper{
+ ipv4Mapping: ipMapping{ipMap: map[string]net.IP{}},
+ ipv6Mapping: ipMapping{ipMap: map[string]net.IP{}},
+ candidateType: candidateType,
+ }
+
+ for _, extIPStr := range ips {
+ ipPair := strings.Split(extIPStr, "/")
+ if len(ipPair) == 0 || len(ipPair) > 2 {
+ return nil, ErrInvalidNAT1To1IPMapping
+ }
+
+ extIP, isExtIPv4, err := validateIPString(ipPair[0])
+ if err != nil {
+ return nil, err
+ }
+ if len(ipPair) == 1 {
+ if isExtIPv4 {
+ if err := m.ipv4Mapping.setSoleIP(extIP); err != nil {
+ return nil, err
+ }
+ } else {
+ if err := m.ipv6Mapping.setSoleIP(extIP); err != nil {
+ return nil, err
+ }
+ }
+ } else {
+ locIP, isLocIPv4, err := validateIPString(ipPair[1])
+ if err != nil {
+ return nil, err
+ }
+ if isExtIPv4 {
+ if !isLocIPv4 {
+ return nil, ErrInvalidNAT1To1IPMapping
+ }
+
+ if err := m.ipv4Mapping.addIPMapping(locIP, extIP); err != nil {
+ return nil, err
+ }
+ } else {
+ if isLocIPv4 {
+ return nil, ErrInvalidNAT1To1IPMapping
+ }
+
+ if err := m.ipv6Mapping.addIPMapping(locIP, extIP); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ return m, nil
+}
+
+func (m *externalIPMapper) findExternalIP(localIPStr string) (net.IP, error) {
+ locIP, isLocIPv4, err := validateIPString(localIPStr)
+ if err != nil {
+ return nil, err
+ }
+
+ if isLocIPv4 {
+ return m.ipv4Mapping.findExternalIP(locIP)
+ }
+
+ return m.ipv6Mapping.findExternalIP(locIP)
+}
diff --git a/vendor/github.com/pion/ice/v2/gather.go b/vendor/github.com/pion/ice/v2/gather.go
new file mode 100644
index 0000000..3bc3d77
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/gather.go
@@ -0,0 +1,497 @@
+package ice
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "net"
+ "reflect"
+ "sync"
+ "time"
+
+ "github.com/pion/dtls/v2"
+ "github.com/pion/logging"
+ "github.com/pion/turn/v2"
+)
+
+const (
+ stunGatherTimeout = time.Second * 5
+)
+
+type closeable interface {
+ Close() error
+}
+
+// Close a net.Conn and log if we have a failure
+func closeConnAndLog(c closeable, log logging.LeveledLogger, msg string) {
+ if c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil()) {
+ log.Warnf("Conn is not allocated (%s)", msg)
+ return
+ }
+
+ log.Warnf(msg)
+ if err := c.Close(); err != nil {
+ log.Warnf("Failed to close conn: %v", err)
+ }
+}
+
+// fakePacketConn wraps a net.Conn and emulates net.PacketConn
+type fakePacketConn struct {
+ nextConn net.Conn
+}
+
+func (f *fakePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
+ n, err = f.nextConn.Read(p)
+ addr = f.nextConn.RemoteAddr()
+ return
+}
+func (f *fakePacketConn) Close() error { return f.nextConn.Close() }
+func (f *fakePacketConn) LocalAddr() net.Addr { return f.nextConn.LocalAddr() }
+func (f *fakePacketConn) SetDeadline(t time.Time) error { return f.nextConn.SetDeadline(t) }
+func (f *fakePacketConn) SetReadDeadline(t time.Time) error { return f.nextConn.SetReadDeadline(t) }
+func (f *fakePacketConn) SetWriteDeadline(t time.Time) error { return f.nextConn.SetWriteDeadline(t) }
+func (f *fakePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
+ return f.nextConn.Write(p)
+}
+
+// GatherCandidates initiates the trickle based gathering process.
+func (a *Agent) GatherCandidates() error {
+ var gatherErr error
+
+ if runErr := a.run(a.context(), func(ctx context.Context, agent *Agent) {
+ if a.gatheringState != GatheringStateNew {
+ gatherErr = ErrMultipleGatherAttempted
+ return
+ } else if a.onCandidateHdlr.Load() == nil {
+ gatherErr = ErrNoOnCandidateHandler
+ return
+ }
+
+ a.gatherCandidateCancel() // Cancel previous gathering routine
+ ctx, cancel := context.WithCancel(ctx)
+ a.gatherCandidateCancel = cancel
+
+ go a.gatherCandidates(ctx)
+ }); runErr != nil {
+ return runErr
+ }
+ return gatherErr
+}
+
+func (a *Agent) gatherCandidates(ctx context.Context) {
+ if err := a.setGatheringState(GatheringStateGathering); err != nil {
+ a.log.Warnf("failed to set gatheringState to GatheringStateGathering: %v", err)
+ return
+ }
+
+ var wg sync.WaitGroup
+ for _, t := range a.candidateTypes {
+ switch t {
+ case CandidateTypeHost:
+ wg.Add(1)
+ go func() {
+ a.gatherCandidatesLocal(ctx, a.networkTypes)
+ wg.Done()
+ }()
+ case CandidateTypeServerReflexive:
+ wg.Add(1)
+ go func() {
+ a.gatherCandidatesSrflx(ctx, a.urls, a.networkTypes)
+ wg.Done()
+ }()
+ if a.extIPMapper != nil && a.extIPMapper.candidateType == CandidateTypeServerReflexive {
+ wg.Add(1)
+ go func() {
+ a.gatherCandidatesSrflxMapped(ctx, a.networkTypes)
+ wg.Done()
+ }()
+ }
+ case CandidateTypeRelay:
+ wg.Add(1)
+ go func() {
+ a.gatherCandidatesRelay(ctx, a.urls)
+ wg.Done()
+ }()
+ case CandidateTypePeerReflexive, CandidateTypeUnspecified:
+ }
+ }
+ // Block until all STUN and TURN URLs have been gathered (or timed out)
+ wg.Wait()
+
+ if err := a.setGatheringState(GatheringStateComplete); err != nil {
+ a.log.Warnf("failed to set gatheringState to GatheringStateComplete: %v", err)
+ }
+}
+
+func (a *Agent) gatherCandidatesLocal(ctx context.Context, networkTypes []NetworkType) { //nolint:gocognit
+ networks := map[string]struct{}{}
+ for _, networkType := range networkTypes {
+ if networkType.IsTCP() {
+ networks[tcp] = struct{}{}
+ } else {
+ networks[udp] = struct{}{}
+ }
+ }
+
+ localIPs, err := localInterfaces(a.net, a.interfaceFilter, networkTypes)
+ if err != nil {
+ a.log.Warnf("failed to iterate local interfaces, host candidates will not be gathered %s", err)
+ return
+ }
+
+ for _, ip := range localIPs {
+ mappedIP := ip
+ if a.mDNSMode != MulticastDNSModeQueryAndGather && a.extIPMapper != nil && a.extIPMapper.candidateType == CandidateTypeHost {
+ if _mappedIP, err := a.extIPMapper.findExternalIP(ip.String()); err == nil {
+ mappedIP = _mappedIP
+ } else {
+ a.log.Warnf("1:1 NAT mapping is enabled but no external IP is found for %s\n", ip.String())
+ }
+ }
+
+ address := mappedIP.String()
+ if a.mDNSMode == MulticastDNSModeQueryAndGather {
+ address = a.mDNSName
+ }
+
+ for network := range networks {
+ var port int
+ var conn net.PacketConn
+ var err error
+
+ var tcpType TCPType
+ switch network {
+ case tcp:
+ // Handle ICE TCP passive mode
+
+ a.log.Debugf("GetConn by ufrag: %s\n", a.localUfrag)
+ conn, err = a.tcpMux.GetConnByUfrag(a.localUfrag)
+ if err != nil {
+ if !errors.Is(err, ErrTCPMuxNotInitialized) {
+ a.log.Warnf("error getting tcp conn by ufrag: %s %s %s\n", network, ip, a.localUfrag)
+ }
+ continue
+ }
+ port = conn.LocalAddr().(*net.TCPAddr).Port
+ tcpType = TCPTypePassive
+ // is there a way to verify that the listen address is even
+ // accessible from the current interface.
+ case udp:
+ conn, err = listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: ip, Port: 0})
+ if err != nil {
+ a.log.Warnf("could not listen %s %s\n", network, ip)
+ continue
+ }
+
+ port = conn.LocalAddr().(*net.UDPAddr).Port
+ }
+ hostConfig := CandidateHostConfig{
+ Network: network,
+ Address: address,
+ Port: port,
+ Component: ComponentRTP,
+ TCPType: tcpType,
+ }
+
+ c, err := NewCandidateHost(&hostConfig)
+ if err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create host candidate: %s %s %d: %v\n", network, mappedIP, port, err))
+ continue
+ }
+
+ if a.mDNSMode == MulticastDNSModeQueryAndGather {
+ if err = c.setIP(ip); err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create host candidate: %s %s %d: %v\n", network, mappedIP, port, err))
+ continue
+ }
+ }
+
+ if err := a.addCandidate(ctx, c, conn); err != nil {
+ if closeErr := c.close(); closeErr != nil {
+ a.log.Warnf("Failed to close candidate: %v", closeErr)
+ }
+ a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err)
+ }
+ }
+ }
+}
+
+func (a *Agent) gatherCandidatesSrflxMapped(ctx context.Context, networkTypes []NetworkType) {
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ for _, networkType := range networkTypes {
+ if networkType.IsTCP() {
+ continue
+ }
+
+ network := networkType.String()
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0})
+ if err != nil {
+ a.log.Warnf("Failed to listen %s: %v\n", network, err)
+ return
+ }
+
+ laddr := conn.LocalAddr().(*net.UDPAddr)
+ mappedIP, err := a.extIPMapper.findExternalIP(laddr.IP.String())
+ if err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("1:1 NAT mapping is enabled but no external IP is found for %s\n", laddr.IP.String()))
+ return
+ }
+
+ srflxConfig := CandidateServerReflexiveConfig{
+ Network: network,
+ Address: mappedIP.String(),
+ Port: laddr.Port,
+ Component: ComponentRTP,
+ RelAddr: laddr.IP.String(),
+ RelPort: laddr.Port,
+ }
+ c, err := NewCandidateServerReflexive(&srflxConfig)
+ if err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create server reflexive candidate: %s %s %d: %v\n",
+ network,
+ mappedIP.String(),
+ laddr.Port,
+ err))
+ return
+ }
+
+ if err := a.addCandidate(ctx, c, conn); err != nil {
+ if closeErr := c.close(); closeErr != nil {
+ a.log.Warnf("Failed to close candidate: %v", closeErr)
+ }
+ a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err)
+ }
+ }()
+ }
+}
+
+func (a *Agent) gatherCandidatesSrflx(ctx context.Context, urls []*URL, networkTypes []NetworkType) {
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ for _, networkType := range networkTypes {
+ if networkType.IsTCP() {
+ continue
+ }
+
+ for i := range urls {
+ wg.Add(1)
+ go func(url URL, network string) {
+ defer wg.Done()
+ hostPort := fmt.Sprintf("%s:%d", url.Host, url.Port)
+ serverAddr, err := a.net.ResolveUDPAddr(network, hostPort)
+ if err != nil {
+ a.log.Warnf("failed to resolve stun host: %s: %v", hostPort, err)
+ return
+ }
+
+ conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0})
+ if err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to listen for %s: %v\n", serverAddr.String(), err))
+ return
+ }
+
+ xoraddr, err := getXORMappedAddr(conn, serverAddr, stunGatherTimeout)
+ if err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("could not get server reflexive address %s %s: %v\n", network, url, err))
+ return
+ }
+
+ ip := xoraddr.IP
+ port := xoraddr.Port
+
+ laddr := conn.LocalAddr().(*net.UDPAddr)
+ srflxConfig := CandidateServerReflexiveConfig{
+ Network: network,
+ Address: ip.String(),
+ Port: port,
+ Component: ComponentRTP,
+ RelAddr: laddr.IP.String(),
+ RelPort: laddr.Port,
+ }
+ c, err := NewCandidateServerReflexive(&srflxConfig)
+ if err != nil {
+ closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create server reflexive candidate: %s %s %d: %v\n", network, ip, port, err))
+ return
+ }
+
+ if err := a.addCandidate(ctx, c, conn); err != nil {
+ if closeErr := c.close(); closeErr != nil {
+ a.log.Warnf("Failed to close candidate: %v", closeErr)
+ }
+ a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err)
+ }
+ }(*urls[i], networkType.String())
+ }
+ }
+}
+
+func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //nolint:gocognit
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ network := NetworkTypeUDP4.String()
+ for i := range urls {
+ switch {
+ case urls[i].Scheme != SchemeTypeTURN && urls[i].Scheme != SchemeTypeTURNS:
+ continue
+ case urls[i].Username == "":
+ a.log.Errorf("Failed to gather relay candidates: %v", ErrUsernameEmpty)
+ return
+ case urls[i].Password == "":
+ a.log.Errorf("Failed to gather relay candidates: %v", ErrPasswordEmpty)
+ return
+ }
+
+ wg.Add(1)
+ go func(url URL) {
+ defer wg.Done()
+ TURNServerAddr := fmt.Sprintf("%s:%d", url.Host, url.Port)
+ var (
+ locConn net.PacketConn
+ err error
+ RelAddr string
+ RelPort int
+ )
+
+ switch {
+ case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURN:
+ if locConn, err = a.net.ListenPacket(network, "0.0.0.0:0"); err != nil {
+ a.log.Warnf("Failed to listen %s: %v\n", network, err)
+ return
+ }
+
+ RelAddr = locConn.LocalAddr().(*net.UDPAddr).IP.String()
+ RelPort = locConn.LocalAddr().(*net.UDPAddr).Port
+ case a.proxyDialer != nil && url.Proto == ProtoTypeTCP &&
+ (url.Scheme == SchemeTypeTURN || url.Scheme == SchemeTypeTURNS):
+ conn, connectErr := a.proxyDialer.Dial(NetworkTypeTCP4.String(), TURNServerAddr)
+ if connectErr != nil {
+ a.log.Warnf("Failed to Dial TCP Addr %s via proxy dialer: %v\n", TURNServerAddr, connectErr)
+ return
+ }
+
+ RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String()
+ RelPort = conn.LocalAddr().(*net.TCPAddr).Port
+ locConn = turn.NewSTUNConn(conn)
+
+ case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURN:
+ tcpAddr, connectErr := net.ResolveTCPAddr(NetworkTypeTCP4.String(), TURNServerAddr)
+ if connectErr != nil {
+ a.log.Warnf("Failed to resolve TCP Addr %s: %v\n", TURNServerAddr, connectErr)
+ return
+ }
+
+ conn, connectErr := net.DialTCP(NetworkTypeTCP4.String(), nil, tcpAddr)
+ if connectErr != nil {
+ a.log.Warnf("Failed to Dial TCP Addr %s: %v\n", TURNServerAddr, connectErr)
+ return
+ }
+
+ RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String()
+ RelPort = conn.LocalAddr().(*net.TCPAddr).Port
+ locConn = turn.NewSTUNConn(conn)
+ case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURNS:
+ udpAddr, connectErr := net.ResolveUDPAddr(network, TURNServerAddr)
+ if connectErr != nil {
+ a.log.Warnf("Failed to resolve UDP Addr %s: %v\n", TURNServerAddr, connectErr)
+ return
+ }
+
+ conn, connectErr := dtls.Dial(network, udpAddr, &dtls.Config{
+ InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec
+ })
+ if connectErr != nil {
+ a.log.Warnf("Failed to Dial DTLS Addr %s: %v\n", TURNServerAddr, connectErr)
+ return
+ }
+
+ RelAddr = conn.LocalAddr().(*net.UDPAddr).IP.String()
+ RelPort = conn.LocalAddr().(*net.UDPAddr).Port
+ locConn = &fakePacketConn{conn}
+ case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURNS:
+ conn, connectErr := tls.Dial(NetworkTypeTCP4.String(), TURNServerAddr, &tls.Config{
+ InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec
+ })
+ if connectErr != nil {
+ a.log.Warnf("Failed to Dial TLS Addr %s: %v\n", TURNServerAddr, connectErr)
+ return
+ }
+ RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String()
+ RelPort = conn.LocalAddr().(*net.TCPAddr).Port
+ locConn = turn.NewSTUNConn(conn)
+ default:
+ a.log.Warnf("Unable to handle URL in gatherCandidatesRelay %v\n", url)
+ return
+ }
+
+ client, err := turn.NewClient(&turn.ClientConfig{
+ TURNServerAddr: TURNServerAddr,
+ Conn: locConn,
+ Username: url.Username,
+ Password: url.Password,
+ LoggerFactory: a.loggerFactory,
+ Net: a.net,
+ })
+ if err != nil {
+ closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to build new turn.Client %s %s\n", TURNServerAddr, err))
+ return
+ }
+
+ if err = client.Listen(); err != nil {
+ client.Close()
+ closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to listen on turn.Client %s %s\n", TURNServerAddr, err))
+ return
+ }
+
+ relayConn, err := client.Allocate()
+ if err != nil {
+ client.Close()
+ closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to allocate on turn.Client %s %s\n", TURNServerAddr, err))
+ return
+ }
+
+ raddr := relayConn.LocalAddr().(*net.UDPAddr)
+ relayConfig := CandidateRelayConfig{
+ Network: network,
+ Component: ComponentRTP,
+ Address: raddr.IP.String(),
+ Port: raddr.Port,
+ RelAddr: RelAddr,
+ RelPort: RelPort,
+ OnClose: func() error {
+ client.Close()
+ return locConn.Close()
+ },
+ }
+ relayConnClose := func() {
+ if relayConErr := relayConn.Close(); relayConErr != nil {
+ a.log.Warnf("Failed to close relay %v", relayConErr)
+ }
+ }
+ candidate, err := NewCandidateRelay(&relayConfig)
+ if err != nil {
+ relayConnClose()
+
+ client.Close()
+ closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to create relay candidate: %s %s: %v\n", network, raddr.String(), err))
+ return
+ }
+
+ if err := a.addCandidate(ctx, candidate, relayConn); err != nil {
+ relayConnClose()
+
+ if closeErr := candidate.close(); closeErr != nil {
+ a.log.Warnf("Failed to close candidate: %v", closeErr)
+ }
+ a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err)
+ }
+ }(*urls[i])
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/go.mod b/vendor/github.com/pion/ice/v2/go.mod
new file mode 100644
index 0000000..410c2e3
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/go.mod
@@ -0,0 +1,16 @@
+module github.com/pion/ice/v2
+
+go 1.13
+
+require (
+ github.com/google/uuid v1.1.5
+ github.com/pion/dtls/v2 v2.0.4
+ github.com/pion/logging v0.2.2
+ github.com/pion/mdns v0.0.4
+ github.com/pion/randutil v0.1.0
+ github.com/pion/stun v0.3.5
+ github.com/pion/transport v0.12.1
+ github.com/pion/turn/v2 v2.0.5
+ github.com/stretchr/testify v1.6.1
+ golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7
+)
diff --git a/vendor/github.com/pion/ice/v2/go.sum b/vendor/github.com/pion/ice/v2/go.sum
new file mode 100644
index 0000000..7e8981a
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/go.sum
@@ -0,0 +1,60 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
+github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI=
+github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI=
+github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
+github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
+github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY=
+github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
+github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
+github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
+github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
+github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
+github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
+github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
+github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM=
+github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
+github.com/pion/transport v0.12.1 h1:6v8lxQGVZpwSICEZjhl/CCv6aErINZlrm3O5ncFXj/c=
+github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
+github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
+github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
+github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
+github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8=
+golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/vendor/github.com/pion/ice/v2/ice.go b/vendor/github.com/pion/ice/v2/ice.go
new file mode 100644
index 0000000..d7094f6
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/ice.go
@@ -0,0 +1,76 @@
+package ice
+
+// ConnectionState is an enum showing the state of a ICE Connection
+type ConnectionState int
+
+// List of supported States
+const (
+ // ConnectionStateNew ICE agent is gathering addresses
+ ConnectionStateNew = iota + 1
+
+ // ConnectionStateChecking ICE agent has been given local and remote candidates, and is attempting to find a match
+ ConnectionStateChecking
+
+ // ConnectionStateConnected ICE agent has a pairing, but is still checking other pairs
+ ConnectionStateConnected
+
+ // ConnectionStateCompleted ICE agent has finished
+ ConnectionStateCompleted
+
+ // ConnectionStateFailed ICE agent never could successfully connect
+ ConnectionStateFailed
+
+ // ConnectionStateDisconnected ICE agent connected successfully, but has entered a failed state
+ ConnectionStateDisconnected
+
+ // ConnectionStateClosed ICE agent has finished and is no longer handling requests
+ ConnectionStateClosed
+)
+
+func (c ConnectionState) String() string {
+ switch c {
+ case ConnectionStateNew:
+ return "New"
+ case ConnectionStateChecking:
+ return "Checking"
+ case ConnectionStateConnected:
+ return "Connected"
+ case ConnectionStateCompleted:
+ return "Completed"
+ case ConnectionStateFailed:
+ return "Failed"
+ case ConnectionStateDisconnected:
+ return "Disconnected"
+ case ConnectionStateClosed:
+ return "Closed"
+ default:
+ return "Invalid"
+ }
+}
+
+// GatheringState describes the state of the candidate gathering process
+type GatheringState int
+
+const (
+ // GatheringStateNew indicates candidate gatering is not yet started
+ GatheringStateNew GatheringState = iota + 1
+
+ // GatheringStateGathering indicates candidate gatering is ongoing
+ GatheringStateGathering
+
+ // GatheringStateComplete indicates candidate gatering has been completed
+ GatheringStateComplete
+)
+
+func (t GatheringState) String() string {
+ switch t {
+ case GatheringStateNew:
+ return "new"
+ case GatheringStateGathering:
+ return "gathering"
+ case GatheringStateComplete:
+ return "complete"
+ default:
+ return ErrUnknownType.Error()
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/icecontrol.go b/vendor/github.com/pion/ice/v2/icecontrol.go
new file mode 100644
index 0000000..ede2e09
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/icecontrol.go
@@ -0,0 +1,87 @@
+package ice
+
+import (
+ "encoding/binary"
+
+ "github.com/pion/stun"
+)
+
+// tiebreaker is common helper for ICE-{CONTROLLED,CONTROLLING}
+// and represents the so-called tiebreaker number.
+type tiebreaker uint64
+
+const tiebreakerSize = 8 // 64 bit
+
+// AddToAs adds tiebreaker value to m as t attribute.
+func (a tiebreaker) AddToAs(m *stun.Message, t stun.AttrType) error {
+ v := make([]byte, tiebreakerSize)
+ binary.BigEndian.PutUint64(v, uint64(a))
+ m.Add(t, v)
+ return nil
+}
+
+// GetFromAs decodes tiebreaker value in message getting it as for t type.
+func (a *tiebreaker) GetFromAs(m *stun.Message, t stun.AttrType) error {
+ v, err := m.Get(t)
+ if err != nil {
+ return err
+ }
+ if err = stun.CheckSize(t, len(v), tiebreakerSize); err != nil {
+ return err
+ }
+ *a = tiebreaker(binary.BigEndian.Uint64(v))
+ return nil
+}
+
+// AttrControlled represents ICE-CONTROLLED attribute.
+type AttrControlled uint64
+
+// AddTo adds ICE-CONTROLLED to message.
+func (c AttrControlled) AddTo(m *stun.Message) error {
+ return tiebreaker(c).AddToAs(m, stun.AttrICEControlled)
+}
+
+// GetFrom decodes ICE-CONTROLLED from message.
+func (c *AttrControlled) GetFrom(m *stun.Message) error {
+ return (*tiebreaker)(c).GetFromAs(m, stun.AttrICEControlled)
+}
+
+// AttrControlling represents ICE-CONTROLLING attribute.
+type AttrControlling uint64
+
+// AddTo adds ICE-CONTROLLING to message.
+func (c AttrControlling) AddTo(m *stun.Message) error {
+ return tiebreaker(c).AddToAs(m, stun.AttrICEControlling)
+}
+
+// GetFrom decodes ICE-CONTROLLING from message.
+func (c *AttrControlling) GetFrom(m *stun.Message) error {
+ return (*tiebreaker)(c).GetFromAs(m, stun.AttrICEControlling)
+}
+
+// AttrControl is helper that wraps ICE-{CONTROLLED,CONTROLLING}.
+type AttrControl struct {
+ Role Role
+ Tiebreaker uint64
+}
+
+// AddTo adds ICE-CONTROLLED or ICE-CONTROLLING attribute depending on Role.
+func (c AttrControl) AddTo(m *stun.Message) error {
+ if c.Role == Controlling {
+ return tiebreaker(c.Tiebreaker).AddToAs(m, stun.AttrICEControlling)
+ }
+ return tiebreaker(c.Tiebreaker).AddToAs(m, stun.AttrICEControlled)
+}
+
+// GetFrom decodes Role and Tiebreaker value from message.
+func (c *AttrControl) GetFrom(m *stun.Message) error {
+ if m.Contains(stun.AttrICEControlling) {
+ c.Role = Controlling
+ return (*tiebreaker)(&c.Tiebreaker).GetFromAs(m, stun.AttrICEControlling)
+ }
+ if m.Contains(stun.AttrICEControlled) {
+ c.Role = Controlled
+ return (*tiebreaker)(&c.Tiebreaker).GetFromAs(m, stun.AttrICEControlled)
+ }
+ return stun.ErrAttributeNotFound
+}
diff --git a/vendor/github.com/pion/ice/v2/mdns.go b/vendor/github.com/pion/ice/v2/mdns.go
new file mode 100644
index 0000000..5a431d1
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/mdns.go
@@ -0,0 +1,63 @@
+package ice
+
+import (
+ "net"
+
+ "github.com/google/uuid"
+ "github.com/pion/logging"
+ "github.com/pion/mdns"
+ "golang.org/x/net/ipv4"
+)
+
+// MulticastDNSMode represents the different Multicast modes ICE can run in
+type MulticastDNSMode byte
+
+// MulticastDNSMode enum
+const (
+ // MulticastDNSModeDisabled means remote mDNS candidates will be discarded, and local host candidates will use IPs
+ MulticastDNSModeDisabled MulticastDNSMode = iota + 1
+
+ // MulticastDNSModeQueryOnly means remote mDNS candidates will be accepted, and local host candidates will use IPs
+ MulticastDNSModeQueryOnly
+
+ // MulticastDNSModeQueryAndGather means remote mDNS candidates will be accepted, and local host candidates will use mDNS
+ MulticastDNSModeQueryAndGather
+)
+
+func generateMulticastDNSName() (string, error) {
+ // https://tools.ietf.org/id/draft-ietf-rtcweb-mdns-ice-candidates-02.html#gathering
+ // The unique name MUST consist of a version 4 UUID as defined in [RFC4122], followed by “.local”.
+ u, err := uuid.NewRandom()
+ return u.String() + ".local", err
+}
+
+func createMulticastDNS(mDNSMode MulticastDNSMode, mDNSName string, log logging.LeveledLogger) (*mdns.Conn, MulticastDNSMode, error) {
+ if mDNSMode == MulticastDNSModeDisabled {
+ return nil, mDNSMode, nil
+ }
+
+ addr, mdnsErr := net.ResolveUDPAddr("udp4", mdns.DefaultAddress)
+ if mdnsErr != nil {
+ return nil, mDNSMode, mdnsErr
+ }
+
+ l, mdnsErr := net.ListenUDP("udp4", addr)
+ if mdnsErr != nil {
+ // If ICE fails to start MulticastDNS server just warn the user and continue
+ log.Errorf("Failed to enable mDNS, continuing in mDNS disabled mode: (%s)", mdnsErr)
+ return nil, MulticastDNSModeDisabled, nil
+ }
+
+ switch mDNSMode {
+ case MulticastDNSModeQueryOnly:
+ conn, err := mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{})
+ return conn, mDNSMode, err
+ case MulticastDNSModeQueryAndGather:
+ conn, err := mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{
+ LocalNames: []string{mDNSName},
+ })
+ return conn, mDNSMode, err
+ default:
+ return nil, mDNSMode, nil
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/networktype.go b/vendor/github.com/pion/ice/v2/networktype.go
new file mode 100644
index 0000000..462ff2d
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/networktype.go
@@ -0,0 +1,130 @@
+package ice
+
+import (
+ "fmt"
+ "net"
+ "strings"
+)
+
+const (
+ udp = "udp"
+ tcp = "tcp"
+)
+
+func supportedNetworkTypes() []NetworkType {
+ return []NetworkType{
+ NetworkTypeUDP4,
+ NetworkTypeUDP6,
+ NetworkTypeTCP4,
+ NetworkTypeTCP6,
+ }
+}
+
+// NetworkType represents the type of network
+type NetworkType int
+
+const (
+ // NetworkTypeUDP4 indicates UDP over IPv4.
+ NetworkTypeUDP4 NetworkType = iota + 1
+
+ // NetworkTypeUDP6 indicates UDP over IPv6.
+ NetworkTypeUDP6
+
+ // NetworkTypeTCP4 indicates TCP over IPv4.
+ NetworkTypeTCP4
+
+ // NetworkTypeTCP6 indicates TCP over IPv6.
+ NetworkTypeTCP6
+)
+
+func (t NetworkType) String() string {
+ switch t {
+ case NetworkTypeUDP4:
+ return "udp4"
+ case NetworkTypeUDP6:
+ return "udp6"
+ case NetworkTypeTCP4:
+ return "tcp4"
+ case NetworkTypeTCP6:
+ return "tcp6"
+ default:
+ return ErrUnknownType.Error()
+ }
+}
+
+// IsUDP returns true when network is UDP4 or UDP6.
+func (t NetworkType) IsUDP() bool {
+ return t == NetworkTypeUDP4 || t == NetworkTypeUDP6
+}
+
+// IsTCP returns true when network is TCP4 or TCP6.
+func (t NetworkType) IsTCP() bool {
+ return t == NetworkTypeTCP4 || t == NetworkTypeTCP6
+}
+
+// NetworkShort returns the short network description
+func (t NetworkType) NetworkShort() string {
+ switch t {
+ case NetworkTypeUDP4, NetworkTypeUDP6:
+ return udp
+ case NetworkTypeTCP4, NetworkTypeTCP6:
+ return tcp
+ default:
+ return ErrUnknownType.Error()
+ }
+}
+
+// IsReliable returns true if the network is reliable
+func (t NetworkType) IsReliable() bool {
+ switch t {
+ case NetworkTypeUDP4, NetworkTypeUDP6:
+ return false
+ case NetworkTypeTCP4, NetworkTypeTCP6:
+ return true
+ }
+ return false
+}
+
+// IsIPv4 returns whether the network type is IPv4 or not.
+func (t NetworkType) IsIPv4() bool {
+ switch t {
+ case NetworkTypeUDP4, NetworkTypeTCP4:
+ return true
+ case NetworkTypeUDP6, NetworkTypeTCP6:
+ return false
+ }
+ return false
+}
+
+// IsIPv6 returns whether the network type is IPv6 or not.
+func (t NetworkType) IsIPv6() bool {
+ switch t {
+ case NetworkTypeUDP4, NetworkTypeTCP4:
+ return false
+ case NetworkTypeUDP6, NetworkTypeTCP6:
+ return true
+ }
+ return false
+}
+
+// determineNetworkType determines the type of network based on
+// the short network string and an IP address.
+func determineNetworkType(network string, ip net.IP) (NetworkType, error) {
+ ipv4 := ip.To4() != nil
+
+ switch {
+ case strings.HasPrefix(strings.ToLower(network), udp):
+ if ipv4 {
+ return NetworkTypeUDP4, nil
+ }
+ return NetworkTypeUDP6, nil
+
+ case strings.HasPrefix(strings.ToLower(network), tcp):
+ if ipv4 {
+ return NetworkTypeTCP4, nil
+ }
+ return NetworkTypeTCP6, nil
+ }
+
+ return NetworkType(0), fmt.Errorf("%w from %s %s", errDetermineNetworkType, network, ip)
+}
diff --git a/vendor/github.com/pion/ice/v2/priority.go b/vendor/github.com/pion/ice/v2/priority.go
new file mode 100644
index 0000000..4218299
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/priority.go
@@ -0,0 +1,33 @@
+package ice
+
+import (
+ "encoding/binary"
+
+ "github.com/pion/stun"
+)
+
+// PriorityAttr represents PRIORITY attribute.
+type PriorityAttr uint32
+
+const prioritySize = 4 // 32 bit
+
+// AddTo adds PRIORITY attribute to message.
+func (p PriorityAttr) AddTo(m *stun.Message) error {
+ v := make([]byte, prioritySize)
+ binary.BigEndian.PutUint32(v, uint32(p))
+ m.Add(stun.AttrPriority, v)
+ return nil
+}
+
+// GetFrom decodes PRIORITY attribute from message.
+func (p *PriorityAttr) GetFrom(m *stun.Message) error {
+ v, err := m.Get(stun.AttrPriority)
+ if err != nil {
+ return err
+ }
+ if err = stun.CheckSize(stun.AttrPriority, len(v), prioritySize); err != nil {
+ return err
+ }
+ *p = PriorityAttr(binary.BigEndian.Uint32(v))
+ return nil
+}
diff --git a/vendor/github.com/pion/ice/v2/rand.go b/vendor/github.com/pion/ice/v2/rand.go
new file mode 100644
index 0000000..918783e
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/rand.go
@@ -0,0 +1,53 @@
+package ice
+
+import "github.com/pion/randutil"
+
+const (
+ runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ runesDigit = "0123456789"
+ runesCandidateIDFoundation = runesAlpha + runesDigit + "+/"
+
+ lenUFrag = 16
+ lenPwd = 32
+)
+
+// Seeding random generator each time limits number of generated sequence to 31-bits,
+// and causes collision on low time accuracy environments.
+// Use global random generator seeded by crypto grade random.
+var (
+ globalMathRandomGenerator = randutil.NewMathRandomGenerator() //nolint:gochecknoglobals
+ globalCandidateIDGenerator = candidateIDGenerator{globalMathRandomGenerator} //nolint:gochecknoglobals
+)
+
+// candidateIDGenerator is a random candidate ID generator.
+// Candidate ID is used in SDP and always shared to the other peer.
+// It doesn't require cryptographic random.
+type candidateIDGenerator struct {
+ randutil.MathRandomGenerator
+}
+
+func newCandidateIDGenerator() *candidateIDGenerator {
+ return &candidateIDGenerator{
+ randutil.NewMathRandomGenerator(),
+ }
+}
+
+func (g *candidateIDGenerator) Generate() string {
+ // https://tools.ietf.org/html/rfc5245#section-15.1
+ // candidate-id = "candidate" ":" foundation
+ // foundation = 1*32ice-char
+ // ice-char = ALPHA / DIGIT / "+" / "/"
+ return "candidate:" + g.MathRandomGenerator.GenerateString(32, runesCandidateIDFoundation)
+}
+
+// generatePwd generates ICE pwd.
+// This internally uses generateCryptoRandomString.
+func generatePwd() (string, error) {
+ return randutil.GenerateCryptoRandomString(lenPwd, runesAlpha)
+}
+
+// generateUFrag generates ICE user fragment.
+// This internally uses generateCryptoRandomString.
+func generateUFrag() (string, error) {
+ return randutil.GenerateCryptoRandomString(lenUFrag, runesAlpha)
+}
diff --git a/vendor/github.com/pion/ice/v2/renovate.json b/vendor/github.com/pion/ice/v2/renovate.json
new file mode 100644
index 0000000..4400fd9
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/renovate.json
@@ -0,0 +1,15 @@
+{
+ "extends": [
+ "config:base"
+ ],
+ "postUpdateOptions": [
+ "gomodTidy"
+ ],
+ "commitBody": "Generated by renovateBot",
+ "packageRules": [
+ {
+ "packagePatterns": ["^golang.org/x/"],
+ "schedule": ["on the first day of the month"]
+ }
+ ]
+}
diff --git a/vendor/github.com/pion/ice/v2/role.go b/vendor/github.com/pion/ice/v2/role.go
new file mode 100644
index 0000000..7a8bc06
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/role.go
@@ -0,0 +1,43 @@
+package ice
+
+import (
+ "fmt"
+)
+
+// Role represents ICE agent role, which can be controlling or controlled.
+type Role byte
+
+// Possible ICE agent roles.
+const (
+ Controlling Role = iota
+ Controlled
+)
+
+// UnmarshalText implements TextUnmarshaler.
+func (r *Role) UnmarshalText(text []byte) error {
+ switch string(text) {
+ case "controlling":
+ *r = Controlling
+ case "controlled":
+ *r = Controlled
+ default:
+ return fmt.Errorf("%w %q", errUnknownRole, text)
+ }
+ return nil
+}
+
+// MarshalText implements TextMarshaler.
+func (r Role) MarshalText() (text []byte, err error) {
+ return []byte(r.String()), nil
+}
+
+func (r Role) String() string {
+ switch r {
+ case Controlling:
+ return "controlling"
+ case Controlled:
+ return "controlled"
+ default:
+ return "unknown"
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/selection.go b/vendor/github.com/pion/ice/v2/selection.go
new file mode 100644
index 0000000..e0cfb10
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/selection.go
@@ -0,0 +1,287 @@
+package ice
+
+import (
+ "net"
+ "time"
+
+ "github.com/pion/logging"
+ "github.com/pion/stun"
+)
+
+type pairCandidateSelector interface {
+ Start()
+ ContactCandidates()
+ PingCandidate(local, remote Candidate)
+ HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr)
+ HandleBindingRequest(m *stun.Message, local, remote Candidate)
+}
+
+type controllingSelector struct {
+ startTime time.Time
+ agent *Agent
+ nominatedPair *candidatePair
+ log logging.LeveledLogger
+}
+
+func (s *controllingSelector) Start() {
+ s.startTime = time.Now()
+ s.nominatedPair = nil
+}
+
+func (s *controllingSelector) isNominatable(c Candidate) bool {
+ switch {
+ case c.Type() == CandidateTypeHost:
+ return time.Since(s.startTime).Nanoseconds() > s.agent.hostAcceptanceMinWait.Nanoseconds()
+ case c.Type() == CandidateTypeServerReflexive:
+ return time.Since(s.startTime).Nanoseconds() > s.agent.srflxAcceptanceMinWait.Nanoseconds()
+ case c.Type() == CandidateTypePeerReflexive:
+ return time.Since(s.startTime).Nanoseconds() > s.agent.prflxAcceptanceMinWait.Nanoseconds()
+ case c.Type() == CandidateTypeRelay:
+ return time.Since(s.startTime).Nanoseconds() > s.agent.relayAcceptanceMinWait.Nanoseconds()
+ }
+
+ s.log.Errorf("isNominatable invalid candidate type %s", c.Type().String())
+ return false
+}
+
+func (s *controllingSelector) ContactCandidates() {
+ switch {
+ case s.agent.getSelectedPair() != nil:
+ if s.agent.validateSelectedPair() {
+ s.log.Trace("checking keepalive")
+ s.agent.checkKeepalive()
+ }
+ case s.nominatedPair != nil:
+ s.nominatePair(s.nominatedPair)
+ default:
+ p := s.agent.getBestValidCandidatePair()
+ if p != nil && s.isNominatable(p.local) && s.isNominatable(p.remote) {
+ s.log.Tracef("Nominatable pair found, nominating (%s, %s)", p.local.String(), p.remote.String())
+ p.nominated = true
+ s.nominatedPair = p
+ s.nominatePair(p)
+ return
+ }
+ s.agent.pingAllCandidates()
+ }
+}
+
+func (s *controllingSelector) nominatePair(pair *candidatePair) {
+ // The controlling agent MUST include the USE-CANDIDATE attribute in
+ // order to nominate a candidate pair (Section 8.1.1). The controlled
+ // agent MUST NOT include the USE-CANDIDATE attribute in a Binding
+ // request.
+ msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
+ stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag),
+ UseCandidate(),
+ AttrControlling(s.agent.tieBreaker),
+ PriorityAttr(pair.local.Priority()),
+ stun.NewShortTermIntegrity(s.agent.remotePwd),
+ stun.Fingerprint,
+ )
+ if err != nil {
+ s.log.Error(err.Error())
+ return
+ }
+
+ s.log.Tracef("ping STUN (nominate candidate pair) from %s to %s\n", pair.local.String(), pair.remote.String())
+ s.agent.sendBindingRequest(msg, pair.local, pair.remote)
+}
+
+func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) {
+ s.agent.sendBindingSuccess(m, local, remote)
+
+ p := s.agent.findPair(local, remote)
+
+ if p == nil {
+ s.agent.addPair(local, remote)
+ return
+ }
+
+ if p.state == CandidatePairStateSucceeded && s.nominatedPair == nil && s.agent.getSelectedPair() == nil {
+ bestPair := s.agent.getBestAvailableCandidatePair()
+ if bestPair == nil {
+ s.log.Tracef("No best pair available\n")
+ } else if bestPair.Equal(p) && s.isNominatable(p.local) && s.isNominatable(p.remote) {
+ s.log.Tracef("The candidate (%s, %s) is the best candidate available, marking it as nominated\n",
+ p.local.String(), p.remote.String())
+ s.nominatedPair = p
+ s.nominatePair(p)
+ }
+ }
+}
+
+func (s *controllingSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) {
+ ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID)
+ if !ok {
+ s.log.Warnf("discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID)
+ return
+ }
+
+ transactionAddr := pendingRequest.destination
+
+ // Assert that NAT is not symmetric
+ // https://tools.ietf.org/html/rfc8445#section-7.2.5.2.1
+ if !addrEqual(transactionAddr, remoteAddr) {
+ s.log.Debugf("discard message: transaction source and destination does not match expected(%s), actual(%s)", transactionAddr, remote)
+ return
+ }
+
+ s.log.Tracef("inbound STUN (SuccessResponse) from %s to %s", remote.String(), local.String())
+ p := s.agent.findPair(local, remote)
+
+ if p == nil {
+ // This shouldn't happen
+ s.log.Error("Success response from invalid candidate pair")
+ return
+ }
+
+ p.state = CandidatePairStateSucceeded
+ s.log.Tracef("Found valid candidate pair: %s", p)
+ if pendingRequest.isUseCandidate && s.agent.getSelectedPair() == nil {
+ s.agent.setSelectedPair(p)
+ }
+}
+
+func (s *controllingSelector) PingCandidate(local, remote Candidate) {
+ msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
+ stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag),
+ AttrControlling(s.agent.tieBreaker),
+ PriorityAttr(local.Priority()),
+ stun.NewShortTermIntegrity(s.agent.remotePwd),
+ stun.Fingerprint,
+ )
+ if err != nil {
+ s.log.Error(err.Error())
+ return
+ }
+
+ s.agent.sendBindingRequest(msg, local, remote)
+}
+
+type controlledSelector struct {
+ agent *Agent
+ log logging.LeveledLogger
+}
+
+func (s *controlledSelector) Start() {
+}
+
+func (s *controlledSelector) ContactCandidates() {
+ if s.agent.getSelectedPair() != nil {
+ if s.agent.validateSelectedPair() {
+ s.log.Trace("checking keepalive")
+ s.agent.checkKeepalive()
+ }
+ } else {
+ s.agent.pingAllCandidates()
+ }
+}
+
+func (s *controlledSelector) PingCandidate(local, remote Candidate) {
+ msg, err := stun.Build(stun.BindingRequest, stun.TransactionID,
+ stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag),
+ AttrControlled(s.agent.tieBreaker),
+ PriorityAttr(local.Priority()),
+ stun.NewShortTermIntegrity(s.agent.remotePwd),
+ stun.Fingerprint,
+ )
+ if err != nil {
+ s.log.Error(err.Error())
+ return
+ }
+
+ s.agent.sendBindingRequest(msg, local, remote)
+}
+
+func (s *controlledSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) {
+ // nolint:godox
+ // TODO according to the standard we should specifically answer a failed nomination:
+ // https://tools.ietf.org/html/rfc8445#section-7.3.1.5
+ // If the controlled agent does not accept the request from the
+ // controlling agent, the controlled agent MUST reject the nomination
+ // request with an appropriate error code response (e.g., 400)
+ // [RFC5389].
+
+ ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID)
+ if !ok {
+ s.log.Warnf("discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID)
+ return
+ }
+
+ transactionAddr := pendingRequest.destination
+
+ // Assert that NAT is not symmetric
+ // https://tools.ietf.org/html/rfc8445#section-7.2.5.2.1
+ if !addrEqual(transactionAddr, remoteAddr) {
+ s.log.Debugf("discard message: transaction source and destination does not match expected(%s), actual(%s)", transactionAddr, remote)
+ return
+ }
+
+ s.log.Tracef("inbound STUN (SuccessResponse) from %s to %s", remote.String(), local.String())
+
+ p := s.agent.findPair(local, remote)
+ if p == nil {
+ // This shouldn't happen
+ s.log.Error("Success response from invalid candidate pair")
+ return
+ }
+
+ p.state = CandidatePairStateSucceeded
+ s.log.Tracef("Found valid candidate pair: %s", p)
+}
+
+func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) {
+ useCandidate := m.Contains(stun.AttrUseCandidate)
+
+ p := s.agent.findPair(local, remote)
+
+ if p == nil {
+ p = s.agent.addPair(local, remote)
+ }
+
+ if useCandidate {
+ // https://tools.ietf.org/html/rfc8445#section-7.3.1.5
+
+ if p.state == CandidatePairStateSucceeded {
+ // If the state of this pair is Succeeded, it means that the check
+ // previously sent by this pair produced a successful response and
+ // generated a valid pair (Section 7.2.5.3.2). The agent sets the
+ // nominated flag value of the valid pair to true.
+ if selectedPair := s.agent.getSelectedPair(); selectedPair == nil {
+ s.agent.setSelectedPair(p)
+ }
+ s.agent.sendBindingSuccess(m, local, remote)
+ } else {
+ // If the received Binding request triggered a new check to be
+ // enqueued in the triggered-check queue (Section 7.3.1.4), once the
+ // check is sent and if it generates a successful response, and
+ // generates a valid pair, the agent sets the nominated flag of the
+ // pair to true. If the request fails (Section 7.2.5.2), the agent
+ // MUST remove the candidate pair from the valid list, set the
+ // candidate pair state to Failed, and set the checklist state to
+ // Failed.
+ s.PingCandidate(local, remote)
+ }
+ } else {
+ s.agent.sendBindingSuccess(m, local, remote)
+ s.PingCandidate(local, remote)
+ }
+}
+
+type liteSelector struct {
+ pairCandidateSelector
+}
+
+// A lite selector should not contact candidates
+func (s *liteSelector) ContactCandidates() {
+ if _, ok := s.pairCandidateSelector.(*controllingSelector); ok {
+ // nolint:godox
+ // pion/ice#96
+ // TODO: implement lite controlling agent. For now falling back to full agent.
+ // This only happens if both peers are lite. See RFC 8445 S6.1.1 and S6.2
+ s.pairCandidateSelector.ContactCandidates()
+ } else if v, ok := s.pairCandidateSelector.(*controlledSelector); ok {
+ v.agent.validateSelectedPair()
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/stats.go b/vendor/github.com/pion/ice/v2/stats.go
new file mode 100644
index 0000000..f59d89f
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/stats.go
@@ -0,0 +1,177 @@
+package ice
+
+import (
+ "time"
+)
+
+// CandidatePairStats contains ICE candidate pair statistics
+type CandidatePairStats struct {
+ // Timestamp is the timestamp associated with this object.
+ Timestamp time.Time
+
+ // LocalCandidateID is the ID of the local candidate
+ LocalCandidateID string
+
+ // RemoteCandidateID is the ID of the remote candidate
+ RemoteCandidateID string
+
+ // State represents the state of the checklist for the local and remote
+ // candidates in a pair.
+ State CandidatePairState
+
+ // Nominated is true when this valid pair that should be used for media
+ // if it is the highest-priority one amongst those whose nominated flag is set
+ Nominated bool
+
+ // PacketsSent represents the total number of packets sent on this candidate pair.
+ PacketsSent uint32
+
+ // PacketsReceived represents the total number of packets received on this candidate pair.
+ PacketsReceived uint32
+
+ // BytesSent represents the total number of payload bytes sent on this candidate pair
+ // not including headers or padding.
+ BytesSent uint64
+
+ // BytesReceived represents the total number of payload bytes received on this candidate pair
+ // not including headers or padding.
+ BytesReceived uint64
+
+ // LastPacketSentTimestamp represents the timestamp at which the last packet was
+ // sent on this particular candidate pair, excluding STUN packets.
+ LastPacketSentTimestamp time.Time
+
+ // LastPacketReceivedTimestamp represents the timestamp at which the last packet
+ // was received on this particular candidate pair, excluding STUN packets.
+ LastPacketReceivedTimestamp time.Time
+
+ // FirstRequestTimestamp represents the timestamp at which the first STUN request
+ // was sent on this particular candidate pair.
+ FirstRequestTimestamp time.Time
+
+ // LastRequestTimestamp represents the timestamp at which the last STUN request
+ // was sent on this particular candidate pair. The average interval between two
+ // consecutive connectivity checks sent can be calculated with
+ // (LastRequestTimestamp - FirstRequestTimestamp) / RequestsSent.
+ LastRequestTimestamp time.Time
+
+ // LastResponseTimestamp represents the timestamp at which the last STUN response
+ // was received on this particular candidate pair.
+ LastResponseTimestamp time.Time
+
+ // TotalRoundTripTime represents the sum of all round trip time measurements
+ // in seconds since the beginning of the session, based on STUN connectivity
+ // check responses (ResponsesReceived), including those that reply to requests
+ // that are sent in order to verify consent. The average round trip time can
+ // be computed from TotalRoundTripTime by dividing it by ResponsesReceived.
+ TotalRoundTripTime float64
+
+ // CurrentRoundTripTime represents the latest round trip time measured in seconds,
+ // computed from both STUN connectivity checks, including those that are sent
+ // for consent verification.
+ CurrentRoundTripTime float64
+
+ // AvailableOutgoingBitrate is calculated by the underlying congestion control
+ // by combining the available bitrate for all the outgoing RTP streams using
+ // this candidate pair. The bitrate measurement does not count the size of the
+ // IP or other transport layers like TCP or UDP. It is similar to the TIAS defined
+ // in RFC 3890, i.e., it is measured in bits per second and the bitrate is calculated
+ // over a 1 second window.
+ AvailableOutgoingBitrate float64
+
+ // AvailableIncomingBitrate is calculated by the underlying congestion control
+ // by combining the available bitrate for all the incoming RTP streams using
+ // this candidate pair. The bitrate measurement does not count the size of the
+ // IP or other transport layers like TCP or UDP. It is similar to the TIAS defined
+ // in RFC 3890, i.e., it is measured in bits per second and the bitrate is
+ // calculated over a 1 second window.
+ AvailableIncomingBitrate float64
+
+ // CircuitBreakerTriggerCount represents the number of times the circuit breaker
+ // is triggered for this particular 5-tuple, ceasing transmission.
+ CircuitBreakerTriggerCount uint32
+
+ // RequestsReceived represents the total number of connectivity check requests
+ // received (including retransmissions). It is impossible for the receiver to
+ // tell whether the request was sent in order to check connectivity or check
+ // consent, so all connectivity checks requests are counted here.
+ RequestsReceived uint64
+
+ // RequestsSent represents the total number of connectivity check requests
+ // sent (not including retransmissions).
+ RequestsSent uint64
+
+ // ResponsesReceived represents the total number of connectivity check responses received.
+ ResponsesReceived uint64
+
+ // ResponsesSent epresents the total number of connectivity check responses sent.
+ // Since we cannot distinguish connectivity check requests and consent requests,
+ // all responses are counted.
+ ResponsesSent uint64
+
+ // RetransmissionsReceived represents the total number of connectivity check
+ // request retransmissions received.
+ RetransmissionsReceived uint64
+
+ // RetransmissionsSent represents the total number of connectivity check
+ // request retransmissions sent.
+ RetransmissionsSent uint64
+
+ // ConsentRequestsSent represents the total number of consent requests sent.
+ ConsentRequestsSent uint64
+
+ // ConsentExpiredTimestamp represents the timestamp at which the latest valid
+ // STUN binding response expired.
+ ConsentExpiredTimestamp time.Time
+}
+
+// CandidateStats contains ICE candidate statistics related to the ICETransport objects.
+type CandidateStats struct {
+ // Timestamp is the timestamp associated with this object.
+ Timestamp time.Time
+
+ // ID is the candidate ID
+ ID string
+
+ // NetworkType represents the type of network interface used by the base of a
+ // local candidate (the address the ICE agent sends from). Only present for
+ // local candidates; it's not possible to know what type of network interface
+ // a remote candidate is using.
+ //
+ // Note:
+ // This stat only tells you about the network interface used by the first "hop";
+ // it's possible that a connection will be bottlenecked by another type of network.
+ // For example, when using Wi-Fi tethering, the networkType of the relevant candidate
+ // would be "wifi", even when the next hop is over a cellular connection.
+ NetworkType NetworkType
+
+ // IP is the IP address of the candidate, allowing for IPv4 addresses and
+ // IPv6 addresses, but fully qualified domain names (FQDNs) are not allowed.
+ IP string
+
+ // Port is the port number of the candidate.
+ Port int
+
+ // CandidateType is the "Type" field of the ICECandidate.
+ CandidateType CandidateType
+
+ // Priority is the "Priority" field of the ICECandidate.
+ Priority uint32
+
+ // URL is the URL of the TURN or STUN server indicated in the that translated
+ // this IP address. It is the URL address surfaced in an PeerConnectionICEEvent.
+ URL string
+
+ // RelayProtocol is the protocol used by the endpoint to communicate with the
+ // TURN server. This is only present for local candidates. Valid values for
+ // the TURN URL protocol is one of udp, tcp, or tls.
+ RelayProtocol string
+
+ // Deleted is true if the candidate has been deleted/freed. For host candidates,
+ // this means that any network resources (typically a socket) associated with the
+ // candidate have been released. For TURN candidates, this means the TURN allocation
+ // is no longer active.
+ //
+ // Only defined for local candidates. For remote candidates, this property is not applicable.
+ Deleted bool
+}
diff --git a/vendor/github.com/pion/ice/v2/stun.go b/vendor/github.com/pion/ice/v2/stun.go
new file mode 100644
index 0000000..bef7c87
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/stun.go
@@ -0,0 +1,24 @@
+package ice
+
+import (
+ "fmt"
+
+ "github.com/pion/stun"
+)
+
+func assertInboundUsername(m *stun.Message, expectedUsername string) error {
+ var username stun.Username
+ if err := username.GetFrom(m); err != nil {
+ return err
+ }
+ if string(username) != expectedUsername {
+ return fmt.Errorf("%w expected(%x) actual(%x)", errMismatchUsername, expectedUsername, string(username))
+ }
+
+ return nil
+}
+
+func assertInboundMessageIntegrity(m *stun.Message, key []byte) error {
+ messageIntegrityAttr := stun.MessageIntegrity(key)
+ return messageIntegrityAttr.Check(m)
+}
diff --git a/vendor/github.com/pion/ice/v2/tcp_mux.go b/vendor/github.com/pion/ice/v2/tcp_mux.go
new file mode 100644
index 0000000..1a9a797
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/tcp_mux.go
@@ -0,0 +1,295 @@
+package ice
+
+import (
+ "encoding/binary"
+ "io"
+ "net"
+ "strings"
+ "sync"
+
+ "github.com/pion/logging"
+ "github.com/pion/stun"
+)
+
+// TCPMux is allows grouping multiple TCP net.Conns and using them like UDP
+// net.PacketConns. The main implementation of this is TCPMuxDefault, and this
+// interface exists to:
+// 1. prevent SEGV panics when TCPMuxDefault is not initialized by using the
+// invalidTCPMux implementation, and
+// 2. allow mocking in tests.
+type TCPMux interface {
+ io.Closer
+ GetConnByUfrag(ufrag string) (net.PacketConn, error)
+ RemoveConnByUfrag(ufrag string)
+}
+
+// invalidTCPMux is an implementation of TCPMux that always returns ErroTCPMuxNotInitialized.
+type invalidTCPMux struct {
+}
+
+func newInvalidTCPMux() *invalidTCPMux {
+ return &invalidTCPMux{}
+}
+
+// Close implements TCPMux interface.
+func (m *invalidTCPMux) Close() error {
+ return ErrTCPMuxNotInitialized
+}
+
+// GetConnByUfrag implements TCPMux interface.
+func (m *invalidTCPMux) GetConnByUfrag(ufrag string) (net.PacketConn, error) {
+ return nil, ErrTCPMuxNotInitialized
+}
+
+// RemoveConnByUfrag implements TCPMux interface.
+func (m *invalidTCPMux) RemoveConnByUfrag(ufrag string) {}
+
+// TCPMuxDefault muxes TCP net.Conns into net.PacketConns and groups them by
+// Ufrag. It is a default implementation of TCPMux interface.
+type TCPMuxDefault struct {
+ params *TCPMuxParams
+ closed bool
+
+ // conns is a map of all tcpPacketConns indexed by ufrag
+ conns map[string]*tcpPacketConn
+
+ mu sync.Mutex
+ wg sync.WaitGroup
+}
+
+// TCPMuxParams are parameters for TCPMux.
+type TCPMuxParams struct {
+ Listener net.Listener
+ Logger logging.LeveledLogger
+ ReadBufferSize int
+}
+
+// NewTCPMuxDefault creates a new instance of TCPMuxDefault.
+func NewTCPMuxDefault(params TCPMuxParams) *TCPMuxDefault {
+ if params.Logger == nil {
+ params.Logger = logging.NewDefaultLoggerFactory().NewLogger("ice")
+ }
+
+ m := &TCPMuxDefault{
+ params: &params,
+
+ conns: map[string]*tcpPacketConn{},
+ }
+
+ m.wg.Add(1)
+ go func() {
+ defer m.wg.Done()
+ m.start()
+ }()
+
+ return m
+}
+
+func (m *TCPMuxDefault) start() {
+ m.params.Logger.Infof("Listening TCP on %s\n", m.params.Listener.Addr())
+ for {
+ conn, err := m.params.Listener.Accept()
+ if err != nil {
+ m.params.Logger.Infof("Error accepting connection: %s\n", err)
+ return
+ }
+
+ m.params.Logger.Debugf("Accepted connection from: %s to %s", conn.RemoteAddr(), conn.LocalAddr())
+
+ m.wg.Add(1)
+ go func() {
+ defer m.wg.Done()
+ m.handleConn(conn)
+ }()
+ }
+}
+
+// LocalAddr returns the listening address of this TCPMuxDefault.
+func (m *TCPMuxDefault) LocalAddr() net.Addr {
+ return m.params.Listener.Addr()
+}
+
+// GetConnByUfrag retrieves an existing or creates a new net.PacketConn.
+func (m *TCPMuxDefault) GetConnByUfrag(ufrag string) (net.PacketConn, error) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ if m.closed {
+ return nil, io.ErrClosedPipe
+ }
+
+ conn, ok := m.conns[ufrag]
+
+ if ok {
+ return conn, nil
+ // return nil, fmt.Errorf("duplicate ufrag %v", ufrag)
+ }
+
+ conn = m.createConn(ufrag, m.LocalAddr())
+
+ return conn, nil
+}
+
+func (m *TCPMuxDefault) createConn(ufrag string, localAddr net.Addr) *tcpPacketConn {
+ conn := newTCPPacketConn(tcpPacketParams{
+ ReadBuffer: m.params.ReadBufferSize,
+ LocalAddr: localAddr,
+ Logger: m.params.Logger,
+ })
+ m.conns[ufrag] = conn
+
+ m.wg.Add(1)
+ go func() {
+ defer m.wg.Done()
+ <-conn.CloseChannel()
+ m.RemoveConnByUfrag(ufrag)
+ }()
+
+ return conn
+}
+
+func (m *TCPMuxDefault) closeAndLogError(closer io.Closer) {
+ err := closer.Close()
+ if err != nil {
+ m.params.Logger.Warnf("Error closing connection: %s", err)
+ }
+}
+
+func (m *TCPMuxDefault) handleConn(conn net.Conn) {
+ buf := make([]byte, receiveMTU)
+
+ n, err := readStreamingPacket(conn, buf)
+ if err != nil {
+ m.params.Logger.Warnf("Error reading first packet: %s", err)
+ return
+ }
+
+ buf = buf[:n]
+
+ msg := &stun.Message{
+ Raw: make([]byte, len(buf)),
+ }
+ // Explicitly copy raw buffer so Message can own the memory.
+ copy(msg.Raw, buf)
+ if err = msg.Decode(); err != nil {
+ m.closeAndLogError(conn)
+ m.params.Logger.Warnf("Failed to handle decode ICE from %s to %s: %v\n", conn.RemoteAddr(), conn.LocalAddr(), err)
+ return
+ }
+
+ if m == nil || msg.Type.Method != stun.MethodBinding { // not a stun
+ m.closeAndLogError(conn)
+ m.params.Logger.Warnf("Not a STUN message from %s to %s\n", conn.RemoteAddr(), conn.LocalAddr())
+ return
+ }
+
+ for _, attr := range msg.Attributes {
+ m.params.Logger.Debugf("msg attr: %s\n", attr.String())
+ }
+
+ attr, err := msg.Get(stun.AttrUsername)
+ if err != nil {
+ m.closeAndLogError(conn)
+ m.params.Logger.Warnf("No Username attribute in STUN message from %s to %s\n", conn.RemoteAddr(), conn.LocalAddr())
+ return
+ }
+
+ ufrag := strings.Split(string(attr), ":")[0]
+ m.params.Logger.Debugf("Ufrag: %s\n", ufrag)
+
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ packetConn, ok := m.conns[ufrag]
+ if !ok {
+ packetConn = m.createConn(ufrag, conn.LocalAddr())
+ }
+
+ if err := packetConn.AddConn(conn, buf); err != nil {
+ m.closeAndLogError(conn)
+ m.params.Logger.Warnf("Error adding conn to tcpPacketConn from %s to %s: %s\n", conn.RemoteAddr(), conn.LocalAddr(), err)
+ return
+ }
+}
+
+// Close closes the listener and waits for all goroutines to exit.
+func (m *TCPMuxDefault) Close() error {
+ m.mu.Lock()
+ m.closed = true
+
+ for _, conn := range m.conns {
+ m.closeAndLogError(conn)
+ }
+ m.conns = map[string]*tcpPacketConn{}
+
+ err := m.params.Listener.Close()
+
+ m.mu.Unlock()
+
+ m.wg.Wait()
+
+ return err
+}
+
+// RemoveConnByUfrag closes and removes a net.PacketConn by Ufrag.
+func (m *TCPMuxDefault) RemoveConnByUfrag(ufrag string) {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ if conn, ok := m.conns[ufrag]; ok {
+ m.closeAndLogError(conn)
+ delete(m.conns, ufrag)
+ }
+}
+
+const streamingPacketHeaderLen = 2
+
+// readStreamingPacket reads 1 packet from stream
+// read packet bytes https://tools.ietf.org/html/rfc4571#section-2
+// 2-byte length header prepends each packet:
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// -----------------------------------------------------------------
+// | LENGTH | RTP or RTCP packet ... |
+// -----------------------------------------------------------------
+func readStreamingPacket(conn net.Conn, buf []byte) (int, error) {
+ header := make([]byte, streamingPacketHeaderLen)
+ var bytesRead, n int
+ var err error
+
+ for bytesRead < streamingPacketHeaderLen {
+ if n, err = conn.Read(header[bytesRead:streamingPacketHeaderLen]); err != nil {
+ return 0, err
+ }
+ bytesRead += n
+ }
+
+ length := int(binary.BigEndian.Uint16(header))
+
+ if length > cap(buf) {
+ return length, io.ErrShortBuffer
+ }
+
+ bytesRead = 0
+ for bytesRead < length {
+ if n, err = conn.Read(buf[bytesRead:length]); err != nil {
+ return 0, err
+ }
+ bytesRead += n
+ }
+
+ return bytesRead, nil
+}
+
+func writeStreamingPacket(conn net.Conn, buf []byte) (int, error) {
+ bufferCopy := make([]byte, streamingPacketHeaderLen+len(buf))
+ binary.BigEndian.PutUint16(bufferCopy, uint16(len(buf)))
+ copy(bufferCopy[2:], buf)
+
+ n, err := conn.Write(bufferCopy)
+ if err != nil {
+ return 0, err
+ }
+
+ return n - streamingPacketHeaderLen, nil
+}
diff --git a/vendor/github.com/pion/ice/v2/tcp_packet_conn.go b/vendor/github.com/pion/ice/v2/tcp_packet_conn.go
new file mode 100644
index 0000000..dc4eaf0
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/tcp_packet_conn.go
@@ -0,0 +1,240 @@
+package ice
+
+import (
+ "fmt"
+ "io"
+ "net"
+ "sync"
+ "time"
+
+ "github.com/pion/logging"
+)
+
+type tcpPacketConn struct {
+ params *tcpPacketParams
+
+ // conns is a map of net.Conns indexed by remote net.Addr.String()
+ conns map[string]net.Conn
+
+ recvChan chan streamingPacket
+
+ mu sync.Mutex
+ wg sync.WaitGroup
+ closedChan chan struct{}
+ closeOnce sync.Once
+}
+
+type streamingPacket struct {
+ Data []byte
+ RAddr net.Addr
+ Err error
+}
+
+type tcpPacketParams struct {
+ ReadBuffer int
+ LocalAddr net.Addr
+ Logger logging.LeveledLogger
+}
+
+func newTCPPacketConn(params tcpPacketParams) *tcpPacketConn {
+ p := &tcpPacketConn{
+ params: &params,
+
+ conns: map[string]net.Conn{},
+
+ recvChan: make(chan streamingPacket, params.ReadBuffer),
+ closedChan: make(chan struct{}),
+ }
+
+ return p
+}
+
+func (t *tcpPacketConn) AddConn(conn net.Conn, firstPacketData []byte) error {
+ t.params.Logger.Infof("AddConn: %s %s", conn.RemoteAddr().Network(), conn.RemoteAddr())
+
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ select {
+ case <-t.closedChan:
+ return io.ErrClosedPipe
+ default:
+ }
+
+ if _, ok := t.conns[conn.RemoteAddr().String()]; ok {
+ return fmt.Errorf("%w: %s", errConnectionAddrAlreadyExist, conn.RemoteAddr().String())
+ }
+
+ t.conns[conn.RemoteAddr().String()] = conn
+
+ t.wg.Add(1)
+ go func() {
+ if firstPacketData != nil {
+ t.recvChan <- streamingPacket{firstPacketData, conn.RemoteAddr(), nil}
+ }
+ defer t.wg.Done()
+ t.startReading(conn)
+ }()
+
+ return nil
+}
+
+func (t *tcpPacketConn) startReading(conn net.Conn) {
+ buf := make([]byte, receiveMTU)
+
+ for {
+ n, err := readStreamingPacket(conn, buf)
+ // t.params.Logger.Infof("readStreamingPacket read %d bytes", n)
+ if err != nil {
+ t.params.Logger.Infof("%w: %s\n", errReadingStreamingPacket, err)
+ t.handleRecv(streamingPacket{nil, conn.RemoteAddr(), err})
+ t.removeConn(conn)
+ return
+ }
+
+ data := make([]byte, n)
+ copy(data, buf[:n])
+
+ // t.params.Logger.Infof("Writing read streaming packet to recvChan: %d bytes", len(data))
+ t.handleRecv(streamingPacket{data, conn.RemoteAddr(), nil})
+ }
+}
+
+func (t *tcpPacketConn) handleRecv(pkt streamingPacket) {
+ t.mu.Lock()
+
+ recvChan := t.recvChan
+ if t.isClosed() {
+ recvChan = nil
+ }
+
+ t.mu.Unlock()
+
+ select {
+ case recvChan <- pkt:
+ case <-t.closedChan:
+ }
+}
+
+func (t *tcpPacketConn) isClosed() bool {
+ select {
+ case <-t.closedChan:
+ return true
+ default:
+ return false
+ }
+}
+
+// WriteTo is for passive and s-o candidates.
+func (t *tcpPacketConn) ReadFrom(b []byte) (n int, raddr net.Addr, err error) {
+ pkt, ok := <-t.recvChan
+
+ if !ok {
+ return 0, nil, io.ErrClosedPipe
+ }
+
+ if pkt.Err != nil {
+ return 0, pkt.RAddr, pkt.Err
+ }
+
+ if cap(b) < len(pkt.Data) {
+ return 0, pkt.RAddr, io.ErrShortBuffer
+ }
+
+ n = len(pkt.Data)
+ copy(b, pkt.Data[:n])
+ return n, pkt.RAddr, err
+}
+
+// WriteTo is for active and s-o candidates.
+func (t *tcpPacketConn) WriteTo(buf []byte, raddr net.Addr) (n int, err error) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ conn, ok := t.conns[raddr.String()]
+ if !ok {
+ return 0, io.ErrClosedPipe
+ // conn, err := net.DialTCP(tcp, nil, raddr.(*net.TCPAddr))
+
+ // if err != nil {
+ // t.params.Logger.Tracef("DialTCP error: %s", err)
+ // return 0, err
+ // }
+
+ // go t.startReading(conn)
+ // t.conns[raddr.String()] = conn
+ }
+
+ n, err = writeStreamingPacket(conn, buf)
+ if err != nil {
+ t.params.Logger.Tracef("%w %s\n", errWriting, raddr)
+ return n, err
+ }
+
+ return n, err
+}
+
+func (t *tcpPacketConn) closeAndLogError(closer io.Closer) {
+ err := closer.Close()
+ if err != nil {
+ t.params.Logger.Warnf("%w: %s", errClosingConnection, err)
+ }
+}
+
+func (t *tcpPacketConn) removeConn(conn net.Conn) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ t.closeAndLogError(conn)
+
+ delete(t.conns, conn.RemoteAddr().String())
+}
+
+func (t *tcpPacketConn) Close() error {
+ t.mu.Lock()
+
+ var shouldCloseRecvChan bool
+ t.closeOnce.Do(func() {
+ close(t.closedChan)
+ shouldCloseRecvChan = true
+ })
+
+ for _, conn := range t.conns {
+ t.closeAndLogError(conn)
+ delete(t.conns, conn.RemoteAddr().String())
+ }
+
+ t.mu.Unlock()
+
+ t.wg.Wait()
+
+ if shouldCloseRecvChan {
+ close(t.recvChan)
+ }
+
+ return nil
+}
+
+func (t *tcpPacketConn) LocalAddr() net.Addr {
+ return t.params.LocalAddr
+}
+
+func (t *tcpPacketConn) SetDeadline(tm time.Time) error {
+ return nil
+}
+
+func (t *tcpPacketConn) SetReadDeadline(tm time.Time) error {
+ return nil
+}
+
+func (t *tcpPacketConn) SetWriteDeadline(tm time.Time) error {
+ return nil
+}
+
+func (t *tcpPacketConn) CloseChannel() <-chan struct{} {
+ return t.closedChan
+}
+
+func (t *tcpPacketConn) String() string {
+ return fmt.Sprintf("tcpPacketConn{LocalAddr: %s}", t.params.LocalAddr)
+}
diff --git a/vendor/github.com/pion/ice/v2/tcptype.go b/vendor/github.com/pion/ice/v2/tcptype.go
new file mode 100644
index 0000000..6700fe5
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/tcptype.go
@@ -0,0 +1,48 @@
+package ice
+
+import "strings"
+
+// TCPType is the type of ICE TCP candidate as described in
+// ttps://tools.ietf.org/html/rfc6544#section-4.5
+type TCPType int
+
+const (
+ // TCPTypeUnspecified is the default value. For example UDP candidates do not
+ // need this field.
+ TCPTypeUnspecified TCPType = iota
+ // TCPTypeActive is active TCP candidate, which initiates TCP connections.
+ TCPTypeActive
+ // TCPTypePassive is passive TCP candidate, only accepts TCP connections.
+ TCPTypePassive
+ // TCPTypeSimultaneousOpen is like active and passive at the same time.
+ TCPTypeSimultaneousOpen
+)
+
+// NewTCPType creates a new TCPType from string.
+func NewTCPType(value string) TCPType {
+ switch strings.ToLower(value) {
+ case "active":
+ return TCPTypeActive
+ case "passive":
+ return TCPTypePassive
+ case "so":
+ return TCPTypeSimultaneousOpen
+ default:
+ return TCPTypeUnspecified
+ }
+}
+
+func (t TCPType) String() string {
+ switch t {
+ case TCPTypeUnspecified:
+ return ""
+ case TCPTypeActive:
+ return "active"
+ case TCPTypePassive:
+ return "passive"
+ case TCPTypeSimultaneousOpen:
+ return "so"
+ default:
+ return ErrUnknownType.Error()
+ }
+}
diff --git a/vendor/github.com/pion/ice/v2/transport.go b/vendor/github.com/pion/ice/v2/transport.go
new file mode 100644
index 0000000..d1c82ff
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/transport.go
@@ -0,0 +1,145 @@
+package ice
+
+import (
+ "context"
+ "net"
+ "sync/atomic"
+ "time"
+
+ "github.com/pion/stun"
+)
+
+// Dial connects to the remote agent, acting as the controlling ice agent.
+// Dial blocks until at least one ice candidate pair has successfully connected.
+func (a *Agent) Dial(ctx context.Context, remoteUfrag, remotePwd string) (*Conn, error) {
+ return a.connect(ctx, true, remoteUfrag, remotePwd)
+}
+
+// Accept connects to the remote agent, acting as the controlled ice agent.
+// Accept blocks until at least one ice candidate pair has successfully connected.
+func (a *Agent) Accept(ctx context.Context, remoteUfrag, remotePwd string) (*Conn, error) {
+ return a.connect(ctx, false, remoteUfrag, remotePwd)
+}
+
+// Conn represents the ICE connection.
+// At the moment the lifetime of the Conn is equal to the Agent.
+type Conn struct {
+ bytesReceived uint64
+ bytesSent uint64
+ agent *Agent
+}
+
+// BytesSent returns the number of bytes sent
+func (c *Conn) BytesSent() uint64 {
+ return atomic.LoadUint64(&c.bytesSent)
+}
+
+// BytesReceived returns the number of bytes received
+func (c *Conn) BytesReceived() uint64 {
+ return atomic.LoadUint64(&c.bytesReceived)
+}
+
+func (a *Agent) connect(ctx context.Context, isControlling bool, remoteUfrag, remotePwd string) (*Conn, error) {
+ err := a.ok()
+ if err != nil {
+ return nil, err
+ }
+ err = a.startConnectivityChecks(isControlling, remoteUfrag, remotePwd)
+ if err != nil {
+ return nil, err
+ }
+
+ // block until pair selected
+ select {
+ case <-a.done:
+ return nil, a.getErr()
+ case <-ctx.Done():
+ return nil, ErrCanceledByCaller
+ case <-a.onConnected:
+ }
+
+ return &Conn{
+ agent: a,
+ }, nil
+}
+
+// Read implements the Conn Read method.
+func (c *Conn) Read(p []byte) (int, error) {
+ err := c.agent.ok()
+ if err != nil {
+ return 0, err
+ }
+
+ n, err := c.agent.buffer.Read(p)
+ atomic.AddUint64(&c.bytesReceived, uint64(n))
+ return n, err
+}
+
+// Write implements the Conn Write method.
+func (c *Conn) Write(p []byte) (int, error) {
+ err := c.agent.ok()
+ if err != nil {
+ return 0, err
+ }
+
+ if stun.IsMessage(p) {
+ return 0, errICEWriteSTUNMessage
+ }
+
+ pair := c.agent.getSelectedPair()
+ if pair == nil {
+ if err = c.agent.run(c.agent.context(), func(ctx context.Context, a *Agent) {
+ pair = a.getBestValidCandidatePair()
+ }); err != nil {
+ return 0, err
+ }
+
+ if pair == nil {
+ return 0, err
+ }
+ }
+
+ atomic.AddUint64(&c.bytesSent, uint64(len(p)))
+ return pair.Write(p)
+}
+
+// Close implements the Conn Close method. It is used to close
+// the connection. Any calls to Read and Write will be unblocked and return an error.
+func (c *Conn) Close() error {
+ return c.agent.Close()
+}
+
+// LocalAddr returns the local address of the current selected pair or nil if there is none.
+func (c *Conn) LocalAddr() net.Addr {
+ pair := c.agent.getSelectedPair()
+ if pair == nil {
+ return nil
+ }
+
+ return pair.local.addr()
+}
+
+// RemoteAddr returns the remote address of the current selected pair or nil if there is none.
+func (c *Conn) RemoteAddr() net.Addr {
+ pair := c.agent.getSelectedPair()
+ if pair == nil {
+ return nil
+ }
+
+ return pair.remote.addr()
+}
+
+// SetDeadline is a stub
+func (c *Conn) SetDeadline(t time.Time) error {
+ return nil
+}
+
+// SetReadDeadline is a stub
+func (c *Conn) SetReadDeadline(t time.Time) error {
+ return nil
+}
+
+// SetWriteDeadline is a stub
+func (c *Conn) SetWriteDeadline(t time.Time) error {
+ return nil
+}
diff --git a/vendor/github.com/pion/ice/v2/url.go b/vendor/github.com/pion/ice/v2/url.go
new file mode 100644
index 0000000..390591e
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/url.go
@@ -0,0 +1,225 @@
+package ice
+
+import (
+ "net"
+ "net/url"
+ "strconv"
+)
+
+// SchemeType indicates the type of server used in the ice.URL structure.
+type SchemeType int
+
+// Unknown defines default public constant to use for "enum" like struct
+// comparisons when no value was defined.
+const Unknown = iota
+
+const (
+ // SchemeTypeSTUN indicates the URL represents a STUN server.
+ SchemeTypeSTUN SchemeType = iota + 1
+
+ // SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server.
+ SchemeTypeSTUNS
+
+ // SchemeTypeTURN indicates the URL represents a TURN server.
+ SchemeTypeTURN
+
+ // SchemeTypeTURNS indicates the URL represents a TURNS (secure) server.
+ SchemeTypeTURNS
+)
+
+// NewSchemeType defines a procedure for creating a new SchemeType from a raw
+// string naming the scheme type.
+func NewSchemeType(raw string) SchemeType {
+ switch raw {
+ case "stun":
+ return SchemeTypeSTUN
+ case "stuns":
+ return SchemeTypeSTUNS
+ case "turn":
+ return SchemeTypeTURN
+ case "turns":
+ return SchemeTypeTURNS
+ default:
+ return SchemeType(Unknown)
+ }
+}
+
+func (t SchemeType) String() string {
+ switch t {
+ case SchemeTypeSTUN:
+ return "stun"
+ case SchemeTypeSTUNS:
+ return "stuns"
+ case SchemeTypeTURN:
+ return "turn"
+ case SchemeTypeTURNS:
+ return "turns"
+ default:
+ return ErrUnknownType.Error()
+ }
+}
+
+// ProtoType indicates the transport protocol type that is used in the ice.URL
+// structure.
+type ProtoType int
+
+const (
+ // ProtoTypeUDP indicates the URL uses a UDP transport.
+ ProtoTypeUDP ProtoType = iota + 1
+
+ // ProtoTypeTCP indicates the URL uses a TCP transport.
+ ProtoTypeTCP
+)
+
+// NewProtoType defines a procedure for creating a new ProtoType from a raw
+// string naming the transport protocol type.
+func NewProtoType(raw string) ProtoType {
+ switch raw {
+ case "udp":
+ return ProtoTypeUDP
+ case "tcp":
+ return ProtoTypeTCP
+ default:
+ return ProtoType(Unknown)
+ }
+}
+
+func (t ProtoType) String() string {
+ switch t {
+ case ProtoTypeUDP:
+ return "udp"
+ case ProtoTypeTCP:
+ return "tcp"
+ default:
+ return ErrUnknownType.Error()
+ }
+}
+
+// URL represents a STUN (rfc7064) or TURN (rfc7065) URL
+type URL struct {
+ Scheme SchemeType
+ Host string
+ Port int
+ Username string
+ Password string
+ Proto ProtoType
+}
+
+// ParseURL parses a STUN or TURN urls following the ABNF syntax described in
+// https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065
+// respectively.
+func ParseURL(raw string) (*URL, error) { //nolint:gocognit
+ rawParts, err := url.Parse(raw)
+ if err != nil {
+ return nil, err
+ }
+
+ var u URL
+ u.Scheme = NewSchemeType(rawParts.Scheme)
+ if u.Scheme == SchemeType(Unknown) {
+ return nil, ErrSchemeType
+ }
+
+ var rawPort string
+ if u.Host, rawPort, err = net.SplitHostPort(rawParts.Opaque); err != nil {
+ if e, ok := err.(*net.AddrError); ok {
+ if e.Err == "missing port in address" {
+ nextRawURL := u.Scheme.String() + ":" + rawParts.Opaque
+ switch {
+ case u.Scheme == SchemeTypeSTUN || u.Scheme == SchemeTypeTURN:
+ nextRawURL += ":3478"
+ if rawParts.RawQuery != "" {
+ nextRawURL += "?" + rawParts.RawQuery
+ }
+ return ParseURL(nextRawURL)
+ case u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS:
+ nextRawURL += ":5349"
+ if rawParts.RawQuery != "" {
+ nextRawURL += "?" + rawParts.RawQuery
+ }
+ return ParseURL(nextRawURL)
+ }
+ }
+ }
+ return nil, err
+ }
+
+ if u.Host == "" {
+ return nil, ErrHost
+ }
+
+ if u.Port, err = strconv.Atoi(rawPort); err != nil {
+ return nil, ErrPort
+ }
+
+ switch u.Scheme {
+ case SchemeTypeSTUN:
+ qArgs, err := url.ParseQuery(rawParts.RawQuery)
+ if err != nil || len(qArgs) > 0 {
+ return nil, ErrSTUNQuery
+ }
+ u.Proto = ProtoTypeUDP
+ case SchemeTypeSTUNS:
+ qArgs, err := url.ParseQuery(rawParts.RawQuery)
+ if err != nil || len(qArgs) > 0 {
+ return nil, ErrSTUNQuery
+ }
+ u.Proto = ProtoTypeTCP
+ case SchemeTypeTURN:
+ proto, err := parseProto(rawParts.RawQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ u.Proto = proto
+ if u.Proto == ProtoType(Unknown) {
+ u.Proto = ProtoTypeUDP
+ }
+ case SchemeTypeTURNS:
+ proto, err := parseProto(rawParts.RawQuery)
+ if err != nil {
+ return nil, err
+ }
+
+ u.Proto = proto
+ if u.Proto == ProtoType(Unknown) {
+ u.Proto = ProtoTypeTCP
+ }
+ }
+
+ return &u, nil
+}
+
+func parseProto(raw string) (ProtoType, error) {
+ qArgs, err := url.ParseQuery(raw)
+ if err != nil || len(qArgs) > 1 {
+ return ProtoType(Unknown), ErrInvalidQuery
+ }
+
+ var proto ProtoType
+ if rawProto := qArgs.Get("transport"); rawProto != "" {
+ if proto = NewProtoType(rawProto); proto == ProtoType(0) {
+ return ProtoType(Unknown), ErrProtoType
+ }
+ return proto, nil
+ }
+
+ if len(qArgs) > 0 {
+ return ProtoType(Unknown), ErrInvalidQuery
+ }
+
+ return proto, nil
+}
+
+func (u URL) String() string {
+ rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port))
+ if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS {
+ rawURL += "?transport=" + u.Proto.String()
+ }
+ return rawURL
+}
+
+// IsSecure returns whether the this URL's scheme describes secure scheme or not.
+func (u URL) IsSecure() bool {
+ return u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS
+}
diff --git a/vendor/github.com/pion/ice/v2/usecandidate.go b/vendor/github.com/pion/ice/v2/usecandidate.go
new file mode 100644
index 0000000..f168c08
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/usecandidate.go
@@ -0,0 +1,23 @@
+package ice
+
+import "github.com/pion/stun"
+
+// UseCandidateAttr represents USE-CANDIDATE attribute.
+type UseCandidateAttr struct{}
+
+// AddTo adds USE-CANDIDATE attribute to message.
+func (UseCandidateAttr) AddTo(m *stun.Message) error {
+ m.Add(stun.AttrUseCandidate, nil)
+ return nil
+}
+
+// IsSet returns true if USE-CANDIDATE attribute is set.
+func (UseCandidateAttr) IsSet(m *stun.Message) bool {
+ _, err := m.Get(stun.AttrUseCandidate)
+ return err == nil
+}
+
+// UseCandidate is shorthand for UseCandidateAttr.
+func UseCandidate() UseCandidateAttr {
+ return UseCandidateAttr{}
+}
diff --git a/vendor/github.com/pion/ice/v2/util.go b/vendor/github.com/pion/ice/v2/util.go
new file mode 100644
index 0000000..7eb13c8
--- /dev/null
+++ b/vendor/github.com/pion/ice/v2/util.go
@@ -0,0 +1,233 @@
+package ice
+
+import (
+ "fmt"
+ "net"
+ "sync/atomic"
+ "time"
+
+ "github.com/pion/logging"
+ "github.com/pion/stun"
+ "github.com/pion/transport/vnet"
+)
+
+type atomicError struct{ v atomic.Value }
+
+func (a *atomicError) Store(err error) {
+ a.v.Store(struct{ error }{err})
+}
+
+func (a *atomicError) Load() error {
+ err, _ := a.v.Load().(struct{ error })
+ return err.error
+}
+
+// The conditions of invalidation written below are defined in
+// https://tools.ietf.org/html/rfc8445#section-5.1.1.1
+func isSupportedIPv6(ip net.IP) bool {
+ if len(ip) != net.IPv6len ||
+ isZeros(ip[0:12]) || // !(IPv4-compatible IPv6)
+ ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 || // !(IPv6 site-local unicast)
+ ip.IsLinkLocalUnicast() ||
+ ip.IsLinkLocalMulticast() {
+ return false
+ }
+ return true
+}
+
+func isZeros(ip net.IP) bool {
+ for i := 0; i < len(ip); i++ {
+ if ip[i] != 0 {
+ return false
+ }
+ }
+ return true
+}
+
+func parseAddr(in net.Addr) (net.IP, int, NetworkType, bool) {
+ switch addr := in.(type) {
+ case *net.UDPAddr:
+ return addr.IP, addr.Port, NetworkTypeUDP4, true
+ case *net.TCPAddr:
+ return addr.IP, addr.Port, NetworkTypeTCP4, true
+ }
+ return nil, 0, 0, false
+}
+
+func createAddr(network NetworkType, ip net.IP, port int) net.Addr {
+ switch {
+ case network.IsTCP():
+ return &net.TCPAddr{IP: ip, Port: port}
+ default:
+ return &net.UDPAddr{IP: ip, Port: port}
+ }
+}
+
+func addrEqual(a, b net.Addr) bool {
+ aIP, aPort, aType, aOk := parseAddr(a)
+ if !aOk {
+ return false
+ }
+
+ bIP, bPort, bType, bOk := parseAddr(b)
+ if !bOk {
+ return false
+ }
+
+ return aType == bType && aIP.Equal(bIP) && aPort == bPort
+}
+
+// getXORMappedAddr initiates a stun requests to serverAddr using conn, reads the response and returns
+// the XORMappedAddress returned by the stun server.
+//
+// Adapted from stun v0.2.
+func getXORMappedAddr(conn net.PacketConn, serverAddr net.Addr, deadline time.Duration) (*stun.XORMappedAddress, error) {
+ if deadline > 0 {
+ if err := conn.SetReadDeadline(time.Now().Add(deadline)); err != nil {
+ return nil, err
+ }
+ }
+ defer func() {
+ if deadline > 0 {
+ _ = conn.SetReadDeadline(time.Time{})
+ }
+ }()
+ resp, err := stunRequest(
+ func(p []byte) (int, error) {
+ n, _, errr := conn.ReadFrom(p)
+ return n, errr
+ },
+ func(b []byte) (int, error) {
+ return conn.WriteTo(b, serverAddr)
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ var addr stun.XORMappedAddress
+ if err = addr.GetFrom(resp); err != nil {
+ return nil, fmt.Errorf("%w: %v", errGetXorMappedAddrResponse, err)
+ }
+ return &addr, nil
+}
+
+func stunRequest(read func([]byte) (int, error), write func([]byte) (int, error)) (*stun.Message, error) {
+ req, err := stun.Build(stun.BindingRequest, stun.TransactionID)
+ if err != nil {
+ return nil, err
+ }
+ if _, err = write(req.Raw); err != nil {
+ return nil, err
+ }
+ const maxMessageSize = 1280
+ bs := make([]byte, maxMessageSize)
+ n, err := read(bs)
+ if err != nil {
+ return nil, err
+ }
+ res := &stun.Message{Raw: bs[:n]}
+ if err := res.Decode(); err != nil {
+ return nil, err
+ }
+ return res, nil
+}
+
+func localInterfaces(vnet *vnet.Net, interfaceFilter func(string) bool, networkTypes []NetworkType) ([]net.IP, error) { //nolint:gocognit
+ ips := []net.IP{}
+ ifaces, err := vnet.Interfaces()
+ if err != nil {
+ return ips, err
+ }
+
+ var IPv4Requested, IPv6Requested bool
+ for _, typ := range networkTypes {
+ if typ.IsIPv4() {
+ IPv4Requested = true
+ }
+
+ if typ.IsIPv6() {
+ IPv6Requested = true
+ }
+ }
+
+ for _, iface := range ifaces {
+ if iface.Flags&net.FlagUp == 0 {
+ continue // interface down
+ }
+ if iface.Flags&net.FlagLoopback != 0 {
+ continue // loopback interface
+ }
+
+ if interfaceFilter != nil && !interfaceFilter(iface.Name) {
+ continue
+ }
+
+ addrs, err := iface.Addrs()
+ if err != nil {
+ continue
+ }
+
+ for _, addr := range addrs {
+ var ip net.IP
+ switch addr := addr.(type) {
+ case *net.IPNet:
+ ip = addr.IP
+ case *net.IPAddr:
+ ip = addr.IP
+ }
+ if ip == nil || ip.IsLoopback() {
+ continue
+ }
+
+ if ipv4 := ip.To4(); ipv4 == nil {
+ if !IPv6Requested {
+ continue
+ } else if !isSupportedIPv6(ip) {
+ continue
+ }
+ } else if !IPv4Requested {
+ continue
+ }
+
+ ips = append(ips, ip)
+ }
+ }
+ return ips, nil
+}
+
+func listenUDPInPortRange(vnet *vnet.Net, log logging.LeveledLogger, portMax, portMin int, network string, laddr *net.UDPAddr) (vnet.UDPPacketConn, error) {
+ if (laddr.Port != 0) || ((portMin == 0) && (portMax == 0)) {
+ return vnet.ListenUDP(network, laddr)
+ }
+ var i, j int
+ i = portMin
+ if i == 0 {
+ i = 1
+ }
+ j = portMax
+ if j == 0 {
+ j = 0xFFFF
+ }
+ if i > j {
+ return nil, ErrPort
+ }
+
+ portStart := globalMathRandomGenerator.Intn(j-i+1) + i
+ portCurrent := portStart
+ for {
+ laddr = &net.UDPAddr{IP: laddr.IP, Port: portCurrent}
+ c, e := vnet.ListenUDP(network, laddr)
+ if e == nil {
+ return c, e
+ }
+ log.Debugf("failed to listen %s: %v", laddr.String(), e)
+ portCurrent++
+ if portCurrent > j {
+ portCurrent = i
+ }
+ if portCurrent == portStart {
+ break
+ }
+ }
+ return nil, ErrPort
+}