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) {