From a691a4402375535f0329eb9026ab904cfdc1b2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 1 Oct 2026 23:26:01 +0800 Subject: [PATCH] Refactor lifecycle to scoped cleanup --- adapter/certificate/manager.go | 49 +---- adapter/endpoint.go | 1 + adapter/endpoint/manager.go | 48 +---- adapter/inbound/manager.go | 45 +--- adapter/lifecycle.go | 122 ++++++----- adapter/outbound/manager.go | 93 ++------- adapter/service/manager.go | 45 +--- box.go | 217 +++++++++----------- common/certificate/store.go | 75 ++++--- common/httpclient/manager.go | 8 +- common/listener/listener.go | 6 +- common/netns/manager_linux.go | 26 +-- common/netns/manager_stub.go | 6 +- common/netns/manager_test.go | 8 +- common/tls/acme.go | 7 +- common/tls/std_server.go | 13 +- common/trafficcontrol/manager.go | 20 +- dns/router.go | 25 +-- dns/router_race_test.go | 12 +- dns/transport/dhcp/dhcp.go | 75 +++---- dns/transport/fakeip/fakeip.go | 13 +- dns/transport/hosts/hosts.go | 6 +- dns/transport/https.go | 11 +- dns/transport/local/local.go | 30 +-- dns/transport/local/local_resolved_linux.go | 101 ++++----- dns/transport/local/local_shared.go | 26 +-- dns/transport/mdns/mdns.go | 10 +- dns/transport/multiplexer_test.go | 17 +- dns/transport/quic/http3.go | 13 +- dns/transport/quic/quic.go | 7 +- dns/transport/tcp.go | 7 +- dns/transport/tls.go | 7 +- dns/transport/udp.go | 7 +- dns/transport_manager.go | 61 +----- experimental/cachefile/cache.go | 20 +- experimental/clashapi/server.go | 10 +- experimental/clashmode/manager.go | 6 +- experimental/libbox/dns.go | 6 +- experimental/libbox/http.go | 19 +- experimental/v2rayapi/server.go | 45 ++-- go.mod | 2 +- go.sum | 4 +- protocol/anytls/inbound.go | 14 +- protocol/anytls/outbound.go | 9 +- protocol/bridge/backend.go | 30 ++- protocol/bridge/backend_darwin.go | 103 ++++------ protocol/bridge/backend_linux.go | 129 ++++++------ protocol/bridge/backend_windows.go | 55 +++-- protocol/bridge/outbound.go | 8 +- protocol/cloudflare/inbound.go | 13 +- protocol/direct/inbound.go | 13 +- protocol/direct/outbound.go | 13 +- protocol/group/selector.go | 5 +- protocol/group/urltest.go | 9 +- protocol/http/inbound.go | 17 +- protocol/hysteria/inbound.go | 19 +- protocol/hysteria/outbound.go | 10 +- protocol/hysteria2/inbound.go | 19 +- protocol/hysteria2/outbound.go | 10 +- protocol/hysteria2/realm.go | 26 ++- protocol/mixed/inbound.go | 17 +- protocol/naive/inbound.go | 17 +- protocol/naive/outbound.go | 7 +- protocol/openconnect/client.go | 129 ++++++------ protocol/openconnect/dns_transport.go | 82 ++++---- protocol/openconnect/status.go | 11 +- protocol/openvpn/client.go | 117 ++++++----- protocol/openvpn/dns_transport.go | 97 ++++----- protocol/openvpn/server.go | 64 +++--- protocol/openvpn/status.go | 6 +- protocol/redirect/redirect.go | 13 +- protocol/redirect/tproxy.go | 13 +- protocol/shadowsocks/inbound.go | 13 +- protocol/shadowsocks/inbound_multi.go | 13 +- protocol/shadowsocks/inbound_relay.go | 13 +- protocol/shadowsocks/outbound.go | 10 +- protocol/shadowtls/inbound.go | 13 +- protocol/snell/inbound.go | 13 +- protocol/snell/outbound.go | 8 +- protocol/socks/inbound.go | 13 +- protocol/ssh/outbound.go | 10 +- protocol/tailscale/certificate_provider.go | 6 +- protocol/tailscale/dns_transport.go | 81 +++++--- protocol/tailscale/endpoint.go | 127 ++++++------ protocol/tor/outbound.go | 32 +-- protocol/trojan/inbound.go | 20 +- protocol/trojan/outbound.go | 13 +- protocol/tuic/inbound.go | 19 +- protocol/tuic/outbound.go | 10 +- protocol/tun/inbound.go | 76 +++---- protocol/vless/inbound.go | 21 +- protocol/vless/outbound.go | 13 +- protocol/vmess/inbound.go | 22 +- protocol/vmess/outbound.go | 13 +- protocol/wireguard/endpoint.go | 17 +- route/conn.go | 14 +- route/network.go | 88 +++----- route/reference.go | 10 +- route/router.go | 55 +---- route/rule/rule_item_rule_set_test.go | 3 +- service/acme/service.go | 15 +- service/api/dashboard.go | 14 +- service/api/server.go | 71 +++---- service/ccm/service.go | 39 ++-- service/derp/service.go | 12 +- service/ocm/service.go | 52 +++-- service/oomkiller/service.go | 5 +- service/oomkiller/service_darwin.go | 38 ++-- service/oomkiller/service_stub.go | 15 +- service/origin_ca/service.go | 52 ++--- service/resolved/service.go | 51 +++-- service/resolved/transport.go | 40 ++-- service/ssmapi/server.go | 45 ++-- service/usbip/client.go | 13 +- service/usbip/server.go | 13 +- 115 files changed, 1595 insertions(+), 2030 deletions(-) diff --git a/adapter/certificate/manager.go b/adapter/certificate/manager.go index 55cd7c03..339f110b 100644 --- a/adapter/certificate/manager.go +++ b/adapter/certificate/manager.go @@ -3,83 +3,42 @@ package certificate import ( "context" "sync" - "time" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" ) var _ adapter.CertificateProviderManager = (*Manager)(nil) type Manager struct { - logger log.ContextLogger registry adapter.CertificateProviderRegistry access sync.Mutex - started bool - stage adapter.StartStage providers []adapter.CertificateProviderService providerByTag map[string]adapter.CertificateProviderService } -func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager { +func NewManager(registry adapter.CertificateProviderRegistry) *Manager { return &Manager{ - logger: logger, registry: registry, providerByTag: make(map[string]adapter.CertificateProviderService), } } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { m.access.Lock() - if m.started && m.stage >= stage { - panic("already started") - } - m.started = true - m.stage = stage providers := m.providers m.access.Unlock() for _, provider := range providers { name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]" - m.logger.Trace(stage, " ", name) - startTime := time.Now() - err := provider.Start(stage) + err := scope.Start(name, provider, stage) if err != nil { - return E.Cause(err, stage, " ", name) + return err } - m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } return nil } -func (m *Manager) Close() error { - m.access.Lock() - defer m.access.Unlock() - if !m.started { - return nil - } - m.started = false - providers := m.providers - m.providers = nil - monitor := taskmonitor.New(m.logger, C.StopTimeout) - var err error - for _, provider := range providers { - name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]" - m.logger.Trace("close ", name) - startTime := time.Now() - monitor.Start("close ", name) - err = E.Append(err, provider.Close(), func(err error) error { - return E.Cause(err, "close ", name) - }) - monitor.Finish() - m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") - } - return err -} - func (m *Manager) CertificateProviders() []adapter.CertificateProviderService { m.access.Lock() defer m.access.Unlock() diff --git a/adapter/endpoint.go b/adapter/endpoint.go index de416e23..63d7ce9d 100644 --- a/adapter/endpoint.go +++ b/adapter/endpoint.go @@ -21,6 +21,7 @@ type EndpointRegistry interface { type EndpointManager interface { Lifecycle + StartEndpoint(endpoint Endpoint) error Endpoints() []Endpoint Get(tag string) (Endpoint, bool) Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error diff --git a/adapter/endpoint/manager.go b/adapter/endpoint/manager.go index 75487538..c4abc31a 100644 --- a/adapter/endpoint/manager.go +++ b/adapter/endpoint/manager.go @@ -5,8 +5,6 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" ) @@ -14,69 +12,41 @@ import ( var _ adapter.EndpointManager = (*Manager)(nil) type Manager struct { - logger log.ContextLogger registry adapter.EndpointRegistry access sync.Mutex - started bool - stage adapter.StartStage + scope *adapter.Scope endpoints []adapter.Endpoint endpointByTag map[string]adapter.Endpoint } -func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager { +func NewManager(registry adapter.EndpointRegistry) *Manager { return &Manager{ - logger: logger, registry: registry, endpointByTag: make(map[string]adapter.Endpoint), } } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { m.access.Lock() defer m.access.Unlock() - if m.started && m.stage >= stage { - panic("already started") + if stage == adapter.StartStateInitialize { + m.scope = scope } - m.started = true - m.stage = stage if stage == adapter.StartStateStart { - // started with outbound manager return nil } for _, endpoint := range m.endpoints { name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" - done := adapter.LogElapsed(m.logger, stage, " ", name) - err := endpoint.Start(stage) - done() + err := scope.Start(name, endpoint, stage) if err != nil { - return E.Cause(err, stage, " ", name) + return err } } return nil } -func (m *Manager) Close() error { - m.access.Lock() - defer m.access.Unlock() - if !m.started { - return nil - } - m.started = false - endpoints := m.endpoints - m.endpoints = nil - monitor := taskmonitor.New(m.logger, C.StopTimeout) - var err error - for _, endpoint := range endpoints { - name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]" - done := adapter.LogElapsed(m.logger, "close ", name) - monitor.Start("close ", name) - err = E.Append(err, endpoint.Close(), func(err error) error { - return E.Cause(err, "close ", name) - }) - monitor.Finish() - done() - } - return nil +func (m *Manager) StartEndpoint(endpoint adapter.Endpoint) error { + return m.scope.Start("endpoint/"+endpoint.Type()+"["+endpoint.Tag()+"]", endpoint, adapter.StartStateStart) } func (m *Manager) Endpoints() []adapter.Endpoint { diff --git a/adapter/inbound/manager.go b/adapter/inbound/manager.go index 74c918d7..bafb15d3 100644 --- a/adapter/inbound/manager.go +++ b/adapter/inbound/manager.go @@ -5,8 +5,6 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" ) @@ -14,70 +12,35 @@ import ( var _ adapter.InboundManager = (*Manager)(nil) type Manager struct { - logger log.ContextLogger registry adapter.InboundRegistry endpoint adapter.EndpointManager access sync.Mutex - started bool - stage adapter.StartStage inbounds []adapter.Inbound inboundByTag map[string]adapter.Inbound } -func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager { +func NewManager(registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager { return &Manager{ - logger: logger, registry: registry, endpoint: endpoint, inboundByTag: make(map[string]adapter.Inbound), } } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { m.access.Lock() - if m.started && m.stage >= stage { - panic("already started") - } - m.started = true - m.stage = stage inbounds := m.inbounds m.access.Unlock() for _, inbound := range inbounds { name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" - done := adapter.LogElapsed(m.logger, stage, " ", name) - err := inbound.Start(stage) - done() + err := scope.Start(name, inbound, stage) if err != nil { - return E.Cause(err, stage, " ", name) + return err } } return nil } -func (m *Manager) Close() error { - m.access.Lock() - defer m.access.Unlock() - if !m.started { - return nil - } - m.started = false - inbounds := m.inbounds - m.inbounds = nil - monitor := taskmonitor.New(m.logger, C.StopTimeout) - var err error - for _, inbound := range inbounds { - name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]" - done := adapter.LogElapsed(m.logger, "close ", name) - monitor.Start("close ", name) - err = E.Append(err, inbound.Close(), func(err error) error { - return E.Cause(err, "close ", name) - }) - monitor.Finish() - done() - } - return nil -} - func (m *Manager) Inbounds() []adapter.Inbound { m.access.Lock() defer m.access.Unlock() diff --git a/adapter/lifecycle.go b/adapter/lifecycle.go index 8fd8830b..7592d649 100644 --- a/adapter/lifecycle.go +++ b/adapter/lifecycle.go @@ -2,10 +2,12 @@ package adapter import ( "context" - "reflect" - "strings" + "slices" + "sync" "time" + "github.com/sagernet/sing-box/common/taskmonitor" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -48,8 +50,7 @@ func (s StartStage) String() string { } type Lifecycle interface { - Start(stage StartStage) error - Close() error + Start(stage StartStage, scope *Scope) error } type LifecycleService interface { @@ -57,55 +58,84 @@ type LifecycleService interface { Lifecycle } -func getServiceName(service any) string { - if named, ok := service.(interface { - Type() string - Tag() string - }); ok { - tag := named.Tag() - if tag != "" { - return named.Type() + "[" + tag + "]" - } - return named.Type() - } - t := reflect.TypeOf(service) - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - return strings.ToLower(t.Name()) +type Scope struct { + ctx context.Context + cancel context.CancelFunc + logger log.ContextLogger + access sync.Mutex + cleanups []func() error + children map[Lifecycle]*Scope } -func Start(ctx context.Context, logger log.ContextLogger, stage StartStage, services ...Lifecycle) error { - for _, service := range services { - err := ctx.Err() - if err != nil { - return err - } - name := getServiceName(service) - done := LogElapsed(logger, stage, " ", name) - err = service.Start(stage) - done() - if err != nil { - return err - } +func NewScope(ctx context.Context, logger log.ContextLogger) *Scope { + ctx, cancel := context.WithCancel(ctx) + return &Scope{ + ctx: ctx, + cancel: cancel, + logger: logger, + children: make(map[Lifecycle]*Scope), + } +} + +func (s *Scope) Context() context.Context { + return s.ctx +} + +func (s *Scope) Add(cleanup func() error) { + s.access.Lock() + s.cleanups = append(s.cleanups, cleanup) + s.access.Unlock() +} + +func (s *Scope) Start(name string, component Lifecycle, stage StartStage) error { + s.access.Lock() + err := s.ctx.Err() + if err != nil { + s.access.Unlock() + return err + } + child, loaded := s.children[component] + if !loaded { + child = NewScope(s.ctx, s.logger) + s.children[component] = child + s.cleanups = append(s.cleanups, func() error { + done := LogElapsed(s.logger, "close ", name) + monitor := taskmonitor.New(s.logger, C.StopTimeout) + monitor.Start("close ", name) + closeErr := child.Close() + monitor.Finish() + done() + if closeErr != nil { + return E.Cause(closeErr, "close ", name) + } + return nil + }) + } + s.access.Unlock() + done := LogElapsed(s.logger, stage, " ", name) + monitor := taskmonitor.New(s.logger, C.StartTimeout) + monitor.Start(stage, " ", name) + err = component.Start(stage, child) + monitor.Finish() + done() + if err != nil { + return E.Cause(err, stage, " ", name) } return nil } -func StartNamed(ctx context.Context, logger log.ContextLogger, stage StartStage, services []LifecycleService) error { - for _, service := range services { - err := ctx.Err() - if err != nil { - return err - } - done := LogElapsed(logger, stage, " ", service.Name()) - err = service.Start(stage) - done() - if err != nil { - return E.Cause(err, stage.String(), " ", service.Name()) - } +func (s *Scope) Close() error { + s.access.Lock() + s.cancel() + cleanups := s.cleanups + s.cleanups = nil + s.children = nil + s.access.Unlock() + var err error + for _, cleanup := range slices.Backward(cleanups) { + err = E.Errors(err, cleanup()) } - return nil + return err } func LogElapsed(logger log.ContextLogger, description ...any) func() { diff --git a/adapter/outbound/manager.go b/adapter/outbound/manager.go index 5354bc9d..fd616363 100644 --- a/adapter/outbound/manager.go +++ b/adapter/outbound/manager.go @@ -2,39 +2,31 @@ package outbound import ( "context" - "io" "os" "strings" "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" ) var _ adapter.OutboundManager = (*Manager)(nil) type Manager struct { - logger log.ContextLogger registry adapter.OutboundRegistry endpoint adapter.EndpointManager defaultTag string access sync.RWMutex - started bool - stage adapter.StartStage outbounds []adapter.Outbound outboundByTag map[string]adapter.Outbound defaultOutbound adapter.Outbound defaultOutboundFallback func() (adapter.Outbound, error) } -func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager { +func NewManager(registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager { return &Manager{ - logger: logger, registry: registry, endpoint: endpoint, defaultTag: defaultTag, @@ -46,13 +38,8 @@ func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, e m.defaultOutboundFallback = defaultOutboundFallback } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { m.access.Lock() - if m.started && m.stage >= stage { - panic("already started") - } - m.started = true - m.stage = stage if stage == adapter.StartStateInitialize { if m.defaultTag != "" && m.defaultOutbound == nil { defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag) @@ -76,26 +63,23 @@ func (m *Manager) Start(stage adapter.StartStage) error { outbounds := m.outbounds m.access.Unlock() if stage == adapter.StartStateStart { - return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) + return m.startOutbounds(scope, append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...)) } for _, outbound := range outbounds { - starter, isStarter := outbound.(adapter.Lifecycle) - if !isStarter { + lifecycle, isLifecycle := outbound.(adapter.Lifecycle) + if !isLifecycle { continue } name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" - done := adapter.LogElapsed(m.logger, stage, " ", name) - err := starter.Start(stage) - done() + err := scope.Start(name, lifecycle, stage) if err != nil { - return E.Cause(err, stage, " ", name) + return err } } return nil } -func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { - monitor := taskmonitor.New(m.logger, C.StartTimeout) +func (m *Manager) startOutbounds(scope *adapter.Scope, outbounds []adapter.Outbound) error { started := make(map[string]bool) for { canContinue := false @@ -113,27 +97,21 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { } started[outboundTag] = true canContinue = true + if endpoint, isEndpoint := outboundToStart.(adapter.Endpoint); isEndpoint { + err := m.endpoint.StartEndpoint(endpoint) + if err != nil { + return err + } + continue + } + lifecycle, isLifecycle := outboundToStart.(adapter.Lifecycle) + if !isLifecycle { + continue + } name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]" - if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter { - done := adapter.LogElapsed(m.logger, "start ", name) - monitor.Start("start ", name) - err := starter.Start(adapter.StartStateStart) - monitor.Finish() - done() - if err != nil { - return E.Cause(err, "start ", name) - } - } else if starter, isStarter := outboundToStart.(interface { - Start() error - }); isStarter { - done := adapter.LogElapsed(m.logger, "start ", name) - monitor.Start("start ", name) - err := starter.Start() - monitor.Finish() - done() - if err != nil { - return E.Cause(err, "start ", name) - } + err := scope.Start(name, lifecycle, adapter.StartStateStart) + if err != nil { + return err } } if len(started) == len(outbounds) { @@ -166,33 +144,6 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error { return nil } -func (m *Manager) Close() error { - monitor := taskmonitor.New(m.logger, C.StopTimeout) - m.access.Lock() - if !m.started { - m.access.Unlock() - return nil - } - m.started = false - outbounds := m.outbounds - m.outbounds = nil - m.access.Unlock() - var err error - for _, outbound := range outbounds { - if closer, isCloser := outbound.(io.Closer); isCloser { - name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]" - done := adapter.LogElapsed(m.logger, "close ", name) - monitor.Start("close ", name) - err = E.Append(err, closer.Close(), func(err error) error { - return E.Cause(err, "close ", name) - }) - monitor.Finish() - done() - } - } - return nil -} - func (m *Manager) Outbounds() []adapter.Outbound { m.access.RLock() defer m.access.RUnlock() diff --git a/adapter/service/manager.go b/adapter/service/manager.go index 22d6740a..4b12665a 100644 --- a/adapter/service/manager.go +++ b/adapter/service/manager.go @@ -5,8 +5,6 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" E "github.com/sagernet/sing/common/exceptions" ) @@ -14,68 +12,33 @@ import ( var _ adapter.ServiceManager = (*Manager)(nil) type Manager struct { - logger log.ContextLogger registry adapter.ServiceRegistry access sync.Mutex - started bool - stage adapter.StartStage services []adapter.Service serviceByTag map[string]adapter.Service } -func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager { +func NewManager(registry adapter.ServiceRegistry) *Manager { return &Manager{ - logger: logger, registry: registry, serviceByTag: make(map[string]adapter.Service), } } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { m.access.Lock() - if m.started && m.stage >= stage { - panic("already started") - } - m.started = true - m.stage = stage services := m.services m.access.Unlock() for _, service := range services { name := "service/" + service.Type() + "[" + service.Tag() + "]" - done := adapter.LogElapsed(m.logger, stage, " ", name) - err := service.Start(stage) - done() + err := scope.Start(name, service, stage) if err != nil { - return E.Cause(err, stage, " ", name) + return err } } return nil } -func (m *Manager) Close() error { - m.access.Lock() - defer m.access.Unlock() - if !m.started { - return nil - } - m.started = false - services := m.services - m.services = nil - monitor := taskmonitor.New(m.logger, C.StopTimeout) - var err error - for _, service := range services { - name := "service/" + service.Type() + "[" + service.Tag() + "]" - done := adapter.LogElapsed(m.logger, "close ", name) - monitor.Start("close ", name) - err = E.Append(err, service.Close(), func(err error) error { - return E.Cause(err, "close ", name) - }) - monitor.Finish() - done() - } - return nil -} - func (m *Manager) Services() []adapter.Service { m.access.Lock() defer m.access.Unlock() diff --git a/box.go b/box.go index ef3d497e..ba5eff38 100644 --- a/box.go +++ b/box.go @@ -2,11 +2,7 @@ package box import ( "context" - "fmt" "io" - "net/http" - "os" - "runtime/debug" "time" "github.com/sagernet/sing-box/adapter" @@ -44,10 +40,8 @@ import ( var _ adapter.SimpleLifecycle = (*Box)(nil) type Box struct { - ctx context.Context createdAt time.Time debugOptions option.DebugOptions - debugHTTPServer *http.Server logFactory log.Factory logger log.ContextLogger network *route.NetworkManager @@ -64,7 +58,7 @@ type Box struct { httpClientService adapter.LifecycleService internalService []adapter.LifecycleService ntpService *ntp.Service - done chan struct{} + scope *adapter.Scope } type Options struct { @@ -211,12 +205,12 @@ func New(options Options) (*Box, error) { service.MustRegister[adapter.NetworkNamespaceManager](ctx, netnsManager) internalServices = append(internalServices, netnsManager) dnsOptions := common.PtrValueOrDefault(options.DNS) - endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) - inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) - outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) - dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final) - serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry) - certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry) + endpointManager := endpoint.NewManager(endpointRegistry) + inboundManager := inbound.NewManager(inboundRegistry, endpointManager) + outboundManager := outbound.NewManager(outboundRegistry, endpointManager, routeOptions.Final) + dnsTransportManager := dns.NewTransportManager(dnsTransportRegistry, outboundManager, dnsOptions.Final) + serviceManager := boxService.NewManager(serviceRegistry) + certificateProviderManager := boxCertificate.NewManager(certificateProviderRegistry) service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager) @@ -472,7 +466,6 @@ func New(options Options) (*Box, error) { timeService.TimeService = ntpService } return &Box{ - ctx: ctx, network: networkManager, endpoint: endpointManager, inbound: inboundManager, @@ -491,22 +484,13 @@ func New(options Options) (*Box, error) { logger: logFactory.Logger(), internalService: internalServices, ntpService: ntpService, - done: make(chan struct{}), + scope: adapter.NewScope(ctx, logFactory.Logger()), }, nil } func (s *Box) PreStart() error { err := s.preStart() if err != nil { - // TODO: remove catch error - defer func() { - v := recover() - if v != nil { - println(err.Error()) - debug.PrintStack() - panic("panic on early close: " + fmt.Sprint(v)) - } - }() s.Close() return err } @@ -517,15 +501,6 @@ func (s *Box) PreStart() error { func (s *Box) Start() error { err := s.start() if err != nil { - // TODO: remove catch error - defer func() { - v := recover() - if v != nil { - println(err.Error()) - debug.PrintStack() - println("panic on early start: " + fmt.Sprint(v)) - } - }() s.Close() return err } @@ -533,6 +508,31 @@ func (s *Box) Start() error { return nil } +type boxComponent struct { + name string + lifecycle adapter.Lifecycle +} + +func (s *Box) startComponents(stage adapter.StartStage, components ...boxComponent) error { + for _, component := range components { + err := s.scope.Start(component.name, component.lifecycle, stage) + if err != nil { + return err + } + } + return nil +} + +func (s *Box) startInternalServices(stage adapter.StartStage) error { + for _, lifecycleService := range s.internalService { + err := s.scope.Start(lifecycleService.Name(), lifecycleService, stage) + if err != nil { + return err + } + } + return nil +} + func (s *Box) preStart() error { monitor := taskmonitor.New(s.logger, C.StartTimeout) monitor.Start("start logger") @@ -541,28 +541,44 @@ func (s *Box) preStart() error { if err != nil { return E.Cause(err, "start logger") } + s.scope.Add(s.logFactory.Close) applyDebugOptions(s.debugOptions) - s.debugHTTPServer, err = startDebugHTTPServer(s.debugOptions) + debugHTTPServer, err := startDebugHTTPServer(s.debugOptions) if err != nil { return err } - err = adapter.StartNamed(s.ctx, s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api + if debugHTTPServer != nil { + s.scope.Add(debugHTTPServer.Close) + } + err = s.startInternalServices(adapter.StartStateInitialize) // cache-file clash-api v2ray-api if err != nil { return err } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service, s.certificateProvider) + err = s.startComponents(adapter.StartStateInitialize, + boxComponent{s.httpClientService.Name(), s.httpClientService}, + boxComponent{"network", s.network}, + boxComponent{"dns-transport", s.dnsTransport}, + boxComponent{"dns-router", s.dnsRouter}, + boxComponent{"connection", s.connection}, + boxComponent{"router", s.router}, + boxComponent{"outbound", s.outbound}, + boxComponent{"endpoint", s.endpoint}, + boxComponent{"certificate-provider", s.certificateProvider}, + boxComponent{"inbound", s.inbound}, + boxComponent{"service", s.service}, + ) if err != nil { return err } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection) - if err != nil { - return err - } - err = adapter.StartNamed(s.ctx, s.logger, adapter.StartStateStart, []adapter.LifecycleService{s.httpClientService}) - if err != nil { - return err - } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateStart, s.router, s.dnsRouter) + err = s.startComponents(adapter.StartStateStart, + boxComponent{"outbound", s.outbound}, + boxComponent{"dns-transport", s.dnsTransport}, + boxComponent{"network", s.network}, + boxComponent{"connection", s.connection}, + boxComponent{s.httpClientService.Name(), s.httpClientService}, + boxComponent{"router", s.router}, + boxComponent{"dns-router", s.dnsRouter}, + ) if err != nil { return err } @@ -574,7 +590,7 @@ func (s *Box) start() error { if err != nil { return err } - err = adapter.StartNamed(s.ctx, s.logger, adapter.StartStateStart, s.internalService) + err = s.startInternalServices(adapter.StartStateStart) if err != nil { return err } @@ -585,32 +601,52 @@ func (s *Box) start() error { if err != nil { return E.Cause(err, "start ntp service") } + s.scope.Add(s.ntpService.Close) } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateStart, s.endpoint) + err = s.startComponents(adapter.StartStateStart, + boxComponent{"endpoint", s.endpoint}, + boxComponent{"certificate-provider", s.certificateProvider}, + boxComponent{"inbound", s.inbound}, + boxComponent{"service", s.service}, + ) if err != nil { return err } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateStart, s.certificateProvider) + err = s.startComponents(adapter.StartStatePostStart, + boxComponent{"outbound", s.outbound}, + boxComponent{"network", s.network}, + boxComponent{"dns-transport", s.dnsTransport}, + boxComponent{"dns-router", s.dnsRouter}, + boxComponent{"connection", s.connection}, + boxComponent{"router", s.router}, + boxComponent{"endpoint", s.endpoint}, + boxComponent{"certificate-provider", s.certificateProvider}, + boxComponent{"inbound", s.inbound}, + boxComponent{"service", s.service}, + ) if err != nil { return err } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateStart, s.inbound, s.service) + err = s.startInternalServices(adapter.StartStatePostStart) if err != nil { return err } - err = adapter.Start(s.ctx, s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.endpoint, s.certificateProvider, s.inbound, s.service) + err = s.startComponents(adapter.StartStateStarted, + boxComponent{"network", s.network}, + boxComponent{"dns-transport", s.dnsTransport}, + boxComponent{"dns-router", s.dnsRouter}, + boxComponent{"connection", s.connection}, + boxComponent{"router", s.router}, + boxComponent{"outbound", s.outbound}, + boxComponent{"endpoint", s.endpoint}, + boxComponent{"certificate-provider", s.certificateProvider}, + boxComponent{"inbound", s.inbound}, + boxComponent{"service", s.service}, + ) if err != nil { return err } - err = adapter.StartNamed(s.ctx, s.logger, adapter.StartStatePostStart, s.internalService) - if err != nil { - return err - } - err = adapter.Start(s.ctx, s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.endpoint, s.certificateProvider, s.inbound, s.service) - if err != nil { - return err - } - err = adapter.StartNamed(s.ctx, s.logger, adapter.StartStateStarted, s.internalService) + err = s.startInternalServices(adapter.StartStateStarted) if err != nil { return err } @@ -618,68 +654,7 @@ func (s *Box) start() error { } func (s *Box) Close() error { - select { - case <-s.done: - return os.ErrClosed - default: - close(s.done) - } - var err error - if s.debugHTTPServer != nil { - err = E.Append(err, s.debugHTTPServer.Close(), func(err error) error { - return E.Cause(err, "close debug HTTP server") - }) - s.debugHTTPServer = nil - } - for _, closeItem := range []struct { - name string - service adapter.Lifecycle - }{ - {"service", s.service}, - {"inbound", s.inbound}, - {"certificate-provider", s.certificateProvider}, - {"endpoint", s.endpoint}, - {"outbound", s.outbound}, - {"router", s.router}, - {"connection", s.connection}, - {"dns-router", s.dnsRouter}, - {"dns-transport", s.dnsTransport}, - {"network", s.network}, - } { - done := adapter.LogElapsed(s.logger, "close ", closeItem.name) - err = E.Append(err, closeItem.service.Close(), func(err error) error { - return E.Cause(err, "close ", closeItem.name) - }) - done() - } - if s.httpClientService != nil { - s.logger.Trace("close ", s.httpClientService.Name()) - startTime := time.Now() - err = E.Append(err, s.httpClientService.Close(), func(err error) error { - return E.Cause(err, "close ", s.httpClientService.Name()) - }) - s.logger.Trace("close ", s.httpClientService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") - } - for _, lifecycleService := range s.internalService { - done := adapter.LogElapsed(s.logger, "close ", lifecycleService.Name()) - err = E.Append(err, lifecycleService.Close(), func(err error) error { - return E.Cause(err, "close ", lifecycleService.Name()) - }) - done() - } - if s.ntpService != nil { - done := adapter.LogElapsed(s.logger, "close ntp service") - err = E.Append(err, s.ntpService.Close(), func(err error) error { - return E.Cause(err, "close ntp service") - }) - done() - } - done := adapter.LogElapsed(s.logger, "close logger") - err = E.Append(err, s.logFactory.Close(), func(err error) error { - return E.Cause(err, "close logger") - }) - done() - return err + return s.scope.Close() } func (s *Box) Network() adapter.NetworkManager { diff --git a/common/certificate/store.go b/common/certificate/store.go index 34871a6b..326e804a 100644 --- a/common/certificate/store.go +++ b/common/certificate/store.go @@ -31,6 +31,7 @@ type Store struct { certificatePaths []string certificateDirectoryPaths []string watcher *fswatch.Watcher + anchorPEM []byte //nolint:unused // populated only on darwin && cgo via the storePlatform embed. platform storePlatform } @@ -92,10 +93,12 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific } store.watcher = watcher } - err := store.update() + currentPool, anchorPEM, err := store.load() if err != nil { return nil, E.Cause(err, "initializing certificate store") } + store.currentPool = currentPool + store.anchorPEM = anchorPEM return store, nil } @@ -103,31 +106,28 @@ func (s *Store) Name() string { return "certificate" } -func (s *Store) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateStart { - return nil - } - if s.watcher != nil { - return s.watcher.Start() +func (s *Store) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + s.access.Lock() + err := s.updatePlatformLocked(s.anchorPEM) + s.access.Unlock() + if err != nil { + return err + } + scope.Add(s.closePlatform) + case adapter.StartStateStart: + if s.watcher != nil { + err := s.watcher.Start() + if err != nil { + return err + } + scope.Add(s.watcher.Close) + } } return nil } -func (s *Store) Close() error { - watcher := s.watcher - s.watcher = nil - - var closeErr error - if watcher != nil { - closeErr = watcher.Close() - } - platformErr := s.closePlatform() - if platformErr != nil { - closeErr = platformErr - } - return closeErr -} - func (s *Store) Pool() *x509.CertPool { s.access.RLock() defer s.access.RUnlock() @@ -143,38 +143,50 @@ func (s *Store) ExclusiveAnchors() bool { } func (s *Store) update() error { - currentPool, err := s.newBasePool() + currentPool, anchorPEM, err := s.load() if err != nil { return err } + s.access.Lock() + defer s.access.Unlock() + s.currentPool = currentPool + s.anchorPEM = anchorPEM + return s.updatePlatformLocked(anchorPEM) +} + +func (s *Store) load() (*x509.CertPool, []byte, error) { + currentPool, err := s.newBasePool() + if err != nil { + return nil, nil, err + } pemBuffer := new(bytes.Buffer) switch s.storeType { case C.CertificateStoreMozilla: pemContent := mozillaIncludedPEM() if !currentPool.AppendCertsFromPEM([]byte(pemContent)) { - return E.New("invalid Mozilla included certificate PEM") + return nil, nil, E.New("invalid Mozilla included certificate PEM") } appendPEMBlock(pemBuffer, string(pemContent)) case C.CertificateStoreChrome: pemContent := chromeIncludedPEM() if !currentPool.AppendCertsFromPEM([]byte(pemContent)) { - return E.New("invalid Chrome included certificate PEM") + return nil, nil, E.New("invalid Chrome included certificate PEM") } appendPEMBlock(pemBuffer, string(pemContent)) } if s.certificate != "" { if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) { - return E.New("invalid certificate PEM strings") + return nil, nil, E.New("invalid certificate PEM strings") } appendPEMBlock(pemBuffer, s.certificate) } for _, path := range s.certificatePaths { pemContent, err := filemanager.ReadFile(s.ctx, path) if err != nil { - return err + return nil, nil, err } if !currentPool.AppendCertsFromPEM(pemContent) { - return E.New("invalid certificate PEM file: ", path) + return nil, nil, E.New("invalid certificate PEM file: ", path) } appendPEMBlock(pemBuffer, string(pemContent)) } @@ -195,12 +207,9 @@ func (s *Store) update() error { } } if firstErr != nil { - return firstErr + return nil, nil, firstErr } - s.access.Lock() - defer s.access.Unlock() - s.currentPool = currentPool - return s.updatePlatformLocked(pemBuffer.Bytes()) + return currentPool, pemBuffer.Bytes(), nil } func appendPEMBlock(buffer *bytes.Buffer, block string) { diff --git a/common/httpclient/manager.go b/common/httpclient/manager.go index 45e05f21..820cbeab 100644 --- a/common/httpclient/manager.go +++ b/common/httpclient/manager.go @@ -61,7 +61,7 @@ func (m *Manager) Name() string { return "http-client" } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -72,6 +72,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { } m.defaultTransport = sharedTransport } + scope.Add(m.close) return nil } @@ -163,12 +164,9 @@ func (m *Manager) ResetNetwork() { } } -func (m *Manager) Close() error { +func (m *Manager) close() error { m.access.Lock() defer m.access.Unlock() - if m.managedTransports == nil { - return nil - } var err error for _, transport := range m.managedTransports { err = E.Append(err, transport.close(), func(err error) error { diff --git a/common/listener/listener.go b/common/listener/listener.go index 7a58015c..2cbbbab5 100644 --- a/common/listener/listener.go +++ b/common/listener/listener.go @@ -89,7 +89,7 @@ func (l *Listener) Start() error { if common.Contains(l.network, N.NetworkUDP) { _, err := l.ListenUDP() if err != nil { - return err + return E.Errors(err, l.Close()) } l.packetOutboundClosed = make(chan struct{}) l.packetOutbound = make(chan *N.PacketBuffer, 64) @@ -109,11 +109,11 @@ func (l *Listener) Start() error { } systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS, nil) if err != nil { - return E.Cause(err, "initialize system proxy") + return E.Errors(E.Cause(err, "initialize system proxy"), l.Close()) } err = systemProxy.Enable() if err != nil { - return E.Errors(E.Cause(err, "set system proxy"), systemProxy.Close()) + return E.Errors(E.Cause(err, "set system proxy"), systemProxy.Close(), l.Close()) } l.systemProxy = systemProxy } diff --git a/common/netns/manager_linux.go b/common/netns/manager_linux.go index f53279c3..a4b906bf 100644 --- a/common/netns/manager_linux.go +++ b/common/netns/manager_linux.go @@ -23,7 +23,6 @@ type Manager struct { namespaces []option.NetworkNamespace holderArgs []string paths map[string]string - holders []*holder } type holder struct { @@ -64,7 +63,7 @@ func (m *Manager) Name() string { return "netns" } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateInitialize { return nil } @@ -80,7 +79,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { return E.Cause(err, "network namespace[", namespace.Tag, "]") } case C.NetNsTypeUnshare: - err := m.startNamespace(namespace) + err := m.startNamespace(namespace, scope) if err != nil { return E.Cause(err, "network namespace[", namespace.Tag, "]") } @@ -89,7 +88,7 @@ func (m *Manager) Start(stage adapter.StartStage) error { return nil } -func (m *Manager) startNamespace(namespace option.NetworkNamespace) error { +func (m *Manager) startNamespace(namespace option.NetworkNamespace, scope *adapter.Scope) error { if len(m.holderArgs) == 0 { return E.New("unshare network namespace is only supported in `sing-box run`") } @@ -97,7 +96,13 @@ func (m *Manager) startNamespace(namespace option.NetworkNamespace) error { if err != nil { return err } - m.holders = append(m.holders, created) + scope.Add(func() error { + created.pipeWriter.Close() + if created.pidFile != "" { + os.Remove(created.pidFile) + } + return nil + }) pid := created.command.Process.Pid if created.pidFile != "" { err = os.WriteFile(created.pidFile, []byte(strconv.Itoa(pid)+"\n"), 0o644) @@ -115,17 +120,6 @@ func (m *Manager) startNamespace(namespace option.NetworkNamespace) error { return nil } -func (m *Manager) Close() error { - for _, created := range m.holders { - created.pipeWriter.Close() - if created.pidFile != "" { - os.Remove(created.pidFile) - } - } - m.holders = nil - return nil -} - func (m *Manager) ResolvePath(nameOrPath string) string { path, loaded := m.paths[nameOrPath] if loaded && path != "" { diff --git a/common/netns/manager_stub.go b/common/netns/manager_stub.go index 0bcd255a..23da25b1 100644 --- a/common/netns/manager_stub.go +++ b/common/netns/manager_stub.go @@ -22,11 +22,7 @@ func (m *Manager) Name() string { return "netns" } -func (m *Manager) Start(stage adapter.StartStage) error { - return nil -} - -func (m *Manager) Close() error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { return nil } diff --git a/common/netns/manager_test.go b/common/netns/manager_test.go index 59b7ae11..6040ba2c 100644 --- a/common/netns/manager_test.go +++ b/common/netns/manager_test.go @@ -4,6 +4,7 @@ package netns import ( "bufio" + "context" "os" "strconv" "strings" @@ -12,6 +13,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" @@ -39,11 +41,11 @@ func TestUnshareNamespace(t *testing.T) { if err != nil { t.Fatal(err) } - err = manager.Start(adapter.StartStateInitialize) + scope := adapter.NewScope(context.Background(), log.NewNOPFactory().Logger()) + err = manager.Start(adapter.StartStateInitialize, scope) if err != nil { t.Fatal(err) } - defer manager.Close() pipeReader.SetReadDeadline(time.Now().Add(10 * time.Second)) pidLine, err := bufio.NewReader(pipeReader).ReadString('\n') @@ -71,7 +73,7 @@ func TestUnshareNamespace(t *testing.T) { t.Fatal("holder is in the current netns") } - err = manager.Close() + err = scope.Close() if err != nil { t.Fatal(err) } diff --git a/common/tls/acme.go b/common/tls/acme.go index c736d08f..0bf554f4 100644 --- a/common/tls/acme.go +++ b/common/tls/acme.go @@ -49,9 +49,14 @@ func (w *acmeWrapper) Start() error { Logger: w.zapLogger, }) config = certmagic.New(cache, *config) + err := config.ManageSync(w.ctx, w.domain) + if err != nil { + cache.Stop() + return err + } w.cfg = config w.cache = cache - return w.cfg.ManageSync(w.ctx, w.domain) + return nil } func (w *acmeWrapper) Close() error { diff --git a/common/tls/std_server.go b/common/tls/std_server.go index d611f72a..171cc4f2 100644 --- a/common/tls/std_server.go +++ b/common/tls/std_server.go @@ -57,21 +57,26 @@ func (p *sharedCertificateProvider) GetACMENextProtos() []string { } type inlineCertificateProvider struct { + ctx context.Context + logger log.ContextLogger provider adapter.CertificateProviderService + scope *adapter.Scope } func (p *inlineCertificateProvider) Start() error { + p.scope = adapter.NewScope(p.ctx, p.logger) + name := "certificate-provider/" + p.provider.Type() for _, stage := range adapter.ListStartStages { - err := p.provider.Start(stage) + err := p.scope.Start(name, p.provider, stage) if err != nil { - return err + return E.Errors(err, p.scope.Close()) } } return nil } func (p *inlineCertificateProvider) Close() error { - return p.provider.Close() + return p.scope.Close() } func (p *inlineCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { @@ -548,6 +553,8 @@ func newCertificateProvider(ctx context.Context, logger log.ContextLogger, optio return nil, E.Cause(err, "create inline certificate provider") } return &inlineCertificateProvider{ + ctx: ctx, + logger: logger, provider: provider, }, nil } diff --git a/common/trafficcontrol/manager.go b/common/trafficcontrol/manager.go index d5b6a2b5..19f6fd0a 100644 --- a/common/trafficcontrol/manager.go +++ b/common/trafficcontrol/manager.go @@ -45,7 +45,6 @@ type Manager struct { eventSubscriber *observable.Subscriber[ConnectionEvent] eventObserver *observable.Observer[ConnectionEvent] - cleaner *cleanup.Cleaner } func NewManager(outbound adapter.OutboundManager) *Manager { @@ -59,20 +58,15 @@ func (m *Manager) Name() string { return "traffic manager" } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage == adapter.StartStateInitialize { m.eventObserver = observable.NewObserver(m.eventSubscriber, 64) - m.cleaner = cleanup.Add(m.Clear) - } - return nil -} - -func (m *Manager) Close() error { - if m.cleaner != nil { - m.cleaner.Close() - } - if m.eventObserver != nil { - return m.eventObserver.Close() + scope.Add(m.eventObserver.Close) + cleaner := cleanup.Add(m.Clear) + scope.Add(func() error { + cleaner.Close() + return nil + }) } return nil } diff --git a/dns/router.go b/dns/router.go index d5b07031..ec8ef4e1 100644 --- a/dns/router.go +++ b/dns/router.go @@ -129,10 +129,19 @@ func (r *Router) Initialize(rules []option.DNSRule) error { return nil } -func (r *Router) Start(stage adapter.StartStage) error { +func (r *Router) Start(stage adapter.StartStage, scope *adapter.Scope) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateStart: + scope.Add(func() error { + r.rulesAccess.Lock() + r.closing = true + runtimeRules := r.rules + r.rules = nil + r.rulesAccess.Unlock() + closeRules(runtimeRules) + return nil + }) monitor.Start("initialize DNS client") r.client.Start() monitor.Finish() @@ -163,20 +172,6 @@ func (r *Router) Start(stage adapter.StartStage) error { return nil } -func (r *Router) Close() error { - r.rulesAccess.Lock() - if r.closing { - r.rulesAccess.Unlock() - return nil - } - r.closing = true - runtimeRules := r.rules - r.rules = nil - r.rulesAccess.Unlock() - closeRules(runtimeRules) - return nil -} - func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, dnsRuleModeFlags, error) { for i, ruleOptions := range r.rawRules { err := R.ValidateNoNestedDNSRuleActions(ruleOptions) diff --git a/dns/router_race_test.go b/dns/router_race_test.go index 910f9e91..eabb5480 100644 --- a/dns/router_race_test.go +++ b/dns/router_race_test.go @@ -31,11 +31,7 @@ type fakeDNSTransport struct { firstQueried time.Time } -func (t *fakeDNSTransport) Start(stage adapter.StartStage) error { - return nil -} - -func (t *fakeDNSTransport) Close() error { +func (t *fakeDNSTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { return nil } @@ -90,11 +86,7 @@ type fakeDNSTransportManager struct { defaultTransport adapter.DNSTransport } -func (m *fakeDNSTransportManager) Start(stage adapter.StartStage) error { - return nil -} - -func (m *fakeDNSTransportManager) Close() error { +func (m *fakeDNSTransportManager) Start(stage adapter.StartStage, scope *adapter.Scope) error { return nil } diff --git a/dns/transport/dhcp/dhcp.go b/dns/transport/dhcp/dhcp.go index 0f0aa102..329dc537 100644 --- a/dns/transport/dhcp/dhcp.go +++ b/dns/transport/dhcp/dhcp.go @@ -18,7 +18,6 @@ import ( "github.com/sagernet/sing-box/dns/transport" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" @@ -27,7 +26,6 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/task" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/insomniacslk/dhcp/dhcpv4" @@ -54,7 +52,6 @@ type Transport struct { networkManager adapter.NetworkManager platformInterface adapter.PlatformInterface interfaceName string - interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] updateAccess sync.Mutex updateCancel context.CancelFunc refreshAccess sync.Mutex @@ -70,6 +67,7 @@ type transportState struct { search []string servers []M.Socksaddr serverTransports []adapter.DNSTransport + serverScope *adapter.Scope } func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) { @@ -104,19 +102,29 @@ func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, } } -func (t *Transport) Start(stage adapter.StartStage) error { +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(func() error { + t.Reset() + return nil + }) if t.interfaceName == "" { interfaceMonitor := t.networkManager.InterfaceMonitor() if interfaceMonitor == nil { return E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } - t.interfaceCallback = interfaceMonitor.RegisterCallback(t.interfaceUpdated) + interfaceCallback := interfaceMonitor.RegisterCallback(func(defaultInterface *control.Interface, flags int) { + t.interfaceUpdated(scope.Context()) + }) + scope.Add(func() error { + interfaceMonitor.UnregisterCallback(interfaceCallback) + return nil + }) } go func() { - err := t.fetch() + err := t.fetch(scope.Context()) if err != nil { if errors.Is(err, errInterfaceIsCellular) && t.optional { t.logger.Debug(E.Cause(errInterfaceIsCellular, "dhcp: fetch DNS servers")) @@ -128,32 +136,12 @@ func (t *Transport) Start(stage adapter.StartStage) error { return nil } -func (t *Transport) Close() error { - if t.interfaceCallback != nil { - t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback) - } - t.updateAccess.Lock() - updateCancel := t.updateCancel - t.updateCancel = nil - t.updateAccess.Unlock() - if updateCancel != nil { - updateCancel() - } - t.refreshAccess.Lock() - defer t.refreshAccess.Unlock() - state := t.savedState.Swap(nil) - if state != nil { - closeServerTransports(state.serverTransports) - } - return nil -} - func (t *Transport) Reset() { t.refreshAccess.Lock() defer t.refreshAccess.Unlock() state := t.savedState.Swap(nil) - if state != nil { - closeServerTransports(state.serverTransports) + if state != nil && state.serverScope != nil { + state.serverScope.Close() } } @@ -169,12 +157,6 @@ func (t *Transport) Environment() []string { return append(environment, state.search...) } -func closeServerTransports(serverTransports []adapter.DNSTransport) { - for _, serverTransport := range serverTransports { - serverTransport.Close() - } -} - func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { done := make(chan struct{}) var ( @@ -211,7 +193,7 @@ func (t *Transport) ExchangeAsync(ctx context.Context, message *mDNS.Msg, callba } func (t *Transport) exchangeCold(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) { - err := t.fetch() + err := t.fetch(t.ctx) if err != nil { callback(nil, E.Cause(err, "dhcp: fetch DNS servers")) return @@ -235,7 +217,7 @@ func (t *Transport) Fetch() []M.Socksaddr { return state.servers } -func (t *Transport) fetch() error { +func (t *Transport) fetch(ctx context.Context) error { state := t.savedState.Load() if state != nil { if state.lastError != nil { @@ -256,7 +238,7 @@ func (t *Transport) fetch() error { return nil } } - return t.updateServersLocked(t.ctx) + return t.updateServersLocked(ctx) } func (t *Transport) startRefresh() { @@ -342,12 +324,13 @@ func (t *Transport) storeFailureLocked(err error) { newState.search = previousState.search newState.servers = previousState.servers newState.serverTransports = previousState.serverTransports + newState.serverScope = previousState.serverScope } t.savedState.Store(newState) } -func (t *Transport) interfaceUpdated(defaultInterface *control.Interface, flags int) { - updateContext, updateCancel := context.WithCancel(t.ctx) +func (t *Transport) interfaceUpdated(ctx context.Context) { + updateContext, updateCancel := context.WithCancel(ctx) t.updateAccess.Lock() previousCancel := t.updateCancel t.updateCancel = updateCancel @@ -483,25 +466,25 @@ func (t *Transport) recreateServersLocked(iface *control.Interface, dhcpPacket * } if serversUnchanged && previousState.serverTransports != nil { newState.serverTransports = previousState.serverTransports + newState.serverScope = previousState.serverScope t.savedState.Store(newState) return nil } + serverScope := adapter.NewScope(t.ctx, t.logger) serverTransports := make([]adapter.DNSTransport, 0, len(newState.servers)) for _, serverAddr := range newState.servers { serverTransport := transport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, "", nil), t.dialer, serverAddr) - err := serverTransport.Start(adapter.StartStateStart) + err := serverTransport.Start(adapter.StartStateStart, serverScope) if err != nil { - for _, startedTransport := range serverTransports { - startedTransport.Close() - } - return E.Cause(err, "initialize transport for ", serverAddr) + return E.Errors(E.Cause(err, "initialize transport for ", serverAddr), serverScope.Close()) } serverTransports = append(serverTransports, serverTransport) } newState.serverTransports = serverTransports + newState.serverScope = serverScope t.savedState.Store(newState) - if previousState != nil { - closeServerTransports(previousState.serverTransports) + if previousState != nil && previousState.serverScope != nil { + previousState.serverScope.Close() } return nil } diff --git a/dns/transport/fakeip/fakeip.go b/dns/transport/fakeip/fakeip.go index 025ddecf..03daeffb 100644 --- a/dns/transport/fakeip/fakeip.go +++ b/dns/transport/fakeip/fakeip.go @@ -45,15 +45,16 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt }, nil } -func (t *Transport) Start(stage adapter.StartStage) error { +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return t.store.Start() -} - -func (t *Transport) Close() error { - return t.store.Close() + err := t.store.Start() + if err != nil { + return err + } + scope.Add(t.store.Close) + return nil } func (t *Transport) Reset() { diff --git a/dns/transport/hosts/hosts.go b/dns/transport/hosts/hosts.go index 2ba47063..d0f8fbff 100644 --- a/dns/transport/hosts/hosts.go +++ b/dns/transport/hosts/hosts.go @@ -58,11 +58,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt }, nil } -func (t *Transport) Start(stage adapter.StartStage) error { - return nil -} - -func (t *Transport) Close() error { +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { return nil } diff --git a/dns/transport/https.go b/dns/transport/https.go index 582677ec..4d5d3fb2 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -149,18 +149,17 @@ func NewHTTPSRaw( return transport } -func (t *HTTPSTransport) Start(stage adapter.StartStage) error { +func (t *HTTPSTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(func() error { + t.Reset() + return nil + }) return dialer.InitializeDetour(t.dialer) } -func (t *HTTPSTransport) Close() error { - t.Reset() - return nil -} - func (t *HTTPSTransport) Reset() { t.transportAccess.Lock() defer t.transportAccess.Unlock() diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index b7bf8347..9c24768b 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -12,7 +12,6 @@ import ( "github.com/sagernet/sing-box/dns/transport/mdns" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" @@ -39,6 +38,7 @@ type Transport struct { preferGo bool resolved ResolvedResolver mdnsTransport adapter.DNSTransport + mdnsScope *adapter.Scope configSource *systemconfig.Source system systemResolver serverSet atomic.Pointer[localServerSet] @@ -65,13 +65,15 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt }, nil } -func (t *Transport) Start(stage adapter.StartStage) error { +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { t.preferredResolver.Start(stage) switch stage { case adapter.StartStateInitialize: + scope.Add(t.configSource.Close) if !t.preferGo && isSystemdResolvedManaged() { resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) if err == nil { + scope.Add(resolvedResolver.Close) err = resolvedResolver.Start() if err == nil { t.resolved = resolvedResolver @@ -81,13 +83,26 @@ func (t *Transport) Start(stage adapter.StartStage) error { } } case adapter.StartStateStart: + scope.Add(func() error { + t.system.close() + return nil + }) + scope.Add(func() error { + serverSet := t.serverSet.Swap(nil) + if serverSet != nil { + return serverSet.serverScope.Close() + } + return nil + }) if !C.IsDarwin { t.mdnsTransport = mdns.NewRawTransport(t.TransportAdapter, t.ctx, t.logger) + t.mdnsScope = adapter.NewScope(t.ctx, t.logger) + scope.Add(t.mdnsScope.Close) } fallthrough default: if t.mdnsTransport != nil { - err := t.mdnsTransport.Start(stage) + err := t.mdnsTransport.Start(stage, t.mdnsScope) if err != nil { return err } @@ -96,15 +111,6 @@ func (t *Transport) Start(stage adapter.StartStage) error { return nil } -func (t *Transport) Close() error { - serverSet := t.serverSet.Swap(nil) - if serverSet != nil { - serverSet.Close() - } - t.system.close() - return common.Close(t.resolved, t.mdnsTransport, t.configSource) -} - func (t *Transport) Reset() { serverSet := t.serverSet.Load() if serverSet != nil { diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go index 6a7eb3cb..0272854d 100644 --- a/dns/transport/local/local_resolved_linux.go +++ b/dns/transport/local/local_resolved_linux.go @@ -66,12 +66,12 @@ type DBusResolvedResolver struct { updateCancel context.CancelFunc updateRunAccess sync.Mutex closed bool - closeOnce sync.Once } type resolvedServerSet struct { - scopes []*resolvedScope - signature []string + scopes []*resolvedScope + signature []string + serverScope *adapter.Scope } // Match levels of dns_scope_good_domain() in systemd-resolved: a routing or search @@ -198,32 +198,26 @@ func (t *DBusResolvedResolver) Start() error { } func (t *DBusResolvedResolver) Close() error { + t.updateAccess.Lock() + updateCancel := t.updateCancel + t.updateCancel = nil + t.updateAccess.Unlock() + if updateCancel != nil { + updateCancel() + } + t.updateRunAccess.Lock() + t.closed = true + serverSet := t.savedServerSet.Swap(nil) + t.updateRunAccess.Unlock() var closeErr error - t.closeOnce.Do(func() { - t.updateAccess.Lock() - updateCancel := t.updateCancel - t.updateCancel = nil - t.updateAccess.Unlock() - if updateCancel != nil { - updateCancel() - } - t.updateRunAccess.Lock() - t.closed = true - serverSet := t.savedServerSet.Swap(nil) - t.updateRunAccess.Unlock() - if serverSet != nil { - closeErr = serverSet.Close() - } - if t.interfaceCallback != nil { - t.interfaceMonitor.UnregisterCallback(t.interfaceCallback) - } - if t.networkCallback != nil { - t.networkMonitor.UnregisterCallback(t.networkCallback) - } - if t.systemBus != nil { - _ = t.systemBus.Close() - } - }) + if serverSet != nil { + closeErr = serverSet.serverScope.Close() + } + t.interfaceMonitor.UnregisterCallback(t.interfaceCallback) + if t.networkCallback != nil { + t.networkMonitor.UnregisterCallback(t.networkCallback) + } + _ = t.systemBus.Close() return closeErr } @@ -385,21 +379,6 @@ func (s *resolvedScope) match(name string) int { return resolvedScopeMaybe } -func (s *resolvedServerSet) Close() error { - return E.Errors(common.Map(s.scopes, (*resolvedScope).Close)...) -} - -func (s *resolvedScope) Close() error { - var errors []error - for _, server := range s.servers { - errors = append(errors, server.primaryTransport.Close()) - if server.fallbackTransport != nil { - errors = append(errors, server.fallbackTransport.Close()) - } - } - return E.Errors(errors...) -} - func (t *DBusResolvedResolver) loopUpdateStatus() { signalChan := make(chan *dbus.Signal, 1) t.systemBus.Signal(signalChan) @@ -457,7 +436,7 @@ func (t *DBusResolvedResolver) updateStatus(ctx context.Context) error { serverSet, err := t.checkResolved(ctx) if t.closed || ctx.Err() != nil { if serverSet != nil { - _ = serverSet.Close() + _ = serverSet.serverScope.Close() } if t.closed { return os.ErrClosed @@ -466,7 +445,7 @@ func (t *DBusResolvedResolver) updateStatus(ctx context.Context) error { } oldServerSet := t.savedServerSet.Swap(serverSet) if oldServerSet != nil { - _ = oldServerSet.Close() + _ = oldServerSet.serverScope.Close() } if err != nil { var dbusErr dbus.Error @@ -579,11 +558,13 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServ if len(scopeSpecifications) == 0 { return nil, E.New("no DNS servers configured") } - serverSet := &resolvedServerSet{} + serverSet := &resolvedServerSet{ + serverScope: adapter.NewScope(t.ctx, t.logger), + } for _, scopeSpecification := range scopeSpecifications { - scope, createErr := t.createResolvedScope(scopeSpecification) + scope, createErr := t.createResolvedScope(serverSet.serverScope, scopeSpecification) if createErr != nil { - _ = serverSet.Close() + _ = serverSet.serverScope.Close() return nil, createErr } serverSet.scopes = append(serverSet.scopes, scope) @@ -630,7 +611,7 @@ func (t *DBusResolvedResolver) loadResolvedLinkScope(ctx context.Context, manage return linkScope, true, nil } -func (t *DBusResolvedResolver) createResolvedScope(scopeSpecification resolvedScopeSpecification) (*resolvedScope, error) { +func (t *DBusResolvedResolver) createResolvedScope(serverScope *adapter.Scope, scopeSpecification resolvedScopeSpecification) (*resolvedScope, error) { scope := &resolvedScope{ domains: scopeSpecification.domains, defaultRoute: scopeSpecification.defaultRoute, @@ -646,9 +627,8 @@ func (t *DBusResolvedResolver) createResolvedScope(scopeSpecification resolvedSc return nil, err } for _, serverSpecification := range scopeSpecification.servers { - server, createErr := t.createResolvedServer(serverDialer, scopeSpecification.dnsOverTLSMode, serverSpecification) + server, createErr := t.createResolvedServer(serverScope, serverDialer, scopeSpecification.dnsOverTLSMode, serverSpecification) if createErr != nil { - _ = scope.Close() return nil, createErr } scope.servers = append(scope.servers, server) @@ -656,9 +636,9 @@ func (t *DBusResolvedResolver) createResolvedScope(scopeSpecification resolvedSc return scope, nil } -func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) { +func (t *DBusResolvedResolver) createResolvedServer(serverScope *adapter.Scope, serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) { if dnsOverTLSMode == "yes" { - primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true) + primaryTransport, err := t.createResolvedTransport(serverScope, serverDialer, serverSpecification, true) if err != nil { return resolvedServer{}, err } @@ -667,13 +647,12 @@ func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOv }, nil } if dnsOverTLSMode == "opportunistic" { - primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true) + primaryTransport, err := t.createResolvedTransport(serverScope, serverDialer, serverSpecification, true) if err != nil { return resolvedServer{}, err } - fallbackTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false) + fallbackTransport, err := t.createResolvedTransport(serverScope, serverDialer, serverSpecification, false) if err != nil { - _ = primaryTransport.Close() return resolvedServer{}, err } return resolvedServer{ @@ -681,7 +660,7 @@ func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOv fallbackTransport: fallbackTransport, }, nil } - primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false) + primaryTransport, err := t.createResolvedTransport(serverScope, serverDialer, serverSpecification, false) if err != nil { return resolvedServer{}, err } @@ -690,7 +669,7 @@ func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOv }, nil } -func (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, serverSpecification resolvedServerSpecification, useTLS bool) (adapter.DNSTransport, error) { +func (t *DBusResolvedResolver) createResolvedTransport(serverScope *adapter.Scope, serverDialer N.Dialer, serverSpecification resolvedServerSpecification, useTLS bool) (adapter.DNSTransport, error) { serverAddress := M.SocksaddrFrom(serverSpecification.address, resolvedServerPort(serverSpecification.port, useTLS)) if useTLS { tlsAddress := serverSpecification.address @@ -709,17 +688,15 @@ func (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, se return nil, err } serverTransport := dnsTransport.NewTLSRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeTLS, "", nil), serverDialer, serverAddress, tlsConfig) - err = serverTransport.Start(adapter.StartStateStart) + err = serverTransport.Start(adapter.StartStateStart, serverScope) if err != nil { - _ = serverTransport.Close() return nil, err } return serverTransport, nil } serverTransport := dnsTransport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, "", nil), serverDialer, serverAddress) - err := serverTransport.Start(adapter.StartStateStart) + err := serverTransport.Start(adapter.StartStateStart, serverScope) if err != nil { - _ = serverTransport.Close() return nil, err } return serverTransport, nil diff --git a/dns/transport/local/local_shared.go b/dns/transport/local/local_shared.go index b86f6d78..e0d5d0f3 100644 --- a/dns/transport/local/local_shared.go +++ b/dns/transport/local/local_shared.go @@ -14,14 +14,9 @@ import ( ) type localServerSet struct { - config *systemconfig.Config - transports []adapter.DNSTransport -} - -func (s *localServerSet) Close() { - for _, serverTransport := range s.transports { - serverTransport.Close() - } + config *systemconfig.Config + transports []adapter.DNSTransport + serverScope *adapter.Scope } func (t *Transport) serverSetFor(systemConfig *systemconfig.Config) (*localServerSet, error) { @@ -35,6 +30,7 @@ func (t *Transport) serverSetFor(systemConfig *systemconfig.Config) (*localServe if serverSet != nil && serverSet.config == systemConfig { return serverSet, nil } + serverScope := adapter.NewScope(t.ctx, t.logger) transports := make([]adapter.DNSTransport, 0, len(systemConfig.Servers)) for _, serverAddr := range systemConfig.Servers { var serverTransport adapter.DNSTransport @@ -43,22 +39,20 @@ func (t *Transport) serverSetFor(systemConfig *systemconfig.Config) (*localServe } else { serverTransport = transport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, "", nil), t.dialer, serverAddr) } - err := serverTransport.Start(adapter.StartStateStart) + err := serverTransport.Start(adapter.StartStateStart, serverScope) if err != nil { - for _, startedTransport := range transports { - startedTransport.Close() - } - return nil, E.Cause(err, "initialize transport for ", serverAddr) + return nil, E.Errors(E.Cause(err, "initialize transport for ", serverAddr), serverScope.Close()) } transports = append(transports, serverTransport) } newServerSet := &localServerSet{ - config: systemConfig, - transports: transports, + config: systemConfig, + transports: transports, + serverScope: serverScope, } oldServerSet := t.serverSet.Swap(newServerSet) if oldServerSet != nil { - oldServerSet.Close() + oldServerSet.serverScope.Close() } return newServerSet, nil } diff --git a/dns/transport/mdns/mdns.go b/dns/transport/mdns/mdns.go index 18542c55..7f7c30b3 100644 --- a/dns/transport/mdns/mdns.go +++ b/dns/transport/mdns/mdns.go @@ -93,13 +93,9 @@ func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, } } -func (t *Transport) Start(stage adapter.StartStage) error { - return nil -} - -func (t *Transport) Close() error { - if t.configSource != nil { - return t.configSource.Close() +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage == adapter.StartStateInitialize && t.configSource != nil { + scope.Add(t.configSource.Close) } return nil } diff --git a/dns/transport/multiplexer_test.go b/dns/transport/multiplexer_test.go index b5bd02b6..9ab3ab8b 100644 --- a/dns/transport/multiplexer_test.go +++ b/dns/transport/multiplexer_test.go @@ -9,9 +9,11 @@ import ( "testing" "time" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" boxDNS "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" @@ -114,7 +116,16 @@ func newTestTCPTransport(t *testing.T, listener net.Listener) *TCPTransport { if err != nil { t.Fatal(err) } - return NewTCPRaw(boxDNS.NewTransportAdapter(C.DNSTypeTCP, "test", nil), transportDialer, M.SocksaddrFromNet(listener.Addr())) + transport := NewTCPRaw(boxDNS.NewTransportAdapter(C.DNSTypeTCP, "test", nil), transportDialer, M.SocksaddrFromNet(listener.Addr())) + scope := adapter.NewScope(context.Background(), log.NewNOPFactory().Logger()) + err = transport.Start(adapter.StartStateStart, scope) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + scope.Close() + }) + return transport } func testExchange(transport *TCPTransport, questionName string) error { @@ -155,7 +166,6 @@ func TestTCPTransportSingleQueryServer(t *testing.T) { }() transport := newTestTCPTransport(t, listener) - defer transport.Close() const queryCount = 8 results := make(chan error, queryCount) @@ -221,7 +231,6 @@ func TestTCPTransportProbeEnablesReuse(t *testing.T) { }() transport := newTestTCPTransport(t, listener) - defer transport.Close() deadline := time.Now().Add(3 * time.Second) for maxServedOnConn.Load() < 3 { @@ -284,7 +293,6 @@ func TestTCPTransportDemotesBrokenReuse(t *testing.T) { }() transport := newTestTCPTransport(t, listener) - defer transport.Close() deadline := time.Now().Add(3 * time.Second) for { @@ -364,7 +372,6 @@ func TestTCPTransportSilentPipelineServer(t *testing.T) { }() transport := newTestTCPTransport(t, listener) - defer transport.Close() const queryCount = 5 results := make(chan error, queryCount) diff --git a/dns/transport/quic/http3.go b/dns/transport/quic/http3.go index ca7d49d0..e50d47c0 100644 --- a/dns/transport/quic/http3.go +++ b/dns/transport/quic/http3.go @@ -137,19 +137,18 @@ func (t *HTTP3Transport) newTransport() *http3.Transport { } } -func (t *HTTP3Transport) Start(stage adapter.StartStage) error { +func (t *HTTP3Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(func() error { + t.transportAccess.Lock() + defer t.transportAccess.Unlock() + return t.transport.Close() + }) return dialer.InitializeDetour(t.dialer) } -func (t *HTTP3Transport) Close() error { - t.transportAccess.Lock() - defer t.transportAccess.Unlock() - return t.transport.Close() -} - func (t *HTTP3Transport) Reset() { t.transportAccess.Lock() defer t.transportAccess.Unlock() diff --git a/dns/transport/quic/quic.go b/dns/transport/quic/quic.go index 0b641bb3..bc2acaf0 100644 --- a/dns/transport/quic/quic.go +++ b/dns/transport/quic/quic.go @@ -79,17 +79,14 @@ func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options }, nil } -func (t *Transport) Start(stage adapter.StartStage) error { +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(t.connection.Close) return dialer.InitializeDetour(t.dialer) } -func (t *Transport) Close() error { - return t.connection.Close() -} - func (t *Transport) Reset() { t.connection.Reset() } diff --git a/dns/transport/tcp.go b/dns/transport/tcp.go index a4b72724..af83ad62 100644 --- a/dns/transport/tcp.go +++ b/dns/transport/tcp.go @@ -79,17 +79,14 @@ func NewTCPRaw(adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socks return t } -func (t *TCPTransport) Start(stage adapter.StartStage) error { +func (t *TCPTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(t.multiplexer.Close) return dialer.InitializeDetour(t.dialer) } -func (t *TCPTransport) Close() error { - return t.multiplexer.Close() -} - func (t *TCPTransport) Reset() { t.multiplexer.Reset() } diff --git a/dns/transport/tls.go b/dns/transport/tls.go index 36cd8dd3..ed70b7ef 100644 --- a/dns/transport/tls.go +++ b/dns/transport/tls.go @@ -85,17 +85,14 @@ func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer return t } -func (t *TLSTransport) Start(stage adapter.StartStage) error { +func (t *TLSTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(t.multiplexer.Close) return dialer.InitializeDetour(t.dialer) } -func (t *TLSTransport) Close() error { - return t.multiplexer.Close() -} - func (t *TLSTransport) Reset() { t.multiplexer.Reset() } diff --git a/dns/transport/udp.go b/dns/transport/udp.go index ea6deb16..a6bcd188 100644 --- a/dns/transport/udp.go +++ b/dns/transport/udp.go @@ -79,17 +79,14 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer return t } -func (t *UDPTransport) Start(stage adapter.StartStage) error { +func (t *UDPTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + scope.Add(t.multiplexer.Close) return dialer.InitializeDetour(t.dialer) } -func (t *UDPTransport) Close() error { - return t.multiplexer.Close() -} - func (t *UDPTransport) Reset() { t.multiplexer.Reset() } diff --git a/dns/transport_manager.go b/dns/transport_manager.go index 120357c0..f1a69ecf 100644 --- a/dns/transport_manager.go +++ b/dns/transport_manager.go @@ -2,30 +2,24 @@ package dns import ( "context" - "io" "os" "strings" "sync" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" ) var _ adapter.DNSTransportManager = (*TransportManager)(nil) type TransportManager struct { - logger log.ContextLogger registry adapter.DNSTransportRegistry outbound adapter.OutboundManager defaultTag string access sync.RWMutex - started bool - stage adapter.StartStage transports []adapter.DNSTransport transportByTag map[string]adapter.DNSTransport defaultTransport adapter.DNSTransport @@ -33,9 +27,8 @@ type TransportManager struct { fakeIPTransport adapter.FakeIPTransport } -func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransportRegistry, outbound adapter.OutboundManager, defaultTag string) *TransportManager { +func NewTransportManager(registry adapter.DNSTransportRegistry, outbound adapter.OutboundManager, defaultTag string) *TransportManager { return &TransportManager{ - logger: logger, registry: registry, outbound: outbound, defaultTag: defaultTag, @@ -47,13 +40,8 @@ func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.D m.defaultTransportFallback = defaultTransportFallback } -func (m *TransportManager) Start(stage adapter.StartStage) error { +func (m *TransportManager) Start(stage adapter.StartStage, scope *adapter.Scope) error { m.access.Lock() - if m.started && m.stage >= stage { - panic("already started") - } - m.started = true - m.stage = stage if stage == adapter.StartStateInitialize { if m.defaultTag != "" && m.defaultTransport == nil { m.access.Unlock() @@ -73,19 +61,19 @@ func (m *TransportManager) Start(stage adapter.StartStage) error { transports := m.transports m.access.Unlock() if stage == adapter.StartStateStart { - return m.startTransports(transports) + return m.startTransports(scope, transports) } for _, transport := range transports { - err := transport.Start(stage) + name := "dns/" + transport.Type() + "[" + transport.Tag() + "]" + err := scope.Start(name, transport, stage) if err != nil { - return E.Cause(err, stage, " dns/", transport.Type(), "[", transport.Tag(), "]") + return err } } return nil } -func (m *TransportManager) startTransports(transports []adapter.DNSTransport) error { - monitor := taskmonitor.New(m.logger, C.StartTimeout) +func (m *TransportManager) startTransports(scope *adapter.Scope, transports []adapter.DNSTransport) error { started := make(map[string]bool) for { canContinue := false @@ -103,13 +91,10 @@ func (m *TransportManager) startTransports(transports []adapter.DNSTransport) er } started[transportTag] = true canContinue = true - if starter, isStarter := transportToStart.(adapter.Lifecycle); isStarter { - monitor.Start("start dns/", transportToStart.Type(), "[", transportTag, "]") - err := starter.Start(adapter.StartStateStart) - monitor.Finish() - if err != nil { - return E.Cause(err, "start dns/", transportToStart.Type(), "[", transportTag, "]") - } + name := "dns/" + transportToStart.Type() + "[" + transportTag + "]" + err := scope.Start(name, transportToStart, adapter.StartStateStart) + if err != nil { + return err } } if len(started) == len(transports) { @@ -142,30 +127,6 @@ func (m *TransportManager) startTransports(transports []adapter.DNSTransport) er return nil } -func (m *TransportManager) Close() error { - monitor := taskmonitor.New(m.logger, C.StopTimeout) - m.access.Lock() - if !m.started { - m.access.Unlock() - return nil - } - m.started = false - transports := m.transports - m.transports = nil - m.access.Unlock() - var err error - for _, transport := range transports { - if closer, isCloser := transport.(io.Closer); isCloser { - monitor.Start("close server/", transport.Type(), "[", transport.Tag(), "]") - err = E.Append(err, closer.Close(), func(err error) error { - return E.Cause(err, "close server/", transport.Type(), "[", transport.Tag(), "]") - }) - monitor.Finish() - } - } - return nil -} - func (m *TransportManager) Transports() []adapter.DNSTransport { m.access.RLock() defer m.access.RUnlock() diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 0d714a60..3cbec734 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -140,10 +140,16 @@ func (c *CacheFile) SetDisableExpire(disableExpire bool) { c.disableExpire = disableExpire } -func (c *CacheFile) Start(stage adapter.StartStage) error { +func (c *CacheFile) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateInitialize: - return c.start() + err := c.start() + if err != nil { + return err + } + scope.Add(func() error { + return c.database().Close() + }) case adapter.StartStateStart: c.startCacheCleanup() } @@ -239,16 +245,6 @@ func (c *CacheFile) start() error { return nil } -func (c *CacheFile) Close() error { - c.dbAccess.RLock() - db := c.DB - c.dbAccess.RUnlock() - if db == nil { - return nil - } - return db.Close() -} - func checkDatabase(db *bbolt.DB) error { return db.View(func(tx *bbolt.Tx) error { var checkErr error diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index b9222ad5..6623906a 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -21,7 +21,6 @@ import ( "github.com/sagernet/sing-box/experimental/clashmode" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/service" @@ -147,7 +146,7 @@ func (s *Server) Name() string { return "clash server" } -func (s *Server) Start(stage adapter.StartStage) error { +func (s *Server) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStarted { return nil } @@ -168,6 +167,7 @@ func (s *Server) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "external controller listen error") } + scope.Add(s.httpServer.Close) s.logger.Info("restful api listening at ", listener.Addr()) go func() { err = s.httpServer.Serve(listener) @@ -179,12 +179,6 @@ func (s *Server) Start(stage adapter.StartStage) error { return nil } -func (s *Server) Close() error { - return common.Close( - common.PtrOrNil(s.httpServer), - ) -} - func authentication(serverSecret string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { diff --git a/experimental/clashmode/manager.go b/experimental/clashmode/manager.go index 6491d754..ae6fd91d 100644 --- a/experimental/clashmode/manager.go +++ b/experimental/clashmode/manager.go @@ -43,7 +43,7 @@ func (m *Manager) Name() string { return "clash mode manager" } -func (m *Manager) Start(stage adapter.StartStage) error { +func (m *Manager) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -59,10 +59,6 @@ func (m *Manager) Start(stage adapter.StartStage) error { return nil } -func (m *Manager) Close() error { - return nil -} - func (m *Manager) Mode() string { return m.mode } diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index 7fcef2ce..3b96b621 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -46,15 +46,11 @@ func newPlatformTransport(ctx context.Context, logger log.ContextLogger, iif Loc }, nil } -func (p *platformTransport) Start(stage adapter.StartStage) error { +func (p *platformTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { p.preferredResolver.Start(stage) return nil } -func (p *platformTransport) Close() error { - return nil -} - func (p *platformTransport) Reset() { } diff --git a/experimental/libbox/http.go b/experimental/libbox/http.go index c9ee3787..b2b029a4 100644 --- a/experimental/libbox/http.go +++ b/experimental/libbox/http.go @@ -18,8 +18,10 @@ import ( "strconv" "sync" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/certificate" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" @@ -69,10 +71,10 @@ var ( ) type httpClient struct { - tls tls.Config - client http.Client - transport http.Transport - store *certificate.Store + tls tls.Config + client http.Client + transport http.Transport + storeScope *adapter.Scope } func NewHTTPClient() HTTPClient { @@ -87,6 +89,11 @@ func NewHTTPClient() HTTPClient { if err != nil { panic(E.Cause(err, "initialize certificate store")) } + client.storeScope = adapter.NewScope(context.Background(), log.NewNOPFactory().Logger()) + err = store.Start(adapter.StartStateInitialize, client.storeScope) + if err != nil { + panic(E.Cause(err, "initialize certificate store")) + } client.tls.RootCAs = store.Pool() } return client @@ -161,8 +168,8 @@ func (c *httpClient) NewRequest() HTTPRequest { } func (c *httpClient) Close() { - if c.store != nil { - c.store.Close() + if c.storeScope != nil { + c.storeScope.Close() } c.transport.CloseIdleConnections() } diff --git a/experimental/v2rayapi/server.go b/experimental/v2rayapi/server.go index 8ebae1c4..b5612ada 100644 --- a/experimental/v2rayapi/server.go +++ b/experimental/v2rayapi/server.go @@ -24,7 +24,6 @@ var _ adapter.V2RayServer = (*Server)(nil) type Server struct { logger log.Logger listen string - tcpListener net.Listener grpcServer *grpc.Server statsService *StatsService } @@ -48,33 +47,27 @@ func (s *Server) Name() string { return "v2ray server" } -func (s *Server) Start(stage adapter.StartStage) error { - if stage != adapter.StartStatePostStart { - return nil - } - listener, err := net.Listen("tcp", s.listen) - if err != nil { - return err - } - s.logger.Info("grpc server started at ", listener.Addr()) - s.tcpListener = listener - go func() { - err = s.grpcServer.Serve(listener) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - s.logger.Error(err) +func (s *Server) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + scope.Add(func() error { + s.grpcServer.Stop() + return nil + }) + case adapter.StartStatePostStart: + listener, err := net.Listen("tcp", s.listen) + if err != nil { + return err } - }() - return nil -} - -func (s *Server) Close() error { - if s.grpcServer != nil { - s.grpcServer.Stop() + s.logger.Info("grpc server started at ", listener.Addr()) + go func() { + err = s.grpcServer.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error(err) + } + }() } - return common.Close( - common.PtrOrNil(s.grpcServer), - s.tcpListener, - ) + return nil } func (s *Server) StatsService() adapter.ConnectionTracker { diff --git a/go.mod b/go.mod index 35df2eae..263a519a 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/sagernet/nftables v0.3.0-mod.4 github.com/sagernet/quic-go v0.61.0-sing-box-mod.9 github.com/sagernet/sing v0.9.7-0.20260929150544-0ad23b637bd4 - github.com/sagernet/sing-cloudflared v0.1.4-0.20260929150702-b3a1e8f3018c + github.com/sagernet/sing-cloudflared v0.1.4-0.20261002084126-c1255ae368f2 github.com/sagernet/sing-mux v0.3.10-0.20260929204512-caf09fe32475 github.com/sagernet/sing-openconnect v0.1.1-0.20260929151225-cbd68c45de58 github.com/sagernet/sing-openvpn v0.1.1-0.20260929151220-7c3045dbddce diff --git a/go.sum b/go.sum index 481f6c6a..81bbade2 100644 --- a/go.sum +++ b/go.sum @@ -320,8 +320,8 @@ github.com/sagernet/quic-go v0.61.0-sing-box-mod.9 h1:Lojx77uC63TxZO38Z1OSo6aOoB github.com/sagernet/quic-go v0.61.0-sing-box-mod.9/go.mod h1:hmLC8GJPp+BrpBgrgJnacvI4fZGIhasuy1ROJJ8GR4E= github.com/sagernet/sing v0.9.7-0.20260929150544-0ad23b637bd4 h1:d4WT3KO4zhp9qzPHUZCKqCP+ZmP1GXBsAmReqlnE4zA= github.com/sagernet/sing v0.9.7-0.20260929150544-0ad23b637bd4/go.mod h1:K3Owt3xPhHugvlnlPPxZJ/exXdaJfEPOTNorGk4AXjo= -github.com/sagernet/sing-cloudflared v0.1.4-0.20260929150702-b3a1e8f3018c h1:36zXD/qnDk7sh/J4zSEJsnHf4W3TYNCbmjo9MNIlrE4= -github.com/sagernet/sing-cloudflared v0.1.4-0.20260929150702-b3a1e8f3018c/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg= +github.com/sagernet/sing-cloudflared v0.1.4-0.20261002084126-c1255ae368f2 h1:+AfbAXGEV8tezIYHeShzwHm7IMUE5kYGVFLo0ly7W24= +github.com/sagernet/sing-cloudflared v0.1.4-0.20261002084126-c1255ae368f2/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg= github.com/sagernet/sing-mux v0.3.10-0.20260929204512-caf09fe32475 h1:aG0DqHEOjax7DyfE1GTAXZotOg3QGVCz3/8rCSua2kg= github.com/sagernet/sing-mux v0.3.10-0.20260929204512-caf09fe32475/go.mod h1:jricpwCOndDsTEM1TM3piJvhL/2Mm7OhLkm+JadJJ1Q= github.com/sagernet/sing-openconnect v0.1.1-0.20260929151225-cbd68c45de58 h1:WOPVlvbw3zGl4IzpRcfqB901blgDKuiplVrpptf/TOY= diff --git a/protocol/anytls/inbound.go b/protocol/anytls/inbound.go index f85b8f78..54c53d18 100644 --- a/protocol/anytls/inbound.go +++ b/protocol/anytls/inbound.go @@ -79,7 +79,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -88,12 +88,14 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.tlsConfig.Close) } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return common.Close(h.listener, h.tlsConfig) + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/anytls/outbound.go b/protocol/anytls/outbound.go index b4c07521..30d262ca 100644 --- a/protocol/anytls/outbound.go +++ b/protocol/anytls/outbound.go @@ -36,7 +36,6 @@ type Outbound struct { tlsConfig tls.Config clientOptions anytls.ClientConfig clientMetadata string - client *anytls.Client sessionClient *session.Client uotClient *uot.Client logger log.ContextLogger @@ -89,7 +88,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return outbound, nil } -func (h *Outbound) Start(stage adapter.StartStage) error { +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateInitialize { return nil } @@ -97,7 +96,7 @@ func (h *Outbound) Start(stage adapter.StartStage) error { if err != nil { return err } - h.client = client + scope.Add(client.Close) h.sessionClient = sessionClientOf(client) h.uotClient = &uot.Client{ Dialer: anytlsDialer(h.createProxy), @@ -160,7 +159,3 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) return h.uotClient.ListenPacket(ctx, destination) } - -func (h *Outbound) Close() error { - return common.Close(common.PtrOrNil(h.client)) -} diff --git a/protocol/bridge/backend.go b/protocol/bridge/backend.go index 0c33c753..2c48af97 100644 --- a/protocol/bridge/backend.go +++ b/protocol/bridge/backend.go @@ -41,16 +41,13 @@ type backendBase struct { returnAccess sync.Mutex returnPaths []tun.Return - egressAccess sync.Mutex - forwardingRestore []sysctlState - unregister func() + egressAccess sync.Mutex session adapter.BridgeSession currentEgress string - closeOnce sync.Once - closed chan struct{} - readDone chan struct{} + closed <-chan struct{} + readGroup sync.WaitGroup } func (b *backendBase) init(ctx context.Context, logger logger.ContextLogger, networkManager adapter.NetworkManager, tag string, options option.BridgeOutboundOptions) error { @@ -100,12 +97,14 @@ func (b *backendBase) DetachReturn(returnPath tun.Return) error { return nil } -func (b *backendBase) registerMonitors(syncFunc func()) { - var unregisterFuncs []func() +func (b *backendBase) registerMonitors(scope *adapter.Scope, syncFunc func()) { networkMonitor := b.networkManager.NetworkMonitor() if networkMonitor != nil { networkElement := networkMonitor.RegisterCallback(syncFunc) - unregisterFuncs = append(unregisterFuncs, func() { networkMonitor.UnregisterCallback(networkElement) }) + scope.Add(func() error { + networkMonitor.UnregisterCallback(networkElement) + return nil + }) } else if b.boundInterface != "" { b.logger.Debug("network monitor unavailable, pinned egress will not track interface changes") } @@ -113,14 +112,10 @@ func (b *backendBase) registerMonitors(syncFunc func()) { interfaceMonitor := b.networkManager.InterfaceMonitor() if interfaceMonitor != nil { interfaceElement := interfaceMonitor.RegisterCallback(func(_ *control.Interface, _ int) { syncFunc() }) - unregisterFuncs = append(unregisterFuncs, func() { interfaceMonitor.UnregisterCallback(interfaceElement) }) - } - } - if len(unregisterFuncs) > 0 { - b.unregister = func() { - for _, unregisterFunc := range unregisterFuncs { - unregisterFunc() - } + scope.Add(func() error { + interfaceMonitor.UnregisterCallback(interfaceElement) + return nil + }) } } } @@ -166,7 +161,6 @@ func (b *backendBase) resolveEgress() string { } func (b *backendBase) readLoop() { - defer close(b.readDone) buffer := make([]byte, tun.PacketOffset+bridgeTunMTU) for { n, err := b.tunInterface.Read(buffer) diff --git a/protocol/bridge/backend_darwin.go b/protocol/bridge/backend_darwin.go index 99580bbd..4da636d7 100644 --- a/protocol/bridge/backend_darwin.go +++ b/protocol/bridge/backend_darwin.go @@ -72,24 +72,30 @@ func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager return instance, nil } -func (b *backendDarwin) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateStart { - return nil - } - err := b.start() - if err != nil { - b.Close() - return err +func (b *backendDarwin) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + scope.Add(func() error { + releaseBridgeIndex(b.index) + return nil + }) + case adapter.StartStateStart: + b.closed = scope.Context().Done() + if b.platform != nil { + return b.startPlatform(scope) + } + return b.start(scope) } return nil } -func (b *backendDarwin) start() error { - if b.platform != nil { - return b.startPlatform() - } +func (b *backendDarwin) start(scope *adapter.Scope) error { b.tunName = tun.CalculateInterfaceName(b.bridgeName) b.anchorName = "com.apple/sing-box-" + b.tunName + scope.Add(func() error { + b.readGroup.Wait() + return nil + }) tunInterface, err := tun.New(tun.Options{ Name: b.tunName, MTU: bridgeTunMTUDarwin, @@ -102,12 +108,17 @@ func (b *backendDarwin) start() error { if err != nil { return E.Cause(err, "create bridge tun") } + scope.Add(tunInterface.Close) b.tunInterface = tunInterface err = tunInterface.Start() if err != nil { return E.Cause(err, "start bridge tun") } - b.forwardingRestore = enableDarwinForwarding(b.logger, b.inet4Port.IsValid(), b.inet6Port.IsValid()) + forwardingRestore := enableDarwinForwarding(b.logger, b.inet4Port.IsValid(), b.inet6Port.IsValid()) + scope.Add(func() error { + restoreDarwinForwarding(forwardingRestore) + return nil + }) err = assignBridgePortAddress(b.tunName, b.inet4Local, b.inet4Port) if err != nil { return E.Cause(err, "add bridge route") @@ -121,23 +132,31 @@ func (b *backendDarwin) start() error { if err != nil { return E.Cause(err, "enable pf") } + scope.Add(func() error { + _ = b.pfDevice.StopReference(b.pfToken) + return b.pfDevice.Close() + }) dropRules := bridgeDropRules(b.tunName, b.inet4Port, b.inet6Port) err = b.pfDevice.LoadAnchor(b.anchorName, dropRules) if err != nil { return E.Cause(err, "initialize bridge pf rules") } + scope.Add(func() error { + b.egressAccess.Lock() + _ = b.pfDevice.LoadAnchor(b.anchorName, nil) + b.egressAccess.Unlock() + return nil + }) b.currentRules = dropRules b.batchTUN = tunInterface.(tun.DarwinTUN) - b.closed = make(chan struct{}) - b.readDone = make(chan struct{}) - b.registerMonitors(b.syncEgress) + b.registerMonitors(scope, b.syncEgress) b.syncEgress() - go b.batchReadLoop() + b.readGroup.Go(b.batchReadLoop) b.logger.Info("bridge started at ", b.tunName, " (masquerade, egress ", b.egressLabel(), ")") return nil } -func (b *backendDarwin) startPlatform() error { +func (b *backendDarwin) startPlatform(scope *adapter.Scope) error { session, err := b.platform.CreateBridge(adapter.BridgeOptions{ BridgeName: b.bridgeName, MTU: bridgeTunMTUDarwin, @@ -148,11 +167,16 @@ func (b *backendDarwin) startPlatform() error { if err != nil { return E.Cause(err, "create bridge") } + scope.Add(session.Close) b.session = session b.tunName = session.Name() if !session.Inet6Active() { b.inet6Port = netip.Addr{} } + scope.Add(func() error { + b.readGroup.Wait() + return nil + }) tunInterface, err := tun.New(tun.Options{ Name: b.tunName, MTU: bridgeTunMTUDarwin, @@ -164,17 +188,16 @@ func (b *backendDarwin) startPlatform() error { if err != nil { return E.Cause(err, "create bridge tun") } + scope.Add(tunInterface.Close) b.tunInterface = tunInterface err = tunInterface.Start() if err != nil { return E.Cause(err, "start bridge tun") } b.batchTUN = tunInterface.(tun.DarwinTUN) - b.closed = make(chan struct{}) - b.readDone = make(chan struct{}) - b.registerMonitors(b.syncSessionEgress) + b.registerMonitors(scope, b.syncSessionEgress) b.syncSessionEgress() - go b.batchReadLoop() + b.readGroup.Go(b.batchReadLoop) b.logger.Info("bridge started at ", b.tunName, " (platform, egress ", b.egressLabel(), ")") return nil } @@ -186,41 +209,6 @@ func (b *backendDarwin) egressLabel() string { return "auto" } -func (b *backendDarwin) Close() error { - b.closeOnce.Do(func() { - if b.closed != nil { - close(b.closed) - } - if b.unregister != nil { - b.unregister() - } - if b.pfDevice != nil && b.anchorName != "" { - b.egressAccess.Lock() - _ = b.pfDevice.LoadAnchor(b.anchorName, nil) - b.egressAccess.Unlock() - } - restoreDarwinForwarding(b.forwardingRestore) - b.forwardingRestore = nil - if b.pfDevice != nil { - if b.pfToken != 0 { - _ = b.pfDevice.StopReference(b.pfToken) - } - _ = b.pfDevice.Close() - } - if b.tunInterface != nil { - b.tunInterface.Close() - } - if b.readDone != nil { - <-b.readDone - } - if b.session != nil { - _ = b.session.Close() - } - releaseBridgeIndex(b.index) - }) - return nil -} - func (b *backendDarwin) PortMTU() uint32 { return bridgeTunMTUDarwin } @@ -247,7 +235,6 @@ func (b *backendDarwin) WritePackets(packets [][]byte) error { } func (b *backendDarwin) batchReadLoop() { - defer close(b.readDone) headroom := -1 var buffers [][]byte var batch [][]byte diff --git a/protocol/bridge/backend_linux.go b/protocol/bridge/backend_linux.go index 8634ccfd..ce6e1176 100644 --- a/protocol/bridge/backend_linux.go +++ b/protocol/bridge/backend_linux.go @@ -63,24 +63,30 @@ func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager return instance, nil } -func (b *backendLinux) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateStart { - return nil - } - err := b.start() - if err != nil { - b.Close() - return err +func (b *backendLinux) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + scope.Add(func() error { + releaseBridgeIndex(b.index) + return nil + }) + case adapter.StartStateStart: + b.closed = scope.Context().Done() + if b.platform != nil { + return b.startPlatform(scope) + } + return b.start(scope) } return nil } -func (b *backendLinux) start() error { - if b.platform != nil { - return b.startPlatform() - } +func (b *backendLinux) start(scope *adapter.Scope) error { b.tunName = tun.CalculateInterfaceName(b.bridgeName) b.nftTableName = "sing-box-" + b.tunName + scope.Add(func() error { + b.readGroup.Wait() + return nil + }) tunInterface, err := tun.New(tun.Options{ Name: b.tunName, MTU: bridgeTunMTU, @@ -92,6 +98,7 @@ func (b *backendLinux) start() error { if err != nil { return E.Cause(err, "create bridge tun") } + scope.Add(tunInterface.Close) b.tunInterface = tunInterface err = tunInterface.Start() if err != nil { @@ -112,13 +119,38 @@ func (b *backendLinux) start() error { if err != nil { return E.Cause(err, "set up bridge netfilter") } + scope.Add(func() error { + b.egressAccess.Lock() + cleanupBridgeNetfilter(b.nftTableName) + b.egressAccess.Unlock() + return nil + }) if !inet6Active { b.inet6Port = netip.Addr{} } - b.forwardingRestore = enableBridgeForwarding(b.logger, b.tunName, b.inet4Port.IsValid(), b.inet6Port.IsValid()) + forwardingRestore := enableBridgeForwarding(b.logger, b.tunName, b.inet4Port.IsValid(), b.inet6Port.IsValid()) + scope.Add(func() error { + restoreBridgeForwarding(forwardingRestore) + return nil + }) + if b.routeTable != 0 { + scope.Add(func() error { + b.egressAccess.Lock() + flushBridgeRouteTable(b.routeTable) + b.egressAccess.Unlock() + return nil + }) + } if b.boundInterface != "" { b.syncEgress() } + scope.Add(func() error { + b.egressAccess.Lock() + removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET, b.inet4Port) + removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET6, b.inet6Port) + b.egressAccess.Unlock() + return nil + }) err = setupBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET, b.inet4Port) if err != nil { return E.Cause(err, "set up bridge routing") @@ -129,12 +161,10 @@ func (b *backendLinux) start() error { removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET6, b.inet6Port) b.inet6Port = netip.Addr{} } - b.closed = make(chan struct{}) - b.readDone = make(chan struct{}) if b.batchTUN != nil { - go b.batchReadLoop() + b.readGroup.Go(b.batchReadLoop) } else { - go b.readLoop() + b.readGroup.Go(b.readLoop) } egress := "auto" if b.boundInterface != "" { @@ -142,7 +172,10 @@ func (b *backendLinux) start() error { monitor := b.networkManager.NetworkMonitor() if monitor != nil { element := monitor.RegisterCallback(func() { b.syncEgress() }) - b.unregister = func() { monitor.UnregisterCallback(element) } + scope.Add(func() error { + monitor.UnregisterCallback(element) + return nil + }) } else { b.logger.Debug("network monitor unavailable, pinned egress will not track interface changes") } @@ -151,7 +184,10 @@ func (b *backendLinux) start() error { monitor := b.networkManager.InterfaceMonitor() if monitor != nil { element := monitor.RegisterCallback(func(_ *control.Interface, _ int) { b.updateClamp() }) - b.unregister = func() { monitor.UnregisterCallback(element) } + scope.Add(func() error { + monitor.UnregisterCallback(element) + return nil + }) } b.updateClamp() } @@ -163,7 +199,7 @@ func (b *backendLinux) start() error { return nil } -func (b *backendLinux) startPlatform() error { +func (b *backendLinux) startPlatform(scope *adapter.Scope) error { session, err := b.platform.CreateBridge(adapter.BridgeOptions{ BridgeName: b.bridgeName, MTU: bridgeTunMTU, @@ -175,11 +211,16 @@ func (b *backendLinux) startPlatform() error { if err != nil { return E.Cause(err, "create bridge") } + scope.Add(session.Close) b.session = session b.tunName = session.Name() if !session.Inet6Active() { b.inet6Port = netip.Addr{} } + scope.Add(func() error { + b.readGroup.Wait() + return nil + }) tunInterface, err := tun.New(tun.Options{ Name: b.tunName, MTU: bridgeTunMTU, @@ -190,6 +231,7 @@ func (b *backendLinux) startPlatform() error { if err != nil { return E.Cause(err, "create bridge tun") } + scope.Add(tunInterface.Close) b.tunInterface = tunInterface err = tunInterface.Start() if err != nil { @@ -204,17 +246,18 @@ func (b *backendLinux) startPlatform() error { b.writeBuffers[i] = make([]byte, b.writeHeadroom+maxPacketLength) } } - b.closed = make(chan struct{}) - b.readDone = make(chan struct{}) if b.batchTUN != nil { - go b.batchReadLoop() + b.readGroup.Go(b.batchReadLoop) } else { - go b.readLoop() + b.readGroup.Go(b.readLoop) } monitor := b.networkManager.InterfaceMonitor() if monitor != nil { element := monitor.RegisterCallback(func(_ *control.Interface, _ int) { b.syncSessionEgress() }) - b.unregister = func() { monitor.UnregisterCallback(element) } + scope.Add(func() error { + monitor.UnregisterCallback(element) + return nil + }) } b.syncSessionEgress() egress := "auto" @@ -225,41 +268,6 @@ func (b *backendLinux) startPlatform() error { return nil } -func (b *backendLinux) Close() error { - b.closeOnce.Do(func() { - if b.closed != nil { - close(b.closed) - } - if b.unregister != nil { - b.unregister() - } - if b.tunInterface != nil { - b.tunInterface.Close() - } - if b.readDone != nil { - <-b.readDone - } - if b.session != nil { - _ = b.session.Close() - } else { - b.egressAccess.Lock() - if b.tunName != "" { - cleanupBridgeNetfilter(b.nftTableName) - removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET, b.inet4Port) - removeBridgeFamily(b.tunName, b.ruleIndex, b.routeTable, unix.AF_INET6, b.inet6Port) - } - if b.routeTable != 0 { - flushBridgeRouteTable(b.routeTable) - } - b.egressAccess.Unlock() - restoreBridgeForwarding(b.forwardingRestore) - b.forwardingRestore = nil - } - releaseBridgeIndex(b.index) - }) - return nil -} - func (b *backendLinux) PortMTU() uint32 { return 0 } @@ -308,7 +316,6 @@ func (b *backendLinux) WritePackets(packets [][]byte) error { // BatchRead completes any kernel-deferred checksums while splitting GRO frames // (virtio NEEDS_CSUM), so unlike readLoop no checksum fix is needed here. func (b *backendLinux) batchReadLoop() { - defer close(b.readDone) batchSize := b.batchTUN.BatchSize() sizes := make([]int, batchSize) batch := make([][]byte, 0, batchSize) diff --git a/protocol/bridge/backend_windows.go b/protocol/bridge/backend_windows.go index ab66fcec..af877ce1 100644 --- a/protocol/bridge/backend_windows.go +++ b/protocol/bridge/backend_windows.go @@ -140,20 +140,21 @@ func newBackend(ctx context.Context, logger logger.ContextLogger, networkManager return instance, nil } -func (b *backendWindows) Start(stage adapter.StartStage) error { - if stage != adapter.StartStateStart { - return nil - } - err := b.start() - if err != nil { - b.Close() - return err +func (b *backendWindows) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + scope.Add(func() error { + releaseBridgeIndex(b.index) + return nil + }) + case adapter.StartStateStart: + return b.start(scope) } return nil } -func (b *backendWindows) start() error { - b.closed = make(chan struct{}) +func (b *backendWindows) start(scope *adapter.Scope) error { + b.closed = scope.Context().Done() state := b.currentEgressState() b.egress.Store(state) @@ -162,11 +163,16 @@ func (b *backendWindows) start() error { if err != nil { return err } + scope.Add(func() error { + b.reservation.Close() + return nil + }) injectHandle, err := windivert.Open(nil, windivert.LayerNetwork, windivert.PriorityHighest, windivert.FlagSendOnly) if err != nil { return E.Cause(err, "bridge: open injection handle (Administrator required)") } + scope.Add(injectHandle.Close) b.injectHandle = injectHandle b.sendBuffer = make([]byte, 0, bridgeBatchBufferSize) b.sendAddrs = make([]windivert.Address, 0, windivert.BatchMax) @@ -177,8 +183,14 @@ func (b *backendWindows) start() error { if err != nil { return err } + scope.Add(func() error { + b.egressAccess.Lock() + b.closeDivertersLocked() + b.egressAccess.Unlock() + return nil + }) - b.registerMonitors(b.syncEgress) + b.registerMonitors(scope, b.syncEgress) b.syncEgress() state = b.egress.Load() if !state.inet4.IsValid() && !state.inet6.IsValid() { @@ -799,27 +811,6 @@ func sortSegments(segments []localSegment) { }) } -func (b *backendWindows) Close() error { - b.closeOnce.Do(func() { - if b.closed != nil { - close(b.closed) - } - if b.unregister != nil { - b.unregister() - } - b.egressAccess.Lock() - b.closeDivertersLocked() - b.egressAccess.Unlock() - if b.injectHandle != nil { - b.injectHandle.Close() - } - b.reservation.Close() - b.reservation = nil - releaseBridgeIndex(b.index) - }) - return nil -} - type transportInfo struct { protocol tcpip.TransportProtocolNumber transport []byte diff --git a/protocol/bridge/outbound.go b/protocol/bridge/outbound.go index cd952841..e4db72d4 100644 --- a/protocol/bridge/outbound.go +++ b/protocol/bridge/outbound.go @@ -57,12 +57,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL }, nil } -func (o *Outbound) Start(stage adapter.StartStage) error { - return o.backend.Start(stage) -} - -func (o *Outbound) Close() error { - return o.backend.Close() +func (o *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + return o.backend.Start(stage, scope) } func (o *Outbound) PreferredDomain(metadata *adapter.InboundContext, domain string) bool { diff --git a/protocol/cloudflare/inbound.go b/protocol/cloudflare/inbound.go index d69dac0b..8d3d848e 100644 --- a/protocol/cloudflare/inbound.go +++ b/protocol/cloudflare/inbound.go @@ -92,15 +92,16 @@ type Inbound struct { references []string } -func (i *Inbound) Start(stage adapter.StartStage) error { +func (i *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return i.service.Start() -} - -func (i *Inbound) Close() error { - return i.service.Close() + err := i.service.Start() + if err != nil { + return err + } + scope.Add(i.service.Close) + return nil } type routerDialer struct { diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go index 34ebbb15..603ba688 100644 --- a/protocol/direct/inbound.go +++ b/protocol/direct/inbound.go @@ -70,21 +70,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (i *Inbound) Start(stage adapter.StartStage) error { +func (i *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return i.listener.Start() + err := i.listener.Start() + if err != nil { + return err + } + scope.Add(i.listener.Close) + return nil } func (i *Inbound) InterfaceUpdated(ctx context.Context) { i.udpNat.Purge() } -func (i *Inbound) Close() error { - return i.listener.Close() -} - func (i *Inbound) NewPacket(buffer *buf.Buffer, source M.Socksaddr) { i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, i.listener.UDPAddr(), nil) } diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 020b3d62..af8826b8 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -88,8 +88,12 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL return outbound, nil } -func (h *Outbound) Start(stage adapter.StartStage) error { +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { + case adapter.StartStateInitialize: + if h.icmpPort != nil { + scope.Add(h.icmpPort.Close) + } case adapter.StartStatePostStart, adapter.StartStateStarted: if len(h.myAddresses.Load()) == 0 { h.fetchMyAddresses() @@ -205,13 +209,6 @@ func (h *Outbound) WritePackets(packets [][]byte) error { return h.icmpPort.WritePackets(packets) } -func (h *Outbound) Close() error { - if h.icmpPort != nil { - return h.icmpPort.Close() - } - return nil -} - func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { if h.isMyLoopbackAddress(destinationAddresses...) { return nil, E.New("loopback connection to TUN range") diff --git a/protocol/group/selector.go b/protocol/group/selector.go index d23ffeb2..c3aaec5a 100644 --- a/protocol/group/selector.go +++ b/protocol/group/selector.go @@ -73,7 +73,10 @@ func (s *Selector) Network() []string { return selected.Network() } -func (s *Selector) Start() error { +func (s *Selector) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateStart { + return nil + } for i, tag := range s.tags { detour, loaded := s.outbound.Outbound(tag) if !loaded { diff --git a/protocol/group/urltest.go b/protocol/group/urltest.go index 01964896..bc50ab70 100644 --- a/protocol/group/urltest.go +++ b/protocol/group/urltest.go @@ -71,7 +71,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo return outbound, nil } -func (s *URLTest) Start(stage adapter.StartStage) error { +func (s *URLTest) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateStart: outbounds := make([]adapter.Outbound, 0, len(s.tags)) @@ -89,16 +89,11 @@ func (s *URLTest) Start(stage adapter.StartStage) error { s.group = group case adapter.StartStateStarted: s.group.PostStart() + scope.Add(s.group.Close) } return nil } -func (s *URLTest) Close() error { - return common.Close( - common.PtrOrNil(s.group), - ) -} - func (s *URLTest) Now() string { if s.group.selectedOutboundTCP != nil { return s.group.selectedOutboundTCP.Tag() diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go index fe573ea1..6e0c3ebd 100644 --- a/protocol/http/inbound.go +++ b/protocol/http/inbound.go @@ -66,7 +66,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -75,15 +75,14 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(h.tlsConfig.Close) } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return common.Close( - h.listener, - h.tlsConfig, - ) + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/hysteria/inbound.go b/protocol/hysteria/inbound.go index a0f86c43..3a186025 100644 --- a/protocol/hysteria/inbound.go +++ b/protocol/hysteria/inbound.go @@ -147,7 +147,7 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -156,18 +156,17 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.tlsConfig.Close) } packetConn, err := h.listener.ListenUDP() if err != nil { return err } - return h.service.Start(packetConn) -} - -func (h *Inbound) Close() error { - return common.Close( - h.listener, - h.tlsConfig, - common.PtrOrNil(h.service), - ) + scope.Add(h.listener.Close) + err = h.service.Start(packetConn) + if err != nil { + return err + } + scope.Add(h.service.Close) + return nil } diff --git a/protocol/hysteria/outbound.go b/protocol/hysteria/outbound.go index a5b7b7c2..9e1244f4 100644 --- a/protocol/hysteria/outbound.go +++ b/protocol/hysteria/outbound.go @@ -131,6 +131,12 @@ func (h *Outbound) CloseIdleConnections() { h.client.CloseIdleConnections() } -func (h *Outbound) Close() error { - return h.client.CloseWithError(os.ErrClosed) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + scope.Add(func() error { + return h.client.CloseWithError(os.ErrClosed) + }) + return nil } diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index f86e2402..8fd73329 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -268,7 +268,7 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -277,22 +277,21 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.tlsConfig.Close) } packetConn, err := h.listener.ListenUDP() if err != nil { return err } - return h.service.Start(packetConn) + scope.Add(h.listener.Close) + err = h.service.Start(packetConn) + if err != nil { + return err + } + scope.Add(h.service.Close) + return nil } func (h *Inbound) InterfaceUpdated(ctx context.Context) { h.service.Reset() } - -func (h *Inbound) Close() error { - return common.Close( - h.listener, - h.tlsConfig, - common.PtrOrNil(h.service), - ) -} diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 398ee66b..14e761fb 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -223,6 +223,12 @@ func (h *Outbound) CloseIdleConnections() { h.client.CloseIdleConnections() } -func (h *Outbound) Close() error { - return h.client.CloseWithError(os.ErrClosed) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + scope.Add(func() error { + return h.client.CloseWithError(os.ErrClosed) + }) + return nil } diff --git a/protocol/hysteria2/realm.go b/protocol/hysteria2/realm.go index de5bdd9f..a5b70330 100644 --- a/protocol/hysteria2/realm.go +++ b/protocol/hysteria2/realm.go @@ -122,7 +122,14 @@ func NewRealmService(ctx context.Context, logger log.ContextLogger, tag string, return s, nil } -func (s *RealmService) Start(stage adapter.StartStage) error { +func (s *RealmService) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage == adapter.StartStateInitialize { + scope.Add(func() error { + s.cancel() + return nil + }) + return nil + } if stage != adapter.StartStateStart { return nil } @@ -131,11 +138,17 @@ func (s *RealmService) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(s.tlsConfig.Close) } tcpListener, err := s.listener.ListenTCP() if err != nil { return err } + scope.Add(s.listener.Close) + scope.Add(func() error { + s.server.closeAll() + return nil + }) if s.tlsConfig != nil { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) @@ -148,15 +161,6 @@ func (s *RealmService) Start(stage adapter.StartStage) error { s.logger.Error("serve error: ", err) } }() + scope.Add(s.httpServer.Close) return nil } - -func (s *RealmService) Close() error { - s.cancel() - err := common.Close(common.PtrOrNil(s.httpServer)) - s.server.closeAll() - return E.Errors(err, common.Close( - common.PtrOrNil(s.listener), - s.tlsConfig, - )) -} diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go index c6cd08cb..22c25f30 100644 --- a/protocol/mixed/inbound.go +++ b/protocol/mixed/inbound.go @@ -79,7 +79,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -88,15 +88,14 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(h.tlsConfig.Close) } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return common.Close( - h.listener, - h.tlsConfig, - ) + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go index dfa022b1..d19cc642 100644 --- a/protocol/naive/inbound.go +++ b/protocol/naive/inbound.go @@ -52,7 +52,6 @@ type Inbound struct { authenticator *auth.Authenticator tlsConfig tls.ServerConfig httpServer *http.Server - h3Server io.Closer } func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (adapter.Inbound, error) { @@ -89,7 +88,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (n *Inbound) Start(stage adapter.StartStage) error { +func (n *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -98,7 +97,9 @@ func (n *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(n.tlsConfig.Close) } + scope.Add(n.listener.Close) if common.Contains(n.network, N.NetworkTCP) { tcpListener, err := n.listener.ListenTCP() if err != nil { @@ -126,12 +127,13 @@ func (n *Inbound) Start(stage adapter.StartStage) error { n.logger.Error("http server serve error: ", sErr) } }() + scope.Add(n.httpServer.Close) } if common.Contains(n.network, N.NetworkUDP) { http3Server, err := ConfigureHTTP3ListenerFunc(n.ctx, n.logger, n.listener, n, n.tlsConfig, n.options) if err == nil { - n.h3Server = http3Server + scope.Add(http3Server.Close) } else if len(n.network) > 1 { n.logger.Warn(E.Cause(err, "naive http3 disabled")) } else { @@ -142,15 +144,6 @@ func (n *Inbound) Start(stage adapter.StartStage) error { return nil } -func (n *Inbound) Close() error { - return common.Close( - n.listener, - common.PtrOrNil(n.httpServer), - n.h3Server, - n.tlsConfig, - ) -} - func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) { ctx := log.ContextWithNewID(request.Context()) if request.Method != "CONNECT" { diff --git a/protocol/naive/outbound.go b/protocol/naive/outbound.go index aa2bb09b..c892aa7a 100644 --- a/protocol/naive/outbound.go +++ b/protocol/naive/outbound.go @@ -221,7 +221,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL }, nil } -func (h *Outbound) Start(stage adapter.StartStage) error { +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -229,6 +229,7 @@ func (h *Outbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.client.Close) h.logger.Info("NaiveProxy started, version: ", h.client.Engine().Version()) return nil } @@ -260,10 +261,6 @@ func (h *Outbound) InterfaceUpdated(ctx context.Context) { h.client.CloseAllConnections() } -func (h *Outbound) Close() error { - return h.client.Close() -} - func (h *Outbound) Client() *cronet.NaiveClient { return h.client } diff --git a/protocol/openconnect/client.go b/protocol/openconnect/client.go index 16216361..2bb9a3d7 100644 --- a/protocol/openconnect/client.go +++ b/protocol/openconnect/client.go @@ -43,25 +43,22 @@ var ( type Endpoint struct { endpointBase - loopContext context.Context - cancelLoop context.CancelFunc - dnsRouter adapter.DNSRouter - client *openconnect.Client - device openconnecttransport.Device - server string - flavor string - stateAccess sync.Mutex - state atomic.Pointer[clientState] - dnsTransportAccess sync.Mutex - dnsTransport *DNSTransport - deviceStarted bool - readLoopDone chan struct{} - statusAccess sync.Mutex - statusUpdated chan struct{} - terminalError string - authFormLoopDone chan struct{} - activeTransportLoopDone chan struct{} - hotpCounter atomic.Uint64 + loopContext context.Context + cancelLoop context.CancelFunc + dnsRouter adapter.DNSRouter + client *openconnect.Client + device openconnecttransport.Device + server string + flavor string + stateAccess sync.Mutex + state atomic.Pointer[clientState] + dnsTransportAccess sync.Mutex + dnsTransport *DNSTransport + deviceStarted bool + statusAccess sync.Mutex + statusUpdated chan struct{} + terminalError string + hotpCounter atomic.Uint64 } type clientState struct { @@ -418,34 +415,60 @@ func (e *Endpoint) updateState(update func(state *clientState)) { e.state.Store(&newState) } -func (e *Endpoint) Start(stage adapter.StartStage) error { - if stage != adapter.StartStatePostStart { - return nil +func (e *Endpoint) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + scope.Add(func() error { + e.notifyStatusUpdated() + return nil + }) + scope.Add(e.device.Close) + scope.Add(func() error { + e.cancelLoop() + return nil + }) + case adapter.StartStatePostStart: + var loopGroup sync.WaitGroup + scope.Add(func() error { + loopGroup.Wait() + return nil + }) + err := e.client.Start() + if err != nil { + return err + } + scope.Add(e.client.Close) + e.stateAccess.Lock() + e.updateState(func(state *clientState) { + state.started = true + }) + e.stateAccess.Unlock() + scope.Add(func() error { + e.stateAccess.Lock() + e.updateState(func(state *clientState) { + state.started = false + }) + e.stateAccess.Unlock() + return nil + }) + loopGroup.Go(func() { + e.readLoop(scope.Context()) + }) + loopGroup.Go(func() { + e.watchAuthForms(scope.Context()) + }) + loopGroup.Go(func() { + e.watchActiveTransport(scope.Context()) + }) } - err := e.client.Start() - if err != nil { - return err - } - e.stateAccess.Lock() - e.updateState(func(state *clientState) { - state.started = true - }) - e.readLoopDone = make(chan struct{}) - e.authFormLoopDone = make(chan struct{}) - e.activeTransportLoopDone = make(chan struct{}) - e.stateAccess.Unlock() - go e.readLoop() - go e.watchAuthForms() - go e.watchActiveTransport() return nil } -func (e *Endpoint) readLoop() { - defer close(e.readLoopDone) +func (e *Endpoint) readLoop(ctx context.Context) { for { - packetBuffers, err := e.client.ReadDataPackets(e.loopContext) + packetBuffers, err := e.client.ReadDataPackets(ctx) if err != nil { - if E.IsClosedOrCanceled(err) || e.loopContext.Err() != nil { + if E.IsClosedOrCanceled(err) || ctx.Err() != nil { return } e.logger.Error(E.Cause(err, "client terminated")) @@ -463,30 +486,6 @@ func (e *Endpoint) readLoop() { } } -func (e *Endpoint) Close() error { - e.stateAccess.Lock() - e.updateState(func(state *clientState) { - state.started = false - }) - readLoopDone := e.readLoopDone - authFormLoopDone := e.authFormLoopDone - activeTransportLoopDone := e.activeTransportLoopDone - e.stateAccess.Unlock() - e.cancelLoop() - err := E.Errors(e.client.Close(), e.device.Close()) - if readLoopDone != nil { - <-readLoopDone - } - if authFormLoopDone != nil { - <-authFormLoopDone - } - if activeTransportLoopDone != nil { - <-activeTransportLoopDone - } - e.notifyStatusUpdated() - return err -} - func (e *Endpoint) InterfaceUpdated(ctx context.Context) { e.client.RestartSession() } diff --git a/protocol/openconnect/dns_transport.go b/protocol/openconnect/dns_transport.go index eb97ff0b..ae70ee23 100644 --- a/protocol/openconnect/dns_transport.go +++ b/protocol/openconnect/dns_transport.go @@ -30,18 +30,19 @@ func RegisterDNSTransport(registry *dns.TransportRegistry) { type DNSTransport struct { dns.TransportAdapter + ctx context.Context logger logger.ContextLogger endpointTag string acceptDefaultResolvers bool acceptSearchDomain bool endpointManager adapter.EndpointManager - endpoint *Endpoint dialer N.Dialer access sync.RWMutex closed bool routes []openConnectDNSRoute searchDomains []string defaultResolvers []adapter.DNSTransport + resolverScope *adapter.Scope } type openConnectDNSRoute struct { @@ -55,6 +56,7 @@ func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, } return &DNSTransport{ TransportAdapter: dns.NewTransportAdapter(C.DNSTypeOpenConnect, tag, nil), + ctx: ctx, logger: logger, endpointTag: options.Endpoint, acceptDefaultResolvers: options.AcceptDefaultResolvers, @@ -63,7 +65,7 @@ func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, }, nil } -func (t *DNSTransport) Start(stage adapter.StartStage) error { +func (t *DNSTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateInitialize { return nil } @@ -75,13 +77,34 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error { if !isOpenConnect { return E.New("endpoint is not OpenConnect: ", t.endpointTag) } + scope.Add(func() error { + t.access.Lock() + resolverScope := t.resolverScope + t.closed = true + t.routes = nil + t.searchDomains = nil + t.defaultResolvers = nil + t.resolverScope = nil + t.access.Unlock() + if resolverScope != nil { + return resolverScope.Close() + } + return nil + }) openConnectEndpoint.dnsTransportAccess.Lock() if openConnectEndpoint.dnsTransport != nil && openConnectEndpoint.dnsTransport.Tag() != t.Tag() { openConnectEndpoint.dnsTransportAccess.Unlock() return E.New("only one DNS server is allowed for an endpoint") } openConnectEndpoint.dnsTransport = t - t.endpoint = openConnectEndpoint + scope.Add(func() error { + openConnectEndpoint.dnsTransportAccess.Lock() + if openConnectEndpoint.dnsTransport == t { + openConnectEndpoint.dnsTransport = nil + } + openConnectEndpoint.dnsTransportAccess.Unlock() + return nil + }) t.dialer = openConnectEndpoint state := openConnectEndpoint.state.Load() if state.started && state.tunnelConfigured && openConnectEndpoint.client.Ready() { @@ -176,32 +199,29 @@ func (t *DNSTransport) updateConfiguration(configuration openconnecttransport.Co defaultResolvers = nil } + resolverScope := adapter.NewScope(t.ctx, t.logger) + for _, resolver := range resolverByAddress { + err := resolver.Start(adapter.StartStateStart, resolverScope) + if err != nil { + resolverScope.Close() + t.logger.Error(E.Cause(err, "start DNS resolver")) + return + } + } t.access.Lock() if t.closed { t.access.Unlock() - for _, resolver := range resolverByAddress { - _ = resolver.Close() - } + resolverScope.Close() return } - oldResolvers := t.collectResolversLocked() + oldResolverScope := t.resolverScope t.routes = routes t.searchDomains = searchDomains t.defaultResolvers = defaultResolvers - activeResolvers := t.collectResolversLocked() + t.resolverScope = resolverScope t.access.Unlock() - - for _, resolver := range oldResolvers { - _ = resolver.Close() - } - activeResolverSet := make(map[adapter.DNSTransport]bool, len(activeResolvers)) - for _, resolver := range activeResolvers { - activeResolverSet[resolver] = true - } - for _, resolver := range resolverByAddress { - if !activeResolverSet[resolver] { - _ = resolver.Close() - } + if oldResolverScope != nil { + oldResolverScope.Close() } if len(resolverByAddress) > 0 { t.logger.Info("updated ", len(routes), " DNS routes and ", len(resolverByAddress), " resolvers") @@ -219,28 +239,6 @@ func (t *DNSTransport) Reset() { } } -func (t *DNSTransport) Close() error { - if t.endpoint != nil { - t.endpoint.dnsTransportAccess.Lock() - if t.endpoint.dnsTransport == t { - t.endpoint.dnsTransport = nil - } - t.endpoint.dnsTransportAccess.Unlock() - } - t.access.Lock() - resolvers := t.collectResolversLocked() - t.closed = true - t.routes = nil - t.searchDomains = nil - t.defaultResolvers = nil - t.access.Unlock() - var closeErr error - for _, resolver := range resolvers { - closeErr = E.Errors(closeErr, resolver.Close()) - } - return closeErr -} - func (t *DNSTransport) PreferredDomain(domain string) bool { canonicalDomain := mDNS.Fqdn(canonicalOpenConnectDomain(domain)) t.access.RLock() diff --git a/protocol/openconnect/status.go b/protocol/openconnect/status.go index b7be1dc5..7eede3a3 100644 --- a/protocol/openconnect/status.go +++ b/protocol/openconnect/status.go @@ -1,6 +1,7 @@ package openconnect import ( + "context" "net/http" "slices" @@ -132,8 +133,7 @@ func (e *Endpoint) setTerminalError(err error) { e.statusAccess.Unlock() } -func (e *Endpoint) watchAuthForms() { - defer close(e.authFormLoopDone) +func (e *Endpoint) watchAuthForms(ctx context.Context) { var loggedAuthChallengeID string for { authChallengeUpdated := e.client.AuthChallengeUpdated() @@ -148,15 +148,14 @@ func (e *Endpoint) watchAuthForms() { } e.notifyStatusUpdated() select { - case <-e.loopContext.Done(): + case <-ctx.Done(): return case <-authChallengeUpdated: } } } -func (e *Endpoint) watchActiveTransport() { - defer close(e.activeTransportLoopDone) +func (e *Endpoint) watchActiveTransport(ctx context.Context) { for { transportUpdated := e.client.ActiveTransportUpdated() transport := e.client.ActiveTransport() @@ -167,7 +166,7 @@ func (e *Endpoint) watchActiveTransport() { e.stateAccess.Unlock() e.notifyStatusUpdated() select { - case <-e.loopContext.Done(): + case <-ctx.Done(): return case <-transportUpdated: } diff --git a/protocol/openvpn/client.go b/protocol/openvpn/client.go index 03f8e07a..a0e585e0 100644 --- a/protocol/openvpn/client.go +++ b/protocol/openvpn/client.go @@ -42,23 +42,21 @@ var ( type ClientEndpoint struct { endpointBase - ctx context.Context - loopContext context.Context - cancelLoop context.CancelFunc - dnsRouter adapter.DNSRouter - outboundDialer N.Dialer - queryOptions adapter.DNSQueryOptions - client *ovpn.Client - device ovpntransport.Device - stateAccess sync.Mutex - state atomic.Pointer[clientState] - dnsTransport *DNSTransport - deviceStarted bool - readLoopDone chan struct{} - statusAccess sync.Mutex - statusUpdated chan struct{} - terminalError string - challengeLoopDone chan struct{} + ctx context.Context + loopContext context.Context + cancelLoop context.CancelFunc + dnsRouter adapter.DNSRouter + outboundDialer N.Dialer + queryOptions adapter.DNSQueryOptions + client *ovpn.Client + device ovpntransport.Device + stateAccess sync.Mutex + state atomic.Pointer[clientState] + dnsTransport *DNSTransport + deviceStarted bool + statusAccess sync.Mutex + statusUpdated chan struct{} + terminalError string } type clientState struct { @@ -596,32 +594,57 @@ func (c *ClientEndpoint) uninstallDNSTransport(dnsTransport *DNSTransport) { c.stateAccess.Unlock() } -func (c *ClientEndpoint) Start(stage adapter.StartStage) error { - if stage != adapter.StartStatePostStart { - return nil +func (c *ClientEndpoint) Start(stage adapter.StartStage, scope *adapter.Scope) error { + switch stage { + case adapter.StartStateInitialize: + scope.Add(func() error { + c.notifyStatusUpdated() + return nil + }) + scope.Add(c.device.Close) + scope.Add(func() error { + c.cancelLoop() + return nil + }) + case adapter.StartStatePostStart: + var loopGroup sync.WaitGroup + scope.Add(func() error { + loopGroup.Wait() + return nil + }) + err := c.client.Start() + if err != nil { + return err + } + scope.Add(c.client.Close) + c.stateAccess.Lock() + c.updateState(func(state *clientState) { + state.started = true + }) + c.stateAccess.Unlock() + scope.Add(func() error { + c.stateAccess.Lock() + c.updateState(func(state *clientState) { + state.started = false + }) + c.stateAccess.Unlock() + return nil + }) + loopGroup.Go(func() { + c.readLoop(scope.Context()) + }) + loopGroup.Go(func() { + c.watchChallenges(scope.Context()) + }) } - err := c.client.Start() - if err != nil { - return err - } - c.stateAccess.Lock() - c.updateState(func(state *clientState) { - state.started = true - }) - c.readLoopDone = make(chan struct{}) - c.challengeLoopDone = make(chan struct{}) - c.stateAccess.Unlock() - go c.readLoop() - go c.watchChallenges() return nil } -func (c *ClientEndpoint) readLoop() { - defer close(c.readLoopDone) +func (c *ClientEndpoint) readLoop(ctx context.Context) { for { - packetBuffers, err := c.client.ReadDataPackets(c.loopContext) + packetBuffers, err := c.client.ReadDataPackets(ctx) if err != nil { - if E.IsClosedOrCanceled(err) || c.loopContext.Err() != nil { + if E.IsClosedOrCanceled(err) || ctx.Err() != nil { return } c.logger.Error(E.Cause(err, "client terminated")) @@ -639,26 +662,6 @@ func (c *ClientEndpoint) readLoop() { } } -func (c *ClientEndpoint) Close() error { - c.stateAccess.Lock() - c.updateState(func(state *clientState) { - state.started = false - }) - readLoopDone := c.readLoopDone - challengeLoopDone := c.challengeLoopDone - c.stateAccess.Unlock() - c.cancelLoop() - err := E.Errors(c.client.Close(), c.device.Close()) - if readLoopDone != nil { - <-readLoopDone - } - if challengeLoopDone != nil { - <-challengeLoopDone - } - c.notifyStatusUpdated() - return err -} - func (c *ClientEndpoint) InterfaceUpdated(ctx context.Context) { c.client.RestartSession() } diff --git a/protocol/openvpn/dns_transport.go b/protocol/openvpn/dns_transport.go index 188da2af..41461979 100644 --- a/protocol/openvpn/dns_transport.go +++ b/protocol/openvpn/dns_transport.go @@ -43,7 +43,6 @@ type DNSTransport struct { acceptDefaultResolvers bool acceptSearchDomain bool endpointManager adapter.EndpointManager - endpoint *ClientEndpoint dialer N.Dialer updateAccess sync.Mutex access sync.RWMutex @@ -51,6 +50,7 @@ type DNSTransport struct { routes map[string][]adapter.DNSTransport searchDomains []string defaultResolvers []adapter.DNSTransport + resolverScope *adapter.Scope } func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.OpenVPNDNSServerOptions) (adapter.DNSTransport, error) { @@ -68,7 +68,7 @@ func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, }, nil } -func (t *DNSTransport) Start(stage adapter.StartStage) error { +func (t *DNSTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateInitialize { return nil } @@ -80,14 +80,31 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error { if !isOpenVPN { return E.New("endpoint is not an OpenVPN client: ", t.endpointTag) } - t.endpoint = endpoint t.dialer = endpoint + scope.Add(func() error { + t.updateAccess.Lock() + t.access.Lock() + resolverScope := t.resolverScope + t.closed = true + t.routes = nil + t.searchDomains = nil + t.defaultResolvers = nil + t.resolverScope = nil + t.access.Unlock() + t.updateAccess.Unlock() + if resolverScope != nil { + return resolverScope.Close() + } + return nil + }) err := endpoint.installDNSTransport(t) if err != nil { - t.endpoint = nil - t.dialer = nil return err } + scope.Add(func() error { + endpoint.uninstallDNSTransport(t) + return nil + }) return nil } @@ -110,7 +127,7 @@ func (t *DNSTransport) updateResolvers(configuration ovpntransport.Configuration routes := make(map[string][]adapter.DNSTransport) searchDomains := normalizeOpenVPNDomains(configuration.SearchDomains) var defaultResolvers []adapter.DNSTransport - var newResolvers []adapter.DNSTransport + resolverScope := adapter.NewScope(t.ctx, t.logger) servers := slices.Clone(configuration.DNSServers) slices.SortFunc(servers, func(left ovpntransport.DNSServer, right ovpntransport.DNSServer) int { return left.Priority - right.Priority @@ -119,18 +136,21 @@ func (t *DNSTransport) updateResolvers(configuration ovpntransport.Configuration if len(servers) > 0 { server := servers[0] if server.DNSSEC == "yes" { - return t.failResolverUpdate(newResolvers, E.New("DNSSEC validation is required but is not supported")) + return t.failResolverUpdate(resolverScope, E.New("DNSSEC validation is required but is not supported")) } for _, address := range server.Addresses { resolver, err := t.createResolver(server, address) if err != nil { - return t.failResolverUpdate(newResolvers, err) + return t.failResolverUpdate(resolverScope, err) + } + err = resolver.Start(adapter.StartStateStart, resolverScope) + if err != nil { + return t.failResolverUpdate(resolverScope, err) } selectedResolvers = append(selectedResolvers, resolver) - newResolvers = append(newResolvers, resolver) } if len(selectedResolvers) == 0 { - return t.failResolverUpdate(newResolvers, E.New("DNS server ", server.Priority, " has no addresses")) + return t.failResolverUpdate(resolverScope, E.New("DNS server ", server.Priority, " has no addresses")) } if len(server.ResolveDomains) == 0 { defaultResolvers = slices.Clone(selectedResolvers) @@ -145,12 +165,15 @@ func (t *DNSTransport) updateResolvers(configuration ovpntransport.Configuration } else { for _, address := range configuration.DNS { resolver := dnsTransport.NewUDPRaw(t.logger, t.TransportAdapter, t.dialer, M.SocksaddrFrom(address, 53)) + err := resolver.Start(adapter.StartStateStart, resolverScope) + if err != nil { + return t.failResolverUpdate(resolverScope, err) + } selectedResolvers = append(selectedResolvers, resolver) - newResolvers = append(newResolvers, resolver) } if len(configuration.DNSRoutes) > 0 { if len(selectedResolvers) == 0 { - return t.failResolverUpdate(newResolvers, E.New("DOMAIN-ROUTE requires traditional pushed DNS servers")) + return t.failResolverUpdate(resolverScope, E.New("DOMAIN-ROUTE requires traditional pushed DNS servers")) } for _, domain := range configuration.DNSRoutes { normalizedDomain := normalizeOpenVPNDomain(domain) @@ -163,32 +186,40 @@ func (t *DNSTransport) updateResolvers(configuration ovpntransport.Configuration } } if len(searchDomains) > 0 && len(selectedResolvers) == 0 { - return t.failResolverUpdate(newResolvers, E.New("search domains require pushed DNS servers")) + return t.failResolverUpdate(resolverScope, E.New("search domains require pushed DNS servers")) } for _, searchDomain := range searchDomains { routes[searchDomain] = slices.Clone(selectedResolvers) } t.access.Lock() - oldResolvers := t.collectResolversLocked() + oldResolverScope := t.resolverScope t.routes = routes t.searchDomains = searchDomains t.defaultResolvers = defaultResolvers + t.resolverScope = resolverScope t.access.Unlock() - closeErr := closeDNSTransports(oldResolvers) + var closeErr error + if oldResolverScope != nil { + closeErr = oldResolverScope.Close() + } t.logger.Info("updated ", len(routes), " DNS routes, ", len(searchDomains), " search domains and ", len(defaultResolvers), " default resolvers") return closeErr } -func (t *DNSTransport) failResolverUpdate(newResolvers []adapter.DNSTransport, updateErr error) error { - newCloseErr := closeDNSTransports(newResolvers) +func (t *DNSTransport) failResolverUpdate(resolverScope *adapter.Scope, updateErr error) error { + newCloseErr := resolverScope.Close() t.access.Lock() - oldResolvers := t.collectResolversLocked() + oldResolverScope := t.resolverScope t.routes = nil t.searchDomains = nil t.defaultResolvers = nil + t.resolverScope = nil t.access.Unlock() - oldCloseErr := closeDNSTransports(oldResolvers) + var oldCloseErr error + if oldResolverScope != nil { + oldCloseErr = oldResolverScope.Close() + } return E.Errors(updateErr, newCloseErr, oldCloseErr) } @@ -255,24 +286,6 @@ func (t *DNSTransport) Reset() { } } -func (t *DNSTransport) Close() error { - if t.endpoint != nil { - t.endpoint.uninstallDNSTransport(t) - } - t.updateAccess.Lock() - t.access.Lock() - resolvers := t.collectResolversLocked() - t.closed = true - t.routes = nil - t.searchDomains = nil - t.defaultResolvers = nil - t.access.Unlock() - t.endpoint = nil - t.dialer = nil - t.updateAccess.Unlock() - return closeDNSTransports(resolvers) -} - func (t *DNSTransport) Raw() bool { return true } @@ -384,16 +397,6 @@ func (t *DNSTransport) collectResolversLocked() []adapter.DNSTransport { return common.Uniq(resolvers) } -func closeDNSTransports(resolvers []adapter.DNSTransport) error { - var err error - for _, resolver := range common.Uniq(resolvers) { - err = E.Append(err, resolver.Close(), func(closeErr error) error { - return E.Cause(closeErr, "close DNS resolver") - }) - } - return err -} - func normalizeOpenVPNDomain(domain string) string { normalized := strings.TrimSpace(strings.ToLower(domain)) if normalized == "." { diff --git a/protocol/openvpn/server.go b/protocol/openvpn/server.go index cd8ccc66..269b7a9e 100644 --- a/protocol/openvpn/server.go +++ b/protocol/openvpn/server.go @@ -6,6 +6,7 @@ import ( "net/netip" "slices" "strconv" + "sync" "sync/atomic" "time" @@ -35,7 +36,6 @@ var ( type ServerEndpoint struct { endpointBase ctx context.Context - loopContext context.Context cancelLoop context.CancelFunc options option.OpenVPNServerEndpointOptions serverOptions ovpn.ServerOptions @@ -45,7 +45,6 @@ type ServerEndpoint struct { device ovpntransport.Device localAddresses []netip.Prefix started atomic.Bool - readLoopDone chan struct{} } type udpEgressPacketConn struct { @@ -80,7 +79,6 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co logger: logger, }, ctx: ctx, - loopContext: loopContext, cancelLoop: cancelLoop, options: options, dnsRouter: service.FromContext[adapter.DNSRouter](ctx), @@ -161,7 +159,15 @@ func validateServerTopology(topology string) error { } } -func (s *ServerEndpoint) Start(stage adapter.StartStage) error { +func (s *ServerEndpoint) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage == adapter.StartStateInitialize { + scope.Add(s.device.Close) + scope.Add(func() error { + s.cancelLoop() + return nil + }) + return nil + } if stage != adapter.StartStateStart { return nil } @@ -225,6 +231,7 @@ func (s *ServerEndpoint) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(s.listener.Close) serverOptions := s.serverOptions if streamListener != nil { serverOptions.Transport.ListenAddress = streamListener.Addr().String() @@ -238,26 +245,31 @@ func (s *ServerEndpoint) Start(stage adapter.StartStage) error { if packetConn != nil { _ = packetConn.Close() } - s.listener.Close() return err } s.server = server + var loopGroup sync.WaitGroup + scope.Add(func() error { + loopGroup.Wait() + return nil + }) + scope.Add(server.Close) err = s.device.Start() if err != nil { - s.listener.Close() - server.Close() return err } err = server.Start() if err != nil { - s.device.Close() - s.listener.Close() - server.Close() return err } s.started.Store(true) - s.readLoopDone = make(chan struct{}) - go s.readLoop() + scope.Add(func() error { + s.started.Store(false) + return nil + }) + loopGroup.Go(func() { + s.readLoop(scope.Context()) + }) return nil } @@ -598,12 +610,11 @@ func applyServerPushOptions(serverOptions *ovpn.ServerOptions, options option.Op return nil } -func (s *ServerEndpoint) readLoop() { - defer close(s.readLoopDone) +func (s *ServerEndpoint) readLoop(ctx context.Context) { for { - serverPacketBuffers, err := s.server.ReadDataPackets(s.loopContext) + serverPacketBuffers, err := s.server.ReadDataPackets(ctx) if err != nil { - if E.IsClosedOrCanceled(err) || s.loopContext.Err() != nil { + if E.IsClosedOrCanceled(err) || ctx.Err() != nil { return } s.logger.Error(E.Cause(err, "server terminated")) @@ -622,27 +633,6 @@ func (s *ServerEndpoint) readLoop() { } } -func (s *ServerEndpoint) Close() error { - s.started.Store(false) - s.cancelLoop() - var serverErr error - if s.server != nil { - serverErr = s.server.Close() - } - if s.readLoopDone != nil { - <-s.readLoopDone - } - var deviceErr error - if s.device != nil { - deviceErr = s.device.Close() - } - var listenerErr error - if s.listener != nil { - listenerErr = s.listener.Close() - } - return E.Errors(serverErr, deviceErr, listenerErr) -} - func (s *ServerEndpoint) PreMatchFlow(network string, destination netip.Addr) adapter.PreMatchAction { return adapter.PreMatchFlow } diff --git a/protocol/openvpn/status.go b/protocol/openvpn/status.go index b39a22e4..6e4032ef 100644 --- a/protocol/openvpn/status.go +++ b/protocol/openvpn/status.go @@ -1,6 +1,7 @@ package openvpn import ( + "context" "slices" "github.com/sagernet/sing-box/adapter" @@ -81,8 +82,7 @@ func (c *ClientEndpoint) setTerminalError(err error) { c.statusAccess.Unlock() } -func (c *ClientEndpoint) watchChallenges() { - defer close(c.challengeLoopDone) +func (c *ClientEndpoint) watchChallenges(ctx context.Context) { var loggedChallengeID string for { challengeUpdated := c.client.ChallengeUpdated() @@ -93,7 +93,7 @@ func (c *ClientEndpoint) watchChallenges() { } c.notifyStatusUpdated() select { - case <-c.loopContext.Done(): + case <-ctx.Done(): return case <-challengeUpdated: } diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go index ff52bd7b..83818999 100644 --- a/protocol/redirect/redirect.go +++ b/protocol/redirect/redirect.go @@ -42,15 +42,16 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL return redirect, nil } -func (h *Redirect) Start(stage adapter.StartStage) error { +func (h *Redirect) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *Redirect) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/redirect/tproxy.go b/protocol/redirect/tproxy.go index ab5ba660..cce01c02 100644 --- a/protocol/redirect/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -70,7 +70,7 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog return tproxy, nil } -func (t *TProxy) Start(stage adapter.StartStage) error { +func (t *TProxy) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -78,22 +78,19 @@ func (t *TProxy) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(t.udpNat.Close) err = t.listener.Start() if err != nil { - _ = t.udpNat.Close() + return err } - return err + scope.Add(t.listener.Close) + return nil } func (t *TProxy) InterfaceUpdated(ctx context.Context) { t.udpNat.Purge() } -func (t *TProxy) Close() error { - _ = t.udpNat.Close() - return t.listener.Close() -} - func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = t.Tag() metadata.InboundType = t.Type() diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go index 3fc9dc74..1ae1b5a5 100644 --- a/protocol/shadowsocks/inbound.go +++ b/protocol/shadowsocks/inbound.go @@ -95,15 +95,16 @@ func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, err } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } //nolint:staticcheck diff --git a/protocol/shadowsocks/inbound_multi.go b/protocol/shadowsocks/inbound_multi.go index 9233c749..8742e417 100644 --- a/protocol/shadowsocks/inbound_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -109,15 +109,16 @@ func newMultiInbound(ctx context.Context, router adapter.Router, logger log.Cont return inbound, err } -func (h *MultiInbound) Start(stage adapter.StartStage) error { +func (h *MultiInbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *MultiInbound) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *MultiInbound) SetTracker(tracker adapter.SSMTracker) { diff --git a/protocol/shadowsocks/inbound_relay.go b/protocol/shadowsocks/inbound_relay.go index c79eeb15..7945b901 100644 --- a/protocol/shadowsocks/inbound_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -86,15 +86,16 @@ func newRelayInbound(ctx context.Context, router adapter.Router, logger log.Cont return inbound, err } -func (h *RelayInbound) Start(stage adapter.StartStage) error { +func (h *RelayInbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *RelayInbound) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } //nolint:staticcheck diff --git a/protocol/shadowsocks/outbound.go b/protocol/shadowsocks/outbound.go index 2ee20031..08f16c18 100644 --- a/protocol/shadowsocks/outbound.go +++ b/protocol/shadowsocks/outbound.go @@ -152,8 +152,14 @@ func (h *Outbound) CloseIdleConnections() { } } -func (h *Outbound) Close() error { - return common.Close(common.PtrOrNil(h.multiplexDialer)) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + if h.multiplexDialer != nil { + scope.Add(h.multiplexDialer.Close) + } + return nil } var _ N.Dialer = (*shadowsocksDialer)(nil) diff --git a/protocol/shadowtls/inbound.go b/protocol/shadowtls/inbound.go index 5781a11c..7dd4524b 100644 --- a/protocol/shadowtls/inbound.go +++ b/protocol/shadowtls/inbound.go @@ -104,15 +104,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/snell/inbound.go b/protocol/snell/inbound.go index 4ef07df1..2d930074 100644 --- a/protocol/snell/inbound.go +++ b/protocol/snell/inbound.go @@ -119,15 +119,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/snell/outbound.go b/protocol/snell/outbound.go index 71ab1219..eb41c362 100644 --- a/protocol/snell/outbound.go +++ b/protocol/snell/outbound.go @@ -163,6 +163,10 @@ func (h *Outbound) CloseIdleConnections() { h.client.CloseIdleConnections() } -func (h *Outbound) Close() error { - return h.client.Close() +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + scope.Add(h.client.Close) + return nil } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go index c498efc4..b37fa9c9 100644 --- a/protocol/socks/inbound.go +++ b/protocol/socks/inbound.go @@ -60,15 +60,16 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return h.listener.Start() -} - -func (h *Inbound) Close() error { - return h.listener.Close() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { diff --git a/protocol/ssh/outbound.go b/protocol/ssh/outbound.go index 7d82a3e1..7e9aa0ac 100644 --- a/protocol/ssh/outbound.go +++ b/protocol/ssh/outbound.go @@ -260,8 +260,14 @@ func (s *Outbound) releaseStream(client *ssh.Client, keepSession bool) { common.Close(clientConn) } -func (s *Outbound) Close() error { - return common.Close(s.clientConn) +func (s *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + scope.Add(func() error { + return common.Close(s.clientConn) + }) + return nil } func (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/protocol/tailscale/certificate_provider.go b/protocol/tailscale/certificate_provider.go index 8170943b..30db9593 100644 --- a/protocol/tailscale/certificate_provider.go +++ b/protocol/tailscale/certificate_provider.go @@ -63,7 +63,7 @@ func NewCertificateProvider(ctx context.Context, _ log.ContextLogger, tag string }, nil } -func (p *CertificateProvider) Start(stage adapter.StartStage) error { +func (p *CertificateProvider) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -82,10 +82,6 @@ func (p *CertificateProvider) Start(stage adapter.StartStage) error { return nil } -func (p *CertificateProvider) Close() error { - return nil -} - func (p *CertificateProvider) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { localClient := p.localClient if localClient == nil { diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go index fecbf702..ba4f42f8 100644 --- a/protocol/tailscale/dns_transport.go +++ b/protocol/tailscale/dns_transport.go @@ -59,6 +59,7 @@ type DNSTransport struct { magicHosts nDNSResolver.MagicDNSHosts searchDomains []string defaultResolvers []adapter.DNSTransport + resolverScope *adapter.Scope } func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleDNSServerOptions) (adapter.DNSTransport, error) { @@ -77,7 +78,7 @@ func NewDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, }, nil } -func (t *DNSTransport) Start(stage adapter.StartStage) error { +func (t *DNSTransport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateInitialize { return nil } @@ -94,6 +95,21 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error { } ep.onReconfigHook = t.onReconfig t.endpoint = ep + scope.Add(func() error { + t.access.Lock() + resolverScope := t.resolverScope + t.routePrefixes = nil + t.routes = nil + t.hosts = nil + t.magicHosts = nil + t.defaultResolvers = nil + t.resolverScope = nil + t.access.Unlock() + if resolverScope != nil { + return resolverScope.Close() + } + return nil + }) return nil } @@ -119,13 +135,14 @@ func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *n directDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{})) return &DNSDialer{transport: t, fallbackDialer: directDialer} }) + resolverScope := adapter.NewScope(t.ctx, t.logger) routes := make(map[string][]adapter.DNSTransport) for domain, resolvers := range dnsConfig.Routes { var myResolvers []adapter.DNSTransport for _, resolver := range resolvers { - myResolver, err := t.createResolver(directDialerOnce, resolver) + myResolver, err := t.createResolver(resolverScope, directDialerOnce, resolver) if err != nil { - return err + return E.Errors(err, resolverScope.Close()) } myResolvers = append(myResolvers, myResolver) } @@ -140,25 +157,26 @@ func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *n }) var defaultResolvers []adapter.DNSTransport for _, resolver := range dnsConfig.DefaultResolvers { - myResolver, err := t.createResolver(directDialerOnce, resolver) + myResolver, err := t.createResolver(resolverScope, directDialerOnce, resolver) if err != nil { - return err + return E.Errors(err, resolverScope.Close()) } defaultResolvers = append(defaultResolvers, myResolver) } t.access.Lock() - oldResolvers := t.collectResolversLocked() + oldResolverScope := t.resolverScope t.routePrefixes = routePrefixes t.routes = routes t.hosts = hosts t.magicHosts = t.endpoint.server.ExportLocalBackend().ExportMagicDNSHosts() t.searchDomains = searchDomains t.defaultResolvers = defaultResolvers + t.resolverScope = resolverScope t.access.Unlock() - for _, transport := range oldResolvers { - transport.Close() + if oldResolverScope != nil { + oldResolverScope.Close() } if len(defaultResolvers) > 0 { @@ -170,7 +188,7 @@ func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *n return nil } -func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) { +func (t *DNSTransport) createResolver(resolverScope *adapter.Scope, directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) { serverURL, parseURLErr := url.Parse(resolver.Addr) isHTTPScheme := parseURLErr == nil && (serverURL.Scheme == "http" || serverURL.Scheme == "https") var myDialer N.Dialer @@ -181,6 +199,10 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn } if len(resolver.BootstrapResolution) > 0 { bootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53)) + err := bootstrapTransport.Start(adapter.StartStateStart, resolverScope) + if err != nil { + return nil, err + } myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0) } else { myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", t.endpoint.queryOptions, 0) @@ -202,12 +224,22 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn if err != nil { return nil, E.Cause(err, "create TLS config for resolver ", resolver.Addr) } - return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil + httpsTransport := transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig) + err = httpsTransport.Start(adapter.StartStateStart, resolverScope) + if err != nil { + return nil, err + } + return httpsTransport, nil case "http": if serverAddr.Port == 0 { serverAddr.Port = 80 } - return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil + httpTransport := transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil) + err := httpTransport.Start(adapter.StartStateStart, resolverScope) + if err != nil { + return nil, err + } + return httpTransport, nil } } serverAddr := M.ParseSocksaddr(resolver.Addr) @@ -220,7 +252,12 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn if serverAddr.Port == 0 { serverAddr.Port = 53 } - return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil + udpTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr) + err := udpTransport.Start(adapter.StartStateStart, resolverScope) + if err != nil { + return nil, err + } + return udpTransport, nil } func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix { @@ -244,26 +281,6 @@ func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix { return ipSet.Prefixes() } -func (t *DNSTransport) Close() error { - t.access.Lock() - transports := t.collectResolversLocked() - t.routePrefixes = nil - t.routes = nil - t.hosts = nil - t.magicHosts = nil - t.defaultResolvers = nil - t.access.Unlock() - - var err error - for _, transport := range transports { - name := "resolver/" + transport.Type() + "[" + transport.Tag() + "]" - err = E.Append(err, transport.Close(), func(err error) error { - return E.Cause(err, "close ", name) - }) - } - return err -} - func (t *DNSTransport) Raw() bool { return true } diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 277a2394..b7b6b90e 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -48,7 +48,6 @@ import ( tailscaleroot "github.com/sagernet/tailscale" _ "github.com/sagernet/tailscale/feature/relayserver" "github.com/sagernet/tailscale/ipn" - "github.com/sagernet/tailscale/ipn/ipnlocal" tsDNS "github.com/sagernet/tailscale/net/dns" "github.com/sagernet/tailscale/net/netmon" "github.com/sagernet/tailscale/net/netns" @@ -93,7 +92,6 @@ type Endpoint struct { detour string server *tsnet.Server stack *stack.Stack - icmpForwarder *tun.ICMPForwarder returnAccess sync.Mutex returnPath tun.Return wgEngine wgengine.ExportedUserspaceEngine @@ -122,17 +120,13 @@ type Endpoint struct { sshServerInstance *tailssh.Server sshServerOptions *option.TailscaleSSHServerOptions taildrop *taildropManager - localBackend *ipnlocal.LocalBackend systemInterface bool systemInterfaceName string systemInterfaceMTU uint32 keyAuth bool - serverStarted bool started atomic.Bool - systemTun tun.Tun systemDialer *dialer.DefaultDialer - fallbackTCPCloser func() } func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) { @@ -261,7 +255,7 @@ func (t *Endpoint) References() []string { return []string{t.detour} } -func (t *Endpoint) Start(stage adapter.StartStage) error { +func (t *Endpoint) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateInitialize: mkdirErr := filemanager.MkdirAll(t.ctx, t.server.Dir, 0o700) @@ -276,14 +270,14 @@ func (t *Endpoint) Start(stage adapter.StartStage) error { } t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t) case adapter.StartStateStart: - return t.start() + return t.start(scope) case adapter.StartStatePostStart: - return t.postStart() + return t.postStart(scope) } return nil } -func (t *Endpoint) start() error { +func (t *Endpoint) start(scope *adapter.Scope) error { if t.platformInterface != nil && t.platformInterface.UsePlatformNetworkInterfaces() { err := t.network.UpdateInterfaces() if err != nil { @@ -308,6 +302,10 @@ func (t *Endpoint) start() error { } }), nil }) + scope.Add(func() error { + netmon.RegisterInterfaceGetter(nil) + return nil + }) } if t.systemInterface { mtu := t.systemInterfaceMTU @@ -333,14 +331,13 @@ func (t *Endpoint) start() error { if err != nil { return err } + scope.Add(systemTun.Close) err = systemTun.Start() if err != nil { - _ = systemTun.Close() return err } wgTunDevice, err := newTunDeviceAdapter(systemTun, int(mtu), t.logger) if err != nil { - _ = systemTun.Close() return err } systemDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{ @@ -349,15 +346,17 @@ func (t *Endpoint) start() error { }, }) if err != nil { - _ = systemTun.Close() return err } - t.systemTun = systemTun t.systemDialer = systemDialer t.server.Tun = wgTunDevice } if t.network.AutoRedirectOutputMark() != 0 { netns.SetControlFunc(t.network.AutoRedirectOutputMarkFunc()) + scope.Add(func() error { + netns.SetControlFunc(nil) + return nil + }) } else if t.platformInterface != nil && t.platformInterface.UsePlatformNetworkInterfaces() { if t.platformInterface.UsePlatformAutoDetectInterfaceControl() { netns.SetControlFunc(func(network, address string, conn syscall.RawConn) error { @@ -365,6 +364,10 @@ func (t *Endpoint) start() error { return t.platformInterface.AutoDetectInterfaceControl(int(fileDescriptor)) }) }) + scope.Add(func() error { + netns.SetControlFunc(nil) + return nil + }) } else { // NEPacketTunnelProvider sockets are excluded from tunnel routes by // NECP; the empty override only suppresses tailscale's own @@ -372,12 +375,21 @@ func (t *Endpoint) start() error { netns.SetControlFunc(func(string, string, syscall.RawConn) error { return nil }) + scope.Add(func() error { + netns.SetControlFunc(nil) + return nil + }) } } else { bindFunc := t.network.AutoDetectInterfaceFunc() if bindFunc != nil { netns.SetControlFunc(bindFunc) netns.SetListenPacketFunc(t.listenPacket) + scope.Add(func() error { + netns.SetControlFunc(nil) + netns.SetListenPacketFunc(nil) + return nil + }) } } return nil @@ -408,29 +420,35 @@ func (t *Endpoint) listenPacket(ctx context.Context, network string, address str return tun.NewUDPEgressConn(udpConn, egressPool), nil } -func (t *Endpoint) postStart() error { +func (t *Endpoint) postStart(scope *adapter.Scope) error { err := t.server.Start() if err != nil { - if t.systemTun != nil { - _ = t.systemTun.Close() - } return err } - t.serverStarted = true - if t.fallbackTCPCloser == nil { - t.fallbackTCPCloser = t.server.RegisterFallbackTCPHandler(func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) { - return func(conn net.Conn) { - ctx := log.ContextWithNewID(t.ctx) - source := M.SocksaddrFrom(src.Addr(), src.Port()) - destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) - t.NewConnectionEx(ctx, conn, source, destination, nil) - }, true - }) - } + scope.Add(t.server.Close) + fallbackTCPCloser := t.server.RegisterFallbackTCPHandler(func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) { + return func(conn net.Conn) { + ctx := log.ContextWithNewID(t.ctx) + source := M.SocksaddrFrom(src.Addr(), src.Port()) + destination := M.SocksaddrFrom(dst.Addr(), dst.Port()) + t.NewConnectionEx(ctx, conn, source, destination, nil) + }, true + }) + scope.Add(func() error { + fallbackTCPCloser() + return nil + }) localBackend := t.server.ExportLocalBackend() - t.localBackend = localBackend + scope.Add(func() error { + t.taildrop.close() + return nil + }) if !version.IsAppleTV() { registerTaildropEndpoint(localBackend, t) + scope.Add(func() error { + unregisterTaildropEndpoint(localBackend) + return nil + }) go t.taildrop.start() } wgEngine := localBackend.ExportEngine().(wgengine.ExportedUserspaceEngine) @@ -447,10 +465,10 @@ func (t *Endpoint) postStart() error { return gonet.TranslateNetstackError(gErr) } icmpForwarder := tun.NewICMPForwarder(ipStack, t, t.logger) + scope.Add(icmpForwarder.Close) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket) ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket) t.stack = ipStack - t.icmpForwarder = icmpForwarder netstack := t.server.ExportNetstack() if netstack != nil { previousTCP := netstack.GetTCPHandlerForFlow @@ -510,15 +528,20 @@ func (t *Endpoint) postStart() error { if err != nil { return E.Cause(err, "start SSH server") } + scope.Add(sshServer.Close) t.sshReconfigHook = sshServer.OnReconfig t.sshServerInstance = sshServer } - go t.watchState() + go t.watchState(scope.Context()) t.started.Store(true) + scope.Add(func() error { + t.started.Store(false) + return nil + }) return nil } -func (t *Endpoint) watchState() { +func (t *Endpoint) watchState(ctx context.Context) { localBackend := t.server.ExportLocalBackend() var reportedAuthURL string exitNodePending := t.exitNode != "" @@ -533,7 +556,7 @@ func (t *Endpoint) watchState() { } for { var busError string - localBackend.WatchNotifications(t.ctx, ipn.NotifyInitialState|ipn.NotifyPeerPatches, nil, func(roNotify *ipn.Notify) (keepGoing bool) { + localBackend.WatchNotifications(ctx, ipn.NotifyInitialState|ipn.NotifyPeerPatches, nil, func(roNotify *ipn.Notify) (keepGoing bool) { if roNotify.ErrMessage != nil { busError = *roNotify.ErrMessage return false @@ -578,7 +601,7 @@ func (t *Endpoint) watchState() { } return true }) - if t.ctx.Err() != nil { + if ctx.Err() != nil { return } if busError != "" { @@ -587,7 +610,7 @@ func (t *Endpoint) watchState() { t.logger.Warn("state watcher stopped unexpectedly, restarting") } select { - case <-t.ctx.Done(): + case <-ctx.Done(): return case <-time.After(time.Second): } @@ -719,38 +742,6 @@ func (t *Endpoint) Logout(ctx context.Context) error { return nil } -func (t *Endpoint) Close() error { - var err error - t.started.Store(false) - if t.localBackend != nil { - unregisterTaildropEndpoint(t.localBackend) - t.localBackend = nil - } - t.taildrop.close() - if t.icmpForwarder != nil { - t.icmpForwarder.Close() - t.icmpForwarder = nil - } - common.Close(common.PtrOrNil(t.sshServerInstance)) - t.sshServerInstance = nil - if t.serverStarted { - err = common.Close(common.PtrOrNil(t.server)) - t.serverStarted = false - } - netmon.RegisterInterfaceGetter(nil) - netns.SetControlFunc(nil) - netns.SetListenPacketFunc(nil) - if t.fallbackTCPCloser != nil { - t.fallbackTCPCloser() - t.fallbackTCPCloser = nil - } - if t.systemTun != nil { - t.systemTun.Close() - t.systemTun = nil - } - return err -} - func (t *Endpoint) InterfaceUpdated(ctx context.Context) { if !t.started.Load() { return diff --git a/protocol/tor/outbound.go b/protocol/tor/outbound.go index 3220f8cf..00156e20 100644 --- a/protocol/tor/outbound.go +++ b/protocol/tor/outbound.go @@ -40,7 +40,6 @@ type Outbound struct { startConf *tor.StartConf options map[string]string events chan control.Event - instance *tor.Tor socksClient *socks.Client } @@ -96,7 +95,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL }, nil } -func (t *Outbound) Start(stage adapter.StartStage) error { +func (t *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateInitialize: if t.startConf.DataDir == "" { @@ -127,11 +126,8 @@ func (t *Outbound) Start(stage adapter.StartStage) error { if err != nil { return err } - err = t.start() - if err != nil { - t.Close() - return err - } + scope.Add(proxy.Close) + return t.start(scope) } return nil } @@ -144,13 +140,17 @@ var torLogEvents = []control.EventCode{ control.EventCodeLogWarn, } -func (t *Outbound) start() error { +func (t *Outbound) start(scope *adapter.Scope) error { + t.events = make(chan control.Event, 8) + scope.Add(func() error { + close(t.events) + return nil + }) torInstance, err := tor.Start(t.ctx, t.startConf) if err != nil { return E.New(strings.ToLower(err.Error())) } - t.instance = torInstance - t.events = make(chan control.Event, 8) + scope.Add(torInstance.Close) err = torInstance.Control.AddEventListener(t.events, torLogEvents...) if err != nil { return err @@ -225,18 +225,6 @@ func (t *Outbound) recvLoop() { } } -func (t *Outbound) Close() error { - err := common.Close( - common.PtrOrNil(t.proxy), - common.PtrOrNil(t.instance), - ) - if t.events != nil { - close(t.events) - t.events = nil - } - return err -} - func (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { t.logger.InfoContext(ctx, "outbound connection to ", destination) return t.socksClient.DialContext(ctx, network, destination) diff --git a/protocol/trojan/inbound.go b/protocol/trojan/inbound.go index 87a84754..525dc87e 100644 --- a/protocol/trojan/inbound.go +++ b/protocol/trojan/inbound.go @@ -120,7 +120,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -129,10 +129,18 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(h.tlsConfig.Close) } if h.transport == nil { - return h.listener.Start() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } + scope.Add(h.transport.Close) + scope.Add(h.listener.Close) if common.Contains(h.transport.Network(), N.NetworkTCP) { tcpListener, err := h.listener.ListenTCP() if err != nil { @@ -160,14 +168,6 @@ func (h *Inbound) Start(stage adapter.StartStage) error { return nil } -func (h *Inbound) Close() error { - return common.Close( - h.listener, - h.tlsConfig, - h.transport, - ) -} - func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) diff --git a/protocol/trojan/outbound.go b/protocol/trojan/outbound.go index 2273c4b5..d1cd853a 100644 --- a/protocol/trojan/outbound.go +++ b/protocol/trojan/outbound.go @@ -150,8 +150,17 @@ func (h *Outbound) CloseIdleConnections() { } } -func (h *Outbound) Close() error { - return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + if h.transport != nil { + scope.Add(h.transport.Close) + } + if h.multiplexDialer != nil { + scope.Add(h.multiplexDialer.Close) + } + return nil } type trojanDialer Outbound diff --git a/protocol/tuic/inbound.go b/protocol/tuic/inbound.go index 11c38efb..eec5bcb9 100644 --- a/protocol/tuic/inbound.go +++ b/protocol/tuic/inbound.go @@ -153,7 +153,7 @@ func (h *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -162,18 +162,17 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.tlsConfig.Close) } packetConn, err := h.listener.ListenUDP() if err != nil { return err } - return h.server.Start(packetConn) -} - -func (h *Inbound) Close() error { - return common.Close( - h.listener, - h.tlsConfig, - common.PtrOrNil(h.server), - ) + scope.Add(h.listener.Close) + err = h.server.Start(packetConn) + if err != nil { + return err + } + scope.Add(h.server.Close) + return nil } diff --git a/protocol/tuic/outbound.go b/protocol/tuic/outbound.go index db7a5658..988f0065 100644 --- a/protocol/tuic/outbound.go +++ b/protocol/tuic/outbound.go @@ -161,6 +161,12 @@ func (h *Outbound) CloseIdleConnections() { h.client.CloseIdleConnections() } -func (h *Outbound) Close() error { - return h.client.CloseWithError(os.ErrClosed) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + scope.Add(func() error { + return h.client.CloseWithError(os.ErrClosed) + }) + return nil } diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go index 719edd81..9347a96e 100644 --- a/protocol/tun/inbound.go +++ b/protocol/tun/inbound.go @@ -25,7 +25,6 @@ import ( M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ranges" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "go4.org/netipx" @@ -36,29 +35,27 @@ func RegisterInbound(registry *inbound.Registry) { } type Inbound struct { - tag string - ctx context.Context - router adapter.Router - networkManager adapter.NetworkManager - logger log.ContextLogger - tunOptions tun.Options - udpTimeout time.Duration - udpMapping tun.NATMapping - udpFiltering tun.NATFiltering - udpNATMax uint32 - dnsHijackAddress []netip.Addr - stack string - tunIf tun.Tun - tunStack tun.Stack - platformInterface adapter.PlatformInterface - platformOptions option.TunPlatformOptions - autoRedirect tun.AutoRedirect - routeRuleSet []adapter.RuleSet - routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback] - routeExcludeRuleSet []adapter.RuleSet - routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback] - routeAddressSet []*netipx.IPSet - routeExcludeAddressSet []*netipx.IPSet + tag string + ctx context.Context + router adapter.Router + networkManager adapter.NetworkManager + logger log.ContextLogger + tunOptions tun.Options + udpTimeout time.Duration + udpMapping tun.NATMapping + udpFiltering tun.NATFiltering + udpNATMax uint32 + dnsHijackAddress []netip.Addr + stack string + tunIf tun.Tun + tunStack tun.Stack + platformInterface adapter.PlatformInterface + platformOptions option.TunPlatformOptions + autoRedirect tun.AutoRedirect + routeRuleSet []adapter.RuleSet + routeExcludeRuleSet []adapter.RuleSet + routeAddressSet []*netipx.IPSet + routeExcludeAddressSet []*netipx.IPSet } func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { @@ -328,9 +325,12 @@ func (t *Inbound) Tag() string { return t.tag } -func (t *Inbound) Start(stage adapter.StartStage) error { +func (t *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateInitialize: + if t.autoRedirect != nil { + scope.Add(t.autoRedirect.Close) + } if t.tunOptions.DNSModeOrDefault() != tun.DNSModeDisabled && len(t.tunOptions.DNSAddress) == 0 { inet4DNSAddress, _ := t.tunOptions.Inet4DNSAddress() inet6DNSAddress, _ := t.tunOptions.Inet6DNSAddress() @@ -383,7 +383,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error { routeRuleSet.IncRef() t.routeAddressSet = append(t.routeAddressSet, ipSets...) if t.autoRedirect != nil { - t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + callback := routeRuleSet.RegisterCallback(t.updateRouteAddressSet) + scope.Add(func() error { + routeRuleSet.UnregisterCallback(callback) + return nil + }) } } for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { @@ -394,7 +398,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error { routeExcludeRuleSet.IncRef() t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...) if t.autoRedirect != nil { - t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) + callback := routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet) + scope.Add(func() error { + routeExcludeRuleSet.UnregisterCallback(callback) + return nil + }) } } } @@ -435,14 +443,13 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "configure tun interface") } + scope.Add(tunInterface.Close) t.logger.Trace("creating stack") t.tunIf = tunInterface if t.platformInterface != nil { err = t.platformInterface.ProcessPlatformOptions(t.platformOptions) if err != nil { - closeError := t.tunIf.Close() - t.tunIf = nil - return E.Errors(E.Cause(err, "process platform options"), closeError) + return E.Cause(err, "process platform options") } } var includeAllNetworks bool @@ -467,6 +474,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(tunStack.Close) t.tunStack = tunStack t.logger.Info("started at ", t.tunOptions.Name) case adapter.StartStatePostStart: @@ -512,14 +520,6 @@ func (t *Inbound) InterfaceUpdated(ctx context.Context) { } } -func (t *Inbound) Close() error { - return common.Close( - t.tunStack, - t.tunIf, - t.autoRedirect, - ) -} - func (t *Inbound) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict { if slices.Contains(t.dnsHijackAddress, destination.Addr()) { if network == uint8(header.UDPProtocolNumber) { diff --git a/protocol/vless/inbound.go b/protocol/vless/inbound.go index 4f77fb13..80bb10f6 100644 --- a/protocol/vless/inbound.go +++ b/protocol/vless/inbound.go @@ -102,7 +102,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -111,10 +111,18 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.tlsConfig.Close) } if h.transport == nil { - return h.listener.Start() + err := h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } + scope.Add(h.transport.Close) + scope.Add(h.listener.Close) if common.Contains(h.transport.Network(), N.NetworkTCP) { tcpListener, err := h.listener.ListenTCP() if err != nil { @@ -142,15 +150,6 @@ func (h *Inbound) Start(stage adapter.StartStage) error { return nil } -func (h *Inbound) Close() error { - return common.Close( - h.service, - h.listener, - h.tlsConfig, - h.transport, - ) -} - func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) diff --git a/protocol/vless/outbound.go b/protocol/vless/outbound.go index f837ac04..c0ece0a2 100644 --- a/protocol/vless/outbound.go +++ b/protocol/vless/outbound.go @@ -170,8 +170,17 @@ func (h *Outbound) CloseIdleConnections() { } } -func (h *Outbound) Close() error { - return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + if h.transport != nil { + scope.Add(h.transport.Close) + } + if h.multiplexDialer != nil { + scope.Add(h.multiplexDialer.Close) + } + return nil } type vlessDialer Outbound diff --git a/protocol/vmess/inbound.go b/protocol/vmess/inbound.go index 8783d3e3..0b4f51cc 100644 --- a/protocol/vmess/inbound.go +++ b/protocol/vmess/inbound.go @@ -100,7 +100,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo return inbound, nil } -func (h *Inbound) Start(stage adapter.StartStage) error { +func (h *Inbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -108,15 +108,24 @@ func (h *Inbound) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(h.service.Close) if h.tlsConfig != nil { err = h.tlsConfig.Start() if err != nil { return err } + scope.Add(h.tlsConfig.Close) } if h.transport == nil { - return h.listener.Start() + err = h.listener.Start() + if err != nil { + return err + } + scope.Add(h.listener.Close) + return nil } + scope.Add(h.transport.Close) + scope.Add(h.listener.Close) if common.Contains(h.transport.Network(), N.NetworkTCP) { tcpListener, err := h.listener.ListenTCP() if err != nil { @@ -144,15 +153,6 @@ func (h *Inbound) Start(stage adapter.StartStage) error { return nil } -func (h *Inbound) Close() error { - return common.Close( - h.service, - h.listener, - h.tlsConfig, - h.transport, - ) -} - func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { if h.tlsConfig != nil && h.transport == nil { tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig) diff --git a/protocol/vmess/outbound.go b/protocol/vmess/outbound.go index 14597c17..2d867676 100644 --- a/protocol/vmess/outbound.go +++ b/protocol/vmess/outbound.go @@ -148,8 +148,17 @@ func (h *Outbound) CloseIdleConnections() { } } -func (h *Outbound) Close() error { - return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) +func (h *Outbound) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + if h.transport != nil { + scope.Add(h.transport.Close) + } + if h.multiplexDialer != nil { + scope.Add(h.multiplexDialer.Close) + } + return nil } func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { diff --git a/protocol/wireguard/endpoint.go b/protocol/wireguard/endpoint.go index 27e73456..c80cf86f 100644 --- a/protocol/wireguard/endpoint.go +++ b/protocol/wireguard/endpoint.go @@ -134,8 +134,10 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL return ep, nil } -func (w *Endpoint) Start(stage adapter.StartStage) error { +func (w *Endpoint) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { + case adapter.StartStateInitialize: + scope.Add(w.endpoint.Close) case adapter.StartStateStart: return w.endpoint.Start(false) case adapter.StartStatePostStart: @@ -144,17 +146,16 @@ func (w *Endpoint) Start(stage adapter.StartStage) error { return err } w.started.Store(true) + scope.Add(func() error { + w.bindAccess.Lock() + w.started.Store(false) + w.bindAccess.Unlock() + return nil + }) } return nil } -func (w *Endpoint) Close() error { - w.bindAccess.Lock() - w.started.Store(false) - w.bindAccess.Unlock() - return w.endpoint.Close() -} - func (w *Endpoint) InterfaceUpdated(ctx context.Context) { if !w.started.Load() { return diff --git a/route/conn.go b/route/conn.go index fe718b50..5029853e 100644 --- a/route/conn.go +++ b/route/conn.go @@ -42,7 +42,14 @@ func NewConnectionManager(logger logger.ContextLogger) *ConnectionManager { } } -func (m *ConnectionManager) Start(stage adapter.StartStage) error { +func (m *ConnectionManager) Start(stage adapter.StartStage, scope *adapter.Scope) error { + if stage != adapter.StartStateInitialize { + return nil + } + scope.Add(func() error { + m.CloseAll() + return nil + }) return nil } @@ -65,11 +72,6 @@ func (m *ConnectionManager) CloseAll() { } } -func (m *ConnectionManager) Close() error { - m.CloseAll() - return nil -} - func (m *ConnectionManager) TrackConn(conn net.Conn) net.Conn { m.access.Lock() element := m.connections.PushBack(conn) diff --git a/route/network.go b/route/network.go index 68d1cd2e..53a1be8d 100644 --- a/route/network.go +++ b/route/network.go @@ -25,7 +25,6 @@ import ( "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/winpowrprof" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" @@ -61,8 +60,6 @@ type NetworkManager struct { environmentUpdateAccess sync.Mutex environmentUpdateTimer *time.Timer startedCtx context.Context - startedCancel context.CancelFunc - interfaceUpdateElement *list.Element[tun.DefaultInterfaceUpdateCallback] interfaceUpdateAccess sync.Mutex interfaceUpdateCancel context.CancelFunc networkResetPending bool @@ -146,11 +143,19 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options return nm, nil } -func (r *NetworkManager) Start(stage adapter.StartStage) error { +func (r *NetworkManager) Start(stage adapter.StartStage, scope *adapter.Scope) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: r.router = service.FromContext[adapter.Router](r.ctx) + scope.Add(func() error { + r.environmentUpdateAccess.Lock() + if r.environmentUpdateTimer != nil { + r.environmentUpdateTimer.Stop() + } + r.environmentUpdateAccess.Unlock() + return nil + }) if r.networkMonitor != nil { monitor.Start("initialize network monitor") err := r.networkMonitor.Start() @@ -158,6 +163,7 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(r.networkMonitor.Close) } if r.interfaceMonitor != nil { monitor.Start("initialize interface monitor") @@ -166,7 +172,12 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { if err != nil { return err } - r.interfaceUpdateElement = r.interfaceMonitor.RegisterCallback(r.notifyInterfaceUpdate) + scope.Add(r.interfaceMonitor.Close) + interfaceUpdateElement := r.interfaceMonitor.RegisterCallback(r.notifyInterfaceUpdate) + scope.Add(func() error { + r.interfaceMonitor.UnregisterCallback(interfaceUpdateElement) + return nil + }) } case adapter.StartStateStart: if C.IsAndroid && r.platformInterface == nil { @@ -186,6 +197,7 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { r.logger.Warn("initialize package manager: ", err) } else { r.packageManager = packageManager + scope.Add(packageManager.Close) } } case adapter.StartStatePostStart: @@ -197,14 +209,20 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { } } else { r.wifiMonitor = wifiMonitor + scope.Add(wifiMonitor.Close) err = r.wifiMonitor.Start() if err != nil { r.logger.Warn(E.Cause(err, "start WIFI monitor")) } } } + scope.Add(func() error { + r.resetRunAccess.Lock() + defer r.resetRunAccess.Unlock() + return nil + }) r.interfaceUpdateAccess.Lock() - r.startedCtx, r.startedCancel = context.WithCancel(r.ctx) + r.startedCtx = scope.Context() if r.interfaceMonitor != nil { r.dispatchInterfaceUpdateLocked() } @@ -224,6 +242,7 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "start power listener") } + scope.Add(r.powerListener.Close) } } return nil @@ -239,63 +258,6 @@ func (r *NetworkManager) Initialize(ruleSets []adapter.RuleSet) { } } -func (r *NetworkManager) Close() error { - monitor := taskmonitor.New(r.logger, C.StopTimeout) - var err error - if r.interfaceUpdateElement != nil { - r.interfaceMonitor.UnregisterCallback(r.interfaceUpdateElement) - r.interfaceUpdateElement = nil - } - if r.powerListener != nil { - monitor.Start("close power listener") - err = E.Append(err, r.powerListener.Close(), func(err error) error { - return E.Cause(err, "close power listener") - }) - monitor.Finish() - } - if r.startedCancel != nil { - r.startedCancel() - monitor.Start("wait network reset") - r.resetRunAccess.Lock() - monitor.Finish() - defer r.resetRunAccess.Unlock() - } - if r.packageManager != nil { - monitor.Start("close package manager") - err = E.Append(err, r.packageManager.Close(), func(err error) error { - return E.Cause(err, "close package manager") - }) - monitor.Finish() - } - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } - r.environmentUpdateAccess.Lock() - if r.environmentUpdateTimer != nil { - r.environmentUpdateTimer.Stop() - } - r.environmentUpdateAccess.Unlock() - if r.wifiMonitor != nil { - monitor.Start("close WIFI monitor") - err = E.Append(err, r.wifiMonitor.Close(), func(err error) error { - return E.Cause(err, "close WIFI monitor") - }) - monitor.Finish() - } - return err -} - func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { return r.interfaceFinder } diff --git a/route/reference.go b/route/reference.go index 708efe5b..ac4511f9 100644 --- a/route/reference.go +++ b/route/reference.go @@ -77,11 +77,12 @@ func (m *ReferenceManager) Name() string { return "reference manager" } -func (m *ReferenceManager) Start(stage adapter.StartStage) error { +func (m *ReferenceManager) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStarted { return nil } m.subscriber = observable.NewSubscriber[struct{}](1) + scope.Add(m.subscriber.Close) history := service.PtrFromContext[urltest.HistoryStorage](m.ctx) if history != nil { history.AddUpdateHook(m.subscriber) @@ -95,13 +96,6 @@ func (m *ReferenceManager) Start(stage adapter.StartStage) error { return nil } -func (m *ReferenceManager) Close() error { - if m.subscriber != nil { - m.subscriber.Close() - } - return nil -} - func (m *ReferenceManager) loop() { subscription, done := m.subscriber.Subscription() for { diff --git a/route/router.go b/route/router.go index 682111d4..0673ddef 100644 --- a/route/router.go +++ b/route/router.go @@ -47,7 +47,6 @@ type Router struct { pauseManager pause.Manager trackers []adapter.ConnectionTracker platformInterface adapter.PlatformInterface - started bool } func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) *Router { @@ -99,10 +98,13 @@ func (r *Router) Initialize(rules []option.Rule, ruleSets []option.RuleSet) erro return nil } -func (r *Router) Start(stage adapter.StartStage) error { +func (r *Router) Start(stage adapter.StartStage, scope *adapter.Scope) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: + for _, ruleSet := range r.ruleSets { + scope.Add(ruleSet.Close) + } if r.needFindNeighbor { if r.platformInterface != nil && r.platformInterface.UsePlatformNeighborResolver() { monitor.Start("initialize neighbor resolver") @@ -113,6 +115,7 @@ func (r *Router) Start(stage adapter.StartStage) error { r.logger.Error(E.Cause(err, "start neighbor resolver")) } else { r.neighborResolver = resolver + scope.Add(resolver.Close) } } else { monitor.Start("initialize neighbor resolver") @@ -128,6 +131,7 @@ func (r *Router) Start(stage adapter.StartStage) error { r.logger.Error(E.Cause(err, "start neighbor resolver")) } else { r.neighborResolver = resolver + scope.Add(resolver.Close) } } } @@ -160,6 +164,9 @@ func (r *Router) Start(stage adapter.StartStage) error { startContext.Close() } r.ruleSetUpdater = R.NewRuleSetUpdater(r.ctx, r.ruleSets) + if r.ruleSetUpdater != nil { + scope.Add(r.ruleSetUpdater.Close) + } r.network.Initialize(r.ruleSets) needFindProcess := r.needFindProcess for _, ruleSet := range r.ruleSets { @@ -192,12 +199,14 @@ func (r *Router) Start(stage adapter.StartStage) error { } } if r.processSearcher != nil { + scope.Add(r.processSearcher.Close) processCache := common.Must1(freelru.New[processCacheKey, processCacheEntry](256, maphash.NewHasher[processCacheKey]().Hash32, true)) processCache.SetLifetime(200 * time.Millisecond) r.processCache = processCache } case adapter.StartStatePostStart: for i, rule := range r.rules { + scope.Add(rule.Close) monitor.Start("initialize rule[", i, "]") err := rule.Start() monitor.Finish() @@ -208,7 +217,6 @@ func (r *Router) Start(stage adapter.StartStage) error { if r.ruleSetUpdater != nil { r.ruleSetUpdater.Start() } - r.started = true return nil case adapter.StartStateStarted: for _, ruleSet := range r.ruleSets { @@ -219,47 +227,6 @@ func (r *Router) Start(stage adapter.StartStage) error { return nil } -func (r *Router) Close() error { - monitor := taskmonitor.New(r.logger, C.StopTimeout) - var err error - if r.neighborResolver != nil { - monitor.Start("close neighbor resolver") - err = E.Append(err, r.neighborResolver.Close(), func(closeErr error) error { - return E.Cause(closeErr, "close neighbor resolver") - }) - monitor.Finish() - } - for i, rule := range r.rules { - monitor.Start("close rule[", i, "]") - err = E.Append(err, rule.Close(), func(err error) error { - return E.Cause(err, "close rule[", i, "]") - }) - monitor.Finish() - } - if r.ruleSetUpdater != nil { - monitor.Start("close rule-set updater") - err = E.Append(err, r.ruleSetUpdater.Close(), func(err error) error { - return E.Cause(err, "close rule-set updater") - }) - monitor.Finish() - } - for i, ruleSet := range r.ruleSets { - monitor.Start("close rule-set[", i, "]") - err = E.Append(err, ruleSet.Close(), func(err error) error { - return E.Cause(err, "close rule-set[", i, "]") - }) - monitor.Finish() - } - if r.processSearcher != nil { - monitor.Start("close process searcher") - err = E.Append(err, r.processSearcher.Close(), func(err error) error { - return E.Cause(err, "close process searcher") - }) - monitor.Finish() - } - return err -} - func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) { ruleSet, loaded := r.ruleSetMap[tag] return ruleSet, loaded diff --git a/route/rule/rule_item_rule_set_test.go b/route/rule/rule_item_rule_set_test.go index 61c3208b..ec34020a 100644 --- a/route/rule/rule_item_rule_set_test.go +++ b/route/rule/rule_item_rule_set_test.go @@ -18,8 +18,7 @@ type ruleSetItemTestRouter struct { ruleSets map[string]adapter.RuleSet } -func (r *ruleSetItemTestRouter) Start(adapter.StartStage) error { return nil } -func (r *ruleSetItemTestRouter) Close() error { return nil } +func (r *ruleSetItemTestRouter) Start(adapter.StartStage, *adapter.Scope) error { return nil } func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext, []byte) adapter.PreMatchResult { return adapter.PreMatchResult{} } diff --git a/service/acme/service.go b/service/acme/service.go index 3e1abd0a..0b67d58e 100644 --- a/service/acme/service.go +++ b/service/acme/service.go @@ -50,7 +50,6 @@ type Service struct { certificate.Adapter ctx context.Context config *certmagic.Config - cache *certmagic.Cache zapLogger *zap.Logger dataDirectory string domain []string @@ -186,7 +185,7 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s }, nil } -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateInitialize: if s.dataDirectory != "" { @@ -202,22 +201,18 @@ func (s *Service) Start(stage adapter.StartStage) error { }, Logger: s.zapLogger, }) + scope.Add(func() error { + cache.Stop() + return nil + }) config = certmagic.New(cache, *config) s.config = config - s.cache = cache case adapter.StartStateStart: return s.config.ManageAsync(s.ctx, s.domain) } return nil } -func (s *Service) Close() error { - if s.cache != nil { - s.cache.Stop() - } - return nil -} - func (s *Service) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { return s.config.GetCertificate(hello) } diff --git a/service/api/dashboard.go b/service/api/dashboard.go index e03dabe8..0f981716 100644 --- a/service/api/dashboard.go +++ b/service/api/dashboard.go @@ -35,7 +35,6 @@ const ( type dashboard struct { ctx context.Context - cancel context.CancelFunc logger log.ContextLogger options option.APIDashboardOptions path string @@ -48,7 +47,6 @@ type dashboard struct { } func newDashboard(ctx context.Context, logger log.ContextLogger, options option.APIDashboardOptions) *dashboard { - ctx, cancel := context.WithCancel(ctx) path := options.Path if path == "" { path = "dashboard" @@ -64,7 +62,6 @@ func newDashboard(ctx context.Context, logger log.ContextLogger, options option. } return &dashboard{ ctx: ctx, - cancel: cancel, logger: logger, options: options, path: path, @@ -74,7 +71,7 @@ func newDashboard(ctx context.Context, logger log.ContextLogger, options option. } } -func (d *dashboard) start() error { +func (d *dashboard) start(ctx context.Context) error { _, err := filemanager.ReadDir(d.ctx, d.path) if err != nil && !os.IsNotExist(err) { return E.Cause(err, "read dashboard directory") @@ -90,12 +87,11 @@ func (d *dashboard) start() error { return E.Cause(err, "create dashboard http client") } d.httpClient = &http.Client{Transport: transport} - go d.loopUpdate(status) + go d.loopUpdate(ctx, status) return nil } func (d *dashboard) close() error { - d.cancel() if d.httpClient != nil { d.httpClient.CloseIdleConnections() } @@ -110,7 +106,7 @@ func (d *dashboard) serveHTTP(writer http.ResponseWriter, request *http.Request) http.Redirect(writer, request, dashboardRoutePrefix, http.StatusFound) } -func (d *dashboard) loopUpdate(status dashboardStatus) { +func (d *dashboard) loopUpdate(ctx context.Context, status dashboardStatus) { var nextUpdate time.Time if status == dashboardManaged { nextUpdate = d.lastUpdated.Add(d.updateInterval) @@ -119,13 +115,13 @@ func (d *dashboard) loopUpdate(status dashboardStatus) { defer timer.Stop() for { select { - case <-d.ctx.Done(): + case <-ctx.Done(): return case <-timer.C: } now := time.Now() if !now.Before(nextUpdate) { - err := d.fetch(d.ctx) + err := d.fetch(ctx) if err != nil { d.logger.Error(E.Cause(err, "update dashboard")) nextUpdate = now.Add(d.updateInterval) diff --git a/service/api/server.go b/service/api/server.go index 658e6564..87af9644 100644 --- a/service/api/server.go +++ b/service/api/server.go @@ -20,7 +20,6 @@ import ( "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" //nolint:staticcheck - "google.golang.org/grpc" ) func RegisterService(registry *boxService.Registry) { @@ -29,24 +28,16 @@ func RegisterService(registry *boxService.Registry) { type Service struct { boxService.Adapter - ctx context.Context - cancel context.CancelFunc - logger log.ContextLogger - options option.APIServiceOptions - listener *listener.Listener - tlsConfig tls.ServerConfig - startedService *daemon.StartedService - grpcServer *grpc.Server - httpServer *http.Server - dashboard *dashboard + logger log.ContextLogger + options option.APIServiceOptions + listener *listener.Listener + tlsConfig tls.ServerConfig + dashboard *dashboard } func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.APIServiceOptions) (adapter.Service, error) { - ctx, cancel := context.WithCancel(ctx) s := &Service{ Adapter: boxService.NewAdapter(C.TypeAPI, tag), - ctx: ctx, - cancel: cancel, logger: logger, options: options, listener: listener.New(listener.Options{ @@ -59,7 +50,6 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio if options.TLS != nil { tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) if err != nil { - cancel() return nil, err } s.tlsConfig = tlsConfig @@ -70,23 +60,33 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio return s, nil } -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStarted { return nil } - s.startedService = daemon.NewAttachedService(s.ctx) - s.grpcServer = daemon.NewServer(s.startedService, s.options.Secret) + ctx := scope.Context() + startedService := daemon.NewAttachedService(ctx) + scope.Add(func() error { + startedService.Close() + return nil + }) + grpcServer := daemon.NewServer(startedService, s.options.Secret) + scope.Add(func() error { + grpcServer.Stop() + return nil + }) if s.dashboard != nil { - err := s.dashboard.start() + err := s.dashboard.start(ctx) if err != nil { return E.Cause(err, "start dashboard") } + scope.Add(s.dashboard.close) } - s.httpServer = &http.Server{ + httpServer := &http.Server{ //nolint:staticcheck - Handler: h2c.NewHandler(newHTTPHandler(s.logger, s.grpcServer, s.options, s.dashboard), new(http2.Server)), + Handler: h2c.NewHandler(newHTTPHandler(s.logger, grpcServer, s.options, s.dashboard), new(http2.Server)), BaseContext: func(net.Listener) context.Context { - return s.ctx + return ctx }, } if s.tlsConfig != nil { @@ -94,6 +94,7 @@ func (s *Service) Start(stage adapter.StartStage) error { if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(s.tlsConfig.Close) if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { s.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, s.tlsConfig.NextProtos()...)) } @@ -105,34 +106,16 @@ func (s *Service) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(s.listener.Close) if s.tlsConfig != nil { tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) } + scope.Add(httpServer.Close) go func() { - serveErr := s.httpServer.Serve(tcpListener) - if serveErr != nil && s.ctx.Err() == nil { + serveErr := httpServer.Serve(tcpListener) + if serveErr != nil && ctx.Err() == nil { s.logger.Error("serve error: ", serveErr) } }() return nil } - -func (s *Service) Close() error { - s.cancel() - if s.dashboard != nil { - s.dashboard.close() - } - if s.httpServer != nil { - s.httpServer.Close() - } - if s.grpcServer != nil { - s.grpcServer.Stop() - } - if s.startedService != nil { - s.startedService.Close() - } - return common.Close( - common.PtrOrNil(s.listener), - s.tlsConfig, - ) -} diff --git a/service/ccm/service.go b/service/ccm/service.go index a211a3ef..c59521a5 100644 --- a/service/ccm/service.go +++ b/service/ccm/service.go @@ -121,7 +121,6 @@ type Service struct { httpHeaders http.Header listener *listener.Listener tlsConfig tls.ServerConfig - httpServer *http.Server userManager *UserManager accessMutex sync.RWMutex usageTracker *AggregatedUsage @@ -197,11 +196,22 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio return service, nil } -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + if s.usageTracker != nil { + scope.Add(func() error { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + return nil + }) + } + s.userManager.UpdateUsers(s.users) credentials, err := platformReadCredentials(s.ctx, s.credentialPath) @@ -220,19 +230,21 @@ func (s *Service) Start(stage adapter.StartStage) error { router := chi.NewRouter() router.Mount("/", s) - s.httpServer = &http.Server{Handler: router} + httpServer := &http.Server{Handler: router} if s.tlsConfig != nil { err = s.tlsConfig.Start() if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(s.tlsConfig.Close) } tcpListener, err := s.listener.ListenTCP() if err != nil { return err } + scope.Add(s.listener.Close) if s.tlsConfig != nil { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { @@ -241,8 +253,9 @@ func (s *Service) Start(stage adapter.StartStage) error { tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) } + scope.Add(httpServer.Close) go func() { - serveErr := s.httpServer.Serve(tcpListener) + serveErr := httpServer.Serve(tcpListener) if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { s.logger.Error("serve error: ", serveErr) } @@ -579,24 +592,6 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons } } -func (s *Service) Close() error { - err := common.Close( - common.PtrOrNil(s.httpServer), - common.PtrOrNil(s.listener), - s.tlsConfig, - ) - - if s.usageTracker != nil { - s.usageTracker.cancelPendingSave() - saveErr := s.usageTracker.Save() - if saveErr != nil { - s.logger.Error("save usage statistics: ", saveErr) - } - } - - return err -} - func (s *Service) References() []string { if s.detour == "" { return nil diff --git a/service/derp/service.go b/service/derp/service.go index 485bc41e..73fa2d7f 100644 --- a/service/derp/service.go +++ b/service/derp/service.go @@ -134,7 +134,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio }, nil } -func (d *Service) Start(stage adapter.StartStage) error { +func (d *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateStart: config, err := readDERPConfig(d.ctx, filemanager.BasePath(d.ctx, d.configPath)) @@ -205,11 +205,13 @@ func (d *Service) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(d.tlsConfig.Close) tcpListener, err := d.listener.ListenTCP() if err != nil { return err } + scope.Add(d.listener.Close) if len(d.tlsConfig.NextProtos()) == 0 { d.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"}) } else if !common.Contains(d.tlsConfig.NextProtos(), http2.NextProtoTLS) { @@ -335,14 +337,6 @@ func (d *Service) startMeshWithHost(derpServer *derpserver.Server, server *optio return nil } -func (d *Service) Close() error { - err := common.Close( - common.PtrOrNil(d.listener), - d.tlsConfig, - ) - return err -} - var homePage = `

DERP

diff --git a/service/ocm/service.go b/service/ocm/service.go index 5b213e23..cafcfadd 100644 --- a/service/ocm/service.go +++ b/service/ocm/service.go @@ -136,7 +136,6 @@ type Service struct { httpHeaders http.Header listener *listener.Listener tlsConfig tls.ServerConfig - httpServer *http.Server userManager *UserManager accessMutex sync.RWMutex usageTracker *AggregatedUsage @@ -218,11 +217,22 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio return service, nil } -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } + if s.usageTracker != nil { + scope.Add(func() error { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + return nil + }) + } + s.userManager.UpdateUsers(s.users) credentials, err := platformReadCredentials(s.ctx, s.credentialPath) @@ -241,19 +251,21 @@ func (s *Service) Start(stage adapter.StartStage) error { router := chi.NewRouter() router.Mount("/", s) - s.httpServer = &http.Server{Handler: router} + httpServer := &http.Server{Handler: router} if s.tlsConfig != nil { err = s.tlsConfig.Start() if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(s.tlsConfig.Close) } tcpListener, err := s.listener.ListenTCP() if err != nil { return err } + scope.Add(s.listener.Close) if s.tlsConfig != nil { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { @@ -262,8 +274,16 @@ func (s *Service) Start(stage adapter.StartStage) error { tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) } + scope.Add(httpServer.Close) + scope.Add(func() error { + for _, session := range s.startWebSocketShutdown() { + session.Close() + } + s.webSocketGroup.Wait() + return nil + }) go func() { - serveErr := s.httpServer.Serve(tcpListener) + serveErr := httpServer.Serve(tcpListener) if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { s.logger.Error("serve error: ", serveErr) } @@ -640,30 +660,6 @@ func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, respons } } -func (s *Service) Close() error { - webSocketSessions := s.startWebSocketShutdown() - - err := common.Close( - common.PtrOrNil(s.httpServer), - common.PtrOrNil(s.listener), - s.tlsConfig, - ) - for _, session := range webSocketSessions { - session.Close() - } - s.webSocketGroup.Wait() - - if s.usageTracker != nil { - s.usageTracker.cancelPendingSave() - saveErr := s.usageTracker.Save() - if saveErr != nil { - s.logger.Error("save usage statistics: ", saveErr) - } - } - - return err -} - func (s *Service) registerWebSocketSession(session *webSocketSession) bool { s.webSocketMutex.Lock() defer s.webSocketMutex.Unlock() diff --git a/service/oomkiller/service.go b/service/oomkiller/service.go index b46d7dfa..60fe968f 100644 --- a/service/oomkiller/service.go +++ b/service/oomkiller/service.go @@ -58,10 +58,7 @@ func (s *Service) startTimer() error { } func (s *Service) stopTimer() { - var state timerState - if s.adaptiveTimer != nil { - state = s.adaptiveTimer.stop() - } + state := s.adaptiveTimer.stop() if s.recorder != nil { s.recorder.instanceStopped(state) } diff --git a/service/oomkiller/service_darwin.go b/service/oomkiller/service_darwin.go index a0a07486..8ab64786 100644 --- a/service/oomkiller/service_darwin.go +++ b/service/oomkiller/service_darwin.go @@ -44,7 +44,7 @@ var ( globalServices []*Service ) -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -52,6 +52,10 @@ func (s *Service) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(func() error { + s.stopTimer() + return nil + }) if s.timerConfig.policyMode == policyModeNetworkExtension { globalAccess.Lock() isFirst := len(globalServices) == 0 @@ -60,25 +64,21 @@ func (s *Service) Start(stage adapter.StartStage) error { if isFirst { C.startMemoryPressureMonitor() } - } - return nil -} - -func (s *Service) Close() error { - s.stopTimer() - if s.timerConfig.policyMode == policyModeNetworkExtension { - globalAccess.Lock() - for i, svc := range globalServices { - if svc == s { - globalServices = append(globalServices[:i], globalServices[i+1:]...) - break + scope.Add(func() error { + globalAccess.Lock() + for i, svc := range globalServices { + if svc == s { + globalServices = append(globalServices[:i], globalServices[i+1:]...) + break + } } - } - isLast := len(globalServices) == 0 - globalAccess.Unlock() - if isLast { - C.stopMemoryPressureMonitor() - } + isLast := len(globalServices) == 0 + globalAccess.Unlock() + if isLast { + C.stopMemoryPressureMonitor() + } + return nil + }) } return nil } diff --git a/service/oomkiller/service_stub.go b/service/oomkiller/service_stub.go index f75fe8d5..2025c731 100644 --- a/service/oomkiller/service_stub.go +++ b/service/oomkiller/service_stub.go @@ -6,14 +6,17 @@ import ( "github.com/sagernet/sing-box/adapter" ) -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return s.startTimer() -} - -func (s *Service) Close() error { - s.stopTimer() + err := s.startTimer() + if err != nil { + return err + } + scope.Add(func() error { + s.stopTimer() + return nil + }) return nil } diff --git a/service/origin_ca/service.go b/service/origin_ca/service.go index 46d7337e..2eb1c827 100644 --- a/service/origin_ca/service.go +++ b/service/origin_ca/service.go @@ -60,8 +60,6 @@ type Service struct { certificate.Adapter logger log.ContextLogger ctx context.Context - cancel context.CancelFunc - done chan struct{} timeFunc func() time.Time httpClient *http.Client storage certmagic.Storage @@ -104,10 +102,8 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s if requestedValidity == 0 { requestedValidity = defaultRequestedValidity } - ctx, cancel := context.WithCancel(ctx) httpClient, err := originCAHTTPClient(ctx, logger, options) if err != nil { - cancel() return nil, err } var ( @@ -135,7 +131,6 @@ func NewCertificateProvider(ctx context.Context, logger log.ContextLogger, tag s Adapter: certificate.NewAdapter(C.TypeCloudflareOriginCA, tag), logger: logger, ctx: ctx, - cancel: cancel, timeFunc: timeFunc, httpClient: httpClient, storage: storage, @@ -161,7 +156,7 @@ func originCAHTTPClient(ctx context.Context, logger log.ContextLogger, options o return &http.Client{Transport: transport}, nil } -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage == adapter.StartStateInitialize { if s.dataDirectory == "" { return nil @@ -174,33 +169,30 @@ func (s *Service) Start(stage adapter.StartStage) error { } else if stage != adapter.StartStateStart { return nil } - cachedCertificate, cachedLeaf, err := s.loadCachedCertificate() + ctx := scope.Context() + cachedCertificate, cachedLeaf, err := s.loadCachedCertificate(ctx) if err != nil { s.logger.Warn(E.Cause(err, "load cached Cloudflare Origin CA certificate")) } else if cachedCertificate != nil { s.setCurrentCertificate(cachedCertificate, cachedLeaf) } if cachedCertificate == nil { - err = s.issueAndStoreCertificate() + err = s.issueAndStoreCertificate(ctx) if err != nil { return err } } else if s.shouldRenew(cachedLeaf, s.timeFunc()) { - err = s.issueAndStoreCertificate() + err = s.issueAndStoreCertificate(ctx) if err != nil { s.logger.Warn(E.Cause(err, "renew cached Cloudflare Origin CA certificate")) } } - s.done = make(chan struct{}) - go s.refreshLoop() - return nil -} - -func (s *Service) Close() error { - s.cancel() - if done := s.done; done != nil { + done := make(chan struct{}) + go s.refreshLoop(ctx, done) + scope.Add(func() error { <-done - } + return nil + }) return nil } @@ -214,8 +206,8 @@ func (s *Service) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, erro return certificate, nil } -func (s *Service) refreshLoop() { - defer close(s.done) +func (s *Service) refreshLoop(ctx context.Context, done chan struct{}) { + defer close(done) var retryDelay time.Duration for { waitDuration := retryDelay @@ -232,7 +224,7 @@ func (s *Service) refreshLoop() { } timer := time.NewTimer(waitDuration) select { - case <-s.ctx.Done(): + case <-ctx.Done(): if !timer.Stop() { select { case <-timer.C: @@ -242,7 +234,7 @@ func (s *Service) refreshLoop() { return case <-timer.C: } - err := s.issueAndStoreCertificate() + err := s.issueAndStoreCertificate(ctx) if err != nil { s.logger.Error(E.Cause(err, "renew Cloudflare Origin CA certificate")) s.access.RLock() @@ -279,25 +271,25 @@ func (s *Service) effectiveRenewBefore(leaf *x509.Certificate) time.Duration { return min(lifetime/3, defaultRenewBefore) } -func (s *Service) issueAndStoreCertificate() error { - err := s.storage.Lock(s.ctx, s.storageLockKey) +func (s *Service) issueAndStoreCertificate(ctx context.Context) error { + err := s.storage.Lock(ctx, s.storageLockKey) if err != nil { return E.Cause(err, "lock Cloudflare Origin CA certificate storage") } defer func() { - err = s.storage.Unlock(context.WithoutCancel(s.ctx), s.storageLockKey) + err = s.storage.Unlock(context.WithoutCancel(ctx), s.storageLockKey) if err != nil { s.logger.Warn(E.Cause(err, "unlock Cloudflare Origin CA certificate storage")) } }() - cachedCertificate, cachedLeaf, err := s.loadCachedCertificate() + cachedCertificate, cachedLeaf, err := s.loadCachedCertificate(ctx) if err != nil { s.logger.Warn(E.Cause(err, "load cached Cloudflare Origin CA certificate")) } else if cachedCertificate != nil && !s.shouldRenew(cachedLeaf, s.timeFunc()) { s.setCurrentCertificate(cachedCertificate, cachedLeaf) return nil } - certificatePEM, privateKeyPEM, tlsCertificate, leaf, err := s.requestCertificate(s.ctx) + certificatePEM, privateKeyPEM, tlsCertificate, leaf, err := s.requestCertificate(ctx) if err != nil { return err } @@ -308,7 +300,7 @@ func (s *Service) issueAndStoreCertificate() error { if err != nil { return E.Cause(err, "encode Cloudflare Origin CA certificate metadata") } - err = storeCertificateResource(s.ctx, s.storage, s.storageIssuerKey, certmagic.CertificateResource{ + err = storeCertificateResource(ctx, s.storage, s.storageIssuerKey, certmagic.CertificateResource{ SANs: slices.Clone(s.domain), CertificatePEM: certificatePEM, PrivateKeyPEM: privateKeyPEM, @@ -415,8 +407,8 @@ func (s *Service) requestCertificate(ctx context.Context) ([]byte, []byte, *tls. return certificatePEM, privateKeyPEM, tlsCertificate, leaf, nil } -func (s *Service) loadCachedCertificate() (*tls.Certificate, *x509.Certificate, error) { - certificateResource, err := loadCertificateResource(s.ctx, s.storage, s.storageIssuerKey, s.storageNamesKey) +func (s *Service) loadCachedCertificate(ctx context.Context) (*tls.Certificate, *x509.Certificate, error) { + certificateResource, err := loadCertificateResource(ctx, s.storage, s.storageIssuerKey, s.storageNamesKey) if err != nil { if errors.Is(err, fs.ErrNotExist) { return nil, nil, nil diff --git a/service/resolved/service.go b/service/resolved/service.go index f0d1aa0f..85641320 100644 --- a/service/resolved/service.go +++ b/service/resolved/service.go @@ -17,14 +17,12 @@ import ( "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" dnsOutbound "github.com/sagernet/sing-box/protocol/dns" - tun "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/godbus/dbus/v5" @@ -37,18 +35,17 @@ func RegisterService(registry *boxService.Registry) { type Service struct { boxService.Adapter - ctx context.Context - logger log.ContextLogger - network adapter.NetworkManager - dnsRouter adapter.DNSRouter - listener *listener.Listener - systemBus *dbus.Conn - linkAccess sync.RWMutex - links map[int32]*TransportLink - defaultRouteSequence []int32 - networkUpdateCallback *list.Element[tun.NetworkUpdateCallback] - updateCallback func(*TransportLink) error - deleteCallback func(*TransportLink) + ctx context.Context + logger log.ContextLogger + network adapter.NetworkManager + dnsRouter adapter.DNSRouter + listener *listener.Listener + systemBus *dbus.Conn + linkAccess sync.RWMutex + links map[int32]*TransportLink + defaultRouteSequence []int32 + updateCallback func(*TransportLink) error + deleteCallback func(*TransportLink) } type TransportLink struct { @@ -82,7 +79,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio return inbound, nil } -func (i *Service) Start(stage adapter.StartStage) error { +func (i *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { switch stage { case adapter.StartStateInitialize: inboundManager := service.FromContext[adapter.ServiceManager](i.ctx) @@ -95,6 +92,7 @@ func (i *Service) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(systemBus.Close) i.systemBus = systemBus err = systemBus.Export((*resolve1Manager)(i), "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager") if err != nil { @@ -104,6 +102,10 @@ func (i *Service) Start(stage adapter.StartStage) error { if err != nil { return err } + scope.Add(func() error { + _, releaseErr := systemBus.ReleaseName("org.freedesktop.resolve1") + return releaseErr + }) switch reply { case dbus.RequestNameReplyPrimaryOwner: case dbus.RequestNameReplyExists: @@ -111,27 +113,22 @@ func (i *Service) Start(stage adapter.StartStage) error { default: return E.New("unknown request name reply: ", reply) } - i.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate) + networkMonitor := i.network.NetworkMonitor() + networkUpdateCallback := networkMonitor.RegisterCallback(i.onNetworkUpdate) + scope.Add(func() error { + networkMonitor.UnregisterCallback(networkUpdateCallback) + return nil + }) case adapter.StartStateStart: err := i.listener.Start() if err != nil { return err } + scope.Add(i.listener.Close) } return nil } -func (i *Service) Close() error { - if i.networkUpdateCallback != nil { - i.network.NetworkMonitor().UnregisterCallback(i.networkUpdateCallback) - } - if i.systemBus != nil { - i.systemBus.ReleaseName("org.freedesktop.resolve1") - i.systemBus.Close() - } - return i.listener.Close() -} - func (i *Service) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Inbound = i.Tag() metadata.InboundType = i.Type() diff --git a/service/resolved/transport.go b/service/resolved/transport.go index 2f476a76..34dfa332 100644 --- a/service/resolved/transport.go +++ b/service/resolved/transport.go @@ -58,6 +58,7 @@ type LinkServers struct { Link *TransportLink Servers []adapter.DNSTransport serverOffset uint32 + serverScope *adapter.Scope } func (c *LinkServers) ServerOffset(rotate bool) uint32 { @@ -85,7 +86,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt }, nil } -func (t *Transport) Start(stage adapter.StartStage) error { +func (t *Transport) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateInitialize { return nil } @@ -101,17 +102,14 @@ func (t *Transport) Start(stage adapter.StartStage) error { resolvedInbound.updateCallback = t.updateTransports resolvedInbound.deleteCallback = t.deleteTransport t.service = resolvedInbound - return nil -} - -func (t *Transport) Close() error { - t.linkAccess.RLock() - defer t.linkAccess.RUnlock() - for _, servers := range t.linkServers { - for _, server := range servers.Servers { - server.Close() + scope.Add(func() error { + t.linkAccess.RLock() + defer t.linkAccess.RUnlock() + for _, servers := range t.linkServers { + servers.serverScope.Close() } - } + return nil + }) return nil } @@ -171,9 +169,7 @@ func (t *Transport) updateTransports(link *TransportLink) error { t.linkAccess.Lock() defer t.linkAccess.Unlock() if servers, loaded := t.linkServers[link]; loaded { - for _, server := range servers.Servers { - server.Close() - } + servers.serverScope.Close() } serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{ AbstractDialerOptions: option.AbstractDialerOptions{ @@ -227,9 +223,17 @@ func (t *Transport) updateTransports(link *TransportLink) error { transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, M.SocksaddrFrom(serverAddr, serverPort))) } } + serverScope := adapter.NewScope(t.ctx, t.logger) + for _, serverTransport := range transports { + err := serverTransport.Start(adapter.StartStateStart, serverScope) + if err != nil { + return E.Errors(err, serverScope.Close()) + } + } t.linkServers[link] = &LinkServers{ - Link: link, - Servers: transports, + Link: link, + Servers: transports, + serverScope: serverScope, } return nil } @@ -241,9 +245,7 @@ func (t *Transport) deleteTransport(link *TransportLink) { if !loaded { return } - for _, server := range servers.Servers { - server.Close() - } + servers.serverScope.Close() delete(t.linkServers, link) } diff --git a/service/ssmapi/server.go b/service/ssmapi/server.go index 157ea150..16a126ac 100644 --- a/service/ssmapi/server.go +++ b/service/ssmapi/server.go @@ -31,7 +31,6 @@ func RegisterService(registry *boxService.Registry) { type Service struct { boxService.Adapter ctx context.Context - cancel context.CancelFunc logger log.ContextLogger listener *listener.Listener tlsConfig tls.ServerConfig @@ -39,18 +38,15 @@ type Service struct { traffics map[string]*TrafficManager users map[string]*UserManager cachePath string - saveTicker *time.Ticker lastSavedCache []byte cacheMutex sync.Mutex } func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.SSMAPIServiceOptions) (adapter.Service, error) { - ctx, cancel := context.WithCancel(ctx) chiRouter := chi.NewRouter() s := &Service{ Adapter: boxService.NewAdapter(C.TypeSSMAPI, tag), ctx: ctx, - cancel: cancel, logger: logger, listener: listener.New(listener.Options{ Context: ctx, @@ -95,7 +91,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio return s, nil } -func (s *Service) Start(stage adapter.StartStage) error { +func (s *Service) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } @@ -103,24 +99,35 @@ func (s *Service) Start(stage adapter.StartStage) error { if err != nil { s.logger.Error(E.Cause(err, "load cache")) } - s.saveTicker = time.NewTicker(1 * time.Minute) - go s.loopSaveCache() + saveTicker := time.NewTicker(1 * time.Minute) + scope.Add(func() error { + saveTicker.Stop() + saveErr := s.saveCache() + if saveErr != nil { + s.logger.Error(E.Cause(saveErr, "save cache")) + } + return nil + }) + go s.loopSaveCache(scope.Context(), saveTicker) if s.tlsConfig != nil { err = s.tlsConfig.Start() if err != nil { return E.Cause(err, "create TLS config") } + scope.Add(s.tlsConfig.Close) } tcpListener, err := s.listener.ListenTCP() if err != nil { return err } + scope.Add(s.listener.Close) if s.tlsConfig != nil { if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) } tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) } + scope.Add(s.httpServer.Close) go func() { err = s.httpServer.Serve(tcpListener) if err != nil && !errors.Is(err, http.ErrServerClosed) { @@ -130,12 +137,12 @@ func (s *Service) Start(stage adapter.StartStage) error { return nil } -func (s *Service) loopSaveCache() { +func (s *Service) loopSaveCache(ctx context.Context, saveTicker *time.Ticker) { for { select { - case <-s.ctx.Done(): + case <-ctx.Done(): return - case <-s.saveTicker.C: + case <-saveTicker.C: err := s.saveCache() if err != nil { s.logger.Error(E.Cause(err, "save cache")) @@ -143,21 +150,3 @@ func (s *Service) loopSaveCache() { } } } - -func (s *Service) Close() error { - if s.cancel != nil { - s.cancel() - } - if s.saveTicker != nil { - s.saveTicker.Stop() - } - err := s.saveCache() - if err != nil { - s.logger.Error(E.Cause(err, "save cache")) - } - return common.Close( - common.PtrOrNil(s.httpServer), - common.PtrOrNil(s.listener), - s.tlsConfig, - ) -} diff --git a/service/usbip/client.go b/service/usbip/client.go index 231c447f..9e0ddcad 100644 --- a/service/usbip/client.go +++ b/service/usbip/client.go @@ -52,15 +52,16 @@ func NewClientService(ctx context.Context, logger log.ContextLogger, tag string, }, nil } -func (s *ClientService) Start(stage adapter.StartStage) error { +func (s *ClientService) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return s.inner.Start() -} - -func (s *ClientService) Close() error { - return s.inner.Close() + err := s.inner.Start() + if err != nil { + return err + } + scope.Add(s.inner.Close) + return nil } func (s *ClientService) References() []string { diff --git a/service/usbip/server.go b/service/usbip/server.go index d1483d94..abeb8ddc 100644 --- a/service/usbip/server.go +++ b/service/usbip/server.go @@ -83,15 +83,16 @@ func NewServerService(ctx context.Context, logger log.ContextLogger, tag string, } } -func (s *ServerService) Start(stage adapter.StartStage) error { +func (s *ServerService) Start(stage adapter.StartStage, scope *adapter.Scope) error { if stage != adapter.StartStateStart { return nil } - return s.inner.Start() -} - -func (s *ServerService) Close() error { - return s.inner.Close() + err := s.inner.Start() + if err != nil { + return err + } + scope.Add(s.inner.Close) + return nil } func (s *dynamicServerService) AddDevice(info usbip.ProvidedDeviceInfo, transport usbip.DeviceTransport) (string, error) {