直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理
Made-with: Cursor
This commit is contained in:
189
server/vendor/github.com/pion/turn/v2/internal/client/allocation.go
generated
vendored
Normal file
189
server/vendor/github.com/pion/turn/v2/internal/client/allocation.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/transport/v2"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
// AllocationConfig is a set of configuration params use by NewUDPConn and NewTCPAllocation
|
||||
type AllocationConfig struct {
|
||||
Client Client
|
||||
RelayedAddr net.Addr
|
||||
ServerAddr net.Addr
|
||||
Integrity stun.MessageIntegrity
|
||||
Nonce stun.Nonce
|
||||
Username stun.Username
|
||||
Realm stun.Realm
|
||||
Lifetime time.Duration
|
||||
Net transport.Net
|
||||
Log logging.LeveledLogger
|
||||
}
|
||||
|
||||
type allocation struct {
|
||||
client Client // Read-only
|
||||
relayedAddr net.Addr // Read-only
|
||||
serverAddr net.Addr // Read-only
|
||||
permMap *permissionMap // Thread-safe
|
||||
integrity stun.MessageIntegrity // Read-only
|
||||
username stun.Username // Read-only
|
||||
realm stun.Realm // Read-only
|
||||
_nonce stun.Nonce // Needs mutex x
|
||||
_lifetime time.Duration // Needs mutex x
|
||||
net transport.Net // Thread-safe
|
||||
refreshAllocTimer *PeriodicTimer // Thread-safe
|
||||
refreshPermsTimer *PeriodicTimer // Thread-safe
|
||||
readTimer *time.Timer // Thread-safe
|
||||
mutex sync.RWMutex // Thread-safe
|
||||
log logging.LeveledLogger // Read-only
|
||||
}
|
||||
|
||||
func (a *allocation) setNonceFromMsg(msg *stun.Message) {
|
||||
// Update nonce
|
||||
var nonce stun.Nonce
|
||||
if err := nonce.GetFrom(msg); err == nil {
|
||||
a.setNonce(nonce)
|
||||
a.log.Debug("Refresh allocation: 438, got new nonce.")
|
||||
} else {
|
||||
a.log.Warn("Refresh allocation: 438 but no nonce.")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *allocation) refreshAllocation(lifetime time.Duration, dontWait bool) error {
|
||||
msg, err := stun.Build(
|
||||
stun.TransactionID,
|
||||
stun.NewType(stun.MethodRefresh, stun.ClassRequest),
|
||||
proto.Lifetime{Duration: lifetime},
|
||||
a.username,
|
||||
a.realm,
|
||||
a.nonce(),
|
||||
a.integrity,
|
||||
stun.Fingerprint,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errFailedToBuildRefreshRequest, err.Error())
|
||||
}
|
||||
|
||||
a.log.Debugf("Send refresh request (dontWait=%v)", dontWait)
|
||||
trRes, err := a.client.PerformTransaction(msg, a.serverAddr, dontWait)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errFailedToRefreshAllocation, err.Error())
|
||||
}
|
||||
|
||||
if dontWait {
|
||||
a.log.Debug("Refresh request sent")
|
||||
return nil
|
||||
}
|
||||
|
||||
a.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 {
|
||||
a.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())
|
||||
}
|
||||
|
||||
a.setLifetime(updatedLifetime.Duration)
|
||||
a.log.Debugf("Updated lifetime: %d seconds", int(a.lifetime().Seconds()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *allocation) refreshPermissions() error {
|
||||
addrs := a.permMap.addrs()
|
||||
if len(addrs) == 0 {
|
||||
a.log.Debug("No permission to refresh")
|
||||
return nil
|
||||
}
|
||||
if err := a.CreatePermissions(addrs...); err != nil {
|
||||
if errors.Is(err, errTryAgain) {
|
||||
return errTryAgain
|
||||
}
|
||||
a.log.Errorf("Fail to refresh permissions: %s", err)
|
||||
return err
|
||||
}
|
||||
a.log.Debug("Refresh permissions successful")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *allocation) onRefreshTimers(id int) {
|
||||
a.log.Debugf("Refresh timer %d expired", id)
|
||||
switch id {
|
||||
case timerIDRefreshAlloc:
|
||||
var err error
|
||||
lifetime := a.lifetime()
|
||||
// Limit the max retries on errTryAgain to 3
|
||||
// when stale nonce returns, sencond retry should succeed
|
||||
for i := 0; i < maxRetryAttempts; i++ {
|
||||
err = a.refreshAllocation(lifetime, false)
|
||||
if !errors.Is(err, errTryAgain) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
a.log.Warnf("Failed to refresh allocation: %s", err)
|
||||
}
|
||||
case timerIDRefreshPerms:
|
||||
var err error
|
||||
for i := 0; i < maxRetryAttempts; i++ {
|
||||
err = a.refreshPermissions()
|
||||
if !errors.Is(err, errTryAgain) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
a.log.Warnf("Failed to refresh permissions: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *allocation) nonce() stun.Nonce {
|
||||
a.mutex.RLock()
|
||||
defer a.mutex.RUnlock()
|
||||
|
||||
return a._nonce
|
||||
}
|
||||
|
||||
func (a *allocation) setNonce(nonce stun.Nonce) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
a.log.Debugf("Set new nonce with %d bytes", len(nonce))
|
||||
a._nonce = nonce
|
||||
}
|
||||
|
||||
func (a *allocation) lifetime() time.Duration {
|
||||
a.mutex.RLock()
|
||||
defer a.mutex.RUnlock()
|
||||
|
||||
return a._lifetime
|
||||
}
|
||||
|
||||
func (a *allocation) setLifetime(lifetime time.Duration) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
a._lifetime = lifetime
|
||||
}
|
||||
155
server/vendor/github.com/pion/turn/v2/internal/client/binding.go
generated
vendored
Normal file
155
server/vendor/github.com/pion/turn/v2/internal/client/binding.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Channel 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)
|
||||
}
|
||||
18
server/vendor/github.com/pion/turn/v2/internal/client/client.go
generated
vendored
Normal file
18
server/vendor/github.com/pion/turn/v2/internal/client/client.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package client implements the API for a TURN client
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/pion/stun"
|
||||
)
|
||||
|
||||
// Client is an interface for the public turn.Client in order to break cyclic dependencies
|
||||
type Client interface {
|
||||
WriteTo(data []byte, to net.Addr) (int, error)
|
||||
PerformTransaction(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error)
|
||||
OnDeallocated(relayedAddr net.Addr)
|
||||
}
|
||||
43
server/vendor/github.com/pion/turn/v2/internal/client/errors.go
generated
vendored
Normal file
43
server/vendor/github.com/pion/turn/v2/internal/client/errors.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errFake = errors.New("fake error")
|
||||
errTryAgain = errors.New("try again")
|
||||
errClosed = errors.New("use of closed network connection")
|
||||
errTCPAddrCast = errors.New("addr is not a TCP address")
|
||||
errUDPAddrCast = errors.New("addr is not a UDP address")
|
||||
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")
|
||||
errInvalidTURNAddress = errors.New("invalid TURN server address")
|
||||
errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message")
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
85
server/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go
generated
vendored
Normal file
85
server/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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)
|
||||
}
|
||||
77
server/vendor/github.com/pion/turn/v2/internal/client/permission.go
generated
vendored
Normal file
77
server/vendor/github.com/pion/turn/v2/internal/client/permission.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/turn/v2/internal/ipnet"
|
||||
)
|
||||
|
||||
type permState int32
|
||||
|
||||
const (
|
||||
permStateIdle permState = iota
|
||||
permStatePermitted
|
||||
)
|
||||
|
||||
type permission struct {
|
||||
addr net.Addr
|
||||
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()
|
||||
p.addr = addr
|
||||
m.permMap[ipnet.FingerprintAddr(addr)] = p
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *permissionMap) find(addr net.Addr) (*permission, bool) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
p, ok := m.permMap[ipnet.FingerprintAddr(addr)]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
func (m *permissionMap) delete(addr net.Addr) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
delete(m.permMap, ipnet.FingerprintAddr(addr))
|
||||
}
|
||||
|
||||
func (m *permissionMap) addrs() []net.Addr {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
addrs := []net.Addr{}
|
||||
for _, p := range m.permMap {
|
||||
addrs = append(addrs, p.addr)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func newPermissionMap() *permissionMap {
|
||||
return &permissionMap{
|
||||
permMap: map[string]*permission{},
|
||||
}
|
||||
}
|
||||
371
server/vendor/github.com/pion/turn/v2/internal/client/tcp_alloc.go
generated
vendored
Normal file
371
server/vendor/github.com/pion/turn/v2/internal/client/tcp_alloc.go
generated
vendored
Normal file
@@ -0,0 +1,371 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/transport/v2"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
_ transport.TCPListener = (*TCPAllocation)(nil) // Includes type check for net.Listener
|
||||
_ transport.Dialer = (*TCPAllocation)(nil)
|
||||
)
|
||||
|
||||
func noDeadline() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// TCPAllocation is an active TCP allocation on the TURN server
|
||||
// as specified by RFC 6062.
|
||||
// The allocation can be used to Dial/Accept relayed outgoing/incoming TCP connections.
|
||||
type TCPAllocation struct {
|
||||
connAttemptCh chan *connectionAttempt
|
||||
acceptTimer *time.Timer
|
||||
allocation
|
||||
}
|
||||
|
||||
// NewTCPAllocation creates a new instance of TCPConn
|
||||
func NewTCPAllocation(config *AllocationConfig) *TCPAllocation {
|
||||
a := &TCPAllocation{
|
||||
connAttemptCh: make(chan *connectionAttempt, 10),
|
||||
acceptTimer: time.NewTimer(time.Duration(math.MaxInt64)),
|
||||
allocation: allocation{
|
||||
client: config.Client,
|
||||
relayedAddr: config.RelayedAddr,
|
||||
serverAddr: config.ServerAddr,
|
||||
username: config.Username,
|
||||
realm: config.Realm,
|
||||
permMap: newPermissionMap(),
|
||||
integrity: config.Integrity,
|
||||
_nonce: config.Nonce,
|
||||
_lifetime: config.Lifetime,
|
||||
net: config.Net,
|
||||
log: config.Log,
|
||||
},
|
||||
}
|
||||
|
||||
a.log.Debugf("Initial lifetime: %d seconds", int(a.lifetime().Seconds()))
|
||||
|
||||
a.refreshAllocTimer = NewPeriodicTimer(
|
||||
timerIDRefreshAlloc,
|
||||
a.onRefreshTimers,
|
||||
a.lifetime()/2,
|
||||
)
|
||||
|
||||
a.refreshPermsTimer = NewPeriodicTimer(
|
||||
timerIDRefreshPerms,
|
||||
a.onRefreshTimers,
|
||||
permRefreshInterval,
|
||||
)
|
||||
|
||||
if a.refreshAllocTimer.Start() {
|
||||
a.log.Debug("Started refreshAllocTimer")
|
||||
}
|
||||
if a.refreshPermsTimer.Start() {
|
||||
a.log.Debug("Started refreshPermsTimer")
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// Connect sends a Connect request to the turn server and returns a chosen connection ID
|
||||
func (a *TCPAllocation) Connect(peer net.Addr) (proto.ConnectionID, error) {
|
||||
setters := []stun.Setter{
|
||||
stun.TransactionID,
|
||||
stun.NewType(stun.MethodConnect, stun.ClassRequest),
|
||||
addr2PeerAddress(peer),
|
||||
a.username,
|
||||
a.realm,
|
||||
a.nonce(),
|
||||
a.integrity,
|
||||
stun.Fingerprint,
|
||||
}
|
||||
|
||||
msg, err := stun.Build(setters...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
a.log.Debugf("Send connect request (peer=%v)", peer)
|
||||
trRes, err := a.client.PerformTransaction(msg, a.serverAddr, false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
res := trRes.Msg
|
||||
|
||||
if res.Type.Class == stun.ClassErrorResponse {
|
||||
var code stun.ErrorCodeAttribute
|
||||
if err = code.GetFrom(res); err == nil {
|
||||
return 0, fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("%s", res.Type) //nolint:goerr113
|
||||
}
|
||||
|
||||
var cid proto.ConnectionID
|
||||
if err := cid.GetFrom(res); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
a.log.Debugf("Connect request successful (cid=%v)", cid)
|
||||
return cid, nil
|
||||
}
|
||||
|
||||
// Dial connects to the address on the named network.
|
||||
func (a *TCPAllocation) Dial(network, rAddrStr string) (net.Conn, error) {
|
||||
rAddr, err := net.ResolveTCPAddr(network, rAddrStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.DialTCP(network, nil, rAddr)
|
||||
}
|
||||
|
||||
// DialWithConn connects to the address on the named network with an already existing connection.
|
||||
// The provided connection must be an already connected TCP connection to the TURN server.
|
||||
func (a *TCPAllocation) DialWithConn(conn net.Conn, network, rAddrStr string) (*TCPConn, error) {
|
||||
rAddr, err := net.ResolveTCPAddr(network, rAddrStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.DialTCPWithConn(conn, network, rAddr)
|
||||
}
|
||||
|
||||
// DialTCP acts like Dial for TCP networks.
|
||||
func (a *TCPAllocation) DialTCP(network string, lAddr, rAddr *net.TCPAddr) (*TCPConn, error) {
|
||||
var rAddrServer *net.TCPAddr
|
||||
if addr, ok := a.serverAddr.(*net.TCPAddr); ok {
|
||||
rAddrServer = &net.TCPAddr{
|
||||
IP: addr.IP,
|
||||
Port: addr.Port,
|
||||
}
|
||||
} else {
|
||||
return nil, errInvalidTURNAddress
|
||||
}
|
||||
|
||||
conn, err := a.net.DialTCP(network, lAddr, rAddrServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataConn, err := a.DialTCPWithConn(conn, network, rAddr)
|
||||
if err != nil {
|
||||
conn.Close() //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
return dataConn, err
|
||||
}
|
||||
|
||||
// DialTCPWithConn acts like DialWithConn for TCP networks.
|
||||
func (a *TCPAllocation) DialTCPWithConn(conn net.Conn, _ string, rAddr *net.TCPAddr) (*TCPConn, error) {
|
||||
var err error
|
||||
|
||||
// Check if we have a permission for the destination IP addr
|
||||
perm, ok := a.permMap.find(rAddr)
|
||||
if !ok {
|
||||
perm = &permission{}
|
||||
a.permMap.insert(rAddr, perm)
|
||||
}
|
||||
|
||||
for i := 0; i < maxRetryAttempts; i++ {
|
||||
if err = a.createPermission(perm, rAddr); !errors.Is(err, errTryAgain) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send connect request if haven't done so.
|
||||
cid, err := a.Connect(rAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpConn, ok := conn.(transport.TCPConn)
|
||||
if !ok {
|
||||
return nil, errTCPAddrCast
|
||||
}
|
||||
|
||||
dataConn := &TCPConn{
|
||||
TCPConn: tcpConn,
|
||||
ConnectionID: cid,
|
||||
remoteAddress: rAddr,
|
||||
allocation: a,
|
||||
}
|
||||
|
||||
if err := a.BindConnection(dataConn, cid); err != nil {
|
||||
return nil, fmt.Errorf("failed to bind connection: %w", err)
|
||||
}
|
||||
|
||||
return dataConn, nil
|
||||
}
|
||||
|
||||
// BindConnection associates the provided connection
|
||||
func (a *TCPAllocation) BindConnection(dataConn *TCPConn, cid proto.ConnectionID) error {
|
||||
msg, err := stun.Build(
|
||||
stun.TransactionID,
|
||||
stun.NewType(stun.MethodConnectionBind, stun.ClassRequest),
|
||||
cid,
|
||||
a.username,
|
||||
a.realm,
|
||||
a.nonce(),
|
||||
a.integrity,
|
||||
stun.Fingerprint,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.log.Debugf("Send connectionBind request (cid=%v)", cid)
|
||||
|
||||
_, err = dataConn.Write(msg.Raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read exactly one STUN message, any data after belongs to the user
|
||||
b := make([]byte, stunHeaderSize)
|
||||
n, err := dataConn.Read(b)
|
||||
if n != stunHeaderSize {
|
||||
return errIncompleteTURNFrame
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !stun.IsMessage(b) {
|
||||
return errInvalidTURNFrame
|
||||
}
|
||||
|
||||
datagramSize := binary.BigEndian.Uint16(b[2:4]) + stunHeaderSize
|
||||
raw := make([]byte, datagramSize)
|
||||
copy(raw, b)
|
||||
_, err = dataConn.Read(raw[stunHeaderSize:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &stun.Message{Raw: raw}
|
||||
if err = res.Decode(); err != nil {
|
||||
return fmt.Errorf("failed to decode STUN message: %w", err)
|
||||
}
|
||||
|
||||
switch res.Type.Class {
|
||||
case stun.ClassErrorResponse:
|
||||
var code stun.ErrorCodeAttribute
|
||||
if err = code.GetFrom(res); err == nil {
|
||||
return fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113
|
||||
}
|
||||
return fmt.Errorf("%s", res.Type) //nolint:goerr113
|
||||
case stun.ClassSuccessResponse:
|
||||
a.log.Debug("Successful connectionBind request")
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", errUnexpectedSTUNRequestMessage, res.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
func (a *TCPAllocation) Accept() (net.Conn, error) {
|
||||
return a.AcceptTCP()
|
||||
}
|
||||
|
||||
// AcceptTCP accepts the next incoming call and returns the new connection.
|
||||
func (a *TCPAllocation) AcceptTCP() (transport.TCPConn, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp4", a.serverAddr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpConn, err := a.net.DialTCP("tcp", nil, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataConn, err := a.AcceptTCPWithConn(tcpConn)
|
||||
if err != nil {
|
||||
tcpConn.Close() //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
return dataConn, err
|
||||
}
|
||||
|
||||
// AcceptTCPWithConn accepts the next incoming call and returns the new connection.
|
||||
func (a *TCPAllocation) AcceptTCPWithConn(conn net.Conn) (*TCPConn, error) {
|
||||
select {
|
||||
case attempt := <-a.connAttemptCh:
|
||||
|
||||
tcpConn, ok := conn.(transport.TCPConn)
|
||||
if !ok {
|
||||
return nil, errTCPAddrCast
|
||||
}
|
||||
|
||||
dataConn := &TCPConn{
|
||||
TCPConn: tcpConn,
|
||||
ConnectionID: attempt.cid,
|
||||
remoteAddress: attempt.from,
|
||||
allocation: a,
|
||||
}
|
||||
|
||||
if err := a.BindConnection(dataConn, attempt.cid); err != nil {
|
||||
return nil, fmt.Errorf("failed to bind connection: %w", err)
|
||||
}
|
||||
|
||||
return dataConn, nil
|
||||
case <-a.acceptTimer.C:
|
||||
return nil, &net.OpError{
|
||||
Op: "accept",
|
||||
Net: a.Addr().Network(),
|
||||
Addr: a.Addr(),
|
||||
Err: newTimeoutError("i/o timeout"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDeadline sets the deadline associated with the listener. A zero time value disables the deadline.
|
||||
func (a *TCPAllocation) SetDeadline(t time.Time) error {
|
||||
var d time.Duration
|
||||
if t == noDeadline() {
|
||||
d = time.Duration(math.MaxInt64)
|
||||
} else {
|
||||
d = time.Until(t)
|
||||
}
|
||||
a.acceptTimer.Reset(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close releases the allocation
|
||||
// Any blocked Accept operations will be unblocked and return errors.
|
||||
// Any opened connection via Dial/Accept will be closed.
|
||||
func (a *TCPAllocation) Close() error {
|
||||
a.refreshAllocTimer.Stop()
|
||||
a.refreshPermsTimer.Stop()
|
||||
|
||||
a.client.OnDeallocated(a.relayedAddr)
|
||||
return a.refreshAllocation(0, true /* dontWait=true */)
|
||||
}
|
||||
|
||||
// Addr returns the relayed address of the allocation
|
||||
func (a *TCPAllocation) Addr() net.Addr {
|
||||
return a.relayedAddr
|
||||
}
|
||||
|
||||
// HandleConnectionAttempt is called by the TURN client
|
||||
// when it receives a ConnectionAttempt indication.
|
||||
func (a *TCPAllocation) HandleConnectionAttempt(from *net.TCPAddr, cid proto.ConnectionID) {
|
||||
a.connAttemptCh <- &connectionAttempt{
|
||||
from: from,
|
||||
cid: cid,
|
||||
}
|
||||
}
|
||||
49
server/vendor/github.com/pion/turn/v2/internal/client/tcp_conn.go
generated
vendored
Normal file
49
server/vendor/github.com/pion/turn/v2/internal/client/tcp_conn.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/pion/transport/v2"
|
||||
"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")
|
||||
)
|
||||
|
||||
const (
|
||||
stunHeaderSize = 20
|
||||
)
|
||||
|
||||
var _ transport.TCPConn = (*TCPConn)(nil) // Includes type check for net.Conn
|
||||
|
||||
// TCPConn wraps a transport.TCPConn and returns the allocations relayed
|
||||
// transport address in response to TCPConn.LocalAddress()
|
||||
type TCPConn struct {
|
||||
transport.TCPConn
|
||||
remoteAddress *net.TCPAddr
|
||||
allocation *TCPAllocation
|
||||
ConnectionID proto.ConnectionID
|
||||
}
|
||||
|
||||
type connectionAttempt struct {
|
||||
from *net.TCPAddr
|
||||
cid proto.ConnectionID
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
// The Addr returned is shared by all invocations of LocalAddr, so do not modify it.
|
||||
func (c *TCPConn) LocalAddr() net.Addr {
|
||||
return c.allocation.Addr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
// The Addr returned is shared by all invocations of RemoteAddr, so do not modify it.
|
||||
func (c *TCPConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddress
|
||||
}
|
||||
188
server/vendor/github.com/pion/turn/v2/internal/client/transaction.go
generated
vendored
Normal file
188
server/vendor/github.com/pion/turn/v2/internal/client/transaction.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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 transaction 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)
|
||||
}
|
||||
27
server/vendor/github.com/pion/turn/v2/internal/client/trylock.go
generated
vendored
Normal file
27
server/vendor/github.com/pion/turn/v2/internal/client/trylock.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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 immediately.
|
||||
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)
|
||||
}
|
||||
455
server/vendor/github.com/pion/turn/v2/internal/client/udp_conn.go
generated
vendored
Normal file
455
server/vendor/github.com/pion/turn/v2/internal/client/udp_conn.go
generated
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package client implements the API for a TURN client
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"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
|
||||
)
|
||||
|
||||
type inboundData struct {
|
||||
data []byte
|
||||
from net.Addr
|
||||
}
|
||||
|
||||
// UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections.
|
||||
// compatible with net.PacketConn and net.Conn
|
||||
type UDPConn struct {
|
||||
bindingMgr *bindingManager // Thread-safe
|
||||
readCh chan *inboundData // Thread-safe
|
||||
closeCh chan struct{} // Thread-safe
|
||||
allocation
|
||||
}
|
||||
|
||||
// NewUDPConn creates a new instance of UDPConn
|
||||
func NewUDPConn(config *AllocationConfig) *UDPConn {
|
||||
c := &UDPConn{
|
||||
bindingMgr: newBindingManager(),
|
||||
readCh: make(chan *inboundData, maxReadQueueSize),
|
||||
closeCh: make(chan struct{}),
|
||||
allocation: allocation{
|
||||
client: config.Client,
|
||||
relayedAddr: config.RelayedAddr,
|
||||
serverAddr: config.ServerAddr,
|
||||
readTimer: time.NewTimer(time.Duration(math.MaxInt64)),
|
||||
permMap: newPermissionMap(),
|
||||
username: config.Username,
|
||||
realm: config.Realm,
|
||||
integrity: config.Integrity,
|
||||
_nonce: config.Nonce,
|
||||
_lifetime: config.Lifetime,
|
||||
net: config.Net,
|
||||
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("Started refresh allocation timer")
|
||||
}
|
||||
if c.refreshPermsTimer.Start() {
|
||||
c.log.Debugf("Started refresh permission timer")
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *allocation) createPermission(perm *permission, addr net.Addr) error {
|
||||
perm.mutex.Lock()
|
||||
defer perm.mutex.Unlock()
|
||||
|
||||
if perm.state() == permStateIdle {
|
||||
// Punch a hole! (this would block a bit..)
|
||||
if err := a.CreatePermissions(addr); err != nil {
|
||||
a.permMap.delete(addr)
|
||||
return err
|
||||
}
|
||||
perm.setState(permStatePermitted)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
for i := 0; i < maxRetryAttempts; i++ {
|
||||
// c.createPermission() 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 most likely successful and we can tolerate some loss of
|
||||
// UDP packet (or reorder), inorder to minimize the latency in most cases.
|
||||
if err = c.createPermission(perm, addr); !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("Failed to bind bind(): %s", err2)
|
||||
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.client.WriteTo(msg.Raw, c.serverAddr)
|
||||
}
|
||||
|
||||
// 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("Failed to bind() for refresh: %s", err)
|
||||
b.setState(bindingStateFailed)
|
||||
// Keep going...
|
||||
} else {
|
||||
b.setRefreshedAt(time.Now())
|
||||
b.setState(bindingStateReady)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
// Send via ChannelData
|
||||
_, err = c.sendChannelData(p, b.number)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// 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.client.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(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
|
||||
}
|
||||
|
||||
// CreatePermissions Issues a CreatePermission request for the supplied addresses
|
||||
// as described in https://datatracker.ietf.org/doc/html/rfc5766#section-9
|
||||
func (a *allocation) 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,
|
||||
a.username,
|
||||
a.realm,
|
||||
a.nonce(),
|
||||
a.integrity,
|
||||
stun.Fingerprint)
|
||||
|
||||
msg, err := stun.Build(setters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trRes, err := a.client.PerformTransaction(msg, a.serverAddr, 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 {
|
||||
a.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) bind(b *binding) error {
|
||||
setters := []stun.Setter{
|
||||
stun.TransactionID,
|
||||
stun.NewType(stun.MethodChannelBind, stun.ClassRequest),
|
||||
addr2PeerAddress(b.addr),
|
||||
proto.ChannelNumber(b.number),
|
||||
c.username,
|
||||
c.realm,
|
||||
c.nonce(),
|
||||
c.integrity,
|
||||
stun.Fingerprint,
|
||||
}
|
||||
|
||||
msg, err := stun.Build(setters...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trRes, err := c.client.PerformTransaction(msg, c.serverAddr, 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, 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()
|
||||
_, err := c.client.WriteTo(chData.Raw, c.serverAddr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
Reference in New Issue
Block a user