package server
import (
"fmt"
"net"
"github.com/pion/randutil"
"github.com/pion/stun/v3"
"github.com/pion/turn/v4/internal/allocation"
"github.com/pion/turn/v4/internal/ipnet"
"github.com/pion/turn/v4/internal/proto"
)
const runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func handleAllocateRequest(req Request , stunMsg *stun .Message ) error {
req .Log .Debugf ("Received AllocateRequest from %s" , req .SrcAddr )
messageIntegrity , hasAuth , err := authenticateRequest (req , stunMsg , stun .MethodAllocate )
if !hasAuth {
return err
}
fiveTuple := &allocation .FiveTuple {
SrcAddr : req .SrcAddr ,
DstAddr : req .Conn .LocalAddr (),
Protocol : allocation .UDP ,
}
requestedPort := 0
reservationToken := ""
badRequestMsg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeBadRequest },
)
insufficientCapacityMsg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeInsufficientCapacity },
)
if alloc := req .AllocationManager .GetAllocation (fiveTuple ); alloc != nil {
id , attrs := alloc .GetResponseCache ()
if id != stunMsg .TransactionID {
msg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeAllocMismatch },
)
return buildAndSendErr (req .Conn , req .SrcAddr , errRelayAlreadyAllocatedForFiveTuple , msg ...)
}
msg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassSuccessResponse ),
append (attrs , messageIntegrity )...,
)
return buildAndSend (req .Conn , req .SrcAddr , msg ...)
}
var requestedTransport proto .RequestedTransport
if err = requestedTransport .GetFrom (stunMsg ); err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , badRequestMsg ...)
} else if requestedTransport .Protocol != proto .ProtoUDP && requestedTransport .Protocol != proto .ProtoTCP {
msg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeUnsupportedTransProto },
)
return buildAndSendErr (req .Conn , req .SrcAddr , errUnsupportedTransportProtocol , msg ...)
}
if stunMsg .Contains (stun .AttrDontFragment ) {
msg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeUnknownAttribute },
&stun .UnknownAttributes {stun .AttrDontFragment },
)
return buildAndSendErr (req .Conn , req .SrcAddr , errNoDontFragmentSupport , msg ...)
}
var reservationTokenAttr proto .ReservationToken
if err = reservationTokenAttr .GetFrom (stunMsg ); err == nil {
var evenPort proto .EvenPort
if err = evenPort .GetFrom (stunMsg ); err == nil {
return buildAndSendErr (req .Conn , req .SrcAddr , errRequestWithReservationTokenAndEvenPort , badRequestMsg ...)
}
}
var evenPort proto .EvenPort
if err = evenPort .GetFrom (stunMsg ); err == nil {
var randomPort int
randomPort , err = req .AllocationManager .GetRandomEvenPort ()
if err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , insufficientCapacityMsg ...)
}
requestedPort = randomPort
reservationToken , err = randutil .GenerateCryptoRandomString (8 , runesAlpha )
if err != nil {
return err
}
}
lifetimeDuration := allocationLifeTime (stunMsg )
alloc , err := req .AllocationManager .CreateAllocation (
fiveTuple ,
req .Conn ,
requestedPort ,
lifetimeDuration )
if err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , insufficientCapacityMsg ...)
}
srcIP , srcPort , err := ipnet .AddrIPPort (req .SrcAddr )
if err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , badRequestMsg ...)
}
relayIP , relayPort , err := ipnet .AddrIPPort (alloc .RelayAddr )
if err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , badRequestMsg ...)
}
responseAttrs := []stun .Setter {
&proto .RelayedAddress {
IP : relayIP ,
Port : relayPort ,
},
&proto .Lifetime {
Duration : lifetimeDuration ,
},
&stun .XORMappedAddress {
IP : srcIP ,
Port : srcPort ,
},
}
if reservationToken != "" {
req .AllocationManager .CreateReservation (reservationToken , relayPort )
responseAttrs = append (responseAttrs , proto .ReservationToken ([]byte (reservationToken )))
}
msg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodAllocate , stun .ClassSuccessResponse ),
append (responseAttrs , messageIntegrity )...,
)
alloc .SetResponseCache (stunMsg .TransactionID , responseAttrs )
return buildAndSend (req .Conn , req .SrcAddr , msg ...)
}
func handleRefreshRequest(req Request , stunMsg *stun .Message ) error {
req .Log .Debugf ("Received RefreshRequest from %s" , req .SrcAddr )
messageIntegrity , hasAuth , err := authenticateRequest (req , stunMsg , stun .MethodRefresh )
if !hasAuth {
return err
}
lifetimeDuration := allocationLifeTime (stunMsg )
fiveTuple := &allocation .FiveTuple {
SrcAddr : req .SrcAddr ,
DstAddr : req .Conn .LocalAddr (),
Protocol : allocation .UDP ,
}
if lifetimeDuration != 0 {
a := req .AllocationManager .GetAllocation (fiveTuple )
if a == nil {
return fmt .Errorf ("%w %v:%v" , errNoAllocationFound , req .SrcAddr , req .Conn .LocalAddr ())
}
a .Refresh (lifetimeDuration )
} else {
req .AllocationManager .DeleteAllocation (fiveTuple )
}
return buildAndSend (
req .Conn ,
req .SrcAddr ,
buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodRefresh , stun .ClassSuccessResponse ),
[]stun .Setter {
&proto .Lifetime {
Duration : lifetimeDuration ,
},
messageIntegrity ,
}...,
)...,
)
}
func handleCreatePermissionRequest(req Request , stunMsg *stun .Message ) error {
req .Log .Debugf ("Received CreatePermission from %s" , req .SrcAddr )
alloc := req .AllocationManager .GetAllocation (&allocation .FiveTuple {
SrcAddr : req .SrcAddr ,
DstAddr : req .Conn .LocalAddr (),
Protocol : allocation .UDP ,
})
if alloc == nil {
return fmt .Errorf ("%w %v:%v" , errNoAllocationFound , req .SrcAddr , req .Conn .LocalAddr ())
}
messageIntegrity , hasAuth , err := authenticateRequest (req , stunMsg , stun .MethodCreatePermission )
if !hasAuth {
return err
}
addCount := 0
if err := stunMsg .ForEach (stun .AttrXORPeerAddress , func (m *stun .Message ) error {
var peerAddress proto .PeerAddress
if err := peerAddress .GetFrom (m ); err != nil {
return err
}
if err := req .AllocationManager .GrantPermission (req .SrcAddr , peerAddress .IP ); err != nil {
req .Log .Infof ("permission denied for client %s to peer %s" , req .SrcAddr , peerAddress .IP )
return err
}
req .Log .Debugf ("Adding permission for %s" , fmt .Sprintf ("%s:%d" ,
peerAddress .IP , peerAddress .Port ))
alloc .AddPermission (allocation .NewPermission (
&net .UDPAddr {
IP : peerAddress .IP ,
Port : peerAddress .Port ,
},
req .Log ,
))
addCount ++
return nil
}); err != nil {
addCount = 0
}
respClass := stun .ClassSuccessResponse
if addCount == 0 {
respClass = stun .ClassErrorResponse
}
return buildAndSend (
req .Conn ,
req .SrcAddr ,
buildMsg (stunMsg .TransactionID , stun .NewType (stun .MethodCreatePermission , respClass ),
[]stun .Setter {messageIntegrity }...)...,
)
}
func handleSendIndication(req Request , stunMsg *stun .Message ) error {
req .Log .Debugf ("Received SendIndication from %s" , req .SrcAddr )
alloc := req .AllocationManager .GetAllocation (&allocation .FiveTuple {
SrcAddr : req .SrcAddr ,
DstAddr : req .Conn .LocalAddr (),
Protocol : allocation .UDP ,
})
if alloc == nil {
return fmt .Errorf ("%w %v:%v" , errNoAllocationFound , req .SrcAddr , req .Conn .LocalAddr ())
}
dataAttr := proto .Data {}
if err := dataAttr .GetFrom (stunMsg ); err != nil {
return err
}
peerAddress := proto .PeerAddress {}
if err := peerAddress .GetFrom (stunMsg ); err != nil {
return err
}
msgDst := &net .UDPAddr {IP : peerAddress .IP , Port : peerAddress .Port }
if perm := alloc .GetPermission (msgDst ); perm == nil {
return fmt .Errorf ("%w: %v" , errNoPermission , msgDst )
}
l , err := alloc .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(req Request , stunMsg *stun .Message ) error {
req .Log .Debugf ("Received ChannelBindRequest from %s" , req .SrcAddr )
alloc := req .AllocationManager .GetAllocation (&allocation .FiveTuple {
SrcAddr : req .SrcAddr ,
DstAddr : req .Conn .LocalAddr (),
Protocol : allocation .UDP ,
})
if alloc == nil {
return fmt .Errorf ("%w %v:%v" , errNoAllocationFound , req .SrcAddr , req .Conn .LocalAddr ())
}
badRequestMsg := buildMsg (
stunMsg .TransactionID ,
stun .NewType (stun .MethodChannelBind , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeBadRequest },
)
messageIntegrity , hasAuth , err := authenticateRequest (req , stunMsg , stun .MethodChannelBind )
if !hasAuth {
return err
}
var channel proto .ChannelNumber
if err = channel .GetFrom (stunMsg ); err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , badRequestMsg ...)
}
peerAddr := proto .PeerAddress {}
if err = peerAddr .GetFrom (stunMsg ); err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , badRequestMsg ...)
}
if err = req .AllocationManager .GrantPermission (req .SrcAddr , peerAddr .IP ); err != nil {
req .Log .Infof ("permission denied for client %s to peer %s" , req .SrcAddr , peerAddr .IP )
unauthorizedRequestMsg := buildMsg (stunMsg .TransactionID ,
stun .NewType (stun .MethodChannelBind , stun .ClassErrorResponse ),
&stun .ErrorCodeAttribute {Code : stun .CodeUnauthorized })
return buildAndSendErr (req .Conn , req .SrcAddr , err , unauthorizedRequestMsg ...)
}
req .Log .Debugf ("Binding channel %d to %s" , channel , peerAddr )
err = alloc .AddChannelBind (allocation .NewChannelBind (
channel ,
&net .UDPAddr {IP : peerAddr .IP , Port : peerAddr .Port },
req .Log ,
), req .ChannelBindTimeout )
if err != nil {
return buildAndSendErr (req .Conn , req .SrcAddr , err , badRequestMsg ...)
}
return buildAndSend (
req .Conn ,
req .SrcAddr ,
buildMsg (stunMsg .TransactionID , stun .NewType (stun .MethodChannelBind , stun .ClassSuccessResponse ),
[]stun .Setter {messageIntegrity }...)...,
)
}
func handleChannelData(req Request , channelData *proto .ChannelData ) error {
req .Log .Debugf ("Received ChannelData from %s" , req .SrcAddr )
alloc := req .AllocationManager .GetAllocation (&allocation .FiveTuple {
SrcAddr : req .SrcAddr ,
DstAddr : req .Conn .LocalAddr (),
Protocol : allocation .UDP ,
})
if alloc == nil {
return fmt .Errorf ("%w %v:%v" , errNoAllocationFound , req .SrcAddr , req .Conn .LocalAddr ())
}
channel := alloc .GetChannelByNumber (channelData .Number )
if channel == nil {
return fmt .Errorf ("%w %x" , errNoSuchChannelBind , uint16 (channelData .Number ))
}
l , err := alloc .RelaySocket .WriteTo (channelData .Data , channel .Peer )
if err != nil {
return fmt .Errorf ("%w: %s" , errFailedWriteSocket , err .Error())
} else if l != len (channelData .Data ) {
return fmt .Errorf ("%w %d != %d (expected)" , errShortWrite , l , len (channelData .Data ))
}
return nil
}
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 .