Improve TCP keepalive and idle timeout for mobile clients

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
This commit is contained in:
appolimp
2026-04-04 12:01:33 +03:00
parent 3a68ea5f2d
commit 5f81ae3743
10 changed files with 233 additions and 10 deletions
+3 -2
View File
@@ -77,8 +77,9 @@ const (
// DefaultIdleTimeout is a default timeout for closing a connection in case of
// idling.
//
// Deprecated: no longer in use because of changed TCP relay algorithm.
DefaultIdleTimeout = time.Minute
// Set to 5 minutes to survive typical mobile sleep periods (2-5 min) and
// avoid racing with MTProto ping_delay_disconnect (~60s interval).
DefaultIdleTimeout = 5 * time.Minute
// DefaultTolerateTimeSkewness is a default timeout for time skewness on a
// faketls timeout verification.
+1 -1
View File
@@ -217,7 +217,7 @@ func (p ProxyOpts) getPreferIP() string {
func (p ProxyOpts) getIdleTimeout() time.Duration {
if p.IdleTimeout == 0 {
return time.Minute
return DefaultIdleTimeout
}
return p.IdleTimeout