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/turn | |
parent | ebcef0d57b6ecb5a40c6579f6be07182dd3033ba (diff) |
[pkg] update vendor
Diffstat (limited to 'vendor/github.com/pion/turn')
52 files changed, 4917 insertions, 0 deletions
diff --git a/vendor/github.com/pion/turn/v2/.gitignore b/vendor/github.com/pion/turn/v2/.gitignore new file mode 100644 index 0000000..98c12d9 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/.gitignore @@ -0,0 +1,9 @@ +*.sw[poe] +examples/turn-client/tcp/tcp +examples/turn-client/udp/udp +examples/turn-server/add-software-attribute/add-software-attribute +examples/turn-server/log/log +examples/turn-server/simple/simple +examples/turn-server/tcp/tcp +examples/lt-cred-generator/lt-cred-generator +examples/turn-server/lt-cred/lt-cred diff --git a/vendor/github.com/pion/turn/v2/.golangci.yml b/vendor/github.com/pion/turn/v2/.golangci.yml new file mode 100644 index 0000000..8e4185a --- /dev/null +++ b/vendor/github.com/pion/turn/v2/.golangci.yml @@ -0,0 +1,88 @@ +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-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/turn/v2/.goreleaser.yml b/vendor/github.com/pion/turn/v2/.goreleaser.yml new file mode 100644 index 0000000..c7f2efd --- /dev/null +++ b/vendor/github.com/pion/turn/v2/.goreleaser.yml @@ -0,0 +1,109 @@ +before: + hooks: + - go mod tidy + +archives: +- replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ .Tag }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +builds: + - binary: turn-client-tcp + id: turn-client-tcp + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-client/tcp + + - binary: turn-client-udp + id: turn-client-udp + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-client/udp + + - binary: turn-server-add-software-attribute + id: turn-server-add-software-attribute + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/add-software-attribute + + - binary: turn-server-log + id: turn-server-log + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/log + + - binary: turn-server-simple + id: turn-server-simple + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/simple/ + + - binary: turn-server-tcp + id: turn-server-tcp + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/tcp/ diff --git a/vendor/github.com/pion/turn/v2/DESIGN.md b/vendor/github.com/pion/turn/v2/DESIGN.md new file mode 100644 index 0000000..2c52466 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/DESIGN.md @@ -0,0 +1,31 @@ +# Why Pion TURN +TURN servers aren't exactly a hot technology, they are usually an after thought when building something. Most of the time +beginners build an interesting WebRTC application, but at the very end realize they need a TURN server. It is really frustrating when you +want to share your cool new project, only to realize you have to run another service. + +Then you find yourself building from source, fighting with config files and making changes you don't fully understand. Pion TURN was born +hoping to solve these frustrations. These are the guiding principals/features that define pion-turn. + +## Easy setup +simple-turn is a statically built TURN server, configured by environment variables. The entire install setup is 5 commands, on any platform! +The goal is that anyone should be able to run a TURN server on any platform. + +## Integration first +pion-turn makes no assumptions about how you authenticate users, how you log, or even your topology! Instead of running a dedicated TURN server you +can inherit from github.com/pion/turn and set whatever logger you want. + +## Embeddable +You can add this to an existing service. This means all your config files stay homogeneous instead of having the mismatch that makes it harder to manage your services. +For small setups it is usually an overkill to deploy dedicated TURN servers, this makes it easier to solve the problems you care about. + +## Safe +Golang provides a great foundation to build safe network services. Especially when running a networked service that is highly concurrent bugs can be devastating. + +## Readable +All network interaction is commented with a link to the spec. This makes learning and debugging easier, the TURN server was written to also serve as a guide for others. + +## Tested +Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. + +## Shared libraries +Every pion product is built using shared libraries, allowing others to build things using existing tested STUN and TURN tools. diff --git a/vendor/github.com/pion/turn/v2/LICENSE.md b/vendor/github.com/pion/turn/v2/LICENSE.md new file mode 100644 index 0000000..5cc9cbd --- /dev/null +++ b/vendor/github.com/pion/turn/v2/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2018 Pion LLC + +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/turn/v2/README.md b/vendor/github.com/pion/turn/v2/README.md new file mode 100644 index 0000000..372b286 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/README.md @@ -0,0 +1,96 @@ +<h1 align="center"> + <a href="https://pion.ly"><img src="./.github/gopher-pion.png" alt="Pion TURN" height="250px"></a> + <br> + Pion TURN + <br> +</h1> +<h4 align="center">A toolkit for building TURN clients and servers in Go</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-turn-gray.svg?longCache=true&colorB=brightgreen" alt="Pion TURN"></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> + <a href="https://github.com/pion/awesome-pion" alt="Awesome Pion"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a> + <br> + <a href="https://travis-ci.org/pion/turn"><img src="https://travis-ci.org/pion/turn.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/turn/v2"><img src="https://godoc.org/github.com/pion/turn?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/turn"><img src="https://codecov.io/gh/pion/turn/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/turn"><img src="https://goreportcard.com/badge/github.com/pion/turn" alt="Go Report Card"></a> + <a href="https://www.codacy.com/app/pion/turn"><img src="https://api.codacy.com/project/badge/Grade/d53ec6c70576476cb16c140c2964afde" alt="Codacy Badge"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +Pion TURN is a Go toolkit for building TURN servers and clients. We wrote it to solve problems we had when building RTC projects. + +* **Deployable** - Use modern tooling of the Go ecosystem. Stop generating config files. +* **Embeddable** - Include `pion/turn` in your existing applications. No need to manage another service. +* **Extendable** - TURN as an API so you can easily integrate with your existing monitoring and metrics. +* **Maintainable** - `pion/turn` is simple and well documented. Designed for learning and easy debugging. +* **Portable** - Quickly deploy to multiple architectures/platforms just by setting an environment variable. +* **Safe** - Stability and safety is important for network services. Go provides everything we need. +* **Scalable** - Create allocations and mutate state at runtime. Designed to make scaling easy. + +# Using +`pion/turn` is an API for building STUN/TURN clients and servers, not a binary you deploy then configure. It may require copying our examples and +making minor modifications to fit your need, no knowledge of Go is required however. You may be able to download the pre-made binaries of our examples +if you wish to get started quickly. + +The advantage of this is that you don't need to deal with complicated config files, or custom APIs to modify the state of Pion TURN. +After you instantiate an instance of a Pion TURN server or client you interact with it like any library. The quickest way to get started is to look at the +[examples](examples) or [GoDoc](https://godoc.org/github.com/pion/turn) + +# Examples +We try to cover most common use cases in [examples](examples). If more examples could be helpful please file an issue, we are always looking +to expand and improve `pion/turn` to make it easier for developers. + +To build any example you just need to run `go build` in the directory of the example you care about. +It is also very easy to [cross compile](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5) Go programs. + +You can also see `pion/turn` usage in [pion/ice](https://github.com/pion/ice) + +# [FAQ](https://github.com/pion/webrtc/wiki/FAQ) + +### RFCs +#### Implemented +* [RFC 5389: Session Traversal Utilities for NAT (STUN)](https://tools.ietf.org/html/rfc5389) +* [RFC 5766: Traversal Using Relays around NAT (TURN)](https://tools.ietf.org/html/rfc5766) + +#### Planned +* [RFC 6062: Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations](https://tools.ietf.org/html/rfc6062) +* [RFC 6156: Traversal Using Relays around NAT (TURN) Extension for IPv6](https://tools.ietf.org/html/rfc6156) + +### Community +Pion has an active community on the [Golang Slack](https://pion.ly/slack). Sign up and join the **#pion** channel for discussions and support. + +We are always looking to support **your projects**. Please reach out if you have something to build! + +### Contributing +Check out the [CONTRIBUTING.md](CONTRIBUTING.md) to join the group of amazing people making this project possible: + +* [Michiel De Backker](https://github.com/backkem) - *Documentation* +* [Ingmar Wittkau](https://github.com/iwittkau) - *STUN client* +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [jose nazario](https://github.com/paralax) - *Documentation* +* [Mészáros Mihály](https://github.com/misi) - *Documentation* +* [Mike Santry](https://github.com/santrym) - *Mascot* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [winds2016](https://github.com/winds2016) - *Windows platform testing* +* [songjiayang](https://github.com/songjiayang) - *SongJiaYang* +* [Yutaka Takeda](https://github.com/enobufs) - *vnet* +* [namreg](https://github.com/namreg) - *Igor German* +* [Aleksandr Razumov](https://github.com/ernado) - *protocol* +* [Robert Eperjesi](https://github.com/epes) +* [Lukas Rezek](https://github.com/lrezek) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Aaron France](https://github.com/AeroNotix) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Tom Clift](https://github.com/tclift) +* [lllf](https://github.com/LittleLightLittleFire) +* nindolabs (Marouane) +* [Onwuka Gideon](https://github.com/dongido001) +* [Herman Banken](https://github.com/hermanbanken) +* [Jannis Mattheis](https://github.com/jmattheis) + +### License +MIT License - see [LICENSE.md](LICENSE.md) for full text + + diff --git a/vendor/github.com/pion/turn/v2/client.go b/vendor/github.com/pion/turn/v2/client.go new file mode 100644 index 0000000..b04e7d1 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/client.go @@ -0,0 +1,569 @@ +package turn + +import ( + b64 "encoding/base64" + "fmt" + "math" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/transport/vnet" + "github.com/pion/turn/v2/internal/client" + "github.com/pion/turn/v2/internal/proto" +) + +const ( + defaultRTO = 200 * time.Millisecond + maxRtxCount = 7 // total 7 requests (Rc) + maxDataBufferSize = math.MaxUint16 // message size limit for Chromium +) + +// interval [msec] +// 0: 0 ms +500 +// 1: 500 ms +1000 +// 2: 1500 ms +2000 +// 3: 3500 ms +4000 +// 4: 7500 ms +8000 +// 5: 15500 ms +16000 +// 6: 31500 ms +32000 +// -: 63500 ms failed + +// ClientConfig is a bag of config parameters for Client. +type ClientConfig struct { + STUNServerAddr string // STUN server address (e.g. "stun.abc.com:3478") + TURNServerAddr string // TURN server addrees (e.g. "turn.abc.com:3478") + Username string + Password string + Realm string + Software string + RTO time.Duration + Conn net.PacketConn // Listening socket (net.PacketConn) + LoggerFactory logging.LoggerFactory + Net *vnet.Net +} + +// Client is a STUN server client +type Client struct { + conn net.PacketConn // read-only + stunServ net.Addr // read-only + turnServ net.Addr // read-only + stunServStr string // read-only, used for dmuxing + turnServStr string // read-only, used for dmuxing + username stun.Username // read-only + password string // read-only + realm stun.Realm // read-only + integrity stun.MessageIntegrity // read-only + software stun.Software // read-only + trMap *client.TransactionMap // thread-safe + rto time.Duration // read-only + relayedConn *client.UDPConn // protected by mutex *** + allocTryLock client.TryLock // thread-safe + listenTryLock client.TryLock // thread-safe + net *vnet.Net // read-only + mutex sync.RWMutex // thread-safe + mutexTrMap sync.Mutex // thread-safe + log logging.LeveledLogger // read-only +} + +// NewClient returns a new Client instance. listeningAddress is the address and port to listen on, default "0.0.0.0:0" +func NewClient(config *ClientConfig) (*Client, error) { + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + log := loggerFactory.NewLogger("turnc") + + if config.Conn == nil { + return nil, errNilConn + } + + if config.Net == nil { + config.Net = vnet.NewNet(nil) // defaults to native operation + } else if config.Net.IsVirtual() { + log.Warn("vnet is enabled") + } + + var stunServ, turnServ net.Addr + var stunServStr, turnServStr string + var err error + if len(config.STUNServerAddr) > 0 { + log.Debugf("resolving %s", config.STUNServerAddr) + stunServ, err = config.Net.ResolveUDPAddr("udp4", config.STUNServerAddr) + if err != nil { + return nil, err + } + stunServStr = stunServ.String() + log.Debugf("stunServ: %s", stunServStr) + } + if len(config.TURNServerAddr) > 0 { + log.Debugf("resolving %s", config.TURNServerAddr) + turnServ, err = config.Net.ResolveUDPAddr("udp4", config.TURNServerAddr) + if err != nil { + return nil, err + } + turnServStr = turnServ.String() + log.Debugf("turnServ: %s", turnServStr) + } + + rto := defaultRTO + if config.RTO > 0 { + rto = config.RTO + } + + c := &Client{ + conn: config.Conn, + stunServ: stunServ, + turnServ: turnServ, + stunServStr: stunServStr, + turnServStr: turnServStr, + username: stun.NewUsername(config.Username), + password: config.Password, + realm: stun.NewRealm(config.Realm), + software: stun.NewSoftware(config.Software), + net: config.Net, + trMap: client.NewTransactionMap(), + rto: rto, + log: log, + } + + return c, nil +} + +// TURNServerAddr return the TURN server address +func (c *Client) TURNServerAddr() net.Addr { + return c.turnServ +} + +// STUNServerAddr return the STUN server address +func (c *Client) STUNServerAddr() net.Addr { + return c.stunServ +} + +// Username returns username +func (c *Client) Username() stun.Username { + return c.username +} + +// Realm return realm +func (c *Client) Realm() stun.Realm { + return c.realm +} + +// WriteTo sends data to the specified destination using the base socket. +func (c *Client) WriteTo(data []byte, to net.Addr) (int, error) { + return c.conn.WriteTo(data, to) +} + +// Listen will have this client start listening on the conn provided via the config. +// This is optional. If not used, you will need to call HandleInbound method +// to supply incoming data, instead. +func (c *Client) Listen() error { + if err := c.listenTryLock.Lock(); err != nil { + return fmt.Errorf("%w: %s", errAlreadyListening, err.Error()) + } + + go func() { + buf := make([]byte, maxDataBufferSize) + for { + n, from, err := c.conn.ReadFrom(buf) + if err != nil { + c.log.Debugf("exiting read loop: %s", err.Error()) + break + } + + _, err = c.HandleInbound(buf[:n], from) + if err != nil { + c.log.Debugf("exiting read loop: %s", err.Error()) + break + } + } + + c.listenTryLock.Unlock() + }() + + return nil +} + +// Close closes this client +func (c *Client) Close() { + c.mutexTrMap.Lock() + defer c.mutexTrMap.Unlock() + + c.trMap.CloseAndDeleteAll() +} + +// TransactionID & Base64: https://play.golang.org/p/EEgmJDI971P + +// SendBindingRequestTo sends a new STUN request to the given transport address +func (c *Client) SendBindingRequestTo(to net.Addr) (net.Addr, error) { + attrs := []stun.Setter{stun.TransactionID, stun.BindingRequest} + if len(c.software) > 0 { + attrs = append(attrs, c.software) + } + + msg, err := stun.Build(attrs...) + if err != nil { + return nil, err + } + trRes, err := c.PerformTransaction(msg, to, false) + if err != nil { + return nil, err + } + + var reflAddr stun.XORMappedAddress + if err := reflAddr.GetFrom(trRes.Msg); err != nil { + return nil, err + } + + return &net.UDPAddr{ + IP: reflAddr.IP, + Port: reflAddr.Port, + }, nil +} + +// SendBindingRequest sends a new STUN request to the STUN server +func (c *Client) SendBindingRequest() (net.Addr, error) { + if c.stunServ == nil { + return nil, errSTUNServerAddressNotSet + } + return c.SendBindingRequestTo(c.stunServ) +} + +// Allocate sends a TURN allocation request to the given transport address +func (c *Client) Allocate() (net.PacketConn, error) { + if err := c.allocTryLock.Lock(); err != nil { + return nil, fmt.Errorf("%w: %s", errOneAllocateOnly, err.Error()) + } + defer c.allocTryLock.Unlock() + + relayedConn := c.relayedUDPConn() + if relayedConn != nil { + return nil, fmt.Errorf("%w: %s", errAlreadyAllocated, relayedConn.LocalAddr().String()) + } + + msg, err := stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodAllocate, stun.ClassRequest), + proto.RequestedTransport{Protocol: proto.ProtoUDP}, + stun.Fingerprint, + ) + if err != nil { + return nil, err + } + + trRes, err := c.PerformTransaction(msg, c.turnServ, false) + if err != nil { + return nil, err + } + + res := trRes.Msg + + // Anonymous allocate failed, trying to authenticate. + var nonce stun.Nonce + if err = nonce.GetFrom(res); err != nil { + return nil, err + } + if err = c.realm.GetFrom(res); err != nil { + return nil, err + } + c.realm = append([]byte(nil), c.realm...) + c.integrity = stun.NewLongTermIntegrity( + c.username.String(), c.realm.String(), c.password, + ) + // Trying to authorize. + msg, err = stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodAllocate, stun.ClassRequest), + proto.RequestedTransport{Protocol: proto.ProtoUDP}, + &c.username, + &c.realm, + &nonce, + &c.integrity, + stun.Fingerprint, + ) + if err != nil { + return nil, err + } + + trRes, err = c.PerformTransaction(msg, c.turnServ, false) + if err != nil { + return nil, err + } + res = trRes.Msg + + if res.Type.Class == stun.ClassErrorResponse { + var code stun.ErrorCodeAttribute + if err = code.GetFrom(res); err == nil { + return nil, fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113 + } + return nil, fmt.Errorf("%s", res.Type) //nolint:goerr113 + } + + // Getting relayed addresses from response. + var relayed proto.RelayedAddress + if err := relayed.GetFrom(res); err != nil { + return nil, err + } + relayedAddr := &net.UDPAddr{ + IP: relayed.IP, + Port: relayed.Port, + } + + // Getting lifetime from response + var lifetime proto.Lifetime + if err := lifetime.GetFrom(res); err != nil { + return nil, err + } + + relayedConn = client.NewUDPConn(&client.UDPConnConfig{ + Observer: c, + RelayedAddr: relayedAddr, + Integrity: c.integrity, + Nonce: nonce, + Lifetime: lifetime.Duration, + Log: c.log, + }) + + c.setRelayedUDPConn(relayedConn) + + return relayedConn, nil +} + +// PerformTransaction performs STUN transaction +func (c *Client) PerformTransaction(msg *stun.Message, to net.Addr, ignoreResult bool) (client.TransactionResult, + error) { + trKey := b64.StdEncoding.EncodeToString(msg.TransactionID[:]) + + raw := make([]byte, len(msg.Raw)) + copy(raw, msg.Raw) + + tr := client.NewTransaction(&client.TransactionConfig{ + Key: trKey, + Raw: raw, + To: to, + Interval: c.rto, + IgnoreResult: ignoreResult, + }) + + c.trMap.Insert(trKey, tr) + + c.log.Tracef("start %s transaction %s to %s", msg.Type, trKey, tr.To.String()) + _, err := c.conn.WriteTo(tr.Raw, to) + if err != nil { + return client.TransactionResult{}, err + } + + tr.StartRtxTimer(c.onRtxTimeout) + + // If dontWait is true, get the transaction going and return immediately + if ignoreResult { + return client.TransactionResult{}, nil + } + + res := tr.WaitForResult() + if res.Err != nil { + return res, res.Err + } + return res, nil +} + +// OnDeallocated is called when deallocation of relay address has been complete. +// (Called by UDPConn) +func (c *Client) OnDeallocated(relayedAddr net.Addr) { + c.setRelayedUDPConn(nil) +} + +// HandleInbound handles data received. +// This method handles incoming packet demultiplex it by the source address +// and the types of the message. +// This return a booleen (handled or not) and if there was an error. +// Caller should check if the packet was handled by this client or not. +// If not handled, it is assumed that the packet is application data. +// If an error is returned, the caller should discard the packet regardless. +func (c *Client) HandleInbound(data []byte, from net.Addr) (bool, error) { + // +-------------------+-------------------------------+ + // | Return Values | | + // +-------------------+ Meaning / Action | + // | handled | error | | + // |=========+=========+===============================+ + // | false | nil | Handle the packet as app data | + // |---------+---------+-------------------------------+ + // | true | nil | Nothing to do | + // |---------+---------+-------------------------------+ + // | false | error | (shouldn't happen) | + // |---------+---------+-------------------------------+ + // | true | error | Error occurred while handling | + // +---------+---------+-------------------------------+ + // Possible causes of the error: + // - Malformed packet (parse error) + // - STUN message was a request + // - Non-STUN message from the STUN server + + switch { + case stun.IsMessage(data): + return true, c.handleSTUNMessage(data, from) + case proto.IsChannelData(data): + return true, c.handleChannelData(data) + case len(c.stunServStr) != 0 && from.String() == c.stunServStr: + // received from STUN server but it is not a STUN message + return true, errNonSTUNMessage + default: + // assume, this is an application data + c.log.Tracef("non-STUN/TURN packect, unhandled") + } + + return false, nil +} + +func (c *Client) handleSTUNMessage(data []byte, from net.Addr) error { + raw := make([]byte, len(data)) + copy(raw, data) + + msg := &stun.Message{Raw: raw} + if err := msg.Decode(); err != nil { + return fmt.Errorf("%w: %s", errFailedToDecodeSTUN, err.Error()) + } + + if msg.Type.Class == stun.ClassRequest { + return fmt.Errorf("%w : %s", errUnexpectedSTUNRequestMessage, msg.String()) + } + + if msg.Type.Class == stun.ClassIndication { + if msg.Type.Method == stun.MethodData { + var peerAddr proto.PeerAddress + if err := peerAddr.GetFrom(msg); err != nil { + return err + } + from = &net.UDPAddr{ + IP: peerAddr.IP, + Port: peerAddr.Port, + } + + var data proto.Data + if err := data.GetFrom(msg); err != nil { + return err + } + + c.log.Debugf("data indication received from %s", from.String()) + + relayedConn := c.relayedUDPConn() + if relayedConn == nil { + c.log.Debug("no relayed conn allocated") + return nil // silently discard + } + + relayedConn.HandleInbound(data, from) + } + return nil + } + + // This is a STUN response message (transactional) + // The type is either: + // - stun.ClassSuccessResponse + // - stun.ClassErrorResponse + + trKey := b64.StdEncoding.EncodeToString(msg.TransactionID[:]) + + c.mutexTrMap.Lock() + tr, ok := c.trMap.Find(trKey) + if !ok { + c.mutexTrMap.Unlock() + // silently discard + c.log.Debugf("no transaction for %s", msg.String()) + return nil + } + + // End the transaction + tr.StopRtxTimer() + c.trMap.Delete(trKey) + c.mutexTrMap.Unlock() + + if !tr.WriteResult(client.TransactionResult{ + Msg: msg, + From: from, + Retries: tr.Retries(), + }) { + c.log.Debugf("no listener for %s", msg.String()) + } + + return nil +} + +func (c *Client) handleChannelData(data []byte) error { + chData := &proto.ChannelData{ + Raw: make([]byte, len(data)), + } + copy(chData.Raw, data) + if err := chData.Decode(); err != nil { + return err + } + + relayedConn := c.relayedUDPConn() + if relayedConn == nil { + c.log.Debug("no relayed conn allocated") + return nil // silently discard + } + + addr, ok := relayedConn.FindAddrByChannelNumber(uint16(chData.Number)) + if !ok { + return fmt.Errorf("%w: %d", errChannelBindNotFound, int(chData.Number)) + } + + c.log.Tracef("channel data received from %s (ch=%d)", addr.String(), int(chData.Number)) + + relayedConn.HandleInbound(chData.Data, addr) + return nil +} + +func (c *Client) onRtxTimeout(trKey string, nRtx int) { + c.mutexTrMap.Lock() + defer c.mutexTrMap.Unlock() + + tr, ok := c.trMap.Find(trKey) + if !ok { + return // already gone + } + + if nRtx == maxRtxCount { + // all retransmisstions failed + c.trMap.Delete(trKey) + if !tr.WriteResult(client.TransactionResult{ + Err: fmt.Errorf("%w %s", errAllRetransmissionsFailed, trKey), + }) { + c.log.Debug("no listener for transaction") + } + return + } + + c.log.Tracef("retransmitting transaction %s to %s (nRtx=%d)", + trKey, tr.To.String(), nRtx) + _, err := c.conn.WriteTo(tr.Raw, tr.To) + if err != nil { + c.trMap.Delete(trKey) + if !tr.WriteResult(client.TransactionResult{ + Err: fmt.Errorf("%w %s", errFailedToRetransmitTransaction, trKey), + }) { + c.log.Debug("no listener for transaction") + } + return + } + tr.StartRtxTimer(c.onRtxTimeout) +} + +func (c *Client) setRelayedUDPConn(conn *client.UDPConn) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.relayedConn = conn +} + +func (c *Client) relayedUDPConn() *client.UDPConn { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c.relayedConn +} diff --git a/vendor/github.com/pion/turn/v2/codecov.yml b/vendor/github.com/pion/turn/v2/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/turn/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/turn/v2/errors.go b/vendor/github.com/pion/turn/v2/errors.go new file mode 100644 index 0000000..12d5e0e --- /dev/null +++ b/vendor/github.com/pion/turn/v2/errors.go @@ -0,0 +1,28 @@ +package turn + +import "errors" + +var ( + errRelayAddressInvalid = errors.New("turn: RelayAddress must be valid IP to use RelayAddressGeneratorStatic") + errNoAvailableConns = errors.New("turn: PacketConnConfigs and ConnConfigs are empty, unable to proceed") + errConnUnset = errors.New("turn: PacketConnConfig must have a non-nil Conn") + errListenerUnset = errors.New("turn: ListenerConfig must have a non-nil Listener") + errListeningAddressInvalid = errors.New("turn: RelayAddressGenerator has invalid ListeningAddress") + errRelayAddressGeneratorUnset = errors.New("turn: RelayAddressGenerator in RelayConfig is unset") + errMaxRetriesExceeded = errors.New("turn: max retries exceeded") + errMaxPortNotZero = errors.New("turn: MaxPort must be not 0") + errMinPortNotZero = errors.New("turn: MaxPort must be not 0") + errNilConn = errors.New("turn: conn cannot not be nil") + errTODO = errors.New("turn: TODO") + errAlreadyListening = errors.New("turn: already listening") + errFailedToClose = errors.New("turn: Server failed to close") + errFailedToRetransmitTransaction = errors.New("turn: failed to retransmit transaction") + errAllRetransmissionsFailed = errors.New("all retransmissions failed for") + errChannelBindNotFound = errors.New("no binding found for channel") + errSTUNServerAddressNotSet = errors.New("STUN server address is not set for the client") + errOneAllocateOnly = errors.New("only one Allocate() caller is allowed") + errAlreadyAllocated = errors.New("already allocated") + errNonSTUNMessage = errors.New("non-STUN message from STUN server") + errFailedToDecodeSTUN = errors.New("failed to decode STUN message") + errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message") +) diff --git a/vendor/github.com/pion/turn/v2/go.mod b/vendor/github.com/pion/turn/v2/go.mod new file mode 100644 index 0000000..de599c3 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/go.mod @@ -0,0 +1,11 @@ +module github.com/pion/turn/v2 + +go 1.13 + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/randutil v0.1.0 + github.com/pion/stun v0.3.5 + github.com/pion/transport v0.10.1 + github.com/stretchr/testify v1.6.1 +) diff --git a/vendor/github.com/pion/turn/v2/go.sum b/vendor/github.com/pion/turn/v2/go.sum new file mode 100644 index 0000000..7756c37 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/go.sum @@ -0,0 +1,28 @@ +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/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/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.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +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.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/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.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/turn/v2/internal/allocation/allocation.go b/vendor/github.com/pion/turn/v2/internal/allocation/allocation.go new file mode 100644 index 0000000..d1cb8c6 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/allocation.go @@ -0,0 +1,259 @@ +// Package allocation contains all CRUD operations for allocations +package allocation + +import ( + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/ipnet" + "github.com/pion/turn/v2/internal/proto" +) + +// Allocation is tied to a FiveTuple and relays traffic +// use CreateAllocation and GetAllocation to operate +type Allocation struct { + RelayAddr net.Addr + Protocol Protocol + TurnSocket net.PacketConn + RelaySocket net.PacketConn + fiveTuple *FiveTuple + permissionsLock sync.RWMutex + permissions map[string]*Permission + channelBindingsLock sync.RWMutex + channelBindings []*ChannelBind + lifetimeTimer *time.Timer + closed chan interface{} + log logging.LeveledLogger +} + +func addr2IPFingerprint(addr net.Addr) string { + switch a := addr.(type) { + case *net.UDPAddr: + return a.IP.String() + case *net.TCPAddr: // Do we really need this case? + return a.IP.String() + } + return "" // shoud never happen +} + +// NewAllocation creates a new instance of NewAllocation. +func NewAllocation(turnSocket net.PacketConn, fiveTuple *FiveTuple, log logging.LeveledLogger) *Allocation { + return &Allocation{ + TurnSocket: turnSocket, + fiveTuple: fiveTuple, + permissions: make(map[string]*Permission, 64), + closed: make(chan interface{}), + log: log, + } +} + +// GetPermission gets the Permission from the allocation +func (a *Allocation) GetPermission(addr net.Addr) *Permission { + a.permissionsLock.RLock() + defer a.permissionsLock.RUnlock() + + return a.permissions[addr2IPFingerprint(addr)] +} + +// AddPermission adds a new permission to the allocation +func (a *Allocation) AddPermission(p *Permission) { + fingerprint := addr2IPFingerprint(p.Addr) + + a.permissionsLock.RLock() + existedPermission, ok := a.permissions[fingerprint] + a.permissionsLock.RUnlock() + + if ok { + existedPermission.refresh(permissionTimeout) + return + } + + p.allocation = a + a.permissionsLock.Lock() + a.permissions[fingerprint] = p + a.permissionsLock.Unlock() + + p.start(permissionTimeout) +} + +// RemovePermission removes the net.Addr's fingerprint from the allocation's permissions +func (a *Allocation) RemovePermission(addr net.Addr) { + a.permissionsLock.Lock() + defer a.permissionsLock.Unlock() + delete(a.permissions, addr2IPFingerprint(addr)) +} + +// AddChannelBind adds a new ChannelBind to the allocation, it also updates the +// permissions needed for this ChannelBind +func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime time.Duration) error { + // Check that this channel id isn't bound to another transport address, and + // that this transport address isn't bound to another channel number. + channelByNumber := a.GetChannelByNumber(c.Number) + + if channelByNumber != a.GetChannelByAddr(c.Peer) { + return errSameChannelDifferentPeer + } + + // Add or refresh this channel. + if channelByNumber == nil { + a.channelBindingsLock.Lock() + defer a.channelBindingsLock.Unlock() + + c.allocation = a + a.channelBindings = append(a.channelBindings, c) + c.start(lifetime) + + // Channel binds also refresh permissions. + a.AddPermission(NewPermission(c.Peer, a.log)) + } else { + channelByNumber.refresh(lifetime) + + // Channel binds also refresh permissions. + a.AddPermission(NewPermission(channelByNumber.Peer, a.log)) + } + + return nil +} + +// RemoveChannelBind removes the ChannelBind from this allocation by id +func (a *Allocation) RemoveChannelBind(number proto.ChannelNumber) bool { + a.channelBindingsLock.Lock() + defer a.channelBindingsLock.Unlock() + + for i := len(a.channelBindings) - 1; i >= 0; i-- { + if a.channelBindings[i].Number == number { + a.channelBindings = append(a.channelBindings[:i], a.channelBindings[i+1:]...) + return true + } + } + + return false +} + +// GetChannelByNumber gets the ChannelBind from this allocation by id +func (a *Allocation) GetChannelByNumber(number proto.ChannelNumber) *ChannelBind { + a.channelBindingsLock.RLock() + defer a.channelBindingsLock.RUnlock() + for _, cb := range a.channelBindings { + if cb.Number == number { + return cb + } + } + return nil +} + +// GetChannelByAddr gets the ChannelBind from this allocation by net.Addr +func (a *Allocation) GetChannelByAddr(addr net.Addr) *ChannelBind { + a.channelBindingsLock.RLock() + defer a.channelBindingsLock.RUnlock() + for _, cb := range a.channelBindings { + if ipnet.AddrEqual(cb.Peer, addr) { + return cb + } + } + return nil +} + +// Refresh updates the allocations lifetime +func (a *Allocation) Refresh(lifetime time.Duration) { + if !a.lifetimeTimer.Reset(lifetime) { + a.log.Errorf("Failed to reset allocation timer for %v", a.fiveTuple) + } +} + +// Close closes the allocation +func (a *Allocation) Close() error { + select { + case <-a.closed: + return nil + default: + } + close(a.closed) + + a.lifetimeTimer.Stop() + + a.permissionsLock.RLock() + for _, p := range a.permissions { + p.lifetimeTimer.Stop() + } + a.permissionsLock.RUnlock() + + a.channelBindingsLock.RLock() + for _, c := range a.channelBindings { + c.lifetimeTimer.Stop() + } + a.channelBindingsLock.RUnlock() + + return a.RelaySocket.Close() +} + +// https://tools.ietf.org/html/rfc5766#section-10.3 +// When the server receives a UDP datagram at a currently allocated +// relayed transport address, the server looks up the allocation +// associated with the relayed transport address. The server then +// checks to see whether the set of permissions for the allocation allow +// the relaying of the UDP datagram as described in Section 8. +// +// If relaying is permitted, then the server checks if there is a +// channel bound to the peer that sent the UDP datagram (see +// Section 11). If a channel is bound, then processing proceeds as +// described in Section 11.7. +// +// If relaying is permitted but no channel is bound to the peer, then +// the server forms and sends a Data indication. The Data indication +// MUST contain both an XOR-PEER-ADDRESS and a DATA attribute. The DATA +// attribute is set to the value of the 'data octets' field from the +// datagram, and the XOR-PEER-ADDRESS attribute is set to the source +// transport address of the received UDP datagram. The Data indication +// is then sent on the 5-tuple associated with the allocation. + +const rtpMTU = 1500 + +func (a *Allocation) packetHandler(m *Manager) { + buffer := make([]byte, rtpMTU) + + for { + n, srcAddr, err := a.RelaySocket.ReadFrom(buffer) + if err != nil { + m.DeleteAllocation(a.fiveTuple) + return + } + + a.log.Debugf("relay socket %s received %d bytes from %s", + a.RelaySocket.LocalAddr().String(), + n, + srcAddr.String()) + + if channel := a.GetChannelByAddr(srcAddr); channel != nil { + channelData := &proto.ChannelData{ + Data: buffer[:n], + Number: channel.Number, + } + channelData.Encode() + + if _, err = a.TurnSocket.WriteTo(channelData.Raw, a.fiveTuple.SrcAddr); err != nil { + a.log.Errorf("Failed to send ChannelData from allocation %v %v", srcAddr, err) + } + } else if p := a.GetPermission(srcAddr); p != nil { + udpAddr := srcAddr.(*net.UDPAddr) + peerAddressAttr := proto.PeerAddress{IP: udpAddr.IP, Port: udpAddr.Port} + dataAttr := proto.Data(buffer[:n]) + + msg, err := stun.Build(stun.TransactionID, stun.NewType(stun.MethodData, stun.ClassIndication), peerAddressAttr, dataAttr) + if err != nil { + a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) + } + a.log.Debugf("relaying message from %s to client at %s", + srcAddr.String(), + a.fiveTuple.SrcAddr.String()) + if _, err = a.TurnSocket.WriteTo(msg.Raw, a.fiveTuple.SrcAddr); err != nil { + a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) + } + } else { + a.log.Infof("No Permission or Channel exists for %v on allocation %v", srcAddr, a.RelayAddr.String()) + } + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/allocation_manager.go b/vendor/github.com/pion/turn/v2/internal/allocation/allocation_manager.go new file mode 100644 index 0000000..9a1271a --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/allocation_manager.go @@ -0,0 +1,186 @@ +package allocation + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" +) + +// ManagerConfig a bag of config params for Manager. +type ManagerConfig struct { + LeveledLogger logging.LeveledLogger + AllocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error) + AllocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error) +} + +type reservation struct { + token string + port int +} + +// Manager is used to hold active allocations +type Manager struct { + lock sync.RWMutex + log logging.LeveledLogger + + allocations map[string]*Allocation + reservations []*reservation + + allocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error) + allocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error) +} + +// NewManager creates a new instance of Manager. +func NewManager(config ManagerConfig) (*Manager, error) { + switch { + case config.AllocatePacketConn == nil: + return nil, errAllocatePacketConnMustBeSet + case config.AllocateConn == nil: + return nil, errAllocateConnMustBeSet + case config.LeveledLogger == nil: + return nil, errLeveledLoggerMustBeSet + } + + return &Manager{ + log: config.LeveledLogger, + allocations: make(map[string]*Allocation, 64), + allocatePacketConn: config.AllocatePacketConn, + allocateConn: config.AllocateConn, + }, nil +} + +// GetAllocation fetches the allocation matching the passed FiveTuple +func (m *Manager) GetAllocation(fiveTuple *FiveTuple) *Allocation { + m.lock.RLock() + defer m.lock.RUnlock() + return m.allocations[fiveTuple.Fingerprint()] +} + +// Close closes the manager and closes all allocations it manages +func (m *Manager) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + + for _, a := range m.allocations { + if err := a.Close(); err != nil { + return err + } + } + return nil +} + +// CreateAllocation creates a new allocation and starts relaying +func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) { + switch { + case fiveTuple == nil: + return nil, errNilFiveTuple + case fiveTuple.SrcAddr == nil: + return nil, errNilFiveTupleSrcAddr + case fiveTuple.DstAddr == nil: + return nil, errNilFiveTupleDstAddr + case turnSocket == nil: + return nil, errNilTurnSocket + case lifetime == 0: + return nil, errLifetimeZero + } + + if a := m.GetAllocation(fiveTuple); a != nil { + return nil, fmt.Errorf("%w: %v", errDupeFiveTuple, fiveTuple) + } + a := NewAllocation(turnSocket, fiveTuple, m.log) + + conn, relayAddr, err := m.allocatePacketConn("udp4", requestedPort) + if err != nil { + return nil, err + } + + a.RelaySocket = conn + a.RelayAddr = relayAddr + + m.log.Debugf("listening on relay addr: %s", a.RelayAddr.String()) + + a.lifetimeTimer = time.AfterFunc(lifetime, func() { + m.DeleteAllocation(a.fiveTuple) + }) + + m.lock.Lock() + m.allocations[fiveTuple.Fingerprint()] = a + m.lock.Unlock() + + go a.packetHandler(m) + return a, nil +} + +// DeleteAllocation removes an allocation +func (m *Manager) DeleteAllocation(fiveTuple *FiveTuple) { + fingerprint := fiveTuple.Fingerprint() + + m.lock.Lock() + allocation := m.allocations[fingerprint] + delete(m.allocations, fingerprint) + m.lock.Unlock() + + if allocation == nil { + return + } + + if err := allocation.Close(); err != nil { + m.log.Errorf("Failed to close allocation: %v", err) + } +} + +// CreateReservation stores the reservation for the token+port +func (m *Manager) CreateReservation(reservationToken string, port int) { + time.AfterFunc(30*time.Second, func() { + m.lock.Lock() + defer m.lock.Unlock() + for i := len(m.reservations) - 1; i >= 0; i-- { + if m.reservations[i].token == reservationToken { + m.reservations = append(m.reservations[:i], m.reservations[i+1:]...) + return + } + } + }) + + m.lock.Lock() + m.reservations = append(m.reservations, &reservation{ + token: reservationToken, + port: port, + }) + m.lock.Unlock() +} + +// GetReservation returns the port for a given reservation if it exists +func (m *Manager) GetReservation(reservationToken string) (int, bool) { + m.lock.RLock() + defer m.lock.RUnlock() + + for _, r := range m.reservations { + if r.token == reservationToken { + return r.port, true + } + } + return 0, false +} + +// GetRandomEvenPort returns a random un-allocated udp4 port +func (m *Manager) GetRandomEvenPort() (int, error) { + conn, addr, err := m.allocatePacketConn("udp4", 0) + if err != nil { + return 0, err + } + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errFailedToCastUDPAddr + } else if err := conn.Close(); err != nil { + return 0, err + } else if udpAddr.Port%2 == 1 { + return m.GetRandomEvenPort() + } + + return udpAddr.Port, nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/channel_bind.go b/vendor/github.com/pion/turn/v2/internal/allocation/channel_bind.go new file mode 100644 index 0000000..6216369 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/channel_bind.go @@ -0,0 +1,43 @@ +package allocation + +import ( + "net" + "time" + + "github.com/pion/logging" + "github.com/pion/turn/v2/internal/proto" +) + +// ChannelBind represents a TURN Channel +// https://tools.ietf.org/html/rfc5766#section-2.5 +type ChannelBind struct { + Peer net.Addr + Number proto.ChannelNumber + + allocation *Allocation + lifetimeTimer *time.Timer + log logging.LeveledLogger +} + +// NewChannelBind creates a new ChannelBind +func NewChannelBind(number proto.ChannelNumber, peer net.Addr, log logging.LeveledLogger) *ChannelBind { + return &ChannelBind{ + Number: number, + Peer: peer, + log: log, + } +} + +func (c *ChannelBind) start(lifetime time.Duration) { + c.lifetimeTimer = time.AfterFunc(lifetime, func() { + if !c.allocation.RemoveChannelBind(c.Number) { + c.log.Errorf("Failed to remove ChannelBind for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple) + } + }) +} + +func (c *ChannelBind) refresh(lifetime time.Duration) { + if !c.lifetimeTimer.Reset(lifetime) { + c.log.Errorf("Failed to reset ChannelBind timer for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple) + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/errors.go b/vendor/github.com/pion/turn/v2/internal/allocation/errors.go new file mode 100644 index 0000000..fdd8335 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/errors.go @@ -0,0 +1,17 @@ +package allocation + +import "errors" + +var ( + errAllocatePacketConnMustBeSet = errors.New("AllocatePacketConn must be set") + errAllocateConnMustBeSet = errors.New("AllocateConn must be set") + errLeveledLoggerMustBeSet = errors.New("LeveledLogger must be set") + errSameChannelDifferentPeer = errors.New("you cannot use the same channel number with different peer") + errNilFiveTuple = errors.New("allocations must not be created with nil FivTuple") + errNilFiveTupleSrcAddr = errors.New("allocations must not be created with nil FiveTuple.SrcAddr") + errNilFiveTupleDstAddr = errors.New("allocations must not be created with nil FiveTuple.DstAddr") + errNilTurnSocket = errors.New("allocations must not be created with nil turnSocket") + errLifetimeZero = errors.New("allocations must not be created with a lifetime of 0") + errDupeFiveTuple = errors.New("allocation attempt created with duplicate FiveTuple") + errFailedToCastUDPAddr = errors.New("failed to cast net.Addr to *net.UDPAddr") +) diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/five_tuple.go b/vendor/github.com/pion/turn/v2/internal/allocation/five_tuple.go new file mode 100644 index 0000000..1f2b3b5 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/five_tuple.go @@ -0,0 +1,36 @@ +package allocation + +import ( + "fmt" + "net" +) + +// Protocol is an enum for relay protocol +type Protocol uint8 + +// Network protocols for relay +const ( + UDP Protocol = iota + TCP +) + +// FiveTuple is the combination (client IP address and port, server IP +// address and port, and transport protocol (currently one of UDP, +// TCP, or TLS)) used to communicate between the client and the +// server. The 5-tuple uniquely identifies this communication +// stream. The 5-tuple also uniquely identifies the Allocation on +// the server. +type FiveTuple struct { + Protocol + SrcAddr, DstAddr net.Addr +} + +// Equal asserts if two FiveTuples are equal +func (f *FiveTuple) Equal(b *FiveTuple) bool { + return f.Fingerprint() == b.Fingerprint() +} + +// Fingerprint is the identity of a FiveTuple +func (f *FiveTuple) Fingerprint() string { + return fmt.Sprintf("%d_%s_%s", f.Protocol, f.SrcAddr.String(), f.DstAddr.String()) +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/permission.go b/vendor/github.com/pion/turn/v2/internal/allocation/permission.go new file mode 100644 index 0000000..03538eb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/permission.go @@ -0,0 +1,40 @@ +package allocation + +import ( + "net" + "time" + + "github.com/pion/logging" +) + +const permissionTimeout = time.Duration(5) * time.Minute + +// Permission represents a TURN permission. TURN permissions mimic the address-restricted +// filtering mechanism of NATs that comply with [RFC4787]. +// https://tools.ietf.org/html/rfc5766#section-2.3 +type Permission struct { + Addr net.Addr + allocation *Allocation + lifetimeTimer *time.Timer + log logging.LeveledLogger +} + +// NewPermission create a new Permission +func NewPermission(addr net.Addr, log logging.LeveledLogger) *Permission { + return &Permission{ + Addr: addr, + log: log, + } +} + +func (p *Permission) start(lifetime time.Duration) { + p.lifetimeTimer = time.AfterFunc(lifetime, func() { + p.allocation.RemovePermission(p.Addr) + }) +} + +func (p *Permission) refresh(lifetime time.Duration) { + if !p.lifetimeTimer.Reset(lifetime) { + p.log.Errorf("Failed to reset permission timer for %v %v", p.Addr, p.allocation.fiveTuple) + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/binding.go b/vendor/github.com/pion/turn/v2/internal/client/binding.go new file mode 100644 index 0000000..ee52053 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/binding.go @@ -0,0 +1,151 @@ +package client + +import ( + "net" + "sync" + "sync/atomic" + "time" +) + +// Chanel number: +// 0x4000 through 0x7FFF: These values are the allowed channel +// numbers (16,383 possible values). +const ( + minChannelNumber uint16 = 0x4000 + maxChannelNumber uint16 = 0x7fff +) + +type bindingState int32 + +const ( + bindingStateIdle bindingState = iota + bindingStateRequest + bindingStateReady + bindingStateRefresh + bindingStateFailed +) + +type binding struct { + number uint16 // read-only + st bindingState // thread-safe (atomic op) + addr net.Addr // read-only + mgr *bindingManager // read-only + muBind sync.Mutex // thread-safe, for ChannelBind ops + _refreshedAt time.Time // protected by mutex + mutex sync.RWMutex // thread-safe +} + +func (b *binding) setState(state bindingState) { + atomic.StoreInt32((*int32)(&b.st), int32(state)) +} + +func (b *binding) state() bindingState { + return bindingState(atomic.LoadInt32((*int32)(&b.st))) +} + +func (b *binding) setRefreshedAt(at time.Time) { + b.mutex.Lock() + defer b.mutex.Unlock() + + b._refreshedAt = at +} + +func (b *binding) refreshedAt() time.Time { + b.mutex.RLock() + defer b.mutex.RUnlock() + + return b._refreshedAt +} + +// Thread-safe binding map +type bindingManager struct { + chanMap map[uint16]*binding + addrMap map[string]*binding + next uint16 + mutex sync.RWMutex +} + +func newBindingManager() *bindingManager { + return &bindingManager{ + chanMap: map[uint16]*binding{}, + addrMap: map[string]*binding{}, + next: minChannelNumber, + } +} + +func (mgr *bindingManager) assignChannelNumber() uint16 { + n := mgr.next + if mgr.next == maxChannelNumber { + mgr.next = minChannelNumber + } else { + mgr.next++ + } + return n +} + +func (mgr *bindingManager) create(addr net.Addr) *binding { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + b := &binding{ + number: mgr.assignChannelNumber(), + addr: addr, + mgr: mgr, + _refreshedAt: time.Now(), + } + + mgr.chanMap[b.number] = b + mgr.addrMap[b.addr.String()] = b + return b +} + +func (mgr *bindingManager) findByAddr(addr net.Addr) (*binding, bool) { + mgr.mutex.RLock() + defer mgr.mutex.RUnlock() + + b, ok := mgr.addrMap[addr.String()] + return b, ok +} + +func (mgr *bindingManager) findByNumber(number uint16) (*binding, bool) { + mgr.mutex.RLock() + defer mgr.mutex.RUnlock() + + b, ok := mgr.chanMap[number] + return b, ok +} + +func (mgr *bindingManager) deleteByAddr(addr net.Addr) bool { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + b, ok := mgr.addrMap[addr.String()] + if !ok { + return false + } + + delete(mgr.addrMap, addr.String()) + delete(mgr.chanMap, b.number) + return true +} + +func (mgr *bindingManager) deleteByNumber(number uint16) bool { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + b, ok := mgr.chanMap[number] + if !ok { + return false + } + + delete(mgr.addrMap, b.addr.String()) + delete(mgr.chanMap, number) + return true +} + +func (mgr *bindingManager) size() int { + mgr.mutex.RLock() + defer mgr.mutex.RUnlock() + + return len(mgr.chanMap) +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/conn.go b/vendor/github.com/pion/turn/v2/internal/client/conn.go new file mode 100644 index 0000000..8a2b1ae --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/conn.go @@ -0,0 +1,613 @@ +// Package client implements the API for a TURN client +package client + +import ( + "errors" + "fmt" + "io" + "math" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/proto" +) + +const ( + maxReadQueueSize = 1024 + permRefreshInterval = 120 * time.Second + maxRetryAttempts = 3 +) + +const ( + timerIDRefreshAlloc int = iota + timerIDRefreshPerms +) + +func noDeadline() time.Time { + return time.Time{} +} + +type inboundData struct { + data []byte + from net.Addr +} + +// UDPConnObserver is an interface to UDPConn observer +type UDPConnObserver interface { + TURNServerAddr() net.Addr + Username() stun.Username + Realm() stun.Realm + WriteTo(data []byte, to net.Addr) (int, error) + PerformTransaction(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) + OnDeallocated(relayedAddr net.Addr) +} + +// UDPConnConfig is a set of configuration params use by NewUDPConn +type UDPConnConfig struct { + Observer UDPConnObserver + RelayedAddr net.Addr + Integrity stun.MessageIntegrity + Nonce stun.Nonce + Lifetime time.Duration + Log logging.LeveledLogger +} + +// UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections. +// comatible with net.PacketConn and net.Conn +type UDPConn struct { + obs UDPConnObserver // read-only + relayedAddr net.Addr // read-only + permMap *permissionMap // thread-safe + bindingMgr *bindingManager // thread-safe + integrity stun.MessageIntegrity // read-only + _nonce stun.Nonce // needs mutex x + _lifetime time.Duration // needs mutex x + readCh chan *inboundData // thread-safe + closeCh chan struct{} // thread-safe + readTimer *time.Timer // thread-safe + refreshAllocTimer *PeriodicTimer // thread-safe + refreshPermsTimer *PeriodicTimer // thread-safe + mutex sync.RWMutex // thread-safe + log logging.LeveledLogger // read-only +} + +// NewUDPConn creates a new instance of UDPConn +func NewUDPConn(config *UDPConnConfig) *UDPConn { + c := &UDPConn{ + obs: config.Observer, + relayedAddr: config.RelayedAddr, + permMap: newPermissionMap(), + bindingMgr: newBindingManager(), + integrity: config.Integrity, + _nonce: config.Nonce, + _lifetime: config.Lifetime, + readCh: make(chan *inboundData, maxReadQueueSize), + closeCh: make(chan struct{}), + readTimer: time.NewTimer(time.Duration(math.MaxInt64)), + log: config.Log, + } + + c.log.Debugf("initial lifetime: %d seconds", int(c.lifetime().Seconds())) + + c.refreshAllocTimer = NewPeriodicTimer( + timerIDRefreshAlloc, + c.onRefreshTimers, + c.lifetime()/2, + ) + + c.refreshPermsTimer = NewPeriodicTimer( + timerIDRefreshPerms, + c.onRefreshTimers, + permRefreshInterval, + ) + + if c.refreshAllocTimer.Start() { + c.log.Debugf("refreshAllocTimer started") + } + if c.refreshPermsTimer.Start() { + c.log.Debugf("refreshPermsTimer started") + } + + return c +} + +// ReadFrom reads a packet from the connection, +// copying the payload into p. It returns the number of +// bytes copied into p and the return address that +// was on the packet. +// It returns the number of bytes read (0 <= n <= len(p)) +// and any error encountered. Callers should always process +// the n > 0 bytes returned before considering the error err. +// ReadFrom can be made to time out and return +// an Error with Timeout() == true after a fixed time limit; +// see SetDeadline and SetReadDeadline. +func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + for { + select { + case ibData := <-c.readCh: + n := copy(p, ibData.data) + if n < len(ibData.data) { + return 0, nil, io.ErrShortBuffer + } + return n, ibData.from, nil + + case <-c.readTimer.C: + return 0, nil, &net.OpError{ + Op: "read", + Net: c.LocalAddr().Network(), + Addr: c.LocalAddr(), + Err: newTimeoutError("i/o timeout"), + } + + case <-c.closeCh: + return 0, nil, &net.OpError{ + Op: "read", + Net: c.LocalAddr().Network(), + Addr: c.LocalAddr(), + Err: errClosed, + } + } + } +} + +// WriteTo writes a packet with payload p to addr. +// WriteTo can be made to time out and return +// an Error with Timeout() == true after a fixed time limit; +// see SetDeadline and SetWriteDeadline. +// On packet-oriented connections, write timeouts are rare. +func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (int, error) { //nolint: gocognit + var err error + _, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errUDPAddrCast + } + + // check if we have a permission for the destination IP addr + perm, ok := c.permMap.find(addr) + if !ok { + perm = &permission{} + c.permMap.insert(addr, perm) + } + + // This func-block would block, per destination IP (, or perm), until + // the perm state becomes "requested". Purpose of this is to guarantee + // the order of packets (within the same perm). + // Note that CreatePermission transaction may not be complete before + // all the data transmission. This is done assuming that the request + // will be mostly likely successful and we can tolerate some loss of + // UDP packet (or reorder), inorder to minimize the latency in most cases. + createPermission := func() error { + perm.mutex.Lock() + defer perm.mutex.Unlock() + + if perm.state() == permStateIdle { + // punch a hole! (this would block a bit..) + if err = c.createPermissions(addr); err != nil { + c.permMap.delete(addr) + return err + } + perm.setState(permStatePermitted) + } + return nil + } + + for i := 0; i < maxRetryAttempts; i++ { + if err = createPermission(); !errors.Is(err, errTryAgain) { + break + } + } + if err != nil { + return 0, err + } + + // bind channel + b, ok := c.bindingMgr.findByAddr(addr) + if !ok { + b = c.bindingMgr.create(addr) + } + + bindSt := b.state() + + if bindSt == bindingStateIdle || bindSt == bindingStateRequest || bindSt == bindingStateFailed { + func() { + // block only callers with the same binding until + // the binding transaction has been complete + b.muBind.Lock() + defer b.muBind.Unlock() + + // binding state may have been changed while waiting. check again. + if b.state() == bindingStateIdle { + b.setState(bindingStateRequest) + go func() { + err2 := c.bind(b) + if err2 != nil { + c.log.Warnf("bind() failed: %s", err2.Error()) + b.setState(bindingStateFailed) + // keep going... + } else { + b.setState(bindingStateReady) + } + }() + } + }() + + // send data using SendIndication + peerAddr := addr2PeerAddress(addr) + var msg *stun.Message + msg, err = stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodSend, stun.ClassIndication), + proto.Data(p), + peerAddr, + stun.Fingerprint, + ) + if err != nil { + return 0, err + } + + // indication has no transaction (fire-and-forget) + + return c.obs.WriteTo(msg.Raw, c.obs.TURNServerAddr()) + } + + // binding is either ready + + // check if the binding needs a refresh + func() { + b.muBind.Lock() + defer b.muBind.Unlock() + + if b.state() == bindingStateReady && time.Since(b.refreshedAt()) > 5*time.Minute { + b.setState(bindingStateRefresh) + go func() { + err = c.bind(b) + if err != nil { + c.log.Warnf("bind() for refresh failed: %s", err.Error()) + b.setState(bindingStateFailed) + // keep going... + } else { + b.setRefreshedAt(time.Now()) + b.setState(bindingStateReady) + } + }() + } + }() + + // send via ChannelData + return c.sendChannelData(p, b.number) +} + +// Close closes the connection. +// Any blocked ReadFrom or WriteTo operations will be unblocked and return errors. +func (c *UDPConn) Close() error { + c.refreshAllocTimer.Stop() + c.refreshPermsTimer.Stop() + + select { + case <-c.closeCh: + return errAlreadyClosed + default: + close(c.closeCh) + } + + c.obs.OnDeallocated(c.relayedAddr) + return c.refreshAllocation(0, true /* dontWait=true */) +} + +// LocalAddr returns the local network address. +func (c *UDPConn) LocalAddr() net.Addr { + return c.relayedAddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail with a timeout (see type Error) instead of +// blocking. The deadline applies to all future and pending +// I/O, not just the immediately following call to ReadFrom or +// WriteTo. After a deadline has been exceeded, the connection +// can be refreshed by setting a deadline in the future. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful ReadFrom or WriteTo calls. +// +// A zero value for t means I/O operations will not time out. +func (c *UDPConn) SetDeadline(t time.Time) error { + return c.SetReadDeadline(t) +} + +// SetReadDeadline sets the deadline for future ReadFrom calls +// and any currently-blocked ReadFrom call. +// A zero value for t means ReadFrom will not time out. +func (c *UDPConn) SetReadDeadline(t time.Time) error { + var d time.Duration + if t == noDeadline() { + d = time.Duration(math.MaxInt64) + } else { + d = time.Until(t) + } + c.readTimer.Reset(d) + return nil +} + +// SetWriteDeadline sets the deadline for future WriteTo calls +// and any currently-blocked WriteTo call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means WriteTo will not time out. +func (c *UDPConn) SetWriteDeadline(t time.Time) error { + // Write never blocks. + return nil +} + +func addr2PeerAddress(addr net.Addr) proto.PeerAddress { + var peerAddr proto.PeerAddress + switch a := addr.(type) { + case *net.UDPAddr: + peerAddr.IP = a.IP + peerAddr.Port = a.Port + case *net.TCPAddr: + peerAddr.IP = a.IP + peerAddr.Port = a.Port + } + + return peerAddr +} + +func (c *UDPConn) createPermissions(addrs ...net.Addr) error { + setters := []stun.Setter{ + stun.TransactionID, + stun.NewType(stun.MethodCreatePermission, stun.ClassRequest), + } + + for _, addr := range addrs { + setters = append(setters, addr2PeerAddress(addr)) + } + + setters = append(setters, + c.obs.Username(), + c.obs.Realm(), + c.nonce(), + c.integrity, + stun.Fingerprint) + + msg, err := stun.Build(setters...) + if err != nil { + return err + } + + trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false) + if err != nil { + return err + } + + res := trRes.Msg + + if res.Type.Class == stun.ClassErrorResponse { + var code stun.ErrorCodeAttribute + if err = code.GetFrom(res); err == nil { + if code.Code == stun.CodeStaleNonce { + c.setNonceFromMsg(res) + return errTryAgain + } + return fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113 + } + + return fmt.Errorf("%s", res.Type) //nolint:goerr113 + } + + return nil +} + +// HandleInbound passes inbound data in UDPConn +func (c *UDPConn) HandleInbound(data []byte, from net.Addr) { + // copy data + copied := make([]byte, len(data)) + copy(copied, data) + + select { + case c.readCh <- &inboundData{data: copied, from: from}: + default: + c.log.Warnf("receive buffer full") + } +} + +// FindAddrByChannelNumber returns a peer address associated with the +// channel number on this UDPConn +func (c *UDPConn) FindAddrByChannelNumber(chNum uint16) (net.Addr, bool) { + b, ok := c.bindingMgr.findByNumber(chNum) + if !ok { + return nil, false + } + return b.addr, true +} + +func (c *UDPConn) setNonceFromMsg(msg *stun.Message) { + // Update nonce + var nonce stun.Nonce + if err := nonce.GetFrom(msg); err == nil { + c.setNonce(nonce) + c.log.Debug("refresh allocation: 438, got new nonce.") + } else { + c.log.Warn("refresh allocation: 438 but no nonce.") + } +} + +func (c *UDPConn) refreshAllocation(lifetime time.Duration, dontWait bool) error { + msg, err := stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodRefresh, stun.ClassRequest), + proto.Lifetime{Duration: lifetime}, + c.obs.Username(), + c.obs.Realm(), + c.nonce(), + c.integrity, + stun.Fingerprint, + ) + if err != nil { + return fmt.Errorf("%w: %s", errFailedToBuildRefreshRequest, err.Error()) + } + + c.log.Debugf("send refresh request (dontWait=%v)", dontWait) + trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), dontWait) + if err != nil { + return fmt.Errorf("%w: %s", errFailedToRefreshAllocation, err.Error()) + } + + if dontWait { + c.log.Debug("refresh request sent") + return nil + } + + c.log.Debug("refresh request sent, and waiting response") + + res := trRes.Msg + if res.Type.Class == stun.ClassErrorResponse { + var code stun.ErrorCodeAttribute + if err = code.GetFrom(res); err == nil { + if code.Code == stun.CodeStaleNonce { + c.setNonceFromMsg(res) + return errTryAgain + } + return err + } + return fmt.Errorf("%s", res.Type) //nolint:goerr113 + } + + // Getting lifetime from response + var updatedLifetime proto.Lifetime + if err := updatedLifetime.GetFrom(res); err != nil { + return fmt.Errorf("%w: %s", errFailedToGetLifetime, err.Error()) + } + + c.setLifetime(updatedLifetime.Duration) + c.log.Debugf("updated lifetime: %d seconds", int(c.lifetime().Seconds())) + return nil +} + +func (c *UDPConn) refreshPermissions() error { + addrs := c.permMap.addrs() + if len(addrs) == 0 { + c.log.Debug("no permission to refresh") + return nil + } + if err := c.createPermissions(addrs...); err != nil { + if errors.Is(err, errTryAgain) { + return errTryAgain + } + c.log.Errorf("fail to refresh permissions: %s", err.Error()) + return err + } + c.log.Debug("refresh permissions successful") + return nil +} + +func (c *UDPConn) bind(b *binding) error { + setters := []stun.Setter{ + stun.TransactionID, + stun.NewType(stun.MethodChannelBind, stun.ClassRequest), + addr2PeerAddress(b.addr), + proto.ChannelNumber(b.number), + c.obs.Username(), + c.obs.Realm(), + c.nonce(), + c.integrity, + stun.Fingerprint, + } + + msg, err := stun.Build(setters...) + if err != nil { + return err + } + + trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false) + if err != nil { + c.bindingMgr.deleteByAddr(b.addr) + return err + } + + res := trRes.Msg + + if res.Type != stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse) { + return fmt.Errorf("unexpected response type %s", res.Type) //nolint:goerr113 + } + + c.log.Debugf("channel binding successful: %s %d", b.addr.String(), b.number) + + // Success. + return nil +} + +func (c *UDPConn) sendChannelData(data []byte, chNum uint16) (int, error) { + chData := &proto.ChannelData{ + Data: data, + Number: proto.ChannelNumber(chNum), + } + chData.Encode() + return c.obs.WriteTo(chData.Raw, c.obs.TURNServerAddr()) +} + +func (c *UDPConn) onRefreshTimers(id int) { + c.log.Debugf("refresh timer %d expired", id) + switch id { + case timerIDRefreshAlloc: + var err error + lifetime := c.lifetime() + // limit the max retries on errTryAgain to 3 + // when stale nonce returns, sencond retry should succeed + for i := 0; i < maxRetryAttempts; i++ { + err = c.refreshAllocation(lifetime, false) + if !errors.Is(err, errTryAgain) { + break + } + } + if err != nil { + c.log.Warnf("refresh allocation failed") + } + case timerIDRefreshPerms: + var err error + for i := 0; i < maxRetryAttempts; i++ { + err = c.refreshPermissions() + if !errors.Is(err, errTryAgain) { + break + } + } + if err != nil { + c.log.Warnf("refresh permissions failed") + } + } +} + +func (c *UDPConn) nonce() stun.Nonce { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c._nonce +} + +func (c *UDPConn) setNonce(nonce stun.Nonce) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.log.Debugf("set new nonce with %d bytes", len(nonce)) + c._nonce = nonce +} + +func (c *UDPConn) lifetime() time.Duration { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c._lifetime +} + +func (c *UDPConn) setLifetime(lifetime time.Duration) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c._lifetime = lifetime +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/errors.go b/vendor/github.com/pion/turn/v2/internal/client/errors.go new file mode 100644 index 0000000..2d4bd30 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/errors.go @@ -0,0 +1,37 @@ +package client + +import ( + "errors" +) + +var ( + errFakeErr = errors.New("fake error") + errTryAgain = errors.New("try again") + errClosed = errors.New("use of closed network connection") + errUDPAddrCast = errors.New("addr is not a net.UDPAddr") + errAlreadyClosed = errors.New("already closed") + errDoubleLock = errors.New("try-lock is already locked") + errTransactionClosed = errors.New("transaction closed") + errWaitForResultOnNonResultTransaction = errors.New("WaitForResult called on non-result transaction") + errFailedToBuildRefreshRequest = errors.New("failed to build refresh request") + errFailedToRefreshAllocation = errors.New("failed to refresh allocation") + errFailedToGetLifetime = errors.New("failed to get lifetime from refresh response") +) + +type timeoutError struct { + msg string +} + +func newTimeoutError(msg string) error { + return &timeoutError{ + msg: msg, + } +} + +func (e *timeoutError) Error() string { + return e.msg +} + +func (e *timeoutError) Timeout() bool { + return true +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go b/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go new file mode 100644 index 0000000..fcd5678 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go @@ -0,0 +1,82 @@ +package client + +import ( + "sync" + "time" +) + +// PeriodicTimerTimeoutHandler is a handler called on timeout +type PeriodicTimerTimeoutHandler func(timerID int) + +// PeriodicTimer is a periodic timer +type PeriodicTimer struct { + id int + interval time.Duration + timeoutHandler PeriodicTimerTimeoutHandler + stopFunc func() + mutex sync.RWMutex +} + +// NewPeriodicTimer create a new timer +func NewPeriodicTimer(id int, timeoutHandler PeriodicTimerTimeoutHandler, interval time.Duration) *PeriodicTimer { + return &PeriodicTimer{ + id: id, + interval: interval, + timeoutHandler: timeoutHandler, + } +} + +// Start starts the timer. +func (t *PeriodicTimer) Start() bool { + t.mutex.Lock() + defer t.mutex.Unlock() + + // this is a noop if the timer is always running + if t.stopFunc != nil { + return false + } + + cancelCh := make(chan struct{}) + + go func() { + canceling := false + + for !canceling { + timer := time.NewTimer(t.interval) + + select { + case <-timer.C: + t.timeoutHandler(t.id) + case <-cancelCh: + canceling = true + timer.Stop() + } + } + }() + + t.stopFunc = func() { + close(cancelCh) + } + + return true +} + +// Stop stops the timer. +func (t *PeriodicTimer) Stop() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.stopFunc != nil { + t.stopFunc() + t.stopFunc = nil + } +} + +// IsRunning tests if the timer is running. +// Debug purpose only +func (t *PeriodicTimer) IsRunning() bool { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return (t.stopFunc != nil) +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/permission.go b/vendor/github.com/pion/turn/v2/internal/client/permission.go new file mode 100644 index 0000000..5546a22 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/permission.go @@ -0,0 +1,90 @@ +package client + +import ( + "net" + "sync" + "sync/atomic" +) + +type permState int32 + +const ( + permStateIdle permState = iota + permStatePermitted +) + +type permission struct { + st permState // thread-safe (atomic op) + mutex sync.RWMutex // thread-safe +} + +func (p *permission) setState(state permState) { + atomic.StoreInt32((*int32)(&p.st), int32(state)) +} + +func (p *permission) state() permState { + return permState(atomic.LoadInt32((*int32)(&p.st))) +} + +// Thread-safe permission map +type permissionMap struct { + permMap map[string]*permission + mutex sync.RWMutex +} + +func (m *permissionMap) insert(addr net.Addr, p *permission) bool { + m.mutex.Lock() + defer m.mutex.Unlock() + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return false + } + + m.permMap[udpAddr.IP.String()] = p + return true +} + +func (m *permissionMap) find(addr net.Addr) (*permission, bool) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return nil, false + } + + p, ok := m.permMap[udpAddr.IP.String()] + return p, ok +} + +func (m *permissionMap) delete(addr net.Addr) { + m.mutex.Lock() + defer m.mutex.Unlock() + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return + } + + delete(m.permMap, udpAddr.IP.String()) +} + +func (m *permissionMap) addrs() []net.Addr { + m.mutex.RLock() + defer m.mutex.RUnlock() + + addrs := []net.Addr{} + for k := range m.permMap { + addrs = append(addrs, &net.UDPAddr{ + IP: net.ParseIP(k), + }) + } + return addrs +} + +func newPermissionMap() *permissionMap { + return &permissionMap{ + permMap: map[string]*permission{}, + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/transaction.go b/vendor/github.com/pion/turn/v2/internal/client/transaction.go new file mode 100644 index 0000000..610a4d4 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/transaction.go @@ -0,0 +1,185 @@ +package client + +import ( + "net" + "sync" + "time" + + "github.com/pion/stun" +) + +const ( + maxRtxInterval time.Duration = 1600 * time.Millisecond +) + +// TransactionResult is a bag of result values of a transaction +type TransactionResult struct { + Msg *stun.Message + From net.Addr + Retries int + Err error +} + +// TransactionConfig is a set of config params used by NewTransaction +type TransactionConfig struct { + Key string + Raw []byte + To net.Addr + Interval time.Duration + IgnoreResult bool // true to throw away the result of this transaction (it will not be readable using WaitForResult) +} + +// Transaction represents a transaction +type Transaction struct { + Key string // read-only + Raw []byte // read-only + To net.Addr // read-only + nRtx int // modified only by the timer thread + interval time.Duration // modified only by the timer thread + timer *time.Timer // thread-safe, set only by the creator, and stopper + resultCh chan TransactionResult // thread-safe + mutex sync.RWMutex +} + +// NewTransaction creates a new instance of Transaction +func NewTransaction(config *TransactionConfig) *Transaction { + var resultCh chan TransactionResult + if !config.IgnoreResult { + resultCh = make(chan TransactionResult) + } + + return &Transaction{ + Key: config.Key, // read-only + Raw: config.Raw, // read-only + To: config.To, // read-only + interval: config.Interval, // modified only by the timer thread + resultCh: resultCh, // thread-safe + } +} + +// StartRtxTimer starts the transaction timer +func (t *Transaction) StartRtxTimer(onTimeout func(trKey string, nRtx int)) { + t.mutex.Lock() + defer t.mutex.Unlock() + + t.timer = time.AfterFunc(t.interval, func() { + t.mutex.Lock() + t.nRtx++ + nRtx := t.nRtx + t.interval *= 2 + if t.interval > maxRtxInterval { + t.interval = maxRtxInterval + } + t.mutex.Unlock() + onTimeout(t.Key, nRtx) + }) +} + +// StopRtxTimer stop the transaction timer +func (t *Transaction) StopRtxTimer() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.timer != nil { + t.timer.Stop() + } +} + +// WriteResult writes the result to the result channel +func (t *Transaction) WriteResult(res TransactionResult) bool { + if t.resultCh == nil { + return false + } + + t.resultCh <- res + + return true +} + +// WaitForResult waits for the transaction result +func (t *Transaction) WaitForResult() TransactionResult { + if t.resultCh == nil { + return TransactionResult{ + Err: errWaitForResultOnNonResultTransaction, + } + } + + result, ok := <-t.resultCh + if !ok { + result.Err = errTransactionClosed + } + return result +} + +// Close closes the transaction +func (t *Transaction) Close() { + if t.resultCh != nil { + close(t.resultCh) + } +} + +// Retries returns the number of retransmission it has made +func (t *Transaction) Retries() int { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return t.nRtx +} + +// TransactionMap is a thread-safe transaction map +type TransactionMap struct { + trMap map[string]*Transaction + mutex sync.RWMutex +} + +// NewTransactionMap create a new instance of the transaction map +func NewTransactionMap() *TransactionMap { + return &TransactionMap{ + trMap: map[string]*Transaction{}, + } +} + +// Insert inserts a trasaction to the map +func (m *TransactionMap) Insert(key string, tr *Transaction) bool { + m.mutex.Lock() + defer m.mutex.Unlock() + + m.trMap[key] = tr + return true +} + +// Find looks up a transaction by its key +func (m *TransactionMap) Find(key string) (*Transaction, bool) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + tr, ok := m.trMap[key] + return tr, ok +} + +// Delete deletes a transaction by its key +func (m *TransactionMap) Delete(key string) { + m.mutex.Lock() + defer m.mutex.Unlock() + + delete(m.trMap, key) +} + +// CloseAndDeleteAll closes and deletes all transactions +func (m *TransactionMap) CloseAndDeleteAll() { + m.mutex.Lock() + defer m.mutex.Unlock() + + for trKey, tr := range m.trMap { + tr.Close() + delete(m.trMap, trKey) + } +} + +// Size returns the length of the transaction map +func (m *TransactionMap) Size() int { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return len(m.trMap) +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/trylock.go b/vendor/github.com/pion/turn/v2/internal/client/trylock.go new file mode 100644 index 0000000..48e25a0 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/trylock.go @@ -0,0 +1,24 @@ +package client + +import ( + "sync/atomic" +) + +// TryLock implement the classic "try-lock" operation. +type TryLock struct { + n int32 +} + +// Lock tries to lock the try-lock. If successful, it returns true. +// Otherwise, it returns false immedidately. +func (c *TryLock) Lock() error { + if !atomic.CompareAndSwapInt32(&c.n, 0, 1) { + return errDoubleLock + } + return nil +} + +// Unlock unlocks the try-lock. +func (c *TryLock) Unlock() { + atomic.StoreInt32(&c.n, 0) +} diff --git a/vendor/github.com/pion/turn/v2/internal/ipnet/util.go b/vendor/github.com/pion/turn/v2/internal/ipnet/util.go new file mode 100644 index 0000000..9df7f56 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/ipnet/util.go @@ -0,0 +1,40 @@ +// Package ipnet contains helper functions around net and IP +package ipnet + +import ( + "errors" + "net" +) + +var errFailedToCastAddr = errors.New("failed to cast net.Addr to *net.UDPAddr or *net.TCPAddr") + +// AddrIPPort extracts the IP and Port from a net.Addr +func AddrIPPort(a net.Addr) (net.IP, int, error) { + aUDP, ok := a.(*net.UDPAddr) + if ok { + return aUDP.IP, aUDP.Port, nil + } + + aTCP, ok := a.(*net.TCPAddr) + if ok { + return aTCP.IP, aTCP.Port, nil + } + + return nil, 0, errFailedToCastAddr +} + +// AddrEqual asserts that two net.Addrs are equal +// Currently only supprots UDP but will be extended in the future to support others +func AddrEqual(a, b net.Addr) bool { + aUDP, ok := a.(*net.UDPAddr) + if !ok { + return false + } + + bUDP, ok := b.(*net.UDPAddr) + if !ok { + return false + } + + return aUDP.IP.Equal(bUDP.IP) && aUDP.Port == bUDP.Port +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/addr.go b/vendor/github.com/pion/turn/v2/internal/proto/addr.go new file mode 100644 index 0000000..b1d654d --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/addr.go @@ -0,0 +1,65 @@ +package proto + +import ( + "fmt" + "net" +) + +// Addr is ip:port. +type Addr struct { + IP net.IP + Port int +} + +// Network implements net.Addr. +func (Addr) Network() string { return "turn" } + +// FromUDPAddr sets addr to UDPAddr. +func (a *Addr) FromUDPAddr(n *net.UDPAddr) { + a.IP = n.IP + a.Port = n.Port +} + +// Equal returns true if b == a. +func (a Addr) Equal(b Addr) bool { + if a.Port != b.Port { + return false + } + return a.IP.Equal(b.IP) +} + +// EqualIP returns true if a and b have equal IP addresses. +func (a Addr) EqualIP(b Addr) bool { + return a.IP.Equal(b.IP) +} + +func (a Addr) String() string { + return fmt.Sprintf("%s:%d", a.IP, a.Port) +} + +// FiveTuple represents 5-TUPLE value. +type FiveTuple struct { + Client Addr + Server Addr + Proto Protocol +} + +func (t FiveTuple) String() string { + return fmt.Sprintf("%s->%s (%s)", + t.Client, t.Server, t.Proto, + ) +} + +// Equal returns true if b == t. +func (t FiveTuple) Equal(b FiveTuple) bool { + if t.Proto != b.Proto { + return false + } + if !t.Client.Equal(b.Client) { + return false + } + if !t.Server.Equal(b.Server) { + return false + } + return true +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/chandata.go b/vendor/github.com/pion/turn/v2/internal/proto/chandata.go new file mode 100644 index 0000000..fb1295b --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/chandata.go @@ -0,0 +1,140 @@ +package proto + +import ( + "bytes" + "encoding/binary" + "errors" + "io" +) + +// ChannelData represents The ChannelData Message. +// +// See RFC 5766 Section 11.4 +type ChannelData struct { + Data []byte // can be subslice of Raw + Length int // ignored while encoding, len(Data) is used + Number ChannelNumber + Raw []byte +} + +// Equal returns true if b == c. +func (c *ChannelData) Equal(b *ChannelData) bool { + if c == nil && b == nil { + return true + } + if c == nil || b == nil { + return false + } + if c.Number != b.Number { + return false + } + if len(c.Data) != len(b.Data) { + return false + } + return bytes.Equal(c.Data, b.Data) +} + +// grow ensures that internal buffer will fit v more bytes and +// increases it capacity if necessary. +// +// Similar to stun.Message.grow method. +func (c *ChannelData) grow(v int) { + n := len(c.Raw) + v + for cap(c.Raw) < n { + c.Raw = append(c.Raw, 0) + } + c.Raw = c.Raw[:n] +} + +// Reset resets Length, Data and Raw length. +func (c *ChannelData) Reset() { + c.Raw = c.Raw[:0] + c.Length = 0 + c.Data = c.Data[:0] +} + +// Encode encodes ChannelData Message to Raw. +func (c *ChannelData) Encode() { + c.Raw = c.Raw[:0] + c.WriteHeader() + c.Raw = append(c.Raw, c.Data...) + padded := nearestPaddedValueLength(len(c.Raw)) + if bytesToAdd := padded - len(c.Raw); bytesToAdd > 0 { + for i := 0; i < bytesToAdd; i++ { + c.Raw = append(c.Raw, 0) + } + } +} + +const padding = 4 + +func nearestPaddedValueLength(l int) int { + n := padding * (l / padding) + if n < l { + n += padding + } + return n +} + +// WriteHeader writes channel number and length. +func (c *ChannelData) WriteHeader() { + if len(c.Raw) < channelDataHeaderSize { + // Making WriteHeader call valid even when c.Raw + // is nil or len(c.Raw) is less than needed for header. + c.grow(channelDataHeaderSize) + } + // Early bounds check to guarantee safety of writes below. + _ = c.Raw[:channelDataHeaderSize] + binary.BigEndian.PutUint16(c.Raw[:channelDataNumberSize], uint16(c.Number)) + binary.BigEndian.PutUint16(c.Raw[channelDataNumberSize:channelDataHeaderSize], + uint16(len(c.Data)), + ) +} + +// ErrBadChannelDataLength means that channel data length is not equal +// to actual data length. +var ErrBadChannelDataLength = errors.New("channelData length != len(Data)") + +// Decode decodes The ChannelData Message from Raw. +func (c *ChannelData) Decode() error { + buf := c.Raw + if len(buf) < channelDataHeaderSize { + return io.ErrUnexpectedEOF + } + num := binary.BigEndian.Uint16(buf[:channelDataNumberSize]) + c.Number = ChannelNumber(num) + l := binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize]) + c.Data = buf[channelDataHeaderSize:] + c.Length = int(l) + if !c.Number.Valid() { + return ErrInvalidChannelNumber + } + if int(l) < len(c.Data) { + c.Data = c.Data[:int(l)] + } + if int(l) > len(buf[channelDataHeaderSize:]) { + return ErrBadChannelDataLength + } + return nil +} + +const ( + channelDataLengthSize = 2 + channelDataNumberSize = channelDataLengthSize + channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize +) + +// IsChannelData returns true if buf looks like the ChannelData Message. +func IsChannelData(buf []byte) bool { + if len(buf) < channelDataHeaderSize { + return false + } + + if int(binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])) > len(buf[channelDataHeaderSize:]) { + return false + } + + // Quick check for channel number. + num := binary.BigEndian.Uint16(buf[0:channelNumberSize]) + return isChannelNumberValid(num) +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/chann.go b/vendor/github.com/pion/turn/v2/internal/proto/chann.go new file mode 100644 index 0000000..d64ef73 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/chann.go @@ -0,0 +1,67 @@ +package proto + +import ( + "encoding/binary" + "errors" + "strconv" + + "github.com/pion/stun" +) + +// ChannelNumber represents CHANNEL-NUMBER attribute. +// +// The CHANNEL-NUMBER attribute contains the number of the channel. +// +// RFC 5766 Section 14.1 +type ChannelNumber uint16 // encoded as uint16 + +func (n ChannelNumber) String() string { return strconv.Itoa(int(n)) } + +// 16 bits of uint + 16 bits of RFFU = 0. +const channelNumberSize = 4 + +// AddTo adds CHANNEL-NUMBER to message. +func (n ChannelNumber) AddTo(m *stun.Message) error { + v := make([]byte, channelNumberSize) + binary.BigEndian.PutUint16(v[:2], uint16(n)) + // v[2:4] are zeroes (RFFU = 0) + m.Add(stun.AttrChannelNumber, v) + return nil +} + +// GetFrom decodes CHANNEL-NUMBER from message. +func (n *ChannelNumber) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrChannelNumber) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrChannelNumber, len(v), channelNumberSize); err != nil { + return err + } + _ = v[channelNumberSize-1] // asserting length + *n = ChannelNumber(binary.BigEndian.Uint16(v[:2])) + // v[2:4] is RFFU and equals to 0. + return nil +} + +// See https://tools.ietf.org/html/rfc5766#section-11: +// +// 0x4000 through 0x7FFF: These values are the allowed channel +// numbers (16,383 possible values). +const ( + MinChannelNumber = 0x4000 + MaxChannelNumber = 0x7FFF +) + +// ErrInvalidChannelNumber means that channel number is not valid as by RFC 5766 Section 11. +var ErrInvalidChannelNumber = errors.New("channel number not in [0x4000, 0x7FFF]") + +// isChannelNumberValid returns true if c in [0x4000, 0x7FFF]. +func isChannelNumberValid(c uint16) bool { + return c >= MinChannelNumber && c <= MaxChannelNumber +} + +// Valid returns true if channel number has correct value that complies RFC 5766 Section 11 range. +func (n ChannelNumber) Valid() bool { + return isChannelNumberValid(uint16(n)) +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/data.go b/vendor/github.com/pion/turn/v2/internal/proto/data.go new file mode 100644 index 0000000..64243e0 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/data.go @@ -0,0 +1,30 @@ +package proto + +import "github.com/pion/stun" + +// Data represents DATA attribute. +// +// The DATA attribute is present in all Send and Data indications. The +// value portion of this attribute is variable length and consists of +// the application data (that is, the data that would immediately follow +// the UDP header if the data was been sent directly between the client +// and the peer). +// +// RFC 5766 Section 14.4 +type Data []byte + +// AddTo adds DATA to message. +func (d Data) AddTo(m *stun.Message) error { + m.Add(stun.AttrData, d) + return nil +} + +// GetFrom decodes DATA from message. +func (d *Data) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrData) + if err != nil { + return err + } + *d = v + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/dontfrag.go b/vendor/github.com/pion/turn/v2/internal/proto/dontfrag.go new file mode 100644 index 0000000..eb4d8ca --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/dontfrag.go @@ -0,0 +1,18 @@ +package proto + +import "github.com/pion/stun" + +// DontFragmentAttr represents DONT-FRAGMENT attribute. +type DontFragmentAttr struct{} + +// AddTo adds DONT-FRAGMENT attribute to message. +func (DontFragmentAttr) AddTo(m *stun.Message) error { + m.Add(stun.AttrDontFragment, nil) + return nil +} + +// IsSet returns true if DONT-FRAGMENT attribute is set. +func (DontFragmentAttr) IsSet(m *stun.Message) bool { + _, err := m.Get(stun.AttrDontFragment) + return err == nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/evenport.go b/vendor/github.com/pion/turn/v2/internal/proto/evenport.go new file mode 100644 index 0000000..a5a3882 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/evenport.go @@ -0,0 +1,55 @@ +package proto + +import "github.com/pion/stun" + +// EvenPort represents EVEN-PORT attribute. +// +// This attribute allows the client to request that the port in the +// relayed transport address be even, and (optionally) that the server +// reserve the next-higher port number. +// +// RFC 5766 Section 14.6 +type EvenPort struct { + // ReservePort means that the server is requested to reserve + // the next-higher port number (on the same IP address) + // for a subsequent allocation. + ReservePort bool +} + +func (p EvenPort) String() string { + if p.ReservePort { + return "reserve: true" + } + return "reserve: false" +} + +const ( + evenPortSize = 1 + firstBitSet = (1 << 8) - 1 // 0b100000000 +) + +// AddTo adds EVEN-PORT to message. +func (p EvenPort) AddTo(m *stun.Message) error { + v := make([]byte, evenPortSize) + if p.ReservePort { + // Set first bit to 1. + v[0] = firstBitSet + } + m.Add(stun.AttrEvenPort, v) + return nil +} + +// GetFrom decodes EVEN-PORT from message. +func (p *EvenPort) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrEvenPort) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrEvenPort, len(v), evenPortSize); err != nil { + return err + } + if v[0]&firstBitSet > 0 { + p.ReservePort = true + } + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/fuzz.go b/vendor/github.com/pion/turn/v2/internal/proto/fuzz.go new file mode 100644 index 0000000..1a171fb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/fuzz.go @@ -0,0 +1,111 @@ +// +build gofuzz + +package proto + +import ( + "fmt" + + "github.com/pion/stun" +) + +type attr interface { + stun.Getter + stun.Setter +} + +type attrs []struct { + g attr + t stun.AttrType +} + +func (a attrs) pick(v byte) struct { + g attr + t stun.AttrType +} { + idx := int(v) % len(a) + return a[idx] +} + +func FuzzSetters(data []byte) int { + var ( + m1 = &stun.Message{ + Raw: make([]byte, 0, 2048), + } + m2 = &stun.Message{ + Raw: make([]byte, 0, 2048), + } + m3 = &stun.Message{ + Raw: make([]byte, 0, 2048), + } + ) + attributes := attrs{ + {new(RequestedTransport), stun.AttrRequestedTransport}, + {new(RelayedAddress), stun.AttrXORRelayedAddress}, + {new(ChannelNumber), stun.AttrChannelNumber}, + {new(Data), stun.AttrData}, + {new(EvenPort), stun.AttrEvenPort}, + {new(Lifetime), stun.AttrLifetime}, + {new(ReservationToken), stun.AttrReservationToken}, + } + var firstByte = byte(0) + if len(data) > 0 { + firstByte = data[0] + } + a := attributes.pick(firstByte) + value := data + if len(data) > 1 { + value = value[1:] + } + m1.WriteHeader() + m1.Add(a.t, value) + err := a.g.GetFrom(m1) + if err == stun.ErrAttributeNotFound { + fmt.Println("unexpected 404") // nolint + panic(err) // nolint + } + if err != nil { + return 1 + } + m2.WriteHeader() + if err := a.g.AddTo(m2); err != nil { + fmt.Println("failed to add attribute to m2") // nolint + panic(err) // nolint + } + m3.WriteHeader() + v, err := m2.Get(a.t) + if err != nil { + panic(err) // nolint + } + m3.Add(a.t, v) + + if !m2.Equal(m3) { + fmt.Println(m2, "not equal", m3) // nolint + panic("not equal") // nolint + } + return 1 +} + +var d = &ChannelData{} + +func FuzzChannelData(data []byte) int { + d.Reset() + if b := bin.Uint16(data[0:4]); b > 20000 { + bin.PutUint16(data[0:4], MinChannelNumber-1) + } else if b > 40000 { + bin.PutUint16(data[0:4], MinChannelNumber+(MaxChannelNumber-MinChannelNumber)%b) + } + d.Raw = append(d.Raw, data...) + if d.Decode() != nil { + return 0 + } + d2 := &ChannelData{} + d.Encode() + if !d.Number.Valid() { + return 1 + } + d2.Raw = d.Raw + if err := d2.Decode(); err != nil { + panic(err) //nolint + } + return 1 +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/lifetime.go b/vendor/github.com/pion/turn/v2/internal/proto/lifetime.go new file mode 100644 index 0000000..b781696 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/lifetime.go @@ -0,0 +1,52 @@ +package proto + +import ( + "encoding/binary" + "time" + + "github.com/pion/stun" +) + +// DefaultLifetime in RFC 5766 is 10 minutes. +// +// RFC 5766 Section 2.2 +const DefaultLifetime = time.Minute * 10 + +// Lifetime represents LIFETIME attribute. +// +// The LIFETIME attribute represents the duration for which the server +// will maintain an allocation in the absence of a refresh. The value +// portion of this attribute is 4-bytes long and consists of a 32-bit +// unsigned integral value representing the number of seconds remaining +// until expiration. +// +// RFC 5766 Section 14.2 +type Lifetime struct { + time.Duration +} + +// uint32 seconds +const lifetimeSize = 4 // 4 bytes, 32 bits + +// AddTo adds LIFETIME to message. +func (l Lifetime) AddTo(m *stun.Message) error { + v := make([]byte, lifetimeSize) + binary.BigEndian.PutUint32(v, uint32(l.Seconds())) + m.Add(stun.AttrLifetime, v) + return nil +} + +// GetFrom decodes LIFETIME from message. +func (l *Lifetime) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrLifetime) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrLifetime, len(v), lifetimeSize); err != nil { + return err + } + _ = v[lifetimeSize-1] // asserting length + seconds := binary.BigEndian.Uint32(v) + l.Duration = time.Second * time.Duration(seconds) + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/peeraddr.go b/vendor/github.com/pion/turn/v2/internal/proto/peeraddr.go new file mode 100644 index 0000000..b357b82 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/peeraddr.go @@ -0,0 +1,42 @@ +package proto + +import ( + "net" + + "github.com/pion/stun" +) + +// PeerAddress implements XOR-PEER-ADDRESS attribute. +// +// The XOR-PEER-ADDRESS specifies the address and port of the peer as +// seen from the TURN server. (For example, the peer's server-reflexive +// transport address if the peer is behind a NAT.) +// +// RFC 5766 Section 14.3 +type PeerAddress struct { + IP net.IP + Port int +} + +func (a PeerAddress) String() string { + return stun.XORMappedAddress(a).String() +} + +// AddTo adds XOR-PEER-ADDRESS to message. +func (a PeerAddress) AddTo(m *stun.Message) error { + return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORPeerAddress) +} + +// GetFrom decodes XOR-PEER-ADDRESS from message. +func (a *PeerAddress) GetFrom(m *stun.Message) error { + return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORPeerAddress) +} + +// XORPeerAddress implements XOR-PEER-ADDRESS attribute. +// +// The XOR-PEER-ADDRESS specifies the address and port of the peer as +// seen from the TURN server. (For example, the peer's server-reflexive +// transport address if the peer is behind a NAT.) +// +// RFC 5766 Section 14.3 +type XORPeerAddress = PeerAddress diff --git a/vendor/github.com/pion/turn/v2/internal/proto/proto.go b/vendor/github.com/pion/turn/v2/internal/proto/proto.go new file mode 100644 index 0000000..4b08c76 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/proto.go @@ -0,0 +1,30 @@ +// Package proto implements RFC 5766 Traversal Using Relays around NAT. +// +// Merged from gortc/turn v0.80. +package proto + +import ( + "github.com/pion/stun" +) + +// Default ports for TURN from RFC 5766 Section 4. +const ( + // DefaultPort for TURN is same as STUN. + DefaultPort = stun.DefaultPort + // DefaultTLSPort is for TURN over TLS and is same as STUN. + DefaultTLSPort = stun.DefaultTLSPort +) + +// CreatePermissionRequest is shorthand for create permission request type. +func CreatePermissionRequest() stun.MessageType { + return stun.NewType(stun.MethodCreatePermission, stun.ClassRequest) +} + +// AllocateRequest is shorthand for allocation request message type. +func AllocateRequest() stun.MessageType { return stun.NewType(stun.MethodAllocate, stun.ClassRequest) } + +// SendIndication is shorthand for send indication message type. +func SendIndication() stun.MessageType { return stun.NewType(stun.MethodSend, stun.ClassIndication) } + +// RefreshRequest is shorthand for refresh request message type. +func RefreshRequest() stun.MessageType { return stun.NewType(stun.MethodRefresh, stun.ClassRequest) } diff --git a/vendor/github.com/pion/turn/v2/internal/proto/relayedaddr.go b/vendor/github.com/pion/turn/v2/internal/proto/relayedaddr.go new file mode 100644 index 0000000..2169cb7 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/relayedaddr.go @@ -0,0 +1,40 @@ +package proto + +import ( + "net" + + "github.com/pion/stun" +) + +// RelayedAddress implements XOR-RELAYED-ADDRESS attribute. +// +// It specifies the address and port that the server allocated to the +// client. It is encoded in the same way as XOR-MAPPED-ADDRESS. +// +// RFC 5766 Section 14.5 +type RelayedAddress struct { + IP net.IP + Port int +} + +func (a RelayedAddress) String() string { + return stun.XORMappedAddress(a).String() +} + +// AddTo adds XOR-PEER-ADDRESS to message. +func (a RelayedAddress) AddTo(m *stun.Message) error { + return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORRelayedAddress) +} + +// GetFrom decodes XOR-PEER-ADDRESS from message. +func (a *RelayedAddress) GetFrom(m *stun.Message) error { + return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORRelayedAddress) +} + +// XORRelayedAddress implements XOR-RELAYED-ADDRESS attribute. +// +// It specifies the address and port that the server allocated to the +// client. It is encoded in the same way as XOR-MAPPED-ADDRESS. +// +// RFC 5766 Section 14.5 +type XORRelayedAddress = RelayedAddress diff --git a/vendor/github.com/pion/turn/v2/internal/proto/reqfamily.go b/vendor/github.com/pion/turn/v2/internal/proto/reqfamily.go new file mode 100644 index 0000000..e83d6bb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/reqfamily.go @@ -0,0 +1,61 @@ +package proto + +import ( + "errors" + + "github.com/pion/stun" +) + +// RequestedAddressFamily represents the REQUESTED-ADDRESS-FAMILY Attribute as +// defined in RFC 6156 Section 4.1.1. +type RequestedAddressFamily byte + +const requestedFamilySize = 4 + +var errInvalidRequestedFamilyValue = errors.New("invalid value for requested family attribute") + +// GetFrom decodes REQUESTED-ADDRESS-FAMILY from message. +func (f *RequestedAddressFamily) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrRequestedAddressFamily) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrRequestedAddressFamily, len(v), requestedFamilySize); err != nil { + return err + } + switch v[0] { + case byte(RequestedFamilyIPv4), byte(RequestedFamilyIPv6): + *f = RequestedAddressFamily(v[0]) + default: + return errInvalidRequestedFamilyValue + } + return nil +} + +func (f RequestedAddressFamily) String() string { + switch f { + case RequestedFamilyIPv4: + return "IPv4" + case RequestedFamilyIPv6: + return "IPv6" + default: + return "unknown" + } +} + +// AddTo adds REQUESTED-ADDRESS-FAMILY to message. +func (f RequestedAddressFamily) AddTo(m *stun.Message) error { + v := make([]byte, requestedFamilySize) + v[0] = byte(f) + // b[1:4] is RFFU = 0. + // The RFFU field MUST be set to zero on transmission and MUST be + // ignored on reception. It is reserved for future uses. + m.Add(stun.AttrRequestedAddressFamily, v) + return nil +} + +// Values for RequestedAddressFamily as defined in RFC 6156 Section 4.1.1. +const ( + RequestedFamilyIPv4 RequestedAddressFamily = 0x01 + RequestedFamilyIPv6 RequestedAddressFamily = 0x02 +) diff --git a/vendor/github.com/pion/turn/v2/internal/proto/reqtrans.go b/vendor/github.com/pion/turn/v2/internal/proto/reqtrans.go new file mode 100644 index 0000000..a4e4863 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/reqtrans.go @@ -0,0 +1,65 @@ +package proto + +import ( + "strconv" + + "github.com/pion/stun" +) + +// Protocol is IANA assigned protocol number. +type Protocol byte + +const ( + // ProtoUDP is IANA assigned protocol number for UDP. + ProtoUDP Protocol = 17 +) + +func (p Protocol) String() string { + switch p { + case ProtoUDP: + return "UDP" + default: + return strconv.Itoa(int(p)) + } +} + +// RequestedTransport represents REQUESTED-TRANSPORT attribute. +// +// This attribute is used by the client to request a specific transport +// protocol for the allocated transport address. RFC 5766 only allows the use of +// codepoint 17 (User Datagram Protocol). +// +// RFC 5766 Section 14.7 +type RequestedTransport struct { + Protocol Protocol +} + +func (t RequestedTransport) String() string { + return "protocol: " + t.Protocol.String() +} + +const requestedTransportSize = 4 + +// AddTo adds REQUESTED-TRANSPORT to message. +func (t RequestedTransport) AddTo(m *stun.Message) error { + v := make([]byte, requestedTransportSize) + v[0] = byte(t.Protocol) + // b[1:4] is RFFU = 0. + // The RFFU field MUST be set to zero on transmission and MUST be + // ignored on reception. It is reserved for future uses. + m.Add(stun.AttrRequestedTransport, v) + return nil +} + +// GetFrom decodes REQUESTED-TRANSPORT from message. +func (t *RequestedTransport) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrRequestedTransport) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrRequestedTransport, len(v), requestedTransportSize); err != nil { + return err + } + t.Protocol = Protocol(v[0]) + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/rsrvtoken.go b/vendor/github.com/pion/turn/v2/internal/proto/rsrvtoken.go new file mode 100644 index 0000000..6e2ed4d --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/rsrvtoken.go @@ -0,0 +1,39 @@ +package proto + +import "github.com/pion/stun" + +// ReservationToken represents RESERVATION-TOKEN attribute. +// +// The RESERVATION-TOKEN attribute contains a token that uniquely +// identifies a relayed transport address being held in reserve by the +// server. The server includes this attribute in a success response to +// tell the client about the token, and the client includes this +// attribute in a subsequent Allocate request to request the server use +// that relayed transport address for the allocation. +// +// RFC 5766 Section 14.9 +type ReservationToken []byte + +const reservationTokenSize = 8 // 8 bytes + +// AddTo adds RESERVATION-TOKEN to message. +func (t ReservationToken) AddTo(m *stun.Message) error { + if err := stun.CheckSize(stun.AttrReservationToken, len(t), reservationTokenSize); err != nil { + return err + } + m.Add(stun.AttrReservationToken, t) + return nil +} + +// GetFrom decodes RESERVATION-TOKEN from message. +func (t *ReservationToken) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrReservationToken) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrReservationToken, len(v), reservationTokenSize); err != nil { + return err + } + *t = v + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/errors.go b/vendor/github.com/pion/turn/v2/internal/server/errors.go new file mode 100644 index 0000000..13f8ee1 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/errors.go @@ -0,0 +1,26 @@ +package server + +import "errors" + +var ( + errFailedToGenerateNonce = errors.New("failed to generate nonce") + errFailedToSendError = errors.New("failed to send error message") + errDuplicatedNonce = errors.New("duplicated Nonce generated, discarding request") + errNoSuchUser = errors.New("no such user exists") + errUnexpectedClass = errors.New("unexpected class") + errUnexpectedMethod = errors.New("unexpected method") + errFailedToHandle = errors.New("failed to handle") + errUnhandledSTUNPacket = errors.New("unhandled STUN packet") + errUnableToHandleChannelData = errors.New("unable to handle ChannelData") + errFailedToCreateSTUNPacket = errors.New("failed to create stun message from packet") + errFailedToCreateChannelData = errors.New("failed to create channel data from packet") + errRelayAlreadyAllocatedForFiveTuple = errors.New("relay already allocated for 5-TUPLE") + errRequestedTransportMustBeUDP = errors.New("RequestedTransport must be UDP") + errNoDontFragmentSupport = errors.New("no support for DONT-FRAGMENT") + errRequestWithReservationTokenAndEvenPort = errors.New("Request must not contain RESERVATION-TOKEN and EVEN-PORT") + errNoAllocationFound = errors.New("no allocation found") + errNoPermission = errors.New("unable to handle send-indication, no permission added") + errShortWrite = errors.New("packet write smaller than packet") + errNoSuchChannelBind = errors.New("no such channel bind") + errFailedWriteSocket = errors.New("failed writing to socket") +) diff --git a/vendor/github.com/pion/turn/v2/internal/server/server.go b/vendor/github.com/pion/turn/v2/internal/server/server.go new file mode 100644 index 0000000..27f3758 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/server.go @@ -0,0 +1,109 @@ +// Package server implements the private API to implement a TURN server +package server + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/proto" +) + +// Request contains all the state needed to process a single incoming datagram +type Request struct { + // Current Request State + Conn net.PacketConn + SrcAddr net.Addr + Buff []byte + + // Server State + AllocationManager *allocation.Manager + Nonces *sync.Map + + // User Configuration + AuthHandler func(username string, realm string, srcAddr net.Addr) (key []byte, ok bool) + Log logging.LeveledLogger + Realm string + ChannelBindTimeout time.Duration +} + +// HandleRequest processes the give Request +func HandleRequest(r Request) error { + r.Log.Debugf("received %d bytes of udp from %s on %s", len(r.Buff), r.SrcAddr.String(), r.Conn.LocalAddr().String()) + + if proto.IsChannelData(r.Buff) { + return handleDataPacket(r) + } + + return handleTURNPacket(r) +} + +func handleDataPacket(r Request) error { + r.Log.Debugf("received DataPacket from %s", r.SrcAddr.String()) + c := proto.ChannelData{Raw: r.Buff} + if err := c.Decode(); err != nil { + return fmt.Errorf("%w: %v", errFailedToCreateChannelData, err) + } + + err := handleChannelData(r, &c) + if err != nil { + err = fmt.Errorf("%w from %v: %v", errUnableToHandleChannelData, r.SrcAddr, err) + } + + return err +} + +func handleTURNPacket(r Request) error { + r.Log.Debug("handleTURNPacket") + m := &stun.Message{Raw: append([]byte{}, r.Buff...)} + if err := m.Decode(); err != nil { + return fmt.Errorf("%w: %v", errFailedToCreateSTUNPacket, err) + } + + h, err := getMessageHandler(m.Type.Class, m.Type.Method) + if err != nil { + return fmt.Errorf("%w %v-%v from %v: %v", errUnhandledSTUNPacket, m.Type.Method, m.Type.Class, r.SrcAddr, err) + } + + err = h(r, m) + if err != nil { + return fmt.Errorf("%w %v-%v from %v: %v", errFailedToHandle, m.Type.Method, m.Type.Class, r.SrcAddr, err) + } + + return nil +} + +func getMessageHandler(class stun.MessageClass, method stun.Method) (func(r Request, m *stun.Message) error, error) { + switch class { + case stun.ClassIndication: + switch method { + case stun.MethodSend: + return handleSendIndication, nil + default: + return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method) + } + + case stun.ClassRequest: + switch method { + case stun.MethodAllocate: + return handleAllocateRequest, nil + case stun.MethodRefresh: + return handleRefreshRequest, nil + case stun.MethodCreatePermission: + return handleCreatePermissionRequest, nil + case stun.MethodChannelBind: + return handleChannelBindRequest, nil + case stun.MethodBinding: + return handleBindingRequest, nil + default: + return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method) + } + + default: + return nil, fmt.Errorf("%w: %s", errUnexpectedClass, class) + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/stun.go b/vendor/github.com/pion/turn/v2/internal/server/stun.go new file mode 100644 index 0000000..673e99f --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/stun.go @@ -0,0 +1,22 @@ +package server + +import ( + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/ipnet" +) + +func handleBindingRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received BindingRequest from %s", r.SrcAddr.String()) + + ip, port, err := ipnet.AddrIPPort(r.SrcAddr) + if err != nil { + return err + } + + attrs := buildMsg(m.TransactionID, stun.BindingSuccess, &stun.XORMappedAddress{ + IP: ip, + Port: port, + }, stun.Fingerprint) + + return buildAndSend(r.Conn, r.SrcAddr, attrs...) +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/turn.go b/vendor/github.com/pion/turn/v2/internal/server/turn.go new file mode 100644 index 0000000..cbac09c --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/turn.go @@ -0,0 +1,352 @@ +package server + +import ( + "fmt" + "net" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/ipnet" + "github.com/pion/turn/v2/internal/proto" +) + +// // https://tools.ietf.org/html/rfc5766#section-6.2 +func handleAllocateRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received AllocateRequest from %s", r.SrcAddr.String()) + + // 1. The server MUST require that the request be authenticated. This + // authentication MUST be done using the long-term credential + // mechanism of [https://tools.ietf.org/html/rfc5389#section-10.2.2] + // unless the client and server agree to use another mechanism through + // some procedure outside the scope of this document. + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodAllocate) + if !hasAuth { + return err + } + + fiveTuple := &allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + } + requestedPort := 0 + reservationToken := "" + + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + insufficentCapacityMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeInsufficientCapacity}) + + // 2. The server checks if the 5-tuple is currently in use by an + // existing allocation. If yes, the server rejects the request with + // a 437 (Allocation Mismatch) error. + if alloc := r.AllocationManager.GetAllocation(fiveTuple); alloc != nil { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeAllocMismatch}) + return buildAndSendErr(r.Conn, r.SrcAddr, errRelayAlreadyAllocatedForFiveTuple, msg...) + } + + // 3. The server checks if the request contains a REQUESTED-TRANSPORT + // attribute. If the REQUESTED-TRANSPORT attribute is not included + // or is malformed, the server rejects the request with a 400 (Bad + // Request) error. Otherwise, if the attribute is included but + // specifies a protocol other that UDP, the server rejects the + // request with a 442 (Unsupported Transport Protocol) error. + var requestedTransport proto.RequestedTransport + if err = requestedTransport.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } else if requestedTransport.Protocol != proto.ProtoUDP { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnsupportedTransProto}) + return buildAndSendErr(r.Conn, r.SrcAddr, errRequestedTransportMustBeUDP, msg...) + } + + // 4. The request may contain a DONT-FRAGMENT attribute. If it does, + // but the server does not support sending UDP datagrams with the DF + // bit set to 1 (see Section 12), then the server treats the DONT- + // FRAGMENT attribute in the Allocate request as an unknown + // comprehension-required attribute. + if m.Contains(stun.AttrDontFragment) { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnknownAttribute}, &stun.UnknownAttributes{stun.AttrDontFragment}) + return buildAndSendErr(r.Conn, r.SrcAddr, errNoDontFragmentSupport, msg...) + } + + // 5. The server checks if the request contains a RESERVATION-TOKEN + // attribute. If yes, and the request also contains an EVEN-PORT + // attribute, then the server rejects the request with a 400 (Bad + // Request) error. Otherwise, it checks to see if the token is + // valid (i.e., the token is in range and has not expired and the + // corresponding relayed transport address is still available). If + // the token is not valid for some reason, the server rejects the + // request with a 508 (Insufficient Capacity) error. + var reservationTokenAttr proto.ReservationToken + if err = reservationTokenAttr.GetFrom(m); err == nil { + var evenPort proto.EvenPort + if err = evenPort.GetFrom(m); err == nil { + return buildAndSendErr(r.Conn, r.SrcAddr, errRequestWithReservationTokenAndEvenPort, badRequestMsg...) + } + } + + // 6. The server checks if the request contains an EVEN-PORT attribute. + // If yes, then the server checks that it can satisfy the request + // (i.e., can allocate a relayed transport address as described + // below). If the server cannot satisfy the request, then the + // server rejects the request with a 508 (Insufficient Capacity) + // error. + var evenPort proto.EvenPort + if err = evenPort.GetFrom(m); err == nil { + randomPort := 0 + randomPort, err = r.AllocationManager.GetRandomEvenPort() + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...) + } + requestedPort = randomPort + reservationToken = randSeq(8) + } + + // 7. At any point, the server MAY choose to reject the request with a + // 486 (Allocation Quota Reached) error if it feels the client is + // trying to exceed some locally defined allocation quota. The + // server is free to define this allocation quota any way it wishes, + // but SHOULD define it based on the username used to authenticate + // the request, and not on the client's transport address. + + // 8. Also at any point, the server MAY choose to reject the request + // with a 300 (Try Alternate) error if it wishes to redirect the + // client to a different server. The use of this error code and + // attribute follow the specification in [RFC5389]. + lifetimeDuration := allocationLifeTime(m) + a, err := r.AllocationManager.CreateAllocation( + fiveTuple, + r.Conn, + requestedPort, + lifetimeDuration) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...) + } + + // Once the allocation is created, the server replies with a success + // response. The success response contains: + // * An XOR-RELAYED-ADDRESS attribute containing the relayed transport + // address. + // * A LIFETIME attribute containing the current value of the time-to- + // expiry timer. + // * A RESERVATION-TOKEN attribute (if a second relayed transport + // address was reserved). + // * An XOR-MAPPED-ADDRESS attribute containing the client's IP address + // and port (from the 5-tuple). + + srcIP, srcPort, err := ipnet.AddrIPPort(r.SrcAddr) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + relayIP, relayPort, err := ipnet.AddrIPPort(a.RelayAddr) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + responseAttrs := []stun.Setter{ + &proto.RelayedAddress{ + IP: relayIP, + Port: relayPort, + }, + &proto.Lifetime{ + Duration: lifetimeDuration, + }, + &stun.XORMappedAddress{ + IP: srcIP, + Port: srcPort, + }, + } + + if reservationToken != "" { + r.AllocationManager.CreateReservation(reservationToken, relayPort) + responseAttrs = append(responseAttrs, proto.ReservationToken([]byte(reservationToken))) + } + + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse), append(responseAttrs, messageIntegrity)...) + return buildAndSend(r.Conn, r.SrcAddr, msg...) +} + +func handleRefreshRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received RefreshRequest from %s", r.SrcAddr.String()) + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodRefresh) + if !hasAuth { + return err + } + + lifetimeDuration := allocationLifeTime(m) + fiveTuple := &allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + } + + if lifetimeDuration != 0 { + a := r.AllocationManager.GetAllocation(fiveTuple) + + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + a.Refresh(lifetimeDuration) + } else { + r.AllocationManager.DeleteAllocation(fiveTuple) + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodRefresh, stun.ClassSuccessResponse), []stun.Setter{ + &proto.Lifetime{ + Duration: lifetimeDuration, + }, + messageIntegrity, + }...)...) +} + +func handleCreatePermissionRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received CreatePermission from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodCreatePermission) + if !hasAuth { + return err + } + + addCount := 0 + + if err := m.ForEach(stun.AttrXORPeerAddress, func(m *stun.Message) error { + var peerAddress proto.PeerAddress + if err := peerAddress.GetFrom(m); err != nil { + return err + } + + r.Log.Debugf("adding permission for %s", fmt.Sprintf("%s:%d", + peerAddress.IP.String(), peerAddress.Port)) + a.AddPermission(allocation.NewPermission( + &net.UDPAddr{ + IP: peerAddress.IP, + Port: peerAddress.Port, + }, + r.Log, + )) + addCount++ + return nil + }); err != nil { + addCount = 0 + } + + respClass := stun.ClassSuccessResponse + if addCount == 0 { + respClass = stun.ClassErrorResponse + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodCreatePermission, respClass), []stun.Setter{messageIntegrity}...)...) +} + +func handleSendIndication(r Request, m *stun.Message) error { + r.Log.Debugf("received SendIndication from %s", r.SrcAddr.String()) + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + dataAttr := proto.Data{} + if err := dataAttr.GetFrom(m); err != nil { + return err + } + + peerAddress := proto.PeerAddress{} + if err := peerAddress.GetFrom(m); err != nil { + return err + } + + msgDst := &net.UDPAddr{IP: peerAddress.IP, Port: peerAddress.Port} + if perm := a.GetPermission(msgDst); perm == nil { + return fmt.Errorf("%w: %v", errNoPermission, msgDst) + } + + l, err := a.RelaySocket.WriteTo(dataAttr, msgDst) + if l != len(dataAttr) { + return fmt.Errorf("%w %d != %d (expected) err: %v", errShortWrite, l, len(dataAttr), err) + } + return err +} + +func handleChannelBindRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received ChannelBindRequest from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodChannelBind) + if !hasAuth { + return err + } + + var channel proto.ChannelNumber + if err = channel.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + peerAddr := proto.PeerAddress{} + if err = peerAddr.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + r.Log.Debugf("binding channel %d to %s", + channel, + fmt.Sprintf("%s:%d", peerAddr.IP.String(), peerAddr.Port)) + err = a.AddChannelBind(allocation.NewChannelBind( + channel, + &net.UDPAddr{IP: peerAddr.IP, Port: peerAddr.Port}, + r.Log, + ), r.ChannelBindTimeout) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse), []stun.Setter{messageIntegrity}...)...) +} + +func handleChannelData(r Request, c *proto.ChannelData) error { + r.Log.Debugf("received ChannelData from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + channel := a.GetChannelByNumber(c.Number) + if channel == nil { + return fmt.Errorf("%w %x", errNoSuchChannelBind, uint16(c.Number)) + } + + l, err := a.RelaySocket.WriteTo(c.Data, channel.Peer) + if err != nil { + return fmt.Errorf("%w: %s", errFailedWriteSocket, err.Error()) + } else if l != len(c.Data) { + return fmt.Errorf("%w %d != %d (expected)", errShortWrite, l, len(c.Data)) + } + + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/util.go b/vendor/github.com/pion/turn/v2/internal/server/util.go new file mode 100644 index 0000000..c6baffb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/util.go @@ -0,0 +1,133 @@ +package server + +import ( + "crypto/md5" //nolint:gosec,gci + "fmt" + "io" + "math/rand" + "net" + "strconv" + "time" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/proto" +) + +const ( + maximumAllocationLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-6.2 defines 3600 seconds recommendation + nonceLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-4 + +) + +func randSeq(n int) string { + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] //nolint:gosec + } + return string(b) +} + +func buildNonce() (string, error) { + /* #nosec */ + h := md5.New() + if _, err := io.WriteString(h, strconv.FormatInt(time.Now().Unix(), 10)); err != nil { + return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err) + } + if _, err := io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)); err != nil { //nolint:gosec + return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err) + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + +func buildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error { + msg, err := stun.Build(attrs...) + if err != nil { + return err + } + _, err = conn.WriteTo(msg.Raw, dst) + return err +} + +// Send a STUN packet and return the original error to the caller +func buildAndSendErr(conn net.PacketConn, dst net.Addr, err error, attrs ...stun.Setter) error { + if sendErr := buildAndSend(conn, dst, attrs...); sendErr != nil { + err = fmt.Errorf("%w %v %v", errFailedToSendError, sendErr, err) + } + return err +} + +func buildMsg(transactionID [stun.TransactionIDSize]byte, msgType stun.MessageType, additional ...stun.Setter) []stun.Setter { + return append([]stun.Setter{&stun.Message{TransactionID: transactionID}, msgType}, additional...) +} + +func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method) (stun.MessageIntegrity, bool, error) { + respondWithNonce := func(responseCode stun.ErrorCode) (stun.MessageIntegrity, bool, error) { + nonce, err := buildNonce() + if err != nil { + return nil, false, err + } + + // Nonce has already been taken + if _, keyCollision := r.Nonces.LoadOrStore(nonce, time.Now()); keyCollision { + return nil, false, errDuplicatedNonce + } + + return nil, false, buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, + stun.NewType(callingMethod, stun.ClassErrorResponse), + &stun.ErrorCodeAttribute{Code: responseCode}, + stun.NewNonce(nonce), + stun.NewRealm(r.Realm), + )...) + } + + if !m.Contains(stun.AttrMessageIntegrity) { + return respondWithNonce(stun.CodeUnauthorized) + } + + nonceAttr := &stun.Nonce{} + usernameAttr := &stun.Username{} + realmAttr := &stun.Realm{} + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + + if err := nonceAttr.GetFrom(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + // Assert Nonce exists and is not expired + nonceCreationTime, ok := r.Nonces.Load(string(*nonceAttr)) + if !ok || time.Since(nonceCreationTime.(time.Time)) >= nonceLifetime { + r.Nonces.Delete(nonceAttr) + return respondWithNonce(stun.CodeStaleNonce) + } + + if err := realmAttr.GetFrom(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } else if err := usernameAttr.GetFrom(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + ourKey, ok := r.AuthHandler(usernameAttr.String(), realmAttr.String(), r.SrcAddr) + if !ok { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, fmt.Errorf("%w %s", errNoSuchUser, usernameAttr.String()), badRequestMsg...) + } + + if err := stun.MessageIntegrity(ourKey).Check(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + return stun.MessageIntegrity(ourKey), true, nil +} + +func allocationLifeTime(m *stun.Message) time.Duration { + lifetimeDuration := proto.DefaultLifetime + + var lifetime proto.Lifetime + if err := lifetime.GetFrom(m); err == nil { + if lifetime.Duration < maximumAllocationLifetime { + lifetimeDuration = lifetime.Duration + } + } + + return lifetimeDuration +} diff --git a/vendor/github.com/pion/turn/v2/lt_cred.go b/vendor/github.com/pion/turn/v2/lt_cred.go new file mode 100644 index 0000000..07520b1 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/lt_cred.go @@ -0,0 +1,56 @@ +package turn + +import ( //nolint:gci + "crypto/hmac" + "crypto/sha1" //nolint:gosec,gci + "encoding/base64" + "net" + "strconv" + "time" + + "github.com/pion/logging" +) + +// GenerateCredentials can be used to create credentials valid for [duration] time +func GenerateLongTermCredentials(sharedSecret string, duration time.Duration) (string, string, error) { + t := time.Now().Add(duration).Unix() + username := strconv.FormatInt(t, 10) + password, err := longTermCredentials(username, sharedSecret) + return username, password, err +} + +func longTermCredentials(username string, sharedSecret string) (string, error) { + mac := hmac.New(sha1.New, []byte(sharedSecret)) + _, err := mac.Write([]byte(username)) + if err != nil { + return "", err // Not sure if this will ever happen + } + password := mac.Sum(nil) + return base64.StdEncoding.EncodeToString(password), nil +} + +// NewAuthHandler returns a turn.AuthAuthHandler used with Long Term (or Time Windowed) Credentials. +// https://tools.ietf.org/search/rfc5389#section-10.2 +func NewLongTermAuthHandler(sharedSecret string, l logging.LeveledLogger) AuthHandler { + if l == nil { + l = logging.NewDefaultLoggerFactory().NewLogger("turn") + } + return func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { + l.Tracef("Authentication username=%q realm=%q srcAddr=%v\n", username, realm, srcAddr) + t, err := strconv.Atoi(username) + if err != nil { + l.Errorf("Invalid time-windowed username %q", username) + return nil, false + } + if int64(t) < time.Now().Unix() { + l.Errorf("Expired time-windowed username %q", username) + return nil, false + } + password, err := longTermCredentials(username, sharedSecret) + if err != nil { + l.Error(err.Error()) + return nil, false + } + return GenerateAuthKey(username, realm, password), true + } +} diff --git a/vendor/github.com/pion/turn/v2/relay_address_generator_none.go b/vendor/github.com/pion/turn/v2/relay_address_generator_none.go new file mode 100644 index 0000000..6fab061 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/relay_address_generator_none.go @@ -0,0 +1,45 @@ +package turn + +import ( + "net" + "strconv" + + "github.com/pion/transport/vnet" +) + +// RelayAddressGeneratorNone returns the listener with no modifications +type RelayAddressGeneratorNone struct { + // Address is passed to Listen/ListenPacket when creating the Relay + Address string + + Net *vnet.Net +} + +// Validate is caled on server startup and confirms the RelayAddressGenerator is properly configured +func (r *RelayAddressGeneratorNone) Validate() error { + if r.Net == nil { + r.Net = vnet.NewNet(nil) + } + + switch { + case r.Address == "": + return errListeningAddressInvalid + default: + return nil + } +} + +// AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorNone) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + conn, err := r.Net.ListenPacket(network, r.Address+":"+strconv.Itoa(requestedPort)) + if err != nil { + return nil, nil, err + } + + return conn, conn.LocalAddr(), nil +} + +// AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorNone) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { + return nil, nil, errTODO +} diff --git a/vendor/github.com/pion/turn/v2/relay_address_generator_range.go b/vendor/github.com/pion/turn/v2/relay_address_generator_range.go new file mode 100644 index 0000000..9f95429 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/relay_address_generator_range.go @@ -0,0 +1,92 @@ +package turn + +import ( + "fmt" + "net" + + "github.com/pion/randutil" + "github.com/pion/transport/vnet" +) + +// RelayAddressGeneratorPortRange can be used to only allocate connections inside a defined port range. +// Similar to the RelayAddressGeneratorStatic a static ip address can be set. +type RelayAddressGeneratorPortRange struct { + // RelayAddress is the IP returned to the user when the relay is created + RelayAddress net.IP + + // MinPort the minimum port to allocate + MinPort uint16 + // MaxPort the maximum (inclusive) port to allocate + MaxPort uint16 + + // MaxRetries the amount of tries to allocate a random port in the defined range + MaxRetries int + + // Rand the random source of numbers + Rand randutil.MathRandomGenerator + + // Address is passed to Listen/ListenPacket when creating the Relay + Address string + + Net *vnet.Net +} + +// Validate is called on server startup and confirms the RelayAddressGenerator is properly configured +func (r *RelayAddressGeneratorPortRange) Validate() error { + if r.Net == nil { + r.Net = vnet.NewNet(nil) + } + + if r.Rand == nil { + r.Rand = randutil.NewMathRandomGenerator() + } + + if r.MaxRetries == 0 { + r.MaxRetries = 10 + } + + switch { + case r.MinPort == 0: + return errMinPortNotZero + case r.MaxPort == 0: + return errMaxPortNotZero + case r.RelayAddress == nil: + return errRelayAddressInvalid + case r.Address == "": + return errListeningAddressInvalid + default: + return nil + } +} + +// AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorPortRange) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + if requestedPort != 0 { + conn, err := r.Net.ListenPacket(network, fmt.Sprintf("%s:%d", r.Address, requestedPort)) + if err != nil { + return nil, nil, err + } + relayAddr := conn.LocalAddr().(*net.UDPAddr) + relayAddr.IP = r.RelayAddress + return conn, relayAddr, nil + } + + for try := 0; try < r.MaxRetries; try++ { + port := r.MinPort + uint16(r.Rand.Intn(int((r.MaxPort+1)-r.MinPort))) + conn, err := r.Net.ListenPacket(network, fmt.Sprintf("%s:%d", r.Address, port)) + if err != nil { + continue + } + + relayAddr := conn.LocalAddr().(*net.UDPAddr) + relayAddr.IP = r.RelayAddress + return conn, relayAddr, nil + } + + return nil, nil, errMaxRetriesExceeded +} + +// AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorPortRange) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { + return nil, nil, errTODO +} diff --git a/vendor/github.com/pion/turn/v2/relay_address_generator_static.go b/vendor/github.com/pion/turn/v2/relay_address_generator_static.go new file mode 100644 index 0000000..ae921e7 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/relay_address_generator_static.go @@ -0,0 +1,55 @@ +package turn + +import ( + "net" + "strconv" + + "github.com/pion/transport/vnet" +) + +// RelayAddressGeneratorStatic can be used to return static IP address each time a relay is created. +// This can be used when you have a single static IP address that you want to use +type RelayAddressGeneratorStatic struct { + // RelayAddress is the IP returned to the user when the relay is created + RelayAddress net.IP + + // Address is passed to Listen/ListenPacket when creating the Relay + Address string + + Net *vnet.Net +} + +// Validate is caled on server startup and confirms the RelayAddressGenerator is properly configured +func (r *RelayAddressGeneratorStatic) Validate() error { + if r.Net == nil { + r.Net = vnet.NewNet(nil) + } + + switch { + case r.RelayAddress == nil: + return errRelayAddressInvalid + case r.Address == "": + return errListeningAddressInvalid + default: + return nil + } +} + +// AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorStatic) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + conn, err := r.Net.ListenPacket(network, r.Address+":"+strconv.Itoa(requestedPort)) + if err != nil { + return nil, nil, err + } + + // Replace actual listening IP with the user requested one of RelayAddressGeneratorStatic + relayAddr := conn.LocalAddr().(*net.UDPAddr) + relayAddr.IP = r.RelayAddress + + return conn, relayAddr, nil +} + +// AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorStatic) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { + return nil, nil, errTODO +} diff --git a/vendor/github.com/pion/turn/v2/renovate.json b/vendor/github.com/pion/turn/v2/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/turn/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/turn/v2/server.go b/vendor/github.com/pion/turn/v2/server.go new file mode 100644 index 0000000..c8deba8 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/server.go @@ -0,0 +1,161 @@ +// Package turn contains the public API for pion/turn, a toolkit for building TURN clients and servers +package turn + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/proto" + "github.com/pion/turn/v2/internal/server" +) + +const ( + inboundMTU = 1500 +) + +// Server is an instance of the Pion TURN Server +type Server struct { + log logging.LeveledLogger + authHandler AuthHandler + realm string + channelBindTimeout time.Duration + nonces *sync.Map + + packetConnConfigs []PacketConnConfig + listenerConfigs []ListenerConfig +} + +// NewServer creates the Pion TURN server +func NewServer(config ServerConfig) (*Server, error) { + if err := config.validate(); err != nil { + return nil, err + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + s := &Server{ + log: loggerFactory.NewLogger("turn"), + authHandler: config.AuthHandler, + realm: config.Realm, + channelBindTimeout: config.ChannelBindTimeout, + packetConnConfigs: config.PacketConnConfigs, + listenerConfigs: config.ListenerConfigs, + nonces: &sync.Map{}, + } + + if s.channelBindTimeout == 0 { + s.channelBindTimeout = proto.DefaultLifetime + } + + for i := range s.packetConnConfigs { + go func(p PacketConnConfig) { + allocationManager, err := allocation.NewManager(allocation.ManagerConfig{ + AllocatePacketConn: p.RelayAddressGenerator.AllocatePacketConn, + AllocateConn: p.RelayAddressGenerator.AllocateConn, + LeveledLogger: s.log, + }) + if err != nil { + s.log.Errorf("exit read loop on error: %s", err.Error()) + return + } + defer func() { + if err := allocationManager.Close(); err != nil { + s.log.Errorf("Failed to close AllocationManager: %s", err.Error()) + } + }() + + s.readLoop(p.PacketConn, allocationManager) + }(s.packetConnConfigs[i]) + } + + for _, listener := range s.listenerConfigs { + go func(l ListenerConfig) { + allocationManager, err := allocation.NewManager(allocation.ManagerConfig{ + AllocatePacketConn: l.RelayAddressGenerator.AllocatePacketConn, + AllocateConn: l.RelayAddressGenerator.AllocateConn, + LeveledLogger: s.log, + }) + if err != nil { + s.log.Errorf("exit read loop on error: %s", err.Error()) + return + } + defer func() { + if err := allocationManager.Close(); err != nil { + s.log.Errorf("Failed to close AllocationManager: %s", err.Error()) + } + }() + + for { + conn, err := l.Listener.Accept() + if err != nil { + s.log.Debugf("exit accept loop on error: %s", err.Error()) + return + } + + go s.readLoop(NewSTUNConn(conn), allocationManager) + } + }(listener) + } + + return s, nil +} + +// Close stops the TURN Server. It cleans up any associated state and closes all connections it is managing +func (s *Server) Close() error { + var errors []error + + for _, p := range s.packetConnConfigs { + if err := p.PacketConn.Close(); err != nil { + errors = append(errors, err) + } + } + + for _, l := range s.listenerConfigs { + if err := l.Listener.Close(); err != nil { + errors = append(errors, err) + } + } + + if len(errors) == 0 { + return nil + } + + err := errFailedToClose + for _, e := range errors { + err = fmt.Errorf("%s; Close error (%v) ", err.Error(), e) //nolint:goerr113 + } + + return err +} + +func (s *Server) readLoop(p net.PacketConn, allocationManager *allocation.Manager) { + buf := make([]byte, inboundMTU) + for { + n, addr, err := p.ReadFrom(buf) + if err != nil { + s.log.Debugf("exit read loop on error: %s", err.Error()) + return + } + + if err := server.HandleRequest(server.Request{ + Conn: p, + SrcAddr: addr, + Buff: buf[:n], + Log: s.log, + AuthHandler: s.authHandler, + Realm: s.realm, + AllocationManager: allocationManager, + ChannelBindTimeout: s.channelBindTimeout, + Nonces: s.nonces, + }); err != nil { + s.log.Errorf("error when handling datagram: %v", err) + } + } +} diff --git a/vendor/github.com/pion/turn/v2/server_config.go b/vendor/github.com/pion/turn/v2/server_config.go new file mode 100644 index 0000000..9ff3872 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/server_config.go @@ -0,0 +1,116 @@ +package turn + +import ( + "crypto/md5" //nolint:gosec,gci + "fmt" + "net" + "strings" + "time" + + "github.com/pion/logging" +) + +// RelayAddressGenerator is used to generate a RelayAddress when creating an allocation. +// You can use one of the provided ones or provide your own. +type RelayAddressGenerator interface { + // Validate confirms that the RelayAddressGenerator is properly initialized + Validate() error + + // Allocate a PacketConn (UDP) RelayAddress + AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) + + // Allocate a Conn (TCP) RelayAddress + AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) +} + +// PacketConnConfig is a single net.PacketConn to listen/write on. This will be used for UDP listeners +type PacketConnConfig struct { + PacketConn net.PacketConn + + // When an allocation is generated the RelayAddressGenerator + // creates the net.PacketConn and returns the IP/Port it is available at + RelayAddressGenerator RelayAddressGenerator +} + +func (c *PacketConnConfig) validate() error { + if c.PacketConn == nil { + return errConnUnset + } + if c.RelayAddressGenerator == nil { + return errRelayAddressGeneratorUnset + } + + return c.RelayAddressGenerator.Validate() +} + +// ListenerConfig is a single net.Listener to accept connections on. This will be used for TCP, TLS and DTLS listeners +type ListenerConfig struct { + Listener net.Listener + + // When an allocation is generated the RelayAddressGenerator + // creates the net.PacketConn and returns the IP/Port it is available at + RelayAddressGenerator RelayAddressGenerator +} + +func (c *ListenerConfig) validate() error { + if c.Listener == nil { + return errListenerUnset + } + + if c.RelayAddressGenerator == nil { + return errRelayAddressGeneratorUnset + } + + return c.RelayAddressGenerator.Validate() +} + +// AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior +type AuthHandler func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) + +// GenerateAuthKey is a convince function to easily generate keys in the format used by AuthHandler +func GenerateAuthKey(username, realm, password string) []byte { + // #nosec + h := md5.New() + fmt.Fprint(h, strings.Join([]string{username, realm, password}, ":")) + return h.Sum(nil) +} + +// ServerConfig configures the Pion TURN Server +type ServerConfig struct { + // PacketConnConfigs and ListenerConfigs are a list of all the turn listeners + // Each listener can have custom behavior around the creation of Relays + PacketConnConfigs []PacketConnConfig + ListenerConfigs []ListenerConfig + + // LoggerFactory must be set for logging from this server. + LoggerFactory logging.LoggerFactory + + // Realm sets the realm for this server + Realm string + + // AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior + AuthHandler AuthHandler + + // ChannelBindTimeout sets the lifetime of channel binding. Defaults to 10 minutes. + ChannelBindTimeout time.Duration +} + +func (s *ServerConfig) validate() error { + if len(s.PacketConnConfigs) == 0 && len(s.ListenerConfigs) == 0 { + return errNoAvailableConns + } + + for _, s := range s.PacketConnConfigs { + if err := s.validate(); err != nil { + return err + } + } + + for _, s := range s.ListenerConfigs { + if err := s.validate(); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/pion/turn/v2/stun_conn.go b/vendor/github.com/pion/turn/v2/stun_conn.go new file mode 100644 index 0000000..e1446bd --- /dev/null +++ b/vendor/github.com/pion/turn/v2/stun_conn.go @@ -0,0 +1,121 @@ +package turn + +import ( + "encoding/binary" + "errors" + "net" + "time" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/proto" +) + +var ( + errInvalidTURNFrame = errors.New("data is not a valid TURN frame, no STUN or ChannelData found") + errIncompleteTURNFrame = errors.New("data contains incomplete STUN or TURN frame") +) + +// STUNConn wraps a net.Conn and implements +// net.PacketConn by being STUN aware and +// packetizing the stream +type STUNConn struct { + nextConn net.Conn + buff []byte +} + +const ( + stunHeaderSize = 20 + + channelDataLengthSize = 2 + channelDataNumberSize = channelDataLengthSize + channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize + channelDataPadding = 4 +) + +// Given a buffer give the last offset of the TURN frame +// If the buffer isn't a valid STUN or ChannelData packet +// or the length doesn't match return false +func consumeSingleTURNFrame(p []byte) (int, error) { + // Too short to determine if ChannelData or STUN + if len(p) < 9 { + return 0, errIncompleteTURNFrame + } + + var datagramSize uint16 + if stun.IsMessage(p) { + datagramSize = binary.BigEndian.Uint16(p[2:4]) + stunHeaderSize + } else if num := binary.BigEndian.Uint16(p[0:4]); proto.ChannelNumber(num).Valid() { + datagramSize = binary.BigEndian.Uint16(p[channelDataNumberSize:channelDataHeaderSize]) + if paddingOverflow := (datagramSize + channelDataPadding) % channelDataPadding; paddingOverflow != 0 { + datagramSize = (datagramSize + channelDataPadding) - paddingOverflow + } + + datagramSize += channelDataHeaderSize + } else { + return 0, errInvalidTURNFrame + } + + if len(p) < int(datagramSize) { + return 0, errIncompleteTURNFrame + } + + return int(datagramSize), nil +} + +// ReadFrom implements ReadFrom from net.PacketConn +func (s *STUNConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + // First pass any buffered data from previous reads + n, err = consumeSingleTURNFrame(s.buff) + if errors.Is(err, errInvalidTURNFrame) { + return 0, nil, err + } else if err == nil { + copy(p, s.buff[:n]) + s.buff = s.buff[n:] + + return n, s.nextConn.RemoteAddr(), nil + } + + // Then read from the nextConn, appending to our buff + n, err = s.nextConn.Read(p) + if err != nil { + return 0, nil, err + } + + s.buff = append(s.buff, append([]byte{}, p[:n]...)...) + return s.ReadFrom(p) +} + +// WriteTo implements WriteTo from net.PacketConn +func (s *STUNConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return s.nextConn.Write(p) +} + +// Close implements Close from net.PacketConn +func (s *STUNConn) Close() error { + return s.nextConn.Close() +} + +// LocalAddr implements LocalAddr from net.PacketConn +func (s *STUNConn) LocalAddr() net.Addr { + return s.nextConn.LocalAddr() +} + +// SetDeadline implements SetDeadline from net.PacketConn +func (s *STUNConn) SetDeadline(t time.Time) error { + return s.nextConn.SetDeadline(t) +} + +// SetReadDeadline implements SetReadDeadline from net.PacketConn +func (s *STUNConn) SetReadDeadline(t time.Time) error { + return s.nextConn.SetReadDeadline(t) +} + +// SetWriteDeadline implements SetWriteDeadline from net.PacketConn +func (s *STUNConn) SetWriteDeadline(t time.Time) error { + return s.nextConn.SetWriteDeadline(t) +} + +// NewSTUNConn creates a STUNConn +func NewSTUNConn(nextConn net.Conn) *STUNConn { + return &STUNConn{nextConn: nextConn} +} |