mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
cmd/docker-proxy: do not eagerly GC one-sided UDP conns
The UDP proxy is setting a deadline of 90 seconds when reading from the backend. If no data is received within this interval, it reclaims the connection. This means, the backend would see a different connection every 90 seconds if the backend never sends back any reply to a client. This change prevents the proxy from eagerly GC'ing such connections by taking into account the last time a datagram was proxyed to the backend. Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -49,7 +50,8 @@ type connTrackMap map[connTrackKey]*connTrackEntry
|
|||||||
// connTrackEntry wraps a UDP connection to provide thread-safe [net.Conn.Write]
|
// connTrackEntry wraps a UDP connection to provide thread-safe [net.Conn.Write]
|
||||||
// and [net.Conn.Close] operations.
|
// and [net.Conn.Close] operations.
|
||||||
type connTrackEntry struct {
|
type connTrackEntry struct {
|
||||||
conn *net.UDPConn
|
conn *net.UDPConn
|
||||||
|
lastW time.Time
|
||||||
// This lock should be held before calling Write or Close on the wrapped
|
// This lock should be held before calling Write or Close on the wrapped
|
||||||
// net.UDPConn. Read can be called concurrently to these operations.
|
// net.UDPConn. Read can be called concurrently to these operations.
|
||||||
//
|
//
|
||||||
@@ -64,6 +66,12 @@ func newConnTrackEntry(conn *net.UDPConn) *connTrackEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cte *connTrackEntry) lastWrite() time.Time {
|
||||||
|
cte.mu.Lock()
|
||||||
|
defer cte.mu.Unlock()
|
||||||
|
return cte.lastW
|
||||||
|
}
|
||||||
|
|
||||||
// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
|
// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
|
||||||
// interface to handle UDP traffic forwarding between the frontend and backend
|
// interface to handle UDP traffic forwarding between the frontend and backend
|
||||||
// addresses.
|
// addresses.
|
||||||
@@ -121,6 +129,15 @@ func (proxy *UDPProxy) replyLoop(cte *connTrackEntry, serverAddr net.IP, clientA
|
|||||||
// expires:
|
// expires:
|
||||||
goto again
|
goto again
|
||||||
}
|
}
|
||||||
|
// If the UDP connection is one-sided (i.e. the backend never sends
|
||||||
|
// replies), the connTrackEntry should not be GC'd until no writes
|
||||||
|
// happen for proxy.connTrackTimeout.
|
||||||
|
//
|
||||||
|
// Since the ReadDeadline is set to proxy.connTrackTimeout, in such
|
||||||
|
// case, the connTrackEntry will be GC'd at most after 2 * proxy.connTrackTimeout.
|
||||||
|
if errors.Is(err, os.ErrDeadlineExceeded) && time.Since(cte.lastWrite()) < proxy.connTrackTimeout {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := 0; i != read; {
|
for i := 0; i != read; {
|
||||||
@@ -186,6 +203,7 @@ func (proxy *UDPProxy) Run() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
i += written
|
i += written
|
||||||
|
cte.lastW = time.Now()
|
||||||
}
|
}
|
||||||
cte.mu.Unlock()
|
cte.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
78
cmd/docker-proxy/udp_proxy_linux_test.go
Normal file
78
cmd/docker-proxy/udp_proxy_linux_test.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestUDPOneSided makes sure that the conntrack entry isn't GC'd if the
|
||||||
|
// backend never writes to the UDP client.
|
||||||
|
func TestUDPOneSided(t *testing.T) {
|
||||||
|
frontend, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer frontend.Close()
|
||||||
|
|
||||||
|
backend, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
type udpMsg struct {
|
||||||
|
data []byte
|
||||||
|
saddr *net.UDPAddr
|
||||||
|
}
|
||||||
|
msgs := make(chan udpMsg)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
n, saddr, err := backend.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgs <- udpMsg{data: buf[:n], saddr: saddr}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
proxy, err := NewUDPProxy(frontend, backend.LocalAddr().(*net.UDPAddr), ip4)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer proxy.Close()
|
||||||
|
|
||||||
|
const connTrackTimeout = 1 * time.Second
|
||||||
|
proxy.connTrackTimeout = connTrackTimeout
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
proxy.Run()
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, err := net.DialUDP("udp", nil, frontend.LocalAddr().(*net.UDPAddr))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
var expSaddr *net.UDPAddr
|
||||||
|
for i := range 15 {
|
||||||
|
_, err = client.Write([]byte("hello"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
msg := <-msgs
|
||||||
|
assert.Equal(t, string(msg.data), "hello")
|
||||||
|
if i == 0 {
|
||||||
|
expSaddr = msg.saddr
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, msg.saddr.Port, expSaddr.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The conntrack entry is checked every connTrackTimeout, but the latest
|
||||||
|
// write might be less than connTrackTimeout ago. So we need to wait for
|
||||||
|
// at least twice the conntrack timeout to make sure the entry is GC'd.
|
||||||
|
time.Sleep(2 * connTrackTimeout)
|
||||||
|
_, err = client.Write([]byte("hello"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
msg := <-msgs
|
||||||
|
assert.Equal(t, string(msg.data), "hello")
|
||||||
|
assert.Check(t, msg.saddr.Port != expSaddr.Port)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user