package turn
import (
"errors"
"fmt"
"net"
"time"
"github.com/pion/logging"
"github.com/pion/turn/v4/internal/allocation"
"github.com/pion/turn/v4/internal/proto"
"github.com/pion/turn/v4/internal/server"
)
const (
defaultInboundMTU = 1600
)
type Server struct {
log logging .LeveledLogger
authHandler AuthHandler
realm string
channelBindTimeout time .Duration
nonceHash *server .NonceHash
packetConnConfigs []PacketConnConfig
listenerConfigs []ListenerConfig
allocationManagers []*allocation .Manager
inboundMTU int
}
func NewServer (config ServerConfig ) (*Server , error ) {
if err := config .validate (); err != nil {
return nil , err
}
loggerFactory := config .LoggerFactory
if loggerFactory == nil {
loggerFactory = logging .NewDefaultLoggerFactory ()
}
mtu := defaultInboundMTU
if config .InboundMTU != 0 {
mtu = config .InboundMTU
}
nonceHash , err := server .NewNonceHash ()
if err != nil {
return nil , err
}
server := &Server {
log : loggerFactory .NewLogger ("turn" ),
authHandler : config .AuthHandler ,
realm : config .Realm ,
channelBindTimeout : config .ChannelBindTimeout ,
packetConnConfigs : config .PacketConnConfigs ,
listenerConfigs : config .ListenerConfigs ,
nonceHash : nonceHash ,
inboundMTU : mtu ,
}
if server .channelBindTimeout == 0 {
server .channelBindTimeout = proto .DefaultLifetime
}
for _ , cfg := range server .packetConnConfigs {
am , err := server .createAllocationManager (cfg .RelayAddressGenerator , cfg .PermissionHandler )
if err != nil {
return nil , fmt .Errorf ("failed to create AllocationManager: %w" , err )
}
go func (cfg PacketConnConfig , am *allocation .Manager ) {
server .readLoop (cfg .PacketConn , am )
if err := am .Close (); err != nil {
server .log .Errorf ("Failed to close AllocationManager: %s" , err )
}
}(cfg , am )
}
for _ , cfg := range server .listenerConfigs {
am , err := server .createAllocationManager (cfg .RelayAddressGenerator , cfg .PermissionHandler )
if err != nil {
return nil , fmt .Errorf ("failed to create AllocationManager: %w" , err )
}
go func (cfg ListenerConfig , am *allocation .Manager ) {
server .readListener (cfg .Listener , am )
if err := am .Close (); err != nil {
server .log .Errorf ("Failed to close AllocationManager: %s" , err )
}
}(cfg , am )
}
return server , nil
}
func (s *Server ) AllocationCount () int {
allocs := 0
for _ , am := range s .allocationManagers {
allocs += am .AllocationCount ()
}
return allocs
}
func (s *Server ) Close () error {
var errors []error
for _ , cfg := range s .packetConnConfigs {
if err := cfg .PacketConn .Close (); err != nil {
errors = append (errors , err )
}
}
for _ , cfg := range s .listenerConfigs {
if err := cfg .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 (%w) " , err , e )
}
return err
}
func (s *Server ) readListener (l net .Listener , am *allocation .Manager ) {
for {
conn , err := l .Accept ()
if err != nil {
s .log .Debugf ("Failed to accept: %s" , err )
return
}
go func () {
s .readLoop (NewSTUNConn (conn ), am )
am .DeleteAllocation (&allocation .FiveTuple {
Protocol : allocation .UDP ,
SrcAddr : conn .RemoteAddr (),
DstAddr : conn .LocalAddr (),
})
if err := conn .Close (); err != nil && !errors .Is (err , net .ErrClosed ) {
s .log .Errorf ("Failed to close conn: %s" , err )
}
}()
}
}
type nilAddressGenerator struct {}
func (n *nilAddressGenerator ) Validate () error { return errRelayAddressGeneratorNil }
func (n *nilAddressGenerator ) AllocatePacketConn (string , int ) (net .PacketConn , net .Addr , error ) {
return nil , nil , errRelayAddressGeneratorNil
}
func (n *nilAddressGenerator ) AllocateConn (string , int ) (net .Conn , net .Addr , error ) {
return nil , nil , errRelayAddressGeneratorNil
}
func (s *Server ) createAllocationManager (
addrGenerator RelayAddressGenerator ,
handler PermissionHandler ,
) (*allocation .Manager , error ) {
if handler == nil {
handler = DefaultPermissionHandler
}
if addrGenerator == nil {
addrGenerator = &nilAddressGenerator {}
}
am , err := allocation .NewManager (allocation .ManagerConfig {
AllocatePacketConn : addrGenerator .AllocatePacketConn ,
AllocateConn : addrGenerator .AllocateConn ,
PermissionHandler : handler ,
LeveledLogger : s .log ,
})
if err != nil {
return am , err
}
s .allocationManagers = append (s .allocationManagers , am )
return am , err
}
func (s *Server ) readLoop (conn net .PacketConn , allocationManager *allocation .Manager ) {
buf := make ([]byte , s .inboundMTU )
for {
n , addr , err := conn .ReadFrom (buf )
switch {
case err != nil :
s .log .Debugf ("Exit read loop on error: %s" , err )
return
case n >= s .inboundMTU :
s .log .Debugf ("Read bytes exceeded MTU, packet is possibly truncated" )
continue
}
if err := server .HandleRequest (server .Request {
Conn : conn ,
SrcAddr : addr ,
Buff : buf [:n ],
Log : s .log ,
AuthHandler : s .authHandler ,
Realm : s .realm ,
AllocationManager : allocationManager ,
ChannelBindTimeout : s .channelBindTimeout ,
NonceHash : s .nonceHash ,
}); err != nil {
s .log .Debugf ("Failed to handle datagram: %v" , err )
}
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .