diff --git a/go.mod b/go.mod index e3f0f32..290f786 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( golang.org/x/crypto v0.50.0 ) -require golang.org/x/sys v0.43.0 // indirect +require golang.org/x/sys v0.43.0 diff --git a/netx/tcpusertimeout_linux.go b/netx/tcpusertimeout_linux.go new file mode 100644 index 0000000..8e304ee --- /dev/null +++ b/netx/tcpusertimeout_linux.go @@ -0,0 +1,36 @@ +//go:build linux + +// Package netx holds network helpers that the standard net package doesn't have. +package netx + +import ( + "crypto/tls" + "net" + "time" + + "golang.org/x/sys/unix" +) + +// SetTCPUserTimeout makes the kernel drop conn when sent data stays unacknowledged for timeout, instead of +// waiting for its ~15 min retry limit (e.g. when the peer's node is gone). Non-TCP connections are left alone. +func SetTCPUserTimeout(conn net.Conn, timeout time.Duration) error { + if tlsConn, ok := conn.(*tls.Conn); ok { + conn = tlsConn.NetConn() + } + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + return nil + } + raw, err := tcpConn.SyscallConn() + if err != nil { + return err + } + var sockErr error + ctrlfn := func(fd uintptr) { + sockErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(timeout.Milliseconds())) + } + if err := raw.Control(ctrlfn); err != nil { + return err + } + return sockErr +} diff --git a/netx/tcpusertimeout_other.go b/netx/tcpusertimeout_other.go new file mode 100644 index 0000000..9a1eafa --- /dev/null +++ b/netx/tcpusertimeout_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +// Package netx holds network helpers that the standard net package doesn't have. +package netx + +import ( + "net" + "time" +) + +// SetTCPUserTimeout does nothing: TCP_USER_TIMEOUT is Linux only. +func SetTCPUserTimeout(net.Conn, time.Duration) error { + return nil +}