Close idle connections of unreferenced outbounds and DNS servers

This commit is contained in:
世界
2026-10-02 18:00:09 +08:00
parent d2cf9747d7
commit fd9a076d25
40 changed files with 851 additions and 81 deletions
+10 -3
View File
@@ -16,6 +16,7 @@ import (
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-cloudflared"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -79,14 +80,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
}
return &Inbound{
Adapter: inbound.NewAdapter(C.TypeCloudflared, tag),
service: service,
Adapter: inbound.NewAdapter(C.TypeCloudflared, tag),
service: service,
references: common.FilterNotDefault([]string{options.ControlDialer.Detour, options.TunnelDialer.Detour}),
}, nil
}
type Inbound struct {
inbound.Adapter
service *cloudflared.Service
service *cloudflared.Service
references []string
}
func (i *Inbound) Start(stage adapter.StartStage) error {
@@ -255,6 +258,10 @@ func (h *icmpRouterHandler) RouteICMPFlow(source netip.Addr, destination netip.A
}
}
func (i *Inbound) References() []string {
return i.references
}
var (
_ N.HandshakeSuccess = (*routedConn)(nil)
_ N.ReaderWithUpstream = (*routedConn)(nil)
+5
View File
@@ -25,6 +25,7 @@ func RegisterSelector(registry *outbound.Registry) {
var (
_ adapter.OutboundGroup = (*Selector)(nil)
_ adapter.Referrer = (*Selector)(nil)
_ adapter.ConnectionHandler = (*Selector)(nil)
_ adapter.PacketConnectionHandler = (*Selector)(nil)
)
@@ -120,6 +121,10 @@ func (s *Selector) All() []string {
return s.tags
}
func (s *Selector) References() []string {
return []string{s.Now()}
}
func (s *Selector) SelectOutbound(tag string) bool {
detour, loaded := s.outbounds[tag]
if !loaded {
+25 -1
View File
@@ -32,6 +32,7 @@ func RegisterURLTest(registry *outbound.Registry) {
var (
_ adapter.OutboundGroup = (*URLTest)(nil)
_ adapter.InterfaceUpdateListener = (*URLTest)(nil)
_ adapter.Referrer = (*URLTest)(nil)
)
type URLTest struct {
@@ -111,6 +112,21 @@ func (s *URLTest) All() []string {
return s.tags
}
func (s *URLTest) References() []string {
group := s.group
if group == nil {
return nil
}
var references []string
if group.selectedOutboundTCP != nil {
references = append(references, group.selectedOutboundTCP.Tag())
}
if group.selectedOutboundUDP != nil && group.selectedOutboundUDP != group.selectedOutboundTCP {
references = append(references, group.selectedOutboundUDP.Tag())
}
return references
}
func (s *URLTest) URLTest(ctx context.Context) (map[string]uint16, error) {
return s.group.URLTest(ctx)
}
@@ -488,20 +504,28 @@ func (b *urlTestBatch) test(outbounds []adapter.Outbound, link string, interval
func (g *URLTestGroup) performUpdateCheck() {
g.updateAccess.Lock()
defer g.updateAccess.Unlock()
var updated bool
var (
updated bool
selected bool
)
if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) {
if g.selectedOutboundTCP != nil {
updated = true
}
g.selectedOutboundTCP = outbound
selected = true
}
if outbound, exists := g.Select(N.NetworkUDP); outbound != nil && (g.selectedOutboundUDP == nil || (exists && outbound != g.selectedOutboundUDP)) {
if g.selectedOutboundUDP != nil {
updated = true
}
g.selectedOutboundUDP = outbound
selected = true
}
if updated {
g.interruptGroup.Interrupt(g.interruptExternalConnections)
}
if selected {
g.history.NotifyUpdated()
}
}
+5
View File
@@ -30,6 +30,7 @@ func RegisterOutbound(registry *outbound.Registry) {
var (
_ adapter.Outbound = (*tuic.Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
@@ -118,6 +119,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
h.client.CloseWithError(E.New("network changed"))
}
func (h *Outbound) CloseIdleConnections() {
h.client.CloseIdleConnections()
}
func (h *Outbound) Close() error {
return h.client.CloseWithError(os.ErrClosed)
}
+5
View File
@@ -37,6 +37,7 @@ func RegisterOutbound(registry *outbound.Registry) {
var (
_ adapter.Outbound = (*tuic.Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
@@ -210,6 +211,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
h.client.CloseWithError(E.New("network changed"))
}
func (h *Outbound) CloseIdleConnections() {
h.client.CloseIdleConnections()
}
func (h *Outbound) Close() error {
return h.client.CloseWithError(os.ErrClosed)
}
+11 -1
View File
@@ -26,7 +26,11 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
var (
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
outbound.Adapter
@@ -136,6 +140,12 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
}
}
func (h *Outbound) CloseIdleConnections() {
if h.multiplexDialer != nil {
h.multiplexDialer.CloseIdleConnections()
}
}
func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer))
}
+15 -4
View File
@@ -26,10 +26,11 @@ func RegisterInbound(registry *inbound.Registry) {
type Inbound struct {
inbound.Adapter
router adapter.Router
logger logger.ContextLogger
listener *listener.Listener
service *shadowtls.Service
router adapter.Router
logger logger.ContextLogger
listener *listener.Listener
service *shadowtls.Service
references []string
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) {
@@ -48,6 +49,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
if options.HandshakeForServerName != nil {
for _, entry := range options.HandshakeForServerName.Entries() {
if entry.Value.Detour != "" {
inbound.references = append(inbound.references, entry.Value.Detour)
}
handshakeDialer, err := dialer.New(ctx, entry.Value.DialerOptions, entry.Value.ServerIsDomain())
if err != nil {
return nil, err
@@ -67,6 +71,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil {
return nil, err
}
if options.Handshake.Detour != "" {
inbound.references = append(inbound.references, options.Handshake.Detour)
}
service, err := shadowtls.NewService(shadowtls.ServiceConfig{
Version: options.Version,
Password: options.Password,
@@ -139,3 +146,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
}
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (h *Inbound) References() []string {
return h.references
}
+9 -1
View File
@@ -32,12 +32,16 @@ type Outbound struct {
serverAddr M.Socksaddr
}
var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
var (
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type snellClient interface {
snellprotocol.Method
DialContext(ctx context.Context, destination M.Socksaddr) (net.Conn, error)
Reset()
CloseIdleConnections()
Close() error
}
@@ -143,6 +147,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
h.client.Reset()
}
func (h *Outbound) CloseIdleConnections() {
h.client.CloseIdleConnections()
}
func (h *Outbound) Close() error {
return h.client.Close()
}
+60 -8
View File
@@ -32,7 +32,10 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound)
}
var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
var (
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
outbound.Adapter
@@ -50,6 +53,8 @@ type Outbound struct {
clientAccess sync.Mutex
clientConn net.Conn
client *ssh.Client
streams int
draining bool
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) {
@@ -127,14 +132,12 @@ func randomVersion() string {
}
func (s *Outbound) connect(ctx context.Context) (client *ssh.Client, err error) {
if s.client != nil {
return s.client, nil
}
s.clientAccess.Lock()
defer s.clientAccess.Unlock()
if s.client != nil {
s.streams++
s.draining = false
return s.client, nil
}
@@ -193,13 +196,17 @@ func (s *Outbound) connect(ctx context.Context) (client *ssh.Client, err error)
s.clientConn = conn
s.client = client
s.streams = 1
s.draining = false
go func() {
client.Wait()
conn.Close()
s.clientAccess.Lock()
s.client = nil
s.clientConn = nil
if s.client == client {
s.client = nil
s.clientConn = nil
}
s.clientAccess.Unlock()
}()
@@ -210,6 +217,42 @@ func (s *Outbound) InterfaceUpdated(ctx context.Context) {
common.Close(s.clientConn)
}
func (s *Outbound) CloseIdleConnections() {
s.clientAccess.Lock()
if s.client == nil {
s.clientAccess.Unlock()
return
}
s.draining = true
if s.streams > 0 {
s.clientAccess.Unlock()
return
}
clientConn := s.clientConn
s.client = nil
s.clientConn = nil
s.clientAccess.Unlock()
common.Close(clientConn)
}
func (s *Outbound) releaseStream(client *ssh.Client) {
s.clientAccess.Lock()
if s.client != client {
s.clientAccess.Unlock()
return
}
s.streams--
if !s.draining || s.streams > 0 {
s.clientAccess.Unlock()
return
}
clientConn := s.clientConn
s.client = nil
s.clientConn = nil
s.clientAccess.Unlock()
common.Close(clientConn)
}
func (s *Outbound) Close() error {
return common.Close(s.clientConn)
}
@@ -221,9 +264,10 @@ func (s *Outbound) DialContext(ctx context.Context, network string, destination
}
conn, err := client.Dial(network, destination.String())
if err != nil {
s.releaseStream(client)
return nil, err
}
return &chanConnWrapper{Conn: conn}, nil
return &chanConnWrapper{Conn: conn, onClose: func() { s.releaseStream(client) }}, nil
}
func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
@@ -232,6 +276,14 @@ func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
type chanConnWrapper struct {
net.Conn
closeOnce sync.Once
onClose func()
}
func (c *chanConnWrapper) Close() error {
err := c.Conn.Close()
c.closeOnce.Do(c.onClose)
return err
}
func (c *chanConnWrapper) SetDeadline(t time.Time) error {
+10
View File
@@ -68,6 +68,7 @@ import (
var (
_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
_ adapter.InterfaceUpdateListener = (*Endpoint)(nil)
_ adapter.Referrer = (*Endpoint)(nil)
_ dialer.PacketDialerWithDestination = (*Endpoint)(nil)
_ tun.Port = (*Endpoint)(nil)
)
@@ -89,6 +90,7 @@ type Endpoint struct {
dnsRouter adapter.DNSRouter
network adapter.NetworkManager
platformInterface adapter.PlatformInterface
detour string
server *tsnet.Server
stack *stack.Stack
icmpForwarder *tun.ICMPForwarder
@@ -200,6 +202,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
queryOptions: dialerQueryOptions,
network: service.FromContext[adapter.NetworkManager](ctx),
platformInterface: platformInterface,
detour: options.Detour,
server: &tsnet.Server{
Dir: stateDirectory,
Hostname: hostname,
@@ -251,6 +254,13 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
}, nil
}
func (t *Endpoint) References() []string {
if t.detour == "" {
return nil
}
return []string{t.detour}
}
func (t *Endpoint) Start(stage adapter.StartStage) error {
switch stage {
case adapter.StartStateInitialize:
+8
View File
@@ -40,6 +40,7 @@ type Inbound struct {
fallbackAddr M.Socksaddr
fallbackAddrTLSNextProto map[string]M.Socksaddr
transport adapter.V2RayServerTransport
references []string
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) {
@@ -61,6 +62,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
return nil, err
}
inbound.tlsConfig = tlsConfig
if options.TLS.Reality != nil && options.TLS.Reality.Enabled && options.TLS.Reality.Handshake.Detour != "" {
inbound.references = []string{options.TLS.Reality.Handshake.Detour}
}
}
var fallbackHandler N.TCPConnectionHandlerEx
if options.Fallback != nil && options.Fallback.Server != "" || len(options.FallbackForALPN) > 0 {
@@ -260,3 +264,7 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnection(ctx, conn, metadata, onClose)
}
func (h *Inbound) References() []string {
return h.references
}
+15 -1
View File
@@ -26,7 +26,11 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
var (
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
outbound.Adapter
@@ -122,6 +126,16 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
}
}
func (h *Outbound) CloseIdleConnections() {
transportCloser, isTransportCloser := h.transport.(adapter.IdleConnectionCloser)
if isTransportCloser {
transportCloser.CloseIdleConnections()
}
if h.multiplexDialer != nil {
h.multiplexDialer.CloseIdleConnections()
}
}
func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
}
+8 -1
View File
@@ -30,7 +30,10 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound)
}
var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
var (
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
outbound.Adapter
@@ -145,6 +148,10 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
_ = h.client.CloseWithError(E.New("network changed"))
}
func (h *Outbound) CloseIdleConnections() {
h.client.CloseIdleConnections()
}
func (h *Outbound) Close() error {
return h.client.CloseWithError(os.ErrClosed)
}
+16 -8
View File
@@ -35,14 +35,15 @@ var _ adapter.TCPInjectableInbound = (*Inbound)(nil)
type Inbound struct {
inbound.Adapter
ctx context.Context
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
users []option.VLESSUser
service *vless.Service[int]
tlsConfig tls.ServerConfig
transport adapter.V2RayServerTransport
ctx context.Context
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
users []option.VLESSUser
service *vless.Service[int]
tlsConfig tls.ServerConfig
transport adapter.V2RayServerTransport
references []string
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) {
@@ -81,6 +82,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil {
return nil, err
}
if options.TLS.Reality != nil && options.TLS.Reality.Enabled && options.TLS.Reality.Handshake.Detour != "" {
inbound.references = []string{options.TLS.Reality.Handshake.Detour}
}
}
if options.Transport != nil {
inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound))
@@ -220,3 +224,7 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnection(ctx, conn, metadata, onClose)
}
func (h *Inbound) References() []string {
return h.references
}
+15 -1
View File
@@ -27,7 +27,11 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
var (
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
outbound.Adapter
@@ -142,6 +146,16 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
}
}
func (h *Outbound) CloseIdleConnections() {
transportCloser, isTransportCloser := h.transport.(adapter.IdleConnectionCloser)
if isTransportCloser {
transportCloser.CloseIdleConnections()
}
if h.multiplexDialer != nil {
h.multiplexDialer.CloseIdleConnections()
}
}
func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
}
+15 -1
View File
@@ -27,7 +27,11 @@ func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound)
}
var _ adapter.OutboundWithMultiplex = (*Outbound)(nil)
var (
_ adapter.OutboundWithMultiplex = (*Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*Outbound)(nil)
_ adapter.IdleConnectionCloser = (*Outbound)(nil)
)
type Outbound struct {
outbound.Adapter
@@ -120,6 +124,16 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) {
}
}
func (h *Outbound) CloseIdleConnections() {
transportCloser, isTransportCloser := h.transport.(adapter.IdleConnectionCloser)
if isTransportCloser {
transportCloser.CloseIdleConnections()
}
if h.multiplexDialer != nil {
h.multiplexDialer.CloseIdleConnections()
}
}
func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
}