Parcourir la source

Merge pull request #14 from dolonet/sync-upstream-keepalive

Sync upstream: improve TCP keepalive and idle timeout for mobile clients
pull/450/head
dolonet il y a 1 mois
Parent
révision
fa56c59132
Aucun compte lié à l'adresse e-mail de l'auteur

+ 1
- 1
example.config.toml Voir le fichier

@@ -226,7 +226,7 @@ proxies = [
226 226
 [network.timeout]
227 227
 tcp = "5s"
228 228
 http = "10s"
229
-idle = "1m"
229
+idle = "5m"
230 230
 
231 231
 # mtg has to mimic real websites. It does not mean domain fronting, it also
232 232
 # means that traffic characteristics should be similar to real world traffic.

+ 1
- 1
internal/cli/run_proxy.go Voir le fichier

@@ -263,7 +263,7 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
263 263
 
264 264
 		AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
265 265
 		TolerateTimeSkewness:     conf.TolerateTimeSkewness.Value,
266
-		IdleTimeout:              conf.Network.Timeout.Idle.Get(time.Minute),
266
+		IdleTimeout:              conf.Network.Timeout.Idle.Get(mtglib.DefaultIdleTimeout),
267 267
 
268 268
 		DoppelGangerURLs:    doppelGangerURLs,
269 269
 		DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid),

+ 3
- 2
mtglib/init.go Voir le fichier

@@ -77,8 +77,9 @@ const (
77 77
 	// DefaultIdleTimeout is a default timeout for closing a connection in case of
78 78
 	// idling.
79 79
 	//
80
-	// Deprecated: no longer in use because of changed TCP relay algorithm.
81
-	DefaultIdleTimeout = time.Minute
80
+	// Set to 5 minutes to survive typical mobile sleep periods (2-5 min) and
81
+	// avoid racing with MTProto ping_delay_disconnect (~60s interval).
82
+	DefaultIdleTimeout = 5 * time.Minute
82 83
 
83 84
 	// DefaultTolerateTimeSkewness is a default timeout for time skewness on a
84 85
 	// faketls timeout verification.

+ 1
- 1
mtglib/proxy_opts.go Voir le fichier

@@ -277,7 +277,7 @@ func (p ProxyOpts) getPreferIP() string {
277 277
 
278 278
 func (p ProxyOpts) getIdleTimeout() time.Duration {
279 279
 	if p.IdleTimeout == 0 {
280
-		return time.Minute
280
+		return DefaultIdleTimeout
281 281
 	}
282 282
 
283 283
 	return p.IdleTimeout

+ 14
- 0
network/init.go Voir le fichier

@@ -36,8 +36,22 @@ const (
36 36
 
37 37
 	// DefaultTCPKeepAlivePeriod defines a time period between 2 consequitive
38 38
 	// probes.
39
+	//
40
+	// Deprecated: use DefaultKeepAliveIdle and DefaultKeepAliveInterval instead.
39 41
 	DefaultTCPKeepAlivePeriod = 10 * time.Second
40 42
 
43
+	// DefaultKeepAliveIdle is the time a connection must be idle before
44
+	// the first keepalive probe is sent.
45
+	DefaultKeepAliveIdle = 30 * time.Second
46
+
47
+	// DefaultKeepAliveInterval is the time between consecutive keepalive
48
+	// probes.
49
+	DefaultKeepAliveInterval = 10 * time.Second
50
+
51
+	// DefaultKeepAliveCount is the number of unacknowledged probes before
52
+	// the connection is considered dead.
53
+	DefaultKeepAliveCount = 3
54
+
41 55
 	// ProxyDialerOpenThreshold is used for load balancing SOCKS5 dialer only.
42 56
 	//
43 57
 	// This dialer uses circuit breaker with of 3 stages: OPEN, HALF_OPEN and

+ 7
- 2
network/sockopts.go Voir le fichier

@@ -20,8 +20,13 @@ func SetServerSocketOptions(conn net.Conn, bufferSize int) error {
20 20
 }
21 21
 
22 22
 func setCommonSocketOptions(conn *net.TCPConn) error {
23
-	if err := conn.SetKeepAlivePeriod(DefaultTCPKeepAlivePeriod); err != nil {
24
-		return fmt.Errorf("cannot set time period of TCP keepalive probes: %w", err)
23
+	if err := conn.SetKeepAliveConfig(net.KeepAliveConfig{
24
+		Enable:   true,
25
+		Idle:     DefaultKeepAliveIdle,
26
+		Interval: DefaultKeepAliveInterval,
27
+		Count:    DefaultKeepAliveCount,
28
+	}); err != nil {
29
+		return fmt.Errorf("cannot configure TCP keepalive: %w", err)
25 30
 	}
26 31
 
27 32
 	if err := conn.SetLinger(tcpLingerTimeout); err != nil {

+ 93
- 0
network/sockopts_test.go Voir le fichier

@@ -0,0 +1,93 @@
1
+//go:build linux || darwin
2
+// +build linux darwin
3
+
4
+package network_test
5
+
6
+import (
7
+	"net"
8
+	"runtime"
9
+	"syscall"
10
+	"testing"
11
+	"time"
12
+
13
+	"github.com/dolonet/mtg-multi/network"
14
+	"github.com/stretchr/testify/require"
15
+	"golang.org/x/sys/unix"
16
+)
17
+
18
+func tcpKeepIdleOption() int {
19
+	if runtime.GOOS == "darwin" {
20
+		return 0x10 // TCP_KEEPALIVE on macOS
21
+	}
22
+
23
+	return 0x4 // TCP_KEEPIDLE on Linux
24
+}
25
+
26
+func TestSetClientSocketOptionsKeepAlive(t *testing.T) {
27
+	t.Parallel()
28
+
29
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
30
+	require.NoError(t, err)
31
+	defer func() {
32
+		err := listener.Close()
33
+		require.NoError(t, err)
34
+	}()
35
+
36
+	type dialResult struct {
37
+		conn net.Conn
38
+		err  error
39
+	}
40
+
41
+	dialDone := make(chan dialResult, 1)
42
+
43
+	go func() {
44
+		c, err := net.Dial("tcp", listener.Addr().String())
45
+		dialDone <- dialResult{conn: c, err: err}
46
+	}()
47
+
48
+	tcpListener, ok := listener.(*net.TCPListener)
49
+	require.True(t, ok, "listener must be a *net.TCPListener")
50
+
51
+	require.NoError(t, tcpListener.SetDeadline(time.Now().Add(5*time.Second)))
52
+
53
+	accepted, err := listener.Accept()
54
+	require.NoError(t, err)
55
+	defer func() {
56
+		err := accepted.Close()
57
+		require.NoError(t, err)
58
+	}()
59
+
60
+	dr := <-dialDone
61
+	require.NoError(t, dr.err)
62
+	defer func() {
63
+		err := dr.conn.Close()
64
+		require.NoError(t, err)
65
+	}()
66
+
67
+	err = network.SetClientSocketOptions(accepted, 0)
68
+	require.NoError(t, err)
69
+
70
+	tcpConn := accepted.(*net.TCPConn)
71
+
72
+	rawConn, err := tcpConn.SyscallConn()
73
+	require.NoError(t, err)
74
+
75
+	err = rawConn.Control(func(fd uintptr) {
76
+		val, err := unix.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
77
+		require.NoError(t, err)
78
+		require.NotEqual(t, 0, val, "SO_KEEPALIVE should be enabled")
79
+
80
+		idle, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, tcpKeepIdleOption())
81
+		require.NoError(t, err)
82
+		require.Equal(t, int(network.DefaultKeepAliveIdle.Seconds()), idle)
83
+
84
+		interval, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_KEEPINTVL)
85
+		require.NoError(t, err)
86
+		require.Equal(t, int(network.DefaultKeepAliveInterval.Seconds()), interval)
87
+
88
+		count, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_KEEPCNT)
89
+		require.NoError(t, err)
90
+		require.Equal(t, network.DefaultKeepAliveCount, count)
91
+	})
92
+	require.NoError(t, err)
93
+}

+ 14
- 0
network/v2/init.go Voir le fichier

@@ -26,8 +26,22 @@ const (
26 26
 
27 27
 	// DefaultTCPKeepAlivePeriod defines a time period between 2 consecuitive
28 28
 	// probes.
29
+	//
30
+	// Deprecated: use DefaultKeepAliveIdle and DefaultKeepAliveInterval instead.
29 31
 	DefaultTCPKeepAlivePeriod = 10 * time.Second
30 32
 
33
+	// DefaultKeepAliveIdle is the time a connection must be idle before
34
+	// the first keepalive probe is sent.
35
+	DefaultKeepAliveIdle = 30 * time.Second
36
+
37
+	// DefaultKeepAliveInterval is the time between consecutive keepalive
38
+	// probes.
39
+	DefaultKeepAliveInterval = 10 * time.Second
40
+
41
+	// DefaultKeepAliveCount is the number of unacknowledged probes before
42
+	// the connection is considered dead.
43
+	DefaultKeepAliveCount = 3
44
+
31 45
 	// User Agent to use in HTTP client.
32 46
 	UserAgent = "curl/8.5.0"
33 47
 

+ 7
- 2
network/v2/sockopts.go Voir le fichier

@@ -6,8 +6,13 @@ import (
6 6
 )
7 7
 
8 8
 func setCommonSocketOptions(conn *net.TCPConn) error {
9
-	if err := conn.SetKeepAlivePeriod(DefaultTCPKeepAlivePeriod); err != nil {
10
-		return fmt.Errorf("cannot set time period of TCP keepalive probes: %w", err)
9
+	if err := conn.SetKeepAliveConfig(net.KeepAliveConfig{
10
+		Enable:   true,
11
+		Idle:     DefaultKeepAliveIdle,
12
+		Interval: DefaultKeepAliveInterval,
13
+		Count:    DefaultKeepAliveCount,
14
+	}); err != nil {
15
+		return fmt.Errorf("cannot configure TCP keepalive: %w", err)
11 16
 	}
12 17
 
13 18
 	if err := conn.SetLinger(tcpLingerTimeout); err != nil {

+ 92
- 0
network/v2/sockopts_test.go Voir le fichier

@@ -0,0 +1,92 @@
1
+//go:build linux || darwin
2
+// +build linux darwin
3
+
4
+package network
5
+
6
+import (
7
+	"net"
8
+	"runtime"
9
+	"syscall"
10
+	"testing"
11
+	"time"
12
+
13
+	"github.com/stretchr/testify/require"
14
+	"golang.org/x/sys/unix"
15
+)
16
+
17
+func tcpKeepIdleOption() int {
18
+	if runtime.GOOS == "darwin" {
19
+		return 0x10 // TCP_KEEPALIVE on macOS
20
+	}
21
+
22
+	return 0x4 // TCP_KEEPIDLE on Linux
23
+}
24
+
25
+func TestSetCommonSocketOptionsKeepAlive(t *testing.T) {
26
+	t.Parallel()
27
+
28
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
29
+	require.NoError(t, err)
30
+	defer func() {
31
+		err := listener.Close()
32
+		require.NoError(t, err)
33
+	}()
34
+
35
+	type dialResult struct {
36
+		conn net.Conn
37
+		err  error
38
+	}
39
+
40
+	dialDone := make(chan dialResult, 1)
41
+
42
+	go func() {
43
+		c, err := net.Dial("tcp", listener.Addr().String())
44
+		dialDone <- dialResult{conn: c, err: err}
45
+	}()
46
+
47
+	tcpListener, ok := listener.(*net.TCPListener)
48
+	require.True(t, ok, "listener must be a *net.TCPListener")
49
+
50
+	require.NoError(t, tcpListener.SetDeadline(time.Now().Add(5*time.Second)))
51
+
52
+	accepted, err := listener.Accept()
53
+	require.NoError(t, err)
54
+	defer func() {
55
+		err := accepted.Close()
56
+		require.NoError(t, err)
57
+	}()
58
+
59
+	dr := <-dialDone
60
+	require.NoError(t, dr.err)
61
+	defer func() {
62
+		err := dr.conn.Close()
63
+		require.NoError(t, err)
64
+	}()
65
+
66
+	tcpConn := accepted.(*net.TCPConn)
67
+
68
+	err = setCommonSocketOptions(tcpConn)
69
+	require.NoError(t, err)
70
+
71
+	rawConn, err := tcpConn.SyscallConn()
72
+	require.NoError(t, err)
73
+
74
+	err = rawConn.Control(func(fd uintptr) {
75
+		val, err := unix.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
76
+		require.NoError(t, err)
77
+		require.NotEqual(t, 0, val, "SO_KEEPALIVE should be enabled")
78
+
79
+		idle, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, tcpKeepIdleOption())
80
+		require.NoError(t, err)
81
+		require.Equal(t, int(DefaultKeepAliveIdle.Seconds()), idle, "keepalive idle should match DefaultKeepAliveIdle")
82
+
83
+		interval, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_KEEPINTVL)
84
+		require.NoError(t, err)
85
+		require.Equal(t, int(DefaultKeepAliveInterval.Seconds()), interval, "keepalive interval should match DefaultKeepAliveInterval")
86
+
87
+		count, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_KEEPCNT)
88
+		require.NoError(t, err)
89
+		require.Equal(t, DefaultKeepAliveCount, count, "keepalive count should match DefaultKeepAliveCount")
90
+	})
91
+	require.NoError(t, err)
92
+}

Chargement…
Annuler
Enregistrer