Add configurable TCP keepalives to TCP listener and dialer

Exposes TCP keepalive configuration via listener and dialer metadata:

  keepalive: true
  keepalive.idle: 30s
  keepalive.interval: 10s
  keepalive.count: 6

When enabled, the OS sends keepalive probes after the idle period and
tears down the socket if the remote does not respond. This causes any
blocked Read() on a dead connection to return promptly, releasing
goroutines and memory — covering both legs of a CONNECT tunnel (the
inbound client connection via the listener, and the outbound upstream
connection via the dialer).

This is the correct alternative to a hard application-level read
deadline (SetReadDeadline) for detecting dead connections, as it
distinguishes truly dead connections from legitimately idle ones.

Relates to: https://github.com/go-gost/x/issues/91
This commit is contained in:
Andrew Beresford
2026-05-08 11:33:54 +01:00
parent 07ca57055a
commit cf70a0cbe1
4 changed files with 67 additions and 0 deletions
+12
View File
@@ -47,6 +47,18 @@ func (d *tcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
return conn, err
}
if d.md.keepalive {
if tc, ok := conn.(*net.TCPConn); ok {
tc.SetKeepAliveConfig(net.KeepAliveConfig{
Enable: true,
Idle: d.md.keepaliveIdle,
Interval: d.md.keepaliveInterval,
Count: d.md.keepaliveCount,
})
}
}
conn = proxyproto.WrapClientConn(