Skip to content

Commit 2eb9164

Browse files
committed
Add a callback to handle allocation requests
1 parent f880e55 commit 2eb9164

File tree

6 files changed

+150
-21
lines changed

6 files changed

+150
-21
lines changed

internal/allocation/allocation_manager.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import (
1010
"time"
1111

1212
"github.com/pion/logging"
13+
"github.com/pion/stun"
1314
)
1415

1516
// ManagerConfig a bag of config params for Manager.
1617
type ManagerConfig struct {
1718
LeveledLogger logging.LeveledLogger
1819
AllocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error)
1920
AllocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error)
21+
AllocationHandler func(clientAddr net.Addr) (alternateServer net.Addr, errorCode stun.ErrorCode)
2022
PermissionHandler func(sourceAddr net.Addr, peerIP net.IP) bool
2123
}
2224

@@ -35,6 +37,7 @@ type Manager struct {
3537

3638
allocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error)
3739
allocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error)
40+
allocationHandler func(clientAddr net.Addr) (alternateServer net.Addr, errorCode stun.ErrorCode)
3841
permissionHandler func(sourceAddr net.Addr, peerIP net.IP) bool
3942
}
4043

@@ -54,6 +57,7 @@ func NewManager(config ManagerConfig) (*Manager, error) {
5457
allocations: make(map[string]*Allocation, 64),
5558
allocatePacketConn: config.AllocatePacketConn,
5659
allocateConn: config.AllocateConn,
60+
allocationHandler: config.AllocationHandler,
5761
permissionHandler: config.PermissionHandler,
5862
}, nil
5963
}
@@ -85,6 +89,32 @@ func (m *Manager) Close() error {
8589
return nil
8690
}
8791

92+
// HandleAllocation calls the allocation handler callback to decide whether to admit a client request.
93+
func (m *Manager) HandleAllocation(clientAddr net.Addr) (net.IP, int, stun.ErrorCode) {
94+
if m.allocationHandler == nil {
95+
return nil, 0, 0
96+
}
97+
98+
altServer, errorCode := m.allocationHandler(clientAddr)
99+
100+
var altIP net.IP
101+
var altPort int
102+
switch addr := altServer.(type) {
103+
case *net.UDPAddr:
104+
altIP = addr.IP
105+
altPort = addr.Port
106+
case *net.TCPAddr:
107+
altIP = addr.IP
108+
altPort = addr.Port
109+
default:
110+
m.log.Warnf("received unknown alternate server address from allocation handler: %s",
111+
altServer.String())
112+
return nil, 0, stun.CodeServerError
113+
}
114+
115+
return altIP, altPort, errorCode
116+
}
117+
88118
// CreateAllocation creates a new allocation and starts relaying
89119
func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) {
90120
switch {

internal/server/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ var (
2626
errShortWrite = errors.New("packet write smaller than packet")
2727
errNoSuchChannelBind = errors.New("no such channel bind")
2828
errFailedWriteSocket = errors.New("failed writing to socket")
29+
errFailedToSetAlternateServer = errors.New("cannot add ALTERNATE-SERVER attribute")
2930
)

internal/server/turn.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,34 @@ func handleAllocateRequest(r Request, m *stun.Message) error {
120120
// with a 300 (Try Alternate) error if it wishes to redirect the
121121
// client to a different server. The use of this error code and
122122
// attribute follow the specification in [RFC5389].
123+
if altIP, altPort, errorCode := r.AllocationManager.HandleAllocation(r.SrcAddr); errorCode != 0 {
124+
r.Log.Debugf("allocation handler sending error code %d to client %s", errorCode, r.SrcAddr.String())
125+
126+
msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse))
127+
if err := errorCode.AddTo(m); err != nil {
128+
return buildAndSendErr(r.Conn, r.SrcAddr, err, msg...)
129+
}
130+
131+
if errorCode == stun.CodeTryAlternate && altIP != nil && altPort != 0 {
132+
addr := &stun.AlternateServer{
133+
IP: altIP,
134+
Port: altPort,
135+
}
136+
if err := addr.AddTo(m); err != nil {
137+
return buildAndSendErr(r.Conn, r.SrcAddr, errFailedToSetAlternateServer, msg...)
138+
}
139+
140+
r.Log.Debugf("redirecting client to %s:%d", addr.IP.String(), addr.Port)
141+
}
142+
143+
fmt.Printf("%#v\n", msg)
144+
145+
return buildAndSend(r.Conn, r.SrcAddr, msg...)
146+
}
147+
148+
// If all the checks pass, the server creates the allocation. The
149+
// 5-tuple is set to the 5-tuple from the Allocate request, while the
150+
// list of permissions and the list of channels are initially empty.
123151
lifetimeDuration := allocationLifeTime(m)
124152
a, err := r.AllocationManager.CreateAllocation(
125153
fiveTuple,

server.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func NewServer(config ServerConfig) (*Server, error) {
6969
}
7070

7171
for _, cfg := range s.packetConnConfigs {
72-
am, err := s.createAllocationManager(cfg.RelayAddressGenerator, cfg.PermissionHandler)
72+
am, err := s.createAllocationManager(cfg.RelayAddressGenerator, cfg.AllocationHandler, cfg.PermissionHandler)
7373
if err != nil {
7474
return nil, fmt.Errorf("failed to create AllocationManager: %w", err)
7575
}
@@ -84,7 +84,7 @@ func NewServer(config ServerConfig) (*Server, error) {
8484
}
8585

8686
for _, cfg := range s.listenerConfigs {
87-
am, err := s.createAllocationManager(cfg.RelayAddressGenerator, cfg.PermissionHandler)
87+
am, err := s.createAllocationManager(cfg.RelayAddressGenerator, cfg.AllocationHandler, cfg.PermissionHandler)
8888
if err != nil {
8989
return nil, fmt.Errorf("failed to create AllocationManager: %w", err)
9090
}
@@ -101,7 +101,7 @@ func NewServer(config ServerConfig) (*Server, error) {
101101
return s, nil
102102
}
103103

104-
// AllocationCount returns the number of active allocations. It can be used to drain the server before closing
104+
// AllocationCount returns the number of active allocations. It can be used to drain the server before closing.
105105
func (s *Server) AllocationCount() int {
106106
allocs := 0
107107
for _, am := range s.allocationManagers {
@@ -156,15 +156,12 @@ func (s *Server) readListener(l net.Listener, am *allocation.Manager) {
156156
}
157157
}
158158

159-
func (s *Server) createAllocationManager(addrGenerator RelayAddressGenerator, handler PermissionHandler) (*allocation.Manager, error) {
160-
if handler == nil {
161-
handler = DefaultPermissionHandler
162-
}
163-
159+
func (s *Server) createAllocationManager(addrGenerator RelayAddressGenerator, allocationHandler AllocationHandler, permissionHandler PermissionHandler) (*allocation.Manager, error) {
164160
am, err := allocation.NewManager(allocation.ManagerConfig{
165161
AllocatePacketConn: addrGenerator.AllocatePacketConn,
166162
AllocateConn: addrGenerator.AllocateConn,
167-
PermissionHandler: handler,
163+
AllocationHandler: allocationHandler,
164+
PermissionHandler: permissionHandler,
168165
LeveledLogger: s.log,
169166
})
170167
if err != nil {

server_config.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/pion/logging"
14+
"github.com/pion/stun"
1415
)
1516

1617
// RelayAddressGenerator is used to generate a RelayAddress when creating an allocation.
@@ -26,6 +27,15 @@ type RelayAddressGenerator interface {
2627
AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error)
2728
}
2829

30+
// AllocationHandler is a callback used to handle incoming allocation requests, allowing users to
31+
// customize Pion TURN with custom behavior. If the returned error code is nonzero then the request
32+
// is rejected with the given error code. This is useful to, e.g., return an "Allocation Quota
33+
// Reached" when the number of allocations from the client address surpasses a limit. If the error
34+
// code is "Try Alternate" then the reject response will also contain an ALTERNATE-SERVER attribute
35+
// with the returned alternate server address. This is useful to redirect the client to another
36+
// TURN server.
37+
type AllocationHandler func(clientAddr net.Addr) (alternateServer net.Addr, errorCode stun.ErrorCode)
38+
2939
// PermissionHandler is a callback to filter incoming CreatePermission and ChannelBindRequest
3040
// requests based on the client IP address and port and the peer IP address the client intends to
3141
// connect to. If the client is behind a NAT then the filter acts on the server reflexive
@@ -34,11 +44,6 @@ type RelayAddressGenerator interface {
3444
// of NATs that comply with [RFC4787], see https://tools.ietf.org/html/rfc5766#section-2.3.
3545
type PermissionHandler func(clientAddr net.Addr, peerIP net.IP) (ok bool)
3646

37-
// DefaultPermissionHandler is convince function that grants permission to all peers
38-
func DefaultPermissionHandler(net.Addr, net.IP) (ok bool) {
39-
return true
40-
}
41-
4247
// PacketConnConfig is a single net.PacketConn to listen/write on. This will be used for UDP listeners
4348
type PacketConnConfig struct {
4449
PacketConn net.PacketConn
@@ -47,9 +52,12 @@ type PacketConnConfig struct {
4752
// creates the net.PacketConn and returns the IP/Port it is available at
4853
RelayAddressGenerator RelayAddressGenerator
4954

50-
// PermissionHandler is a callback to filter peer addresses. Can be set as nil, in which
51-
// case the DefaultPermissionHandler is automatically instantiated to admit all peer
52-
// connections
55+
// AllocationHandler is a callback to filter client addresses or redirect clients to an
56+
// alternate server.
57+
AllocationHandler AllocationHandler
58+
59+
// PermissionHandler is a callback to filter peer addresses. Specifying no permission
60+
// handler will admit all peer connections.
5361
PermissionHandler PermissionHandler
5462
}
5563

@@ -72,9 +80,12 @@ type ListenerConfig struct {
7280
// creates the net.PacketConn and returns the IP/Port it is available at
7381
RelayAddressGenerator RelayAddressGenerator
7482

75-
// PermissionHandler is a callback to filter peer addresses. Can be set as nil, in which
76-
// case the DefaultPermissionHandler is automatically instantiated to admit all peer
77-
// connections
83+
// AllocationHandler is a callback to filter client addresses or redirect clients to an
84+
// alternate server.
85+
AllocationHandler AllocationHandler
86+
87+
// PermissionHandler is a callback to filter peer addresses. Specifying no permission
88+
// handler will admit all peer connections.
7889
PermissionHandler PermissionHandler
7990
}
8091

@@ -114,7 +125,7 @@ type ServerConfig struct {
114125
// Realm sets the realm for this server
115126
Realm string
116127

117-
// AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior
128+
// AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior.
118129
AuthHandler AuthHandler
119130

120131
// ChannelBindTimeout sets the lifetime of channel binding. Defaults to 10 minutes.

server_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/pion/logging"
16+
"github.com/pion/stun"
1617
"github.com/pion/transport/v2/test"
1718
"github.com/pion/transport/v2/vnet"
1819
"github.com/pion/turn/v2/internal/proto"
@@ -27,6 +28,7 @@ func TestServer(t *testing.T) {
2728
defer report()
2829

2930
loggerFactory := logging.NewDefaultLoggerFactory()
31+
loggerFactory.DefaultLogLevel = logging.LogLevelTrace
3032

3133
credMap := map[string][]byte{
3234
"user": GenerateAuthKey("user", "pion.ly", "pass"),
@@ -119,6 +121,66 @@ func TestServer(t *testing.T) {
119121
assert.NoError(t, server.Close())
120122
})
121123

124+
t.Run("redirect", func(t *testing.T) {
125+
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478")
126+
assert.NoError(t, err)
127+
128+
server, err := NewServer(ServerConfig{
129+
AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) {
130+
if pw, ok := credMap[username]; ok {
131+
return pw, true
132+
}
133+
return nil, false
134+
},
135+
PacketConnConfigs: []PacketConnConfig{
136+
{
137+
PacketConn: udpListener,
138+
AllocationHandler: func(clientAddr net.Addr) (alternateServer net.Addr, errorCode stun.ErrorCode) {
139+
return &net.UDPAddr{
140+
IP: net.ParseIP("1.2.3.4"),
141+
Port: 8743,
142+
}, stun.CodeTryAlternate
143+
},
144+
RelayAddressGenerator: &RelayAddressGeneratorStatic{
145+
RelayAddress: net.ParseIP("127.0.0.1"),
146+
Address: "0.0.0.0",
147+
},
148+
},
149+
},
150+
Realm: "pion.ly",
151+
LoggerFactory: loggerFactory,
152+
})
153+
assert.NoError(t, err)
154+
155+
conn, err := net.ListenPacket("udp4", "0.0.0.0:0")
156+
assert.NoError(t, err)
157+
158+
serverAddr, err := net.ResolveUDPAddr("udp4", "127.0.0.1:3478")
159+
assert.NoError(t, err)
160+
161+
client, err := NewClient(&ClientConfig{
162+
Conn: conn,
163+
STUNServerAddr: serverAddr,
164+
TURNServerAddr: serverAddr,
165+
Username: "user",
166+
Password: "pass",
167+
Realm: "pion.ly",
168+
LoggerFactory: loggerFactory,
169+
})
170+
assert.NoError(t, err)
171+
assert.NoError(t, client.Listen())
172+
173+
_, err = client.Allocate()
174+
assert.Error(t, err, "should return error")
175+
176+
fmt.Printf("%#v\n", err)
177+
178+
client.Close()
179+
assert.NoError(t, conn.Close())
180+
181+
assert.NoError(t, server.Close())
182+
})
183+
122184
t.Run("Filter on client address and peer IP", func(t *testing.T) {
123185
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478")
124186
assert.NoError(t, err)

0 commit comments

Comments
 (0)