diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index af183b52..720f96f9 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,6 +3,8 @@ package outbound import ( "context" "errors" + + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" @@ -24,7 +26,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ... if err != nil { return nil, err } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return NewConn(c, d), nil } diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index 0b652ca9..acc75d37 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -7,11 +7,13 @@ import ( "encoding/base64" "errors" "fmt" + "io" "net" "net/http" "strconv" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" tlsC "github.com/Dreamacro/clash/component/tls" @@ -74,7 +76,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad if err != nil { return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 0f46cf79..f744ec53 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index cd6854af..0f03f86d 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia if err != nil { return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index d0b9e748..16405fcf 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -6,6 +6,7 @@ import ( "net" "strconv" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" @@ -93,7 +94,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -121,7 +122,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, err } - tcpKeepAlive(c) + N.TCPKeepAlive(c) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) err = snell.WriteUDPHeader(c, s.version) @@ -207,7 +208,7 @@ func NewSnell(option SnellOption) (*Snell, error) { return nil, err } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil }) } diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index f451cd1a..2e9bccd6 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -9,6 +9,7 @@ import ( "net" "strconv" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" tlsC "github.com/Dreamacro/clash/component/tls" @@ -80,7 +81,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -126,7 +127,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, safeConnClose(c, err) }(c) - tcpKeepAlive(c) + N.TCPKeepAlive(c) var user *socks5.User if ss.user != "" { user = &socks5.User{ diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index ec420bf3..6339b476 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" tlsC "github.com/Dreamacro/clash/component/tls" @@ -131,7 +132,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -184,7 +185,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me defer func(c net.Conn) { safeConnClose(c, err) }(c) - tcpKeepAlive(c) + N.TCPKeepAlive(c) c, err = t.plainStream(ctx, c) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) @@ -268,7 +269,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return c, nil } diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index 7f3ec4c3..36607e4f 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -7,7 +7,6 @@ import ( "net" "net/netip" "sync" - "time" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" @@ -19,13 +18,6 @@ var ( once sync.Once ) -func tcpKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - _ = tcp.SetKeepAlive(true) - _ = tcp.SetKeepAlivePeriod(30 * time.Second) - } -} - func getClientSessionCache() tls.ClientSessionCache { once.Do(func() { globalClientSessionCache = tls.NewLRUClientSessionCache(128) diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 2456c2c3..81408e5f 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -263,7 +263,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -328,7 +328,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -578,7 +578,7 @@ func NewVless(option VlessOption) (*Vless, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return c, nil } diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 0fa0b1d9..3e7694d1 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -308,7 +308,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -369,7 +369,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -469,7 +469,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return c, nil } diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go index 85373a1f..84216377 100644 --- a/adapter/outboundgroup/util.go +++ b/adapter/outboundgroup/util.go @@ -1,17 +1,5 @@ package outboundgroup -import ( - "net" - "time" -) - -func tcpKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - _ = tcp.SetKeepAlive(true) - _ = tcp.SetKeepAlivePeriod(30 * time.Second) - } -} - type SelectAble interface { Set(string) error ForceSet(name string) diff --git a/common/net/tcpip.go b/common/net/tcpip.go index a84e7e4c..171191e2 100644 --- a/common/net/tcpip.go +++ b/common/net/tcpip.go @@ -4,8 +4,11 @@ import ( "fmt" "net" "strings" + "time" ) +var KeepAliveInterval time.Duration + func SplitNetworkType(s string) (string, string, error) { var ( shecme string @@ -44,3 +47,10 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) { host, port, err = net.SplitHostPort(temp) return } + +func TCPKeepAlive(c net.Conn) { + if tcp, ok := c.(*net.TCPConn); ok { + _ = tcp.SetKeepAlive(true) + _ = tcp.SetKeepAlivePeriod(KeepAliveInterval * time.Second) + } +} diff --git a/config/config.go b/config/config.go index 3db51776..62fef1bd 100644 --- a/config/config.go +++ b/config/config.go @@ -16,6 +16,7 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/provider" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" @@ -59,6 +60,7 @@ type General struct { Sniffing bool `json:"sniffing"` EBpf EBpf `json:"-"` GlobalClientFingerprint string `json:"global-client-fingerprint"` + KeepAliveInterval int `json:"keep-alive-interval"` } // Inbound config @@ -280,6 +282,7 @@ type RawConfig struct { TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` GlobalClientFingerprint string `yaml:"global-client-fingerprint"` + KeepAliveInterval int `yaml:"keep-alive-interval"` Sniffer RawSniffer `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -559,6 +562,11 @@ func parseGeneral(cfg *RawConfig) (*General, error) { C.GeoSiteUrl = cfg.GeoXUrl.GeoSite C.MmdbUrl = cfg.GeoXUrl.Mmdb C.GeodataMode = cfg.GeodataMode + if cfg.KeepAliveInterval == 0 { + cfg.KeepAliveInterval = 30 + } + N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second + log.Infoln("Keep Alive Interval set %+v", N.KeepAliveInterval) // checkout externalUI exist if externalUI != "" { externalUI = C.Path.Resolve(externalUI) @@ -600,6 +608,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { FindProcessMode: cfg.FindProcessMode, EBpf: cfg.EBpf, GlobalClientFingerprint: cfg.GlobalClientFingerprint, + KeepAliveInterval: cfg.KeepAliveInterval, }, nil } diff --git a/docs/config.yaml b/docs/config.yaml index 62cd0c58..ca33911e 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -50,6 +50,8 @@ external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{externa # Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan. global-client-fingerprint: chrome +keep-alive-interval: 30 + # routing-mark:6666 # 配置 fwmark 仅用于 Linux experimental: diff --git a/listener/autoredir/tcp.go b/listener/autoredir/tcp.go index 854d31d6..c390d89a 100644 --- a/listener/autoredir/tcp.go +++ b/listener/autoredir/tcp.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/transport/socks5" @@ -55,7 +56,7 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) { return } - _ = conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...) } diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index e8385873..7241927d 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -1,9 +1,9 @@ package mixed import ( - "github.com/Dreamacro/clash/adapter/inbound" "net" + "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/cache" N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" @@ -70,7 +70,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) { - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) diff --git a/listener/redir/tcp.go b/listener/redir/tcp.go index ad4a91bc..9a843af8 100644 --- a/listener/redir/tcp.go +++ b/listener/redir/tcp.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" ) @@ -66,6 +67,6 @@ func handleRedir(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Ad conn.Close() return } - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) in <- inbound.NewSocket(target, conn, C.REDIR, additions...) } diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go index febf87c3..2d0958a0 100644 --- a/listener/shadowsocks/tcp.go +++ b/listener/shadowsocks/tcp.go @@ -59,7 +59,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C } continue } - _ = c.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(c) go sl.HandleConn(c, tcpIn) } }() diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index 13ddde0d..d0e137a7 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/sockopt" C "github.com/Dreamacro/clash/constant" LC "github.com/Dreamacro/clash/listener/config" @@ -145,7 +146,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C } continue } - _ = c.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(c) go sl.HandleConn(c, tcpIn) } diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index 374a378b..06f3e051 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" LC "github.com/Dreamacro/clash/listener/config" "github.com/Dreamacro/clash/listener/sing" @@ -84,7 +85,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe } continue } - _ = c.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(c) go sl.HandleConn(c, tcpIn) } diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index cbaac987..2fd252a3 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -67,7 +67,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } func handleSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) if err != nil { diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index 198481f7..8c868609 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -32,7 +33,7 @@ func (l *Listener) Close() error { func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) in <- inbound.NewSocket(target, conn, C.TPROXY, additions...) } diff --git a/listener/tunnel/tcp.go b/listener/tunnel/tcp.go index c1d896ad..d660d2b8 100644 --- a/listener/tunnel/tcp.go +++ b/listener/tunnel/tcp.go @@ -5,6 +5,7 @@ import ( "net" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -34,7 +35,7 @@ func (l *Listener) Close() error { } func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) ctx := inbound.NewSocket(l.target, conn, C.TUNNEL, additions...) ctx.Metadata().SpecialProxy = l.proxy in <- ctx