// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package allocation contains all CRUD operations for allocations
package allocation import ( ) type allocationResponse struct { transactionID [stun.TransactionIDSize]byte responseAttrs []stun.Setter } // Allocation is tied to a FiveTuple and relays traffic // use CreateAllocation and GetAllocation to operate. type Allocation struct { RelayAddr net.Addr Protocol Protocol TurnSocket net.PacketConn RelaySocket net.PacketConn fiveTuple *FiveTuple permissionsLock sync.RWMutex permissions map[string]*Permission channelBindingsLock sync.RWMutex channelBindings []*ChannelBind lifetimeTimer *time.Timer closed chan interface{} log logging.LeveledLogger // Some clients (Firefox or others using resiprocate's nICE lib) may retry allocation // with same 5 tuple when received 413, for compatible with these clients, // cache for response lost and client retry to implement 'stateless stack approach' // See: https://datatracker.ietf.org/doc/html/rfc5766#section-6.2 responseCache atomic.Value // *allocationResponse } // NewAllocation creates a new instance of NewAllocation. func ( net.PacketConn, *FiveTuple, logging.LeveledLogger) *Allocation { return &Allocation{ TurnSocket: , fiveTuple: , permissions: make(map[string]*Permission, 64), closed: make(chan interface{}), log: , } } // GetPermission gets the Permission from the allocation. func ( *Allocation) ( net.Addr) *Permission { .permissionsLock.RLock() defer .permissionsLock.RUnlock() return .permissions[ipnet.FingerprintAddr()] } // AddPermission adds a new permission to the allocation. func ( *Allocation) ( *Permission) { := ipnet.FingerprintAddr(.Addr) .permissionsLock.RLock() , := .permissions[] .permissionsLock.RUnlock() if { .refresh(permissionTimeout) return } .allocation = .permissionsLock.Lock() .permissions[] = .permissionsLock.Unlock() .start(permissionTimeout) } // RemovePermission removes the net.Addr's fingerprint from the allocation's permissions. func ( *Allocation) ( net.Addr) { .permissionsLock.Lock() defer .permissionsLock.Unlock() delete(.permissions, ipnet.FingerprintAddr()) } // AddChannelBind adds a new ChannelBind to the allocation, it also updates the // permissions needed for this ChannelBind. func ( *Allocation) ( *ChannelBind, time.Duration) error { // Check that this channel id isn't bound to another transport address, and // that this transport address isn't bound to another channel number. := .GetChannelByNumber(.Number) if != .GetChannelByAddr(.Peer) { return errSameChannelDifferentPeer } // Add or refresh this channel. if == nil { .channelBindingsLock.Lock() defer .channelBindingsLock.Unlock() .allocation = .channelBindings = append(.channelBindings, ) .start() // Channel binds also refresh permissions. .AddPermission(NewPermission(.Peer, .log)) } else { .refresh() // Channel binds also refresh permissions. .AddPermission(NewPermission(.Peer, .log)) } return nil } // RemoveChannelBind removes the ChannelBind from this allocation by id. func ( *Allocation) ( proto.ChannelNumber) bool { .channelBindingsLock.Lock() defer .channelBindingsLock.Unlock() for := len(.channelBindings) - 1; >= 0; -- { if .channelBindings[].Number == { .channelBindings = append(.channelBindings[:], .channelBindings[+1:]...) return true } } return false } // GetChannelByNumber gets the ChannelBind from this allocation by id. func ( *Allocation) ( proto.ChannelNumber) *ChannelBind { .channelBindingsLock.RLock() defer .channelBindingsLock.RUnlock() for , := range .channelBindings { if .Number == { return } } return nil } // GetChannelByAddr gets the ChannelBind from this allocation by net.Addr. func ( *Allocation) ( net.Addr) *ChannelBind { .channelBindingsLock.RLock() defer .channelBindingsLock.RUnlock() for , := range .channelBindings { if ipnet.AddrEqual(.Peer, ) { return } } return nil } // Refresh updates the allocations lifetime. func ( *Allocation) ( time.Duration) { if !.lifetimeTimer.Reset() { .log.Errorf("Failed to reset allocation timer for %v", .fiveTuple) } } // SetResponseCache cache allocation response for retransmit allocation request. func ( *Allocation) ( [stun.TransactionIDSize]byte, []stun.Setter) { .responseCache.Store(&allocationResponse{ transactionID: , responseAttrs: , }) } // GetResponseCache return response cache for retransmit allocation request. func ( *Allocation) () ( [stun.TransactionIDSize]byte, []stun.Setter) { if , := .responseCache.Load().(*allocationResponse); && != nil { , = .transactionID, .responseAttrs } return } // Close closes the allocation. func ( *Allocation) () error { select { case <-.closed: return nil default: } close(.closed) .lifetimeTimer.Stop() .permissionsLock.RLock() for , := range .permissions { .lifetimeTimer.Stop() } .permissionsLock.RUnlock() .channelBindingsLock.RLock() for , := range .channelBindings { .lifetimeTimer.Stop() } .channelBindingsLock.RUnlock() return .RelaySocket.Close() } // https://tools.ietf.org/html/rfc5766#section-10.3 // When the server receives a UDP datagram at a currently allocated // relayed transport address, the server looks up the allocation // associated with the relayed transport address. The server then // checks to see whether the set of permissions for the allocation allow // the relaying of the UDP datagram as described in Section 8. // // If relaying is permitted, then the server checks if there is a // channel bound to the peer that sent the UDP datagram (see // Section 11). If a channel is bound, then processing proceeds as // described in Section 11.7. // // If relaying is permitted but no channel is bound to the peer, then // the server forms and sends a Data indication. The Data indication // MUST contain both an XOR-PEER-ADDRESS and a DATA attribute. The DATA // attribute is set to the value of the 'data octets' field from the // datagram, and the XOR-PEER-ADDRESS attribute is set to the source // transport address of the received UDP datagram. The Data indication // is then sent on the 5-tuple associated with the allocation. const rtpMTU = 1600 func ( *Allocation) ( *Manager) { := make([]byte, rtpMTU) for { , , := .RelaySocket.ReadFrom() if != nil { .DeleteAllocation(.fiveTuple) return } .log.Debugf("Relay socket %s received %d bytes from %s", .RelaySocket.LocalAddr(), , ) if := .GetChannelByAddr(); != nil { // nolint:nestif := &proto.ChannelData{ Data: [:], Number: .Number, } .Encode() if _, = .TurnSocket.WriteTo(.Raw, .fiveTuple.SrcAddr); != nil { .log.Errorf("Failed to send ChannelData from allocation %v %v", , ) } } else if := .GetPermission(); != nil { , := .(*net.UDPAddr) if ! { .log.Errorf("Failed to send DataIndication from allocation %v %v", , ) return } := proto.PeerAddress{IP: .IP, Port: .Port} := proto.Data([:]) , := stun.Build( stun.TransactionID, stun.NewType(stun.MethodData, stun.ClassIndication), , , ) if != nil { .log.Errorf("Failed to send DataIndication from allocation %v %v", , ) return } .log.Debugf("Relaying message from %s to client at %s", , .fiveTuple.SrcAddr) if _, = .TurnSocket.WriteTo(.Raw, .fiveTuple.SrcAddr); != nil { .log.Errorf("Failed to send DataIndication from allocation %v %v", , ) } } else { .log.Infof("No Permission or Channel exists for %v on allocation %v", , .RelayAddr) } } }