From b58ac669d57a5fc8523ea7bbda41fff13285ee42 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Tue, 7 Apr 2026 15:03:39 +0200 Subject: [PATCH] Add TCP_USER_TIMEOUT support --- network/v2/sockopts.go | 1 + network/v2/sockopts_linux.go | 39 ++++++++++++++++++++++++++++++++++ network/v2/sockopts_nolinux.go | 8 +++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/network/v2/sockopts.go b/network/v2/sockopts.go index 5f9a7a5fb..c1eafc116 100644 --- a/network/v2/sockopts.go +++ b/network/v2/sockopts.go @@ -24,6 +24,7 @@ func setCommonSocketOptions(conn *net.TCPConn, keepAliveConfig net.KeepAliveConf } setCongestionControl(rawConn) + setTCPUserTimeout(rawConn, keepAliveConfig) return nil } diff --git a/network/v2/sockopts_linux.go b/network/v2/sockopts_linux.go index 802862e1e..60224e6e8 100644 --- a/network/v2/sockopts_linux.go +++ b/network/v2/sockopts_linux.go @@ -3,11 +3,50 @@ package network import ( + "net" "syscall" + "time" "golang.org/x/sys/unix" ) +// Go runtime defaults for KeepAliveConfig when fields are zero. +const ( + goDefaultKeepAliveIdle = 15 * time.Second + goDefaultKeepAliveInterval = 15 * time.Second + goDefaultKeepAliveCount = 9 +) + +// setTCPUserTimeout sets TCP_USER_TIMEOUT on a socket. If transmitted +// data remains unacknowledged for this long, the kernel closes the +// connection. As recommended by Cloudflare +// (https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/), +// the value is computed as: keepidle + keepintvl * keepcnt. This +// ensures TCP_USER_TIMEOUT and keepalives agree on when to give up. +// Best-effort: silently ignored if unsupported. +func setTCPUserTimeout(conn syscall.RawConn, cfg net.KeepAliveConfig) { + idle := cfg.Idle + if idle == 0 { + idle = goDefaultKeepAliveIdle + } + + interval := cfg.Interval + if interval == 0 { + interval = goDefaultKeepAliveInterval + } + + count := cfg.Count + if count == 0 { + count = goDefaultKeepAliveCount + } + + timeout := idle + interval*time.Duration(count) + + conn.Control(func(fd uintptr) { //nolint: errcheck + unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(timeout.Milliseconds())) //nolint: errcheck + }) +} + func setCongestionControl(conn syscall.RawConn) { conn.Control(func(fd uintptr) { //nolint: errcheck // BBR provides better throughput over lossy and high-latency links compared diff --git a/network/v2/sockopts_nolinux.go b/network/v2/sockopts_nolinux.go index bb7540ceb..f2d59416e 100644 --- a/network/v2/sockopts_nolinux.go +++ b/network/v2/sockopts_nolinux.go @@ -2,6 +2,10 @@ package network -import "syscall" +import ( + "net" + "syscall" +) -func setCongestionControl(conn syscall.RawConn) {} +func setCongestionControl(conn syscall.RawConn) {} +func setTCPUserTimeout(conn syscall.RawConn, cfg net.KeepAliveConfig) {}