status-go/vendor/github.com/pion/turn/v2/internal/client/allocation.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
}