Files
ginuerzh 2112532e0a x: set TCP_NODELAY on ws/wss/tls transports to cut relay UDP latency
Disable Nagle on the underlying TCP socket for relay+ws, relay+wss, and
relay+tls listeners/dialers. Small latency-sensitive datagrams (e.g.
WireGuard UDP over a relay tunnel) were being coalesced by Nagle until a
peer ACK returned, adding up to one RTT per datagram and inflating
round-trip latency (go-gost/gost#858). wstunnel sets TCP_NODELAY on its
tunnel socket by default and keeps latency flat; gost did not.

Add ApplyNoDelay helper and WrapNoDelayListener (mirrors ApplyKeepalive),
applied on the raw *net.TCPConn before the proxyproto/tls wrapper chain.
2026-08-01 16:59:54 +08:00

65 lines
1.8 KiB
Go

package net
import "net"
// WrapKeepaliveListener wraps ln so that TCP keepalive config is applied to
// every accepted *net.TCPConn before it enters the middleware wrapper chain.
func WrapKeepaliveListener(ln net.Listener, cfg net.KeepAliveConfig) net.Listener {
return &keepaliveListener{Listener: ln, cfg: cfg}
}
// ApplyKeepalive applies cfg to conn if it is a *net.TCPConn.
func ApplyKeepalive(conn net.Conn, cfg net.KeepAliveConfig) {
if tc, ok := conn.(*net.TCPConn); ok {
tc.SetKeepAliveConfig(cfg)
}
}
// ApplyNoDelay disables Nagle's algorithm on conn if it is a *net.TCPConn.
//
// gost tunnels carry small, latency-sensitive datagrams (e.g. WireGuard UDP
// over relay+ws/wss). Without this, Nagle coalesces each frame until a peer
// ACK returns, adding up to one RTT per datagram — the root cause of the
// latency regression reported in go-gost/gost#858. Match wstunnel, which sets
// TCP_NODELAY on its tunnel socket by default.
func ApplyNoDelay(conn net.Conn) {
if tc, ok := conn.(*net.TCPConn); ok {
tc.SetNoDelay(true)
}
}
// WrapNoDelayListener wraps ln so that TCP_NODELAY is applied to every accepted
// *net.TCPConn before it enters the middleware wrapper chain.
func WrapNoDelayListener(ln net.Listener) net.Listener {
return &noDelayListener{Listener: ln}
}
type noDelayListener struct {
net.Listener
}
func (l *noDelayListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
ApplyNoDelay(conn)
return conn, nil
}
type keepaliveListener struct {
net.Listener
cfg net.KeepAliveConfig
}
func (l *keepaliveListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
if tc, ok := conn.(*net.TCPConn); ok {
tc.SetKeepAliveConfig(l.cfg)
}
return conn, nil
}