diff options
author | kali kaneko (leap communications) <kali@leap.se> | 2021-11-29 01:46:27 +0100 |
---|---|---|
committer | kali kaneko (leap communications) <kali@leap.se> | 2021-11-29 18:14:16 +0100 |
commit | 18f52af5be3a9a0c73811706108f790d65ee9c67 (patch) | |
tree | e13cbacb47d56919caa9c44a2b45dec1497a7860 /vendor/github.com/pion/ice | |
parent | ebcef0d57b6ecb5a40c6579f6be07182dd3033ba (diff) |
[pkg] update vendor
Diffstat (limited to 'vendor/github.com/pion/ice')
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: ¶ms, + + 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: ¶ms, + + 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 +} |