mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 11:34:01 +03:00
FILE / ScuroNeko/mtg
network/sockopts_test.go
Исходный файл и его история в репозитории.
TCP keepalive was configured (SetKeepAlivePeriod) but never actually enabled (SO_KEEPALIVE) on accepted client connections. Go 1.26's SetKeepAlivePeriod only sets TCP_KEEPIDLE — it does not call setsockopt(SO_KEEPALIVE, 1). Without SO_KEEPALIVE the kernel never sends probe packets, so dead connections from sleeping mobile clients linger until the idle timeout fires. Replace SetKeepAlive + SetKeepAlivePeriod with net.KeepAliveConfig (available since Go 1.24) for explicit per-socket control: Idle: 30s (time before first probe) Interval: 10s (between probes) Count: 3 (failed probes to declare dead) This detects dead connections in ~60s instead of relying on system defaults (tcp_keepalive_intvl=75s, probes=9 → up to 11 minutes). Increase the default idle timeout from 1 minute to 5 minutes. MTProto clients send ping_delay_disconnect every ~60s, which resets the idle timer. The previous 1-minute default created a race: if a ping arrived even 1–2 seconds late the relay was killed. A 5-minute window also survives typical mobile sleep periods (phone idle 2–5 min) where the NAT mapping is still alive and the connection can resume without reconnection. Ref: #132
94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
//go:build linux || darwin
|
|
// +build linux darwin
|
|
|
|
package network_test
|
|
|
|
import (
|
|
"net"
|
|
"runtime"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/9seconds/mtg/v2/network"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func tcpKeepIdleOption() int {
|
|
if runtime.GOOS == "darwin" {
|
|
return 0x10 // TCP_KEEPALIVE on macOS
|
|
}
|
|
|
|
return 0x4 // TCP_KEEPIDLE on Linux
|
|
}
|
|
|
|
func TestSetClientSocketOptionsKeepAlive(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err := listener.Close()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
type dialResult struct {
|
|
conn net.Conn
|
|
err error
|
|
}
|
|
|
|
dialDone := make(chan dialResult, 1)
|
|
|
|
go func() {
|
|
c, err := net.Dial("tcp", listener.Addr().String())
|
|
dialDone <- dialResult{conn: c, err: err}
|
|
}()
|
|
|
|
tcpListener, ok := listener.(*net.TCPListener)
|
|
require.True(t, ok, "listener must be a *net.TCPListener")
|
|
|
|
require.NoError(t, tcpListener.SetDeadline(time.Now().Add(5*time.Second)))
|
|
|
|
accepted, err := listener.Accept()
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err := accepted.Close()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
dr := <-dialDone
|
|
require.NoError(t, dr.err)
|
|
defer func() {
|
|
err := dr.conn.Close()
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
err = network.SetClientSocketOptions(accepted, 0)
|
|
require.NoError(t, err)
|
|
|
|
tcpConn := accepted.(*net.TCPConn)
|
|
|
|
rawConn, err := tcpConn.SyscallConn()
|
|
require.NoError(t, err)
|
|
|
|
err = rawConn.Control(func(fd uintptr) {
|
|
val, err := unix.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, 0, val, "SO_KEEPALIVE should be enabled")
|
|
|
|
idle, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, tcpKeepIdleOption())
|
|
require.NoError(t, err)
|
|
require.Equal(t, int(network.DefaultKeepAliveIdle.Seconds()), idle)
|
|
|
|
interval, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_KEEPINTVL)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int(network.DefaultKeepAliveInterval.Seconds()), interval)
|
|
|
|
count, err := unix.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_KEEPCNT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, network.DefaultKeepAliveCount, count)
|
|
})
|
|
require.NoError(t, err)
|
|
}
|