diff --git a/adapter/outbound.go b/adapter/outbound.go index a45c27fd..b6980fb9 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -13,6 +13,7 @@ type Outbound interface { Type() string Tag() string Network() []string + Dependencies() []string N.Dialer NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error diff --git a/box.go b/box.go index f640c049..dc589071 100644 --- a/box.go +++ b/box.go @@ -217,20 +217,9 @@ func (s *Box) preStart() error { return E.Cause(err, "pre-starting ", serviceName) } } - for i, out := range s.outbounds { - var tag string - if out.Tag() == "" { - tag = F.ToString(i) - } else { - tag = out.Tag() - } - if starter, isStarter := out.(common.Starter); isStarter { - s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]") - err := starter.Start() - if err != nil { - return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]") - } - } + err := s.startOutbounds() + if err != nil { + return err } return s.router.Start() } diff --git a/box_outbound.go b/box_outbound.go new file mode 100644 index 00000000..d637d766 --- /dev/null +++ b/box_outbound.go @@ -0,0 +1,76 @@ +package box + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" +) + +func (s *Box) startOutbounds() error { + outboundTags := make(map[adapter.Outbound]string) + outbounds := make(map[string]adapter.Outbound) + for i, outboundToStart := range s.outbounds { + var outboundTag string + if outboundToStart.Tag() == "" { + outboundTag = F.ToString(i) + } else { + outboundTag = outboundToStart.Tag() + } + outboundTags[outboundToStart] = outboundTag + outbounds[outboundTag] = outboundToStart + } + started := make(map[string]bool) + for { + canContinue := false + startOne: + for _, outboundToStart := range s.outbounds { + outboundTag := outboundTags[outboundToStart] + if started[outboundTag] { + continue + } + dependencies := outboundToStart.Dependencies() + for _, dependency := range dependencies { + if !started[dependency] { + continue startOne + } + } + started[outboundTag] = true + canContinue = true + if starter, isStarter := outboundToStart.(common.Starter); isStarter { + s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]") + err := starter.Start() + if err != nil { + return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") + } + } + } + if len(started) == len(s.outbounds) { + break + } + if canContinue { + continue + } + currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool { + return !started[outboundTags[it]] + }) + var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error + lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { + problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { + return !started[it] + }) + if common.Contains(oTree, problemOutboundTag) { + return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) + } + problemOutbound := outbounds[problemOutboundTag] + if problemOutbound == nil { + return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]") + } + return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) + } + return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound) + } + return nil +} diff --git a/go.mod b/go.mod index 63f5ea62..1395cb50 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/gvisor v0.0.0-20230611140528-4411f7659a08 github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.2.5-0.20230611070640-2812461739c3 + github.com/sagernet/sing v0.2.5-0.20230613142554-a3b120b25eab github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223 github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 github.com/sagernet/sing-shadowsocks v0.2.2-0.20230509053848-d83f8fe1194c diff --git a/go.sum b/go.sum index 3a2853f9..aa657968 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,8 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.5-0.20230611070640-2812461739c3 h1:OnlfGM8HncECbFiV4s6uAX961fm2UpEKSCXYBLZ1Chg= -github.com/sagernet/sing v0.2.5-0.20230611070640-2812461739c3/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= +github.com/sagernet/sing v0.2.5-0.20230613142554-a3b120b25eab h1:9AVUIqqz/UJCgIrcJBU9mb06JXzfQ/FgEmlgdPgo2dk= +github.com/sagernet/sing v0.2.5-0.20230613142554-a3b120b25eab/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223 h1:L4eMuM07iSHY3UCknFnuFuHoe5clZuF2Xnf2wwA6Lwc= github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY= github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 h1:X3ADfMqeGns1Q1FlXc9kaL9FwW1UM6D6tEQo8jFstpc= diff --git a/outbound/default.go b/outbound/default.go index 81251176..f5f4611e 100644 --- a/outbound/default.go +++ b/outbound/default.go @@ -11,6 +11,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" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -20,11 +21,12 @@ import ( ) type myOutboundAdapter struct { - protocol string - network []string - router adapter.Router - logger log.ContextLogger - tag string + protocol string + network []string + router adapter.Router + logger log.ContextLogger + tag string + dependencies []string } func (a *myOutboundAdapter) Type() string { @@ -39,10 +41,21 @@ func (a *myOutboundAdapter) Network() []string { return a.network } +func (a *myOutboundAdapter) Dependencies() []string { + return a.dependencies +} + func (a *myOutboundAdapter) NewError(ctx context.Context, err error) { NewError(a.logger, ctx, err) } +func withDialerDependency(options option.DialerOptions) []string { + if options.Detour != "" { + return []string{options.Detour} + } + return nil +} + func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn diff --git a/outbound/direct.go b/outbound/direct.go index 256af894..5a0cd34d 100644 --- a/outbound/direct.go +++ b/outbound/direct.go @@ -40,11 +40,12 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti options.UDPFragmentDefault = true outbound := &Direct{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeDirect, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeDirect, + network: []string{N.NetworkTCP, N.NetworkUDP}, + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), diff --git a/outbound/http.go b/outbound/http.go index 019cb37e..e07f2f01 100644 --- a/outbound/http.go +++ b/outbound/http.go @@ -39,11 +39,12 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option } return &HTTP{ myOutboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeHTTP, + network: []string{N.NetworkTCP}, + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, sHTTP.NewClient(sHTTP.Options{ Dialer: detour, diff --git a/outbound/hysteria.go b/outbound/hysteria.go index abe301b2..05b255e8 100644 --- a/outbound/hysteria.go +++ b/outbound/hysteria.go @@ -119,11 +119,12 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL } return &Hysteria{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeHysteria, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, ctx: ctx, dialer: dialer.New(router, options.DialerOptions), diff --git a/outbound/selector.go b/outbound/selector.go index 84b50aac..c99d7af9 100644 --- a/outbound/selector.go +++ b/outbound/selector.go @@ -29,10 +29,11 @@ type Selector struct { func NewSelector(router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) { outbound := &Selector{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSelector, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeSelector, + router: router, + logger: logger, + tag: tag, + dependencies: options.Outbounds, }, tags: options.Outbounds, defaultTag: options.Default, diff --git a/outbound/shadowsocks.go b/outbound/shadowsocks.go index d7168b75..79c18427 100644 --- a/outbound/shadowsocks.go +++ b/outbound/shadowsocks.go @@ -41,11 +41,12 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte } outbound := &Shadowsocks{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeShadowsocks, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, dialer: dialer.New(router, options.DialerOptions), method: method, diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go index 3ce9640a..6b30595d 100644 --- a/outbound/shadowsocksr.go +++ b/outbound/shadowsocksr.go @@ -39,11 +39,12 @@ type ShadowsocksR struct { func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) { outbound := &ShadowsocksR{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowsocksR, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeShadowsocksR, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, dialer: dialer.New(router, options.DialerOptions), serverAddr: options.ServerOptions.Build(), diff --git a/outbound/shadowtls.go b/outbound/shadowtls.go index 6847d2e6..7eeb1bae 100644 --- a/outbound/shadowtls.go +++ b/outbound/shadowtls.go @@ -27,11 +27,12 @@ type ShadowTLS struct { func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) { outbound := &ShadowTLS{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeShadowTLS, + network: []string{N.NetworkTCP}, + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, } if options.TLS == nil || !options.TLS.Enabled { diff --git a/outbound/socks.go b/outbound/socks.go index 3fc4b6e9..f314c9be 100644 --- a/outbound/socks.go +++ b/outbound/socks.go @@ -39,11 +39,12 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio } outbound := &Socks{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSocks, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeSocks, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password), resolve: version == socks.Version4, diff --git a/outbound/ssh.go b/outbound/ssh.go index ad1ca2e3..4f434e78 100644 --- a/outbound/ssh.go +++ b/outbound/ssh.go @@ -46,11 +46,12 @@ type SSH struct { func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { outbound := &SSH{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSSH, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeSSH, + network: []string{N.NetworkTCP}, + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, ctx: ctx, dialer: dialer.New(router, options.DialerOptions), diff --git a/outbound/tor.go b/outbound/tor.go index 07f4e588..0e81066e 100644 --- a/outbound/tor.go +++ b/outbound/tor.go @@ -68,11 +68,12 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger } return &Tor{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTor, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeTor, + network: []string{N.NetworkTCP}, + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, ctx: ctx, proxy: NewProxyListener(ctx, logger, dialer.New(router, options.DialerOptions)), diff --git a/outbound/trojan.go b/outbound/trojan.go index c33f9867..3fc53171 100644 --- a/outbound/trojan.go +++ b/outbound/trojan.go @@ -35,11 +35,12 @@ type Trojan struct { func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { outbound := &Trojan{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTrojan, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeTrojan, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, dialer: dialer.New(router, options.DialerOptions), serverAddr: options.ServerOptions.Build(), diff --git a/outbound/urltest.go b/outbound/urltest.go index 9717a86c..a1996652 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -39,10 +39,11 @@ type URLTest struct { func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { outbound := &URLTest{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeURLTest, - router: router, - logger: logger, - tag: tag, + protocol: C.TypeURLTest, + router: router, + logger: logger, + tag: tag, + dependencies: options.Outbounds, }, ctx: ctx, tags: options.Outbounds, diff --git a/outbound/vless.go b/outbound/vless.go index dd194f7e..85f79fc6 100644 --- a/outbound/vless.go +++ b/outbound/vless.go @@ -38,11 +38,12 @@ type VLESS struct { func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { outbound := &VLESS{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVLESS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeVLESS, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, dialer: dialer.New(router, options.DialerOptions), serverAddr: options.ServerOptions.Build(), diff --git a/outbound/vmess.go b/outbound/vmess.go index 288bc42d..e4113854 100644 --- a/outbound/vmess.go +++ b/outbound/vmess.go @@ -37,11 +37,12 @@ type VMess struct { func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { outbound := &VMess{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVMess, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeVMess, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, dialer: dialer.New(router, options.DialerOptions), serverAddr: options.ServerOptions.Build(), diff --git a/outbound/wireguard.go b/outbound/wireguard.go index c956dade..4c2126c6 100644 --- a/outbound/wireguard.go +++ b/outbound/wireguard.go @@ -40,11 +40,12 @@ type WireGuard struct { func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { outbound := &WireGuard{ myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeWireGuard, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, + protocol: C.TypeWireGuard, + network: options.Network.Build(), + router: router, + logger: logger, + tag: tag, + dependencies: withDialerDependency(options.DialerOptions), }, } var reserved [3]uint8 diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index fd51f8ef..21599305 100644 --- a/transport/wireguard/device_stack.go +++ b/transport/wireguard/device_stack.go @@ -8,13 +8,6 @@ import ( "net/netip" "os" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - wgTun "github.com/sagernet/wireguard-go/tun" - "github.com/sagernet/gvisor/pkg/buffer" "github.com/sagernet/gvisor/pkg/tcpip" "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" @@ -25,6 +18,12 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/transport/icmp" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" + "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + wgTun "github.com/sagernet/wireguard-go/tun" ) var _ Device = (*StackDevice)(nil) diff --git a/transport/wireguard/gonet.go b/transport/wireguard/gonet.go index 6d5d21f9..bc695205 100644 --- a/transport/wireguard/gonet.go +++ b/transport/wireguard/gonet.go @@ -10,14 +10,13 @@ import ( "net/netip" "time" - "github.com/sagernet/sing-tun" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/gvisor/pkg/tcpip" "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" "github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" "github.com/sagernet/gvisor/pkg/waiter" + "github.com/sagernet/sing-tun" + M "github.com/sagernet/sing/common/metadata" ) func DialTCPWithBind(ctx context.Context, s *stack.Stack, localAddr, remoteAddr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*gonet.TCPConn, error) {