mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-20 08:14:24 +00:00
236 lines
7.1 KiB
Go
236 lines
7.1 KiB
Go
package route
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing-box/common/urltest"
|
|
"github.com/sagernet/sing-box/experimental/clashmode"
|
|
"github.com/sagernet/sing-box/log"
|
|
"github.com/sagernet/sing-box/option"
|
|
"github.com/sagernet/sing/common/observable"
|
|
"github.com/sagernet/sing/service"
|
|
)
|
|
|
|
var _ adapter.LifecycleService = (*ReferenceManager)(nil)
|
|
|
|
type ReferenceManager struct {
|
|
ctx context.Context
|
|
logger log.ContextLogger
|
|
rules []option.Rule
|
|
dnsRules []option.DNSRule
|
|
staticOutbounds []string
|
|
staticTransports []string
|
|
subscriber *observable.Subscriber[struct{}]
|
|
unreferencedOutbounds map[string]bool
|
|
unreferencedTransports map[string]bool
|
|
}
|
|
|
|
func NewReferenceManager(ctx context.Context, logger log.ContextLogger, options option.Options) *ReferenceManager {
|
|
var (
|
|
staticOutbounds []string
|
|
staticTransports []string
|
|
)
|
|
if options.NTP != nil && options.NTP.Enabled && options.NTP.Detour != "" {
|
|
staticOutbounds = append(staticOutbounds, options.NTP.Detour)
|
|
}
|
|
for _, outboundOptions := range options.Outbounds {
|
|
staticTransports = appendDomainResolver(staticTransports, outboundOptions.Options)
|
|
}
|
|
for _, endpointOptions := range options.Endpoints {
|
|
staticTransports = appendDomainResolver(staticTransports, endpointOptions.Options)
|
|
}
|
|
var (
|
|
rules []option.Rule
|
|
dnsRules []option.DNSRule
|
|
)
|
|
if options.Route != nil {
|
|
rules = options.Route.Rules
|
|
}
|
|
if options.DNS != nil {
|
|
dnsRules = options.DNS.Rules
|
|
}
|
|
return &ReferenceManager{
|
|
ctx: ctx,
|
|
logger: logger,
|
|
rules: rules,
|
|
dnsRules: dnsRules,
|
|
staticOutbounds: staticOutbounds,
|
|
staticTransports: staticTransports,
|
|
}
|
|
}
|
|
|
|
func appendDomainResolver(transports []string, rawOptions any) []string {
|
|
dialerOptionsWrapper, isDialerOptionsWrapper := rawOptions.(option.DialerOptionsWrapper)
|
|
if !isDialerOptionsWrapper {
|
|
return transports
|
|
}
|
|
dialerOptions := dialerOptionsWrapper.TakeDialerOptions()
|
|
if dialerOptions.DomainResolver == nil || dialerOptions.DomainResolver.Server == "" {
|
|
return transports
|
|
}
|
|
return append(transports, dialerOptions.DomainResolver.Server)
|
|
}
|
|
|
|
func (m *ReferenceManager) Name() string {
|
|
return "reference manager"
|
|
}
|
|
|
|
func (m *ReferenceManager) Start(stage adapter.StartStage) error {
|
|
if stage != adapter.StartStateStarted {
|
|
return nil
|
|
}
|
|
m.subscriber = observable.NewSubscriber[struct{}](1)
|
|
history := service.PtrFromContext[urltest.HistoryStorage](m.ctx)
|
|
if history != nil {
|
|
history.AddUpdateHook(m.subscriber)
|
|
}
|
|
clashMode := service.PtrFromContext[clashmode.Manager](m.ctx)
|
|
if clashMode != nil {
|
|
clashMode.AddUpdateHook(m.subscriber)
|
|
}
|
|
m.update()
|
|
go m.loop()
|
|
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 {
|
|
select {
|
|
case <-subscription:
|
|
case <-done:
|
|
return
|
|
}
|
|
m.update()
|
|
}
|
|
}
|
|
|
|
func (m *ReferenceManager) update() {
|
|
var mode string
|
|
clashMode := service.PtrFromContext[clashmode.Manager](m.ctx)
|
|
if clashMode != nil {
|
|
mode = clashMode.Mode()
|
|
}
|
|
outboundManager := service.FromContext[adapter.OutboundManager](m.ctx)
|
|
endpointManager := service.FromContext[adapter.EndpointManager](m.ctx)
|
|
inboundManager := service.FromContext[adapter.InboundManager](m.ctx)
|
|
serviceManager := service.FromContext[adapter.ServiceManager](m.ctx)
|
|
transportManager := service.FromContext[adapter.DNSTransportManager](m.ctx)
|
|
networkManager := service.FromContext[adapter.NetworkManager](m.ctx)
|
|
httpClientManager := service.FromContext[adapter.HTTPClientManager](m.ctx)
|
|
|
|
transportQueue := slices.Clone(m.staticTransports)
|
|
outboundQueue := slices.Clone(m.staticOutbounds)
|
|
if !collectDNSRuleReferences(m.dnsRules, mode, &transportQueue) {
|
|
defaultTransport := transportManager.Default()
|
|
if defaultTransport != nil {
|
|
transportQueue = append(transportQueue, defaultTransport.Tag())
|
|
}
|
|
}
|
|
transportQueue = append(transportQueue, networkManager.DefaultOptions().DomainResolver)
|
|
if !collectRuleReferences(m.rules, mode, &outboundQueue, &transportQueue) {
|
|
defaultOutbound := outboundManager.Default()
|
|
if defaultOutbound != nil {
|
|
outboundQueue = append(outboundQueue, defaultOutbound.Tag())
|
|
}
|
|
}
|
|
for _, endpoint := range endpointManager.Endpoints() {
|
|
outboundQueue = append(outboundQueue, endpoint.Tag())
|
|
}
|
|
for _, inbound := range inboundManager.Inbounds() {
|
|
referrer, isReferrer := inbound.(adapter.Referrer)
|
|
if isReferrer {
|
|
outboundQueue = append(outboundQueue, referrer.References()...)
|
|
}
|
|
}
|
|
for _, boxService := range serviceManager.Services() {
|
|
referrer, isReferrer := boxService.(adapter.Referrer)
|
|
if isReferrer {
|
|
outboundQueue = append(outboundQueue, referrer.References()...)
|
|
}
|
|
}
|
|
referrer, isReferrer := httpClientManager.(adapter.Referrer)
|
|
if isReferrer {
|
|
outboundQueue = append(outboundQueue, referrer.References()...)
|
|
}
|
|
|
|
referencedTransports := make(map[string]bool)
|
|
for len(transportQueue) > 0 {
|
|
tag := transportQueue[0]
|
|
transportQueue = transportQueue[1:]
|
|
if tag == "" || referencedTransports[tag] {
|
|
continue
|
|
}
|
|
transport, loaded := transportManager.Transport(tag)
|
|
if !loaded {
|
|
continue
|
|
}
|
|
referencedTransports[tag] = true
|
|
transportQueue = append(transportQueue, transport.Dependencies()...)
|
|
transportReferrer, isTransportReferrer := transport.(adapter.Referrer)
|
|
if isTransportReferrer {
|
|
outboundQueue = append(outboundQueue, transportReferrer.References()...)
|
|
}
|
|
}
|
|
referencedOutbounds := make(map[string]bool)
|
|
for len(outboundQueue) > 0 {
|
|
tag := outboundQueue[0]
|
|
outboundQueue = outboundQueue[1:]
|
|
if tag == "" || referencedOutbounds[tag] {
|
|
continue
|
|
}
|
|
outbound, loaded := outboundManager.Outbound(tag)
|
|
if !loaded {
|
|
continue
|
|
}
|
|
referencedOutbounds[tag] = true
|
|
outboundReferrer, isOutboundReferrer := outbound.(adapter.Referrer)
|
|
if isOutboundReferrer {
|
|
outboundQueue = append(outboundQueue, outboundReferrer.References()...)
|
|
} else {
|
|
outboundQueue = append(outboundQueue, outbound.Dependencies()...)
|
|
}
|
|
}
|
|
|
|
unreferencedOutbounds := make(map[string]bool)
|
|
for _, outbound := range outboundManager.Outbounds() {
|
|
tag := outbound.Tag()
|
|
if referencedOutbounds[tag] {
|
|
continue
|
|
}
|
|
unreferencedOutbounds[tag] = true
|
|
closer, isCloser := outbound.(adapter.IdleConnectionCloser)
|
|
if !isCloser {
|
|
continue
|
|
}
|
|
if !m.unreferencedOutbounds[tag] {
|
|
m.logger.Debug("outbound/", outbound.Type(), "[", tag, "] is unreferenced, closing idle connections")
|
|
}
|
|
closer.CloseIdleConnections()
|
|
}
|
|
m.unreferencedOutbounds = unreferencedOutbounds
|
|
|
|
unreferencedTransports := make(map[string]bool)
|
|
for _, transport := range transportManager.Transports() {
|
|
tag := transport.Tag()
|
|
if referencedTransports[tag] {
|
|
continue
|
|
}
|
|
unreferencedTransports[tag] = true
|
|
if m.unreferencedTransports != nil && !m.unreferencedTransports[tag] {
|
|
m.logger.Debug("dns/", transport.Type(), "[", tag, "] is unreferenced, resetting")
|
|
transport.Reset()
|
|
}
|
|
}
|
|
m.unreferencedTransports = unreferencedTransports
|
|
}
|