26
vendor/github.com/pion/turn/v2/internal/server/errors.go
generated
vendored
Normal file
26
vendor/github.com/pion/turn/v2/internal/server/errors.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package server
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errFailedToGenerateNonce = errors.New("failed to generate nonce")
|
||||
errFailedToSendError = errors.New("failed to send error message")
|
||||
errDuplicatedNonce = errors.New("duplicated Nonce generated, discarding request")
|
||||
errNoSuchUser = errors.New("no such user exists")
|
||||
errUnexpectedClass = errors.New("unexpected class")
|
||||
errUnexpectedMethod = errors.New("unexpected method")
|
||||
errFailedToHandle = errors.New("failed to handle")
|
||||
errUnhandledSTUNPacket = errors.New("unhandled STUN packet")
|
||||
errUnableToHandleChannelData = errors.New("unable to handle ChannelData")
|
||||
errFailedToCreateSTUNPacket = errors.New("failed to create stun message from packet")
|
||||
errFailedToCreateChannelData = errors.New("failed to create channel data from packet")
|
||||
errRelayAlreadyAllocatedForFiveTuple = errors.New("relay already allocated for 5-TUPLE")
|
||||
errRequestedTransportMustBeUDP = errors.New("RequestedTransport must be UDP")
|
||||
errNoDontFragmentSupport = errors.New("no support for DONT-FRAGMENT")
|
||||
errRequestWithReservationTokenAndEvenPort = errors.New("Request must not contain RESERVATION-TOKEN and EVEN-PORT")
|
||||
errNoAllocationFound = errors.New("no allocation found")
|
||||
errNoPermission = errors.New("unable to handle send-indication, no permission added")
|
||||
errShortWrite = errors.New("packet write smaller than packet")
|
||||
errNoSuchChannelBind = errors.New("no such channel bind")
|
||||
errFailedWriteSocket = errors.New("failed writing to socket")
|
||||
)
|
||||
109
vendor/github.com/pion/turn/v2/internal/server/server.go
generated
vendored
Normal file
109
vendor/github.com/pion/turn/v2/internal/server/server.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
// Package server implements the private API to implement a TURN server
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/turn/v2/internal/allocation"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
// Request contains all the state needed to process a single incoming datagram
|
||||
type Request struct {
|
||||
// Current Request State
|
||||
Conn net.PacketConn
|
||||
SrcAddr net.Addr
|
||||
Buff []byte
|
||||
|
||||
// Server State
|
||||
AllocationManager *allocation.Manager
|
||||
Nonces *sync.Map
|
||||
|
||||
// User Configuration
|
||||
AuthHandler func(username string, realm string, srcAddr net.Addr) (key []byte, ok bool)
|
||||
Log logging.LeveledLogger
|
||||
Realm string
|
||||
ChannelBindTimeout time.Duration
|
||||
}
|
||||
|
||||
// HandleRequest processes the give Request
|
||||
func HandleRequest(r Request) error {
|
||||
r.Log.Debugf("received %d bytes of udp from %s on %s", len(r.Buff), r.SrcAddr.String(), r.Conn.LocalAddr().String())
|
||||
|
||||
if proto.IsChannelData(r.Buff) {
|
||||
return handleDataPacket(r)
|
||||
}
|
||||
|
||||
return handleTURNPacket(r)
|
||||
}
|
||||
|
||||
func handleDataPacket(r Request) error {
|
||||
r.Log.Debugf("received DataPacket from %s", r.SrcAddr.String())
|
||||
c := proto.ChannelData{Raw: r.Buff}
|
||||
if err := c.Decode(); err != nil {
|
||||
return fmt.Errorf("%w: %v", errFailedToCreateChannelData, err)
|
||||
}
|
||||
|
||||
err := handleChannelData(r, &c)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w from %v: %v", errUnableToHandleChannelData, r.SrcAddr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func handleTURNPacket(r Request) error {
|
||||
r.Log.Debug("handleTURNPacket")
|
||||
m := &stun.Message{Raw: append([]byte{}, r.Buff...)}
|
||||
if err := m.Decode(); err != nil {
|
||||
return fmt.Errorf("%w: %v", errFailedToCreateSTUNPacket, err)
|
||||
}
|
||||
|
||||
h, err := getMessageHandler(m.Type.Class, m.Type.Method)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w %v-%v from %v: %v", errUnhandledSTUNPacket, m.Type.Method, m.Type.Class, r.SrcAddr, err)
|
||||
}
|
||||
|
||||
err = h(r, m)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w %v-%v from %v: %v", errFailedToHandle, m.Type.Method, m.Type.Class, r.SrcAddr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMessageHandler(class stun.MessageClass, method stun.Method) (func(r Request, m *stun.Message) error, error) {
|
||||
switch class {
|
||||
case stun.ClassIndication:
|
||||
switch method {
|
||||
case stun.MethodSend:
|
||||
return handleSendIndication, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method)
|
||||
}
|
||||
|
||||
case stun.ClassRequest:
|
||||
switch method {
|
||||
case stun.MethodAllocate:
|
||||
return handleAllocateRequest, nil
|
||||
case stun.MethodRefresh:
|
||||
return handleRefreshRequest, nil
|
||||
case stun.MethodCreatePermission:
|
||||
return handleCreatePermissionRequest, nil
|
||||
case stun.MethodChannelBind:
|
||||
return handleChannelBindRequest, nil
|
||||
case stun.MethodBinding:
|
||||
return handleBindingRequest, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errUnexpectedClass, class)
|
||||
}
|
||||
}
|
||||
22
vendor/github.com/pion/turn/v2/internal/server/stun.go
generated
vendored
Normal file
22
vendor/github.com/pion/turn/v2/internal/server/stun.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/turn/v2/internal/ipnet"
|
||||
)
|
||||
|
||||
func handleBindingRequest(r Request, m *stun.Message) error {
|
||||
r.Log.Debugf("received BindingRequest from %s", r.SrcAddr.String())
|
||||
|
||||
ip, port, err := ipnet.AddrIPPort(r.SrcAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attrs := buildMsg(m.TransactionID, stun.BindingSuccess, &stun.XORMappedAddress{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
}, stun.Fingerprint)
|
||||
|
||||
return buildAndSend(r.Conn, r.SrcAddr, attrs...)
|
||||
}
|
||||
352
vendor/github.com/pion/turn/v2/internal/server/turn.go
generated
vendored
Normal file
352
vendor/github.com/pion/turn/v2/internal/server/turn.go
generated
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/turn/v2/internal/allocation"
|
||||
"github.com/pion/turn/v2/internal/ipnet"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
// // https://tools.ietf.org/html/rfc5766#section-6.2
|
||||
func handleAllocateRequest(r Request, m *stun.Message) error {
|
||||
r.Log.Debugf("received AllocateRequest from %s", r.SrcAddr.String())
|
||||
|
||||
// 1. The server MUST require that the request be authenticated. This
|
||||
// authentication MUST be done using the long-term credential
|
||||
// mechanism of [https://tools.ietf.org/html/rfc5389#section-10.2.2]
|
||||
// unless the client and server agree to use another mechanism through
|
||||
// some procedure outside the scope of this document.
|
||||
messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodAllocate)
|
||||
if !hasAuth {
|
||||
return err
|
||||
}
|
||||
|
||||
fiveTuple := &allocation.FiveTuple{
|
||||
SrcAddr: r.SrcAddr,
|
||||
DstAddr: r.Conn.LocalAddr(),
|
||||
Protocol: allocation.UDP,
|
||||
}
|
||||
requestedPort := 0
|
||||
reservationToken := ""
|
||||
|
||||
badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest})
|
||||
insufficentCapacityMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeInsufficientCapacity})
|
||||
|
||||
// 2. The server checks if the 5-tuple is currently in use by an
|
||||
// existing allocation. If yes, the server rejects the request with
|
||||
// a 437 (Allocation Mismatch) error.
|
||||
if alloc := r.AllocationManager.GetAllocation(fiveTuple); alloc != nil {
|
||||
msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeAllocMismatch})
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, errRelayAlreadyAllocatedForFiveTuple, msg...)
|
||||
}
|
||||
|
||||
// 3. The server checks if the request contains a REQUESTED-TRANSPORT
|
||||
// attribute. If the REQUESTED-TRANSPORT attribute is not included
|
||||
// or is malformed, the server rejects the request with a 400 (Bad
|
||||
// Request) error. Otherwise, if the attribute is included but
|
||||
// specifies a protocol other that UDP, the server rejects the
|
||||
// request with a 442 (Unsupported Transport Protocol) error.
|
||||
var requestedTransport proto.RequestedTransport
|
||||
if err = requestedTransport.GetFrom(m); err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
} else if requestedTransport.Protocol != proto.ProtoUDP {
|
||||
msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnsupportedTransProto})
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, errRequestedTransportMustBeUDP, msg...)
|
||||
}
|
||||
|
||||
// 4. The request may contain a DONT-FRAGMENT attribute. If it does,
|
||||
// but the server does not support sending UDP datagrams with the DF
|
||||
// bit set to 1 (see Section 12), then the server treats the DONT-
|
||||
// FRAGMENT attribute in the Allocate request as an unknown
|
||||
// comprehension-required attribute.
|
||||
if m.Contains(stun.AttrDontFragment) {
|
||||
msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnknownAttribute}, &stun.UnknownAttributes{stun.AttrDontFragment})
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, errNoDontFragmentSupport, msg...)
|
||||
}
|
||||
|
||||
// 5. The server checks if the request contains a RESERVATION-TOKEN
|
||||
// attribute. If yes, and the request also contains an EVEN-PORT
|
||||
// attribute, then the server rejects the request with a 400 (Bad
|
||||
// Request) error. Otherwise, it checks to see if the token is
|
||||
// valid (i.e., the token is in range and has not expired and the
|
||||
// corresponding relayed transport address is still available). If
|
||||
// the token is not valid for some reason, the server rejects the
|
||||
// request with a 508 (Insufficient Capacity) error.
|
||||
var reservationTokenAttr proto.ReservationToken
|
||||
if err = reservationTokenAttr.GetFrom(m); err == nil {
|
||||
var evenPort proto.EvenPort
|
||||
if err = evenPort.GetFrom(m); err == nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, errRequestWithReservationTokenAndEvenPort, badRequestMsg...)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. The server checks if the request contains an EVEN-PORT attribute.
|
||||
// If yes, then the server checks that it can satisfy the request
|
||||
// (i.e., can allocate a relayed transport address as described
|
||||
// below). If the server cannot satisfy the request, then the
|
||||
// server rejects the request with a 508 (Insufficient Capacity)
|
||||
// error.
|
||||
var evenPort proto.EvenPort
|
||||
if err = evenPort.GetFrom(m); err == nil {
|
||||
randomPort := 0
|
||||
randomPort, err = r.AllocationManager.GetRandomEvenPort()
|
||||
if err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...)
|
||||
}
|
||||
requestedPort = randomPort
|
||||
reservationToken = randSeq(8)
|
||||
}
|
||||
|
||||
// 7. At any point, the server MAY choose to reject the request with a
|
||||
// 486 (Allocation Quota Reached) error if it feels the client is
|
||||
// trying to exceed some locally defined allocation quota. The
|
||||
// server is free to define this allocation quota any way it wishes,
|
||||
// but SHOULD define it based on the username used to authenticate
|
||||
// the request, and not on the client's transport address.
|
||||
|
||||
// 8. Also at any point, the server MAY choose to reject the request
|
||||
// with a 300 (Try Alternate) error if it wishes to redirect the
|
||||
// client to a different server. The use of this error code and
|
||||
// attribute follow the specification in [RFC5389].
|
||||
lifetimeDuration := allocationLifeTime(m)
|
||||
a, err := r.AllocationManager.CreateAllocation(
|
||||
fiveTuple,
|
||||
r.Conn,
|
||||
requestedPort,
|
||||
lifetimeDuration)
|
||||
if err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...)
|
||||
}
|
||||
|
||||
// Once the allocation is created, the server replies with a success
|
||||
// response. The success response contains:
|
||||
// * An XOR-RELAYED-ADDRESS attribute containing the relayed transport
|
||||
// address.
|
||||
// * A LIFETIME attribute containing the current value of the time-to-
|
||||
// expiry timer.
|
||||
// * A RESERVATION-TOKEN attribute (if a second relayed transport
|
||||
// address was reserved).
|
||||
// * An XOR-MAPPED-ADDRESS attribute containing the client's IP address
|
||||
// and port (from the 5-tuple).
|
||||
|
||||
srcIP, srcPort, err := ipnet.AddrIPPort(r.SrcAddr)
|
||||
if err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
relayIP, relayPort, err := ipnet.AddrIPPort(a.RelayAddr)
|
||||
if err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
responseAttrs := []stun.Setter{
|
||||
&proto.RelayedAddress{
|
||||
IP: relayIP,
|
||||
Port: relayPort,
|
||||
},
|
||||
&proto.Lifetime{
|
||||
Duration: lifetimeDuration,
|
||||
},
|
||||
&stun.XORMappedAddress{
|
||||
IP: srcIP,
|
||||
Port: srcPort,
|
||||
},
|
||||
}
|
||||
|
||||
if reservationToken != "" {
|
||||
r.AllocationManager.CreateReservation(reservationToken, relayPort)
|
||||
responseAttrs = append(responseAttrs, proto.ReservationToken([]byte(reservationToken)))
|
||||
}
|
||||
|
||||
msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse), append(responseAttrs, messageIntegrity)...)
|
||||
return buildAndSend(r.Conn, r.SrcAddr, msg...)
|
||||
}
|
||||
|
||||
func handleRefreshRequest(r Request, m *stun.Message) error {
|
||||
r.Log.Debugf("received RefreshRequest from %s", r.SrcAddr.String())
|
||||
|
||||
messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodRefresh)
|
||||
if !hasAuth {
|
||||
return err
|
||||
}
|
||||
|
||||
lifetimeDuration := allocationLifeTime(m)
|
||||
fiveTuple := &allocation.FiveTuple{
|
||||
SrcAddr: r.SrcAddr,
|
||||
DstAddr: r.Conn.LocalAddr(),
|
||||
Protocol: allocation.UDP,
|
||||
}
|
||||
|
||||
if lifetimeDuration != 0 {
|
||||
a := r.AllocationManager.GetAllocation(fiveTuple)
|
||||
|
||||
if a == nil {
|
||||
return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr())
|
||||
}
|
||||
a.Refresh(lifetimeDuration)
|
||||
} else {
|
||||
r.AllocationManager.DeleteAllocation(fiveTuple)
|
||||
}
|
||||
|
||||
return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodRefresh, stun.ClassSuccessResponse), []stun.Setter{
|
||||
&proto.Lifetime{
|
||||
Duration: lifetimeDuration,
|
||||
},
|
||||
messageIntegrity,
|
||||
}...)...)
|
||||
}
|
||||
|
||||
func handleCreatePermissionRequest(r Request, m *stun.Message) error {
|
||||
r.Log.Debugf("received CreatePermission from %s", r.SrcAddr.String())
|
||||
|
||||
a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{
|
||||
SrcAddr: r.SrcAddr,
|
||||
DstAddr: r.Conn.LocalAddr(),
|
||||
Protocol: allocation.UDP,
|
||||
})
|
||||
if a == nil {
|
||||
return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr())
|
||||
}
|
||||
|
||||
messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodCreatePermission)
|
||||
if !hasAuth {
|
||||
return err
|
||||
}
|
||||
|
||||
addCount := 0
|
||||
|
||||
if err := m.ForEach(stun.AttrXORPeerAddress, func(m *stun.Message) error {
|
||||
var peerAddress proto.PeerAddress
|
||||
if err := peerAddress.GetFrom(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.Log.Debugf("adding permission for %s", fmt.Sprintf("%s:%d",
|
||||
peerAddress.IP.String(), peerAddress.Port))
|
||||
a.AddPermission(allocation.NewPermission(
|
||||
&net.UDPAddr{
|
||||
IP: peerAddress.IP,
|
||||
Port: peerAddress.Port,
|
||||
},
|
||||
r.Log,
|
||||
))
|
||||
addCount++
|
||||
return nil
|
||||
}); err != nil {
|
||||
addCount = 0
|
||||
}
|
||||
|
||||
respClass := stun.ClassSuccessResponse
|
||||
if addCount == 0 {
|
||||
respClass = stun.ClassErrorResponse
|
||||
}
|
||||
|
||||
return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodCreatePermission, respClass), []stun.Setter{messageIntegrity}...)...)
|
||||
}
|
||||
|
||||
func handleSendIndication(r Request, m *stun.Message) error {
|
||||
r.Log.Debugf("received SendIndication from %s", r.SrcAddr.String())
|
||||
a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{
|
||||
SrcAddr: r.SrcAddr,
|
||||
DstAddr: r.Conn.LocalAddr(),
|
||||
Protocol: allocation.UDP,
|
||||
})
|
||||
if a == nil {
|
||||
return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr())
|
||||
}
|
||||
|
||||
dataAttr := proto.Data{}
|
||||
if err := dataAttr.GetFrom(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerAddress := proto.PeerAddress{}
|
||||
if err := peerAddress.GetFrom(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgDst := &net.UDPAddr{IP: peerAddress.IP, Port: peerAddress.Port}
|
||||
if perm := a.GetPermission(msgDst); perm == nil {
|
||||
return fmt.Errorf("%w: %v", errNoPermission, msgDst)
|
||||
}
|
||||
|
||||
l, err := a.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(r Request, m *stun.Message) error {
|
||||
r.Log.Debugf("received ChannelBindRequest from %s", r.SrcAddr.String())
|
||||
|
||||
a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{
|
||||
SrcAddr: r.SrcAddr,
|
||||
DstAddr: r.Conn.LocalAddr(),
|
||||
Protocol: allocation.UDP,
|
||||
})
|
||||
if a == nil {
|
||||
return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr())
|
||||
}
|
||||
|
||||
badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest})
|
||||
|
||||
messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodChannelBind)
|
||||
if !hasAuth {
|
||||
return err
|
||||
}
|
||||
|
||||
var channel proto.ChannelNumber
|
||||
if err = channel.GetFrom(m); err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
peerAddr := proto.PeerAddress{}
|
||||
if err = peerAddr.GetFrom(m); err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
r.Log.Debugf("binding channel %d to %s",
|
||||
channel,
|
||||
fmt.Sprintf("%s:%d", peerAddr.IP.String(), peerAddr.Port))
|
||||
err = a.AddChannelBind(allocation.NewChannelBind(
|
||||
channel,
|
||||
&net.UDPAddr{IP: peerAddr.IP, Port: peerAddr.Port},
|
||||
r.Log,
|
||||
), r.ChannelBindTimeout)
|
||||
if err != nil {
|
||||
return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse), []stun.Setter{messageIntegrity}...)...)
|
||||
}
|
||||
|
||||
func handleChannelData(r Request, c *proto.ChannelData) error {
|
||||
r.Log.Debugf("received ChannelData from %s", r.SrcAddr.String())
|
||||
|
||||
a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{
|
||||
SrcAddr: r.SrcAddr,
|
||||
DstAddr: r.Conn.LocalAddr(),
|
||||
Protocol: allocation.UDP,
|
||||
})
|
||||
if a == nil {
|
||||
return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr())
|
||||
}
|
||||
|
||||
channel := a.GetChannelByNumber(c.Number)
|
||||
if channel == nil {
|
||||
return fmt.Errorf("%w %x", errNoSuchChannelBind, uint16(c.Number))
|
||||
}
|
||||
|
||||
l, err := a.RelaySocket.WriteTo(c.Data, channel.Peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errFailedWriteSocket, err.Error())
|
||||
} else if l != len(c.Data) {
|
||||
return fmt.Errorf("%w %d != %d (expected)", errShortWrite, l, len(c.Data))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
133
vendor/github.com/pion/turn/v2/internal/server/util.go
generated
vendored
Normal file
133
vendor/github.com/pion/turn/v2/internal/server/util.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/md5" //nolint:gosec,gci
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
maximumAllocationLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-6.2 defines 3600 seconds recommendation
|
||||
nonceLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-4
|
||||
|
||||
)
|
||||
|
||||
func randSeq(n int) string {
|
||||
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))] //nolint:gosec
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func buildNonce() (string, error) {
|
||||
/* #nosec */
|
||||
h := md5.New()
|
||||
if _, err := io.WriteString(h, strconv.FormatInt(time.Now().Unix(), 10)); err != nil {
|
||||
return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err)
|
||||
}
|
||||
if _, err := io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)); err != nil { //nolint:gosec
|
||||
return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func buildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error {
|
||||
msg, err := stun.Build(attrs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = conn.WriteTo(msg.Raw, dst)
|
||||
return err
|
||||
}
|
||||
|
||||
// Send a STUN packet and return the original error to the caller
|
||||
func buildAndSendErr(conn net.PacketConn, dst net.Addr, err error, attrs ...stun.Setter) error {
|
||||
if sendErr := buildAndSend(conn, dst, attrs...); sendErr != nil {
|
||||
err = fmt.Errorf("%w %v %v", errFailedToSendError, sendErr, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func buildMsg(transactionID [stun.TransactionIDSize]byte, msgType stun.MessageType, additional ...stun.Setter) []stun.Setter {
|
||||
return append([]stun.Setter{&stun.Message{TransactionID: transactionID}, msgType}, additional...)
|
||||
}
|
||||
|
||||
func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method) (stun.MessageIntegrity, bool, error) {
|
||||
respondWithNonce := func(responseCode stun.ErrorCode) (stun.MessageIntegrity, bool, error) {
|
||||
nonce, err := buildNonce()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Nonce has already been taken
|
||||
if _, keyCollision := r.Nonces.LoadOrStore(nonce, time.Now()); keyCollision {
|
||||
return nil, false, errDuplicatedNonce
|
||||
}
|
||||
|
||||
return nil, false, buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID,
|
||||
stun.NewType(callingMethod, stun.ClassErrorResponse),
|
||||
&stun.ErrorCodeAttribute{Code: responseCode},
|
||||
stun.NewNonce(nonce),
|
||||
stun.NewRealm(r.Realm),
|
||||
)...)
|
||||
}
|
||||
|
||||
if !m.Contains(stun.AttrMessageIntegrity) {
|
||||
return respondWithNonce(stun.CodeUnauthorized)
|
||||
}
|
||||
|
||||
nonceAttr := &stun.Nonce{}
|
||||
usernameAttr := &stun.Username{}
|
||||
realmAttr := &stun.Realm{}
|
||||
badRequestMsg := buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest})
|
||||
|
||||
if err := nonceAttr.GetFrom(m); err != nil {
|
||||
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
// Assert Nonce exists and is not expired
|
||||
nonceCreationTime, ok := r.Nonces.Load(string(*nonceAttr))
|
||||
if !ok || time.Since(nonceCreationTime.(time.Time)) >= nonceLifetime {
|
||||
r.Nonces.Delete(nonceAttr)
|
||||
return respondWithNonce(stun.CodeStaleNonce)
|
||||
}
|
||||
|
||||
if err := realmAttr.GetFrom(m); err != nil {
|
||||
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
} else if err := usernameAttr.GetFrom(m); err != nil {
|
||||
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
ourKey, ok := r.AuthHandler(usernameAttr.String(), realmAttr.String(), r.SrcAddr)
|
||||
if !ok {
|
||||
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, fmt.Errorf("%w %s", errNoSuchUser, usernameAttr.String()), badRequestMsg...)
|
||||
}
|
||||
|
||||
if err := stun.MessageIntegrity(ourKey).Check(m); err != nil {
|
||||
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
||||
}
|
||||
|
||||
return stun.MessageIntegrity(ourKey), true, nil
|
||||
}
|
||||
|
||||
func allocationLifeTime(m *stun.Message) time.Duration {
|
||||
lifetimeDuration := proto.DefaultLifetime
|
||||
|
||||
var lifetime proto.Lifetime
|
||||
if err := lifetime.GetFrom(m); err == nil {
|
||||
if lifetime.Duration < maximumAllocationLifetime {
|
||||
lifetimeDuration = lifetime.Duration
|
||||
}
|
||||
}
|
||||
|
||||
return lifetimeDuration
|
||||
}
|
||||
Reference in New Issue
Block a user