190 lines
4.8 KiB
Go
190 lines
4.8 KiB
Go
|
// 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
|
||
|
}
|