Skip to content

Commit 6a55914

Browse files
committed
Fix: Delete allocation on TCP broken pipe
Explicitly delete allocation when a TURN/TCP client ends a TURN allocation by spontaneously closing the underlying TCP connection instead of properly closing it down by refreshing with zero lifetime.
1 parent 5223e57 commit 6a55914

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

server.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ func (s *Server) readListener(l net.Listener, am *allocation.Manager) {
149149
go func() {
150150
s.readLoop(NewSTUNConn(conn), am)
151151

152+
// Delete allocation
153+
am.DeleteAllocation(&allocation.FiveTuple{
154+
Protocol: allocation.UDP, // fixed UDP
155+
SrcAddr: conn.RemoteAddr(),
156+
DstAddr: conn.LocalAddr(),
157+
})
158+
152159
if err := conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
153160
s.log.Errorf("Failed to close conn: %s", err)
154161
}

server_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ package turn
99
import (
1010
"fmt"
1111
"net"
12+
"syscall"
1213
"testing"
1314
"time"
1415

1516
"github.com/pion/logging"
1617
"github.com/pion/transport/v3/test"
1718
"github.com/pion/transport/v3/vnet"
19+
"github.com/pion/turn/v3/internal/allocation"
1820
"github.com/pion/turn/v3/internal/proto"
1921
"github.com/stretchr/testify/assert"
2022
)
@@ -119,6 +121,93 @@ func TestServer(t *testing.T) {
119121
assert.NoError(t, server.Close())
120122
})
121123

124+
t.Run("Delete allocation on spontaneous TCP close", func(t *testing.T) {
125+
// Test whether allocation is properly deleted when client spontaneously closes the
126+
// TCP connection underlying it
127+
tcpListener, err := net.Listen("tcp4", "127.0.0.1:3478")
128+
assert.NoError(t, err)
129+
130+
server, err := NewServer(ServerConfig{
131+
AuthHandler: func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) {
132+
if pw, ok := credMap[username]; ok {
133+
return pw, true
134+
}
135+
return nil, false
136+
},
137+
ListenerConfigs: []ListenerConfig{
138+
{
139+
Listener: tcpListener,
140+
RelayAddressGenerator: &RelayAddressGeneratorStatic{
141+
RelayAddress: net.ParseIP("127.0.0.1"),
142+
Address: "127.0.0.1",
143+
},
144+
},
145+
},
146+
Realm: "pion.ly",
147+
LoggerFactory: loggerFactory,
148+
})
149+
assert.NoError(t, err)
150+
151+
// make sure we can reuse the client port
152+
dialer := &net.Dialer{
153+
Control: func(network, address string, conn syscall.RawConn) error {
154+
return conn.Control(func(descriptor uintptr) {
155+
_ = syscall.SetsockoptInt(int(descriptor), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
156+
})
157+
},
158+
}
159+
conn, err := dialer.Dial("tcp", "127.0.0.1:3478")
160+
assert.NoError(t, err)
161+
162+
clientAddr := conn.LocalAddr()
163+
164+
serverAddr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:3478")
165+
assert.NoError(t, err)
166+
167+
client, err := NewClient(&ClientConfig{
168+
STUNServerAddr: serverAddr.String(),
169+
TURNServerAddr: serverAddr.String(),
170+
Conn: NewSTUNConn(conn),
171+
Username: "user",
172+
Password: "pass",
173+
Realm: "pion.ly",
174+
LoggerFactory: loggerFactory,
175+
})
176+
assert.NoError(t, err)
177+
assert.NoError(t, client.Listen())
178+
179+
_, err = client.SendBindingRequestTo(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 3478})
180+
assert.NoError(t, err, "should succeed")
181+
182+
relayConn, err := client.Allocate()
183+
assert.NoError(t, err)
184+
assert.NotNil(t, relayConn)
185+
186+
fiveTuple := &allocation.FiveTuple{
187+
Protocol: allocation.UDP, // Fixed UDP
188+
SrcAddr: clientAddr,
189+
DstAddr: serverAddr,
190+
}
191+
// Allocation exists
192+
assert.Len(t, server.allocationManagers, 1)
193+
assert.NotNil(t, server.allocationManagers[0].GetAllocation(fiveTuple))
194+
195+
// client.Close()
196+
// This should properly close the client and delete the allocation on the server
197+
assert.NoError(t, conn.Close())
198+
199+
// Let connection to properly close
200+
time.Sleep(100 * time.Millisecond)
201+
// to we still have the allocation on the server?
202+
assert.Nil(t, server.allocationManagers[0].GetAllocation(fiveTuple))
203+
204+
client.Close()
205+
// This should err: client connection has gone so we cannot send the Refresh(0)
206+
// message
207+
assert.Error(t, relayConn.Close())
208+
assert.NoError(t, server.Close())
209+
})
210+
122211
t.Run("Filter on client address and peer IP", func(t *testing.T) {
123212
udpListener, err := net.ListenPacket("udp4", "0.0.0.0:3478")
124213
assert.NoError(t, err)

0 commit comments

Comments
 (0)