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
+259
View File
@@ -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())
}
}
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}