feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

View 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")
)

View 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
View 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
View 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
View 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
}