+259
@@ -0,0 +1,259 @@
|
||||
// Package allocation contains all CRUD operations for allocations
|
||||
package allocation
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/stun"
|
||||
"github.com/pion/turn/v2/internal/ipnet"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func addr2IPFingerprint(addr net.Addr) string {
|
||||
switch a := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
return a.IP.String()
|
||||
case *net.TCPAddr: // Do we really need this case?
|
||||
return a.IP.String()
|
||||
}
|
||||
return "" // shoud never happen
|
||||
}
|
||||
|
||||
// NewAllocation creates a new instance of NewAllocation.
|
||||
func NewAllocation(turnSocket net.PacketConn, fiveTuple *FiveTuple, log logging.LeveledLogger) *Allocation {
|
||||
return &Allocation{
|
||||
TurnSocket: turnSocket,
|
||||
fiveTuple: fiveTuple,
|
||||
permissions: make(map[string]*Permission, 64),
|
||||
closed: make(chan interface{}),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPermission gets the Permission from the allocation
|
||||
func (a *Allocation) GetPermission(addr net.Addr) *Permission {
|
||||
a.permissionsLock.RLock()
|
||||
defer a.permissionsLock.RUnlock()
|
||||
|
||||
return a.permissions[addr2IPFingerprint(addr)]
|
||||
}
|
||||
|
||||
// AddPermission adds a new permission to the allocation
|
||||
func (a *Allocation) AddPermission(p *Permission) {
|
||||
fingerprint := addr2IPFingerprint(p.Addr)
|
||||
|
||||
a.permissionsLock.RLock()
|
||||
existedPermission, ok := a.permissions[fingerprint]
|
||||
a.permissionsLock.RUnlock()
|
||||
|
||||
if ok {
|
||||
existedPermission.refresh(permissionTimeout)
|
||||
return
|
||||
}
|
||||
|
||||
p.allocation = a
|
||||
a.permissionsLock.Lock()
|
||||
a.permissions[fingerprint] = p
|
||||
a.permissionsLock.Unlock()
|
||||
|
||||
p.start(permissionTimeout)
|
||||
}
|
||||
|
||||
// RemovePermission removes the net.Addr's fingerprint from the allocation's permissions
|
||||
func (a *Allocation) RemovePermission(addr net.Addr) {
|
||||
a.permissionsLock.Lock()
|
||||
defer a.permissionsLock.Unlock()
|
||||
delete(a.permissions, addr2IPFingerprint(addr))
|
||||
}
|
||||
|
||||
// AddChannelBind adds a new ChannelBind to the allocation, it also updates the
|
||||
// permissions needed for this ChannelBind
|
||||
func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime 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.
|
||||
channelByNumber := a.GetChannelByNumber(c.Number)
|
||||
|
||||
if channelByNumber != a.GetChannelByAddr(c.Peer) {
|
||||
return errSameChannelDifferentPeer
|
||||
}
|
||||
|
||||
// Add or refresh this channel.
|
||||
if channelByNumber == nil {
|
||||
a.channelBindingsLock.Lock()
|
||||
defer a.channelBindingsLock.Unlock()
|
||||
|
||||
c.allocation = a
|
||||
a.channelBindings = append(a.channelBindings, c)
|
||||
c.start(lifetime)
|
||||
|
||||
// Channel binds also refresh permissions.
|
||||
a.AddPermission(NewPermission(c.Peer, a.log))
|
||||
} else {
|
||||
channelByNumber.refresh(lifetime)
|
||||
|
||||
// Channel binds also refresh permissions.
|
||||
a.AddPermission(NewPermission(channelByNumber.Peer, a.log))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveChannelBind removes the ChannelBind from this allocation by id
|
||||
func (a *Allocation) RemoveChannelBind(number proto.ChannelNumber) bool {
|
||||
a.channelBindingsLock.Lock()
|
||||
defer a.channelBindingsLock.Unlock()
|
||||
|
||||
for i := len(a.channelBindings) - 1; i >= 0; i-- {
|
||||
if a.channelBindings[i].Number == number {
|
||||
a.channelBindings = append(a.channelBindings[:i], a.channelBindings[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetChannelByNumber gets the ChannelBind from this allocation by id
|
||||
func (a *Allocation) GetChannelByNumber(number proto.ChannelNumber) *ChannelBind {
|
||||
a.channelBindingsLock.RLock()
|
||||
defer a.channelBindingsLock.RUnlock()
|
||||
for _, cb := range a.channelBindings {
|
||||
if cb.Number == number {
|
||||
return cb
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChannelByAddr gets the ChannelBind from this allocation by net.Addr
|
||||
func (a *Allocation) GetChannelByAddr(addr net.Addr) *ChannelBind {
|
||||
a.channelBindingsLock.RLock()
|
||||
defer a.channelBindingsLock.RUnlock()
|
||||
for _, cb := range a.channelBindings {
|
||||
if ipnet.AddrEqual(cb.Peer, addr) {
|
||||
return cb
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh updates the allocations lifetime
|
||||
func (a *Allocation) Refresh(lifetime time.Duration) {
|
||||
if !a.lifetimeTimer.Reset(lifetime) {
|
||||
a.log.Errorf("Failed to reset allocation timer for %v", a.fiveTuple)
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the allocation
|
||||
func (a *Allocation) Close() error {
|
||||
select {
|
||||
case <-a.closed:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
close(a.closed)
|
||||
|
||||
a.lifetimeTimer.Stop()
|
||||
|
||||
a.permissionsLock.RLock()
|
||||
for _, p := range a.permissions {
|
||||
p.lifetimeTimer.Stop()
|
||||
}
|
||||
a.permissionsLock.RUnlock()
|
||||
|
||||
a.channelBindingsLock.RLock()
|
||||
for _, c := range a.channelBindings {
|
||||
c.lifetimeTimer.Stop()
|
||||
}
|
||||
a.channelBindingsLock.RUnlock()
|
||||
|
||||
return a.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 (a *Allocation) packetHandler(m *Manager) {
|
||||
buffer := make([]byte, rtpMTU)
|
||||
|
||||
for {
|
||||
n, srcAddr, err := a.RelaySocket.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
m.DeleteAllocation(a.fiveTuple)
|
||||
return
|
||||
}
|
||||
|
||||
a.log.Debugf("relay socket %s received %d bytes from %s",
|
||||
a.RelaySocket.LocalAddr().String(),
|
||||
n,
|
||||
srcAddr.String())
|
||||
|
||||
if channel := a.GetChannelByAddr(srcAddr); channel != nil {
|
||||
channelData := &proto.ChannelData{
|
||||
Data: buffer[:n],
|
||||
Number: channel.Number,
|
||||
}
|
||||
channelData.Encode()
|
||||
|
||||
if _, err = a.TurnSocket.WriteTo(channelData.Raw, a.fiveTuple.SrcAddr); err != nil {
|
||||
a.log.Errorf("Failed to send ChannelData from allocation %v %v", srcAddr, err)
|
||||
}
|
||||
} else if p := a.GetPermission(srcAddr); p != nil {
|
||||
udpAddr := srcAddr.(*net.UDPAddr)
|
||||
peerAddressAttr := proto.PeerAddress{IP: udpAddr.IP, Port: udpAddr.Port}
|
||||
dataAttr := proto.Data(buffer[:n])
|
||||
|
||||
msg, err := stun.Build(stun.TransactionID, stun.NewType(stun.MethodData, stun.ClassIndication), peerAddressAttr, dataAttr)
|
||||
if err != nil {
|
||||
a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err)
|
||||
}
|
||||
a.log.Debugf("relaying message from %s to client at %s",
|
||||
srcAddr.String(),
|
||||
a.fiveTuple.SrcAddr.String())
|
||||
if _, err = a.TurnSocket.WriteTo(msg.Raw, a.fiveTuple.SrcAddr); err != nil {
|
||||
a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err)
|
||||
}
|
||||
} else {
|
||||
a.log.Infof("No Permission or Channel exists for %v on allocation %v", srcAddr, a.RelayAddr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package allocation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
// ManagerConfig a bag of config params for Manager.
|
||||
type ManagerConfig struct {
|
||||
LeveledLogger logging.LeveledLogger
|
||||
AllocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error)
|
||||
AllocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error)
|
||||
}
|
||||
|
||||
type reservation struct {
|
||||
token string
|
||||
port int
|
||||
}
|
||||
|
||||
// Manager is used to hold active allocations
|
||||
type Manager struct {
|
||||
lock sync.RWMutex
|
||||
log logging.LeveledLogger
|
||||
|
||||
allocations map[string]*Allocation
|
||||
reservations []*reservation
|
||||
|
||||
allocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error)
|
||||
allocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error)
|
||||
}
|
||||
|
||||
// NewManager creates a new instance of Manager.
|
||||
func NewManager(config ManagerConfig) (*Manager, error) {
|
||||
switch {
|
||||
case config.AllocatePacketConn == nil:
|
||||
return nil, errAllocatePacketConnMustBeSet
|
||||
case config.AllocateConn == nil:
|
||||
return nil, errAllocateConnMustBeSet
|
||||
case config.LeveledLogger == nil:
|
||||
return nil, errLeveledLoggerMustBeSet
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
log: config.LeveledLogger,
|
||||
allocations: make(map[string]*Allocation, 64),
|
||||
allocatePacketConn: config.AllocatePacketConn,
|
||||
allocateConn: config.AllocateConn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAllocation fetches the allocation matching the passed FiveTuple
|
||||
func (m *Manager) GetAllocation(fiveTuple *FiveTuple) *Allocation {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
return m.allocations[fiveTuple.Fingerprint()]
|
||||
}
|
||||
|
||||
// AllocationCount returns the number of existing allocations
|
||||
func (m *Manager) AllocationCount() int {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
return len(m.allocations)
|
||||
}
|
||||
|
||||
// Close closes the manager and closes all allocations it manages
|
||||
func (m *Manager) Close() error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
for _, a := range m.allocations {
|
||||
if err := a.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAllocation creates a new allocation and starts relaying
|
||||
func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) {
|
||||
switch {
|
||||
case fiveTuple == nil:
|
||||
return nil, errNilFiveTuple
|
||||
case fiveTuple.SrcAddr == nil:
|
||||
return nil, errNilFiveTupleSrcAddr
|
||||
case fiveTuple.DstAddr == nil:
|
||||
return nil, errNilFiveTupleDstAddr
|
||||
case turnSocket == nil:
|
||||
return nil, errNilTurnSocket
|
||||
case lifetime == 0:
|
||||
return nil, errLifetimeZero
|
||||
}
|
||||
|
||||
if a := m.GetAllocation(fiveTuple); a != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errDupeFiveTuple, fiveTuple)
|
||||
}
|
||||
a := NewAllocation(turnSocket, fiveTuple, m.log)
|
||||
|
||||
conn, relayAddr, err := m.allocatePacketConn("udp4", requestedPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.RelaySocket = conn
|
||||
a.RelayAddr = relayAddr
|
||||
|
||||
m.log.Debugf("listening on relay addr: %s", a.RelayAddr.String())
|
||||
|
||||
a.lifetimeTimer = time.AfterFunc(lifetime, func() {
|
||||
m.DeleteAllocation(a.fiveTuple)
|
||||
})
|
||||
|
||||
m.lock.Lock()
|
||||
m.allocations[fiveTuple.Fingerprint()] = a
|
||||
m.lock.Unlock()
|
||||
|
||||
go a.packetHandler(m)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// DeleteAllocation removes an allocation
|
||||
func (m *Manager) DeleteAllocation(fiveTuple *FiveTuple) {
|
||||
fingerprint := fiveTuple.Fingerprint()
|
||||
|
||||
m.lock.Lock()
|
||||
allocation := m.allocations[fingerprint]
|
||||
delete(m.allocations, fingerprint)
|
||||
m.lock.Unlock()
|
||||
|
||||
if allocation == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := allocation.Close(); err != nil {
|
||||
m.log.Errorf("Failed to close allocation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// CreateReservation stores the reservation for the token+port
|
||||
func (m *Manager) CreateReservation(reservationToken string, port int) {
|
||||
time.AfterFunc(30*time.Second, func() {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
for i := len(m.reservations) - 1; i >= 0; i-- {
|
||||
if m.reservations[i].token == reservationToken {
|
||||
m.reservations = append(m.reservations[:i], m.reservations[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
m.lock.Lock()
|
||||
m.reservations = append(m.reservations, &reservation{
|
||||
token: reservationToken,
|
||||
port: port,
|
||||
})
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
// GetReservation returns the port for a given reservation if it exists
|
||||
func (m *Manager) GetReservation(reservationToken string) (int, bool) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
|
||||
for _, r := range m.reservations {
|
||||
if r.token == reservationToken {
|
||||
return r.port, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// GetRandomEvenPort returns a random un-allocated udp4 port
|
||||
func (m *Manager) GetRandomEvenPort() (int, error) {
|
||||
for i := 0; i < 128; i++ {
|
||||
conn, addr, err := m.allocatePacketConn("udp4", 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
udpAddr, ok := addr.(*net.UDPAddr)
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return 0, errFailedToCastUDPAddr
|
||||
}
|
||||
if udpAddr.Port%2 == 0 {
|
||||
return udpAddr.Port, nil
|
||||
}
|
||||
}
|
||||
return 0, errFailedToAllocateEvenPort
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package allocation
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/turn/v2/internal/proto"
|
||||
)
|
||||
|
||||
// ChannelBind represents a TURN Channel
|
||||
// https://tools.ietf.org/html/rfc5766#section-2.5
|
||||
type ChannelBind struct {
|
||||
Peer net.Addr
|
||||
Number proto.ChannelNumber
|
||||
|
||||
allocation *Allocation
|
||||
lifetimeTimer *time.Timer
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
// NewChannelBind creates a new ChannelBind
|
||||
func NewChannelBind(number proto.ChannelNumber, peer net.Addr, log logging.LeveledLogger) *ChannelBind {
|
||||
return &ChannelBind{
|
||||
Number: number,
|
||||
Peer: peer,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChannelBind) start(lifetime time.Duration) {
|
||||
c.lifetimeTimer = time.AfterFunc(lifetime, func() {
|
||||
if !c.allocation.RemoveChannelBind(c.Number) {
|
||||
c.log.Errorf("Failed to remove ChannelBind for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ChannelBind) refresh(lifetime time.Duration) {
|
||||
if !c.lifetimeTimer.Reset(lifetime) {
|
||||
c.log.Errorf("Failed to reset ChannelBind timer for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package allocation
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
errAllocatePacketConnMustBeSet = errors.New("AllocatePacketConn must be set")
|
||||
errAllocateConnMustBeSet = errors.New("AllocateConn must be set")
|
||||
errLeveledLoggerMustBeSet = errors.New("LeveledLogger must be set")
|
||||
errSameChannelDifferentPeer = errors.New("you cannot use the same channel number with different peer")
|
||||
errNilFiveTuple = errors.New("allocations must not be created with nil FivTuple")
|
||||
errNilFiveTupleSrcAddr = errors.New("allocations must not be created with nil FiveTuple.SrcAddr")
|
||||
errNilFiveTupleDstAddr = errors.New("allocations must not be created with nil FiveTuple.DstAddr")
|
||||
errNilTurnSocket = errors.New("allocations must not be created with nil turnSocket")
|
||||
errLifetimeZero = errors.New("allocations must not be created with a lifetime of 0")
|
||||
errDupeFiveTuple = errors.New("allocation attempt created with duplicate FiveTuple")
|
||||
errFailedToCastUDPAddr = errors.New("failed to cast net.Addr to *net.UDPAddr")
|
||||
errFailedToAllocateEvenPort = errors.New("failed to allocate an even port")
|
||||
)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package allocation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Protocol is an enum for relay protocol
|
||||
type Protocol uint8
|
||||
|
||||
// Network protocols for relay
|
||||
const (
|
||||
UDP Protocol = iota
|
||||
TCP
|
||||
)
|
||||
|
||||
// FiveTuple is the combination (client IP address and port, server IP
|
||||
// address and port, and transport protocol (currently one of UDP,
|
||||
// TCP, or TLS)) used to communicate between the client and the
|
||||
// server. The 5-tuple uniquely identifies this communication
|
||||
// stream. The 5-tuple also uniquely identifies the Allocation on
|
||||
// the server.
|
||||
type FiveTuple struct {
|
||||
Protocol
|
||||
SrcAddr, DstAddr net.Addr
|
||||
}
|
||||
|
||||
// Equal asserts if two FiveTuples are equal
|
||||
func (f *FiveTuple) Equal(b *FiveTuple) bool {
|
||||
return f.Fingerprint() == b.Fingerprint()
|
||||
}
|
||||
|
||||
// Fingerprint is the identity of a FiveTuple
|
||||
func (f *FiveTuple) Fingerprint() string {
|
||||
return fmt.Sprintf("%d_%s_%s", f.Protocol, f.SrcAddr.String(), f.DstAddr.String())
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package allocation
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/pion/logging"
|
||||
)
|
||||
|
||||
const permissionTimeout = time.Duration(5) * time.Minute
|
||||
|
||||
// Permission represents a TURN permission. TURN permissions mimic the address-restricted
|
||||
// filtering mechanism of NATs that comply with [RFC4787].
|
||||
// https://tools.ietf.org/html/rfc5766#section-2.3
|
||||
type Permission struct {
|
||||
Addr net.Addr
|
||||
allocation *Allocation
|
||||
lifetimeTimer *time.Timer
|
||||
log logging.LeveledLogger
|
||||
}
|
||||
|
||||
// NewPermission create a new Permission
|
||||
func NewPermission(addr net.Addr, log logging.LeveledLogger) *Permission {
|
||||
return &Permission{
|
||||
Addr: addr,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Permission) start(lifetime time.Duration) {
|
||||
p.lifetimeTimer = time.AfterFunc(lifetime, func() {
|
||||
p.allocation.RemovePermission(p.Addr)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Permission) refresh(lifetime time.Duration) {
|
||||
if !p.lifetimeTimer.Reset(lifetime) {
|
||||
p.log.Errorf("Failed to reset permission timer for %v %v", p.Addr, p.allocation.fiveTuple)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user