123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- package route
- import (
- "context"
- "net/netip"
- "net/url"
- "os"
- "runtime"
- "strings"
- "time"
- "github.com/sagernet/sing-box/adapter"
- "github.com/sagernet/sing-box/common/dialer"
- "github.com/sagernet/sing-box/common/geoip"
- "github.com/sagernet/sing-box/common/geosite"
- "github.com/sagernet/sing-box/common/process"
- "github.com/sagernet/sing-box/common/taskmonitor"
- C "github.com/sagernet/sing-box/constant"
- "github.com/sagernet/sing-box/experimental/libbox/platform"
- "github.com/sagernet/sing-box/log"
- "github.com/sagernet/sing-box/option"
- R "github.com/sagernet/sing-box/route/rule"
- "github.com/sagernet/sing-box/transport/fakeip"
- "github.com/sagernet/sing-dns"
- "github.com/sagernet/sing/common"
- E "github.com/sagernet/sing/common/exceptions"
- F "github.com/sagernet/sing/common/format"
- M "github.com/sagernet/sing/common/metadata"
- N "github.com/sagernet/sing/common/network"
- "github.com/sagernet/sing/common/task"
- "github.com/sagernet/sing/service"
- "github.com/sagernet/sing/service/pause"
- )
- var _ adapter.Router = (*Router)(nil)
- type Router struct {
- ctx context.Context
- logger log.ContextLogger
- dnsLogger log.ContextLogger
- inboundManager adapter.InboundManager
- outboundManager adapter.OutboundManager
- networkManager adapter.NetworkManager
- rules []adapter.Rule
- needGeoIPDatabase bool
- needGeositeDatabase bool
- geoIPOptions option.GeoIPOptions
- geositeOptions option.GeositeOptions
- geoIPReader *geoip.Reader
- geositeReader *geosite.Reader
- geositeCache map[string]adapter.Rule
- needFindProcess bool
- dnsClient *dns.Client
- defaultDomainStrategy dns.DomainStrategy
- dnsRules []adapter.DNSRule
- ruleSets []adapter.RuleSet
- ruleSetMap map[string]adapter.RuleSet
- defaultTransport dns.Transport
- transports []dns.Transport
- transportMap map[string]dns.Transport
- transportDomainStrategy map[dns.Transport]dns.DomainStrategy
- dnsReverseMapping *DNSReverseMapping
- fakeIPStore adapter.FakeIPStore
- processSearcher process.Searcher
- pauseManager pause.Manager
- tracker adapter.ConnectionTracker
- platformInterface platform.Interface
- needWIFIState bool
- started bool
- }
- func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
- router := &Router{
- ctx: ctx,
- logger: logFactory.NewLogger("router"),
- dnsLogger: logFactory.NewLogger("dns"),
- inboundManager: service.FromContext[adapter.InboundManager](ctx),
- outboundManager: service.FromContext[adapter.OutboundManager](ctx),
- networkManager: service.FromContext[adapter.NetworkManager](ctx),
- rules: make([]adapter.Rule, 0, len(options.Rules)),
- dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
- ruleSetMap: make(map[string]adapter.RuleSet),
- needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
- needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
- geoIPOptions: common.PtrValueOrDefault(options.GeoIP),
- geositeOptions: common.PtrValueOrDefault(options.Geosite),
- geositeCache: make(map[string]adapter.Rule),
- needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
- defaultDomainStrategy: dns.DomainStrategy(dnsOptions.Strategy),
- pauseManager: service.FromContext[pause.Manager](ctx),
- platformInterface: service.FromContext[platform.Interface](ctx),
- needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),
- }
- service.MustRegister[adapter.Router](ctx, router)
- router.dnsClient = dns.NewClient(dns.ClientOptions{
- DisableCache: dnsOptions.DNSClientOptions.DisableCache,
- DisableExpire: dnsOptions.DNSClientOptions.DisableExpire,
- IndependentCache: dnsOptions.DNSClientOptions.IndependentCache,
- RDRC: func() dns.RDRCStore {
- cacheFile := service.FromContext[adapter.CacheFile](ctx)
- if cacheFile == nil {
- return nil
- }
- if !cacheFile.StoreRDRC() {
- return nil
- }
- return cacheFile
- },
- Logger: router.dnsLogger,
- })
- for i, ruleOptions := range options.Rules {
- routeRule, err := R.NewRule(ctx, router.logger, ruleOptions, true)
- if err != nil {
- return nil, E.Cause(err, "parse rule[", i, "]")
- }
- router.rules = append(router.rules, routeRule)
- }
- for i, dnsRuleOptions := range dnsOptions.Rules {
- dnsRule, err := R.NewDNSRule(ctx, router.logger, dnsRuleOptions, true)
- if err != nil {
- return nil, E.Cause(err, "parse dns rule[", i, "]")
- }
- router.dnsRules = append(router.dnsRules, dnsRule)
- }
- for i, ruleSetOptions := range options.RuleSet {
- if _, exists := router.ruleSetMap[ruleSetOptions.Tag]; exists {
- return nil, E.New("duplicate rule-set tag: ", ruleSetOptions.Tag)
- }
- ruleSet, err := R.NewRuleSet(ctx, router.logger, ruleSetOptions)
- if err != nil {
- return nil, E.Cause(err, "parse rule-set[", i, "]")
- }
- router.ruleSets = append(router.ruleSets, ruleSet)
- router.ruleSetMap[ruleSetOptions.Tag] = ruleSet
- }
- transports := make([]dns.Transport, len(dnsOptions.Servers))
- dummyTransportMap := make(map[string]dns.Transport)
- transportMap := make(map[string]dns.Transport)
- transportTags := make([]string, len(dnsOptions.Servers))
- transportTagMap := make(map[string]bool)
- transportDomainStrategy := make(map[dns.Transport]dns.DomainStrategy)
- for i, server := range dnsOptions.Servers {
- var tag string
- if server.Tag != "" {
- tag = server.Tag
- } else {
- tag = F.ToString(i)
- }
- if transportTagMap[tag] {
- return nil, E.New("duplicate dns server tag: ", tag)
- }
- transportTags[i] = tag
- transportTagMap[tag] = true
- }
- outboundManager := service.FromContext[adapter.OutboundManager](ctx)
- for {
- lastLen := len(dummyTransportMap)
- for i, server := range dnsOptions.Servers {
- tag := transportTags[i]
- if _, exists := dummyTransportMap[tag]; exists {
- continue
- }
- var detour N.Dialer
- if server.Detour == "" {
- detour = dialer.NewDefaultOutbound(outboundManager)
- } else {
- detour = dialer.NewDetour(outboundManager, server.Detour)
- }
- var serverProtocol string
- switch server.Address {
- case "local":
- serverProtocol = "local"
- default:
- serverURL, _ := url.Parse(server.Address)
- var serverAddress string
- if serverURL != nil {
- if serverURL.Scheme == "" {
- serverProtocol = "udp"
- } else {
- serverProtocol = serverURL.Scheme
- }
- serverAddress = serverURL.Hostname()
- }
- if serverAddress == "" {
- serverAddress = server.Address
- }
- notIpAddress := !M.ParseSocksaddr(serverAddress).Addr.IsValid()
- if server.AddressResolver != "" {
- if !transportTagMap[server.AddressResolver] {
- return nil, E.New("parse dns server[", tag, "]: address resolver not found: ", server.AddressResolver)
- }
- if upstream, exists := dummyTransportMap[server.AddressResolver]; exists {
- detour = dns.NewDialerWrapper(detour, router.dnsClient, upstream, dns.DomainStrategy(server.AddressStrategy), time.Duration(server.AddressFallbackDelay))
- } else {
- continue
- }
- } else if notIpAddress && strings.Contains(server.Address, ".") {
- return nil, E.New("parse dns server[", tag, "]: missing address_resolver")
- }
- }
- var clientSubnet netip.Prefix
- if server.ClientSubnet != nil {
- clientSubnet = netip.Prefix(common.PtrValueOrDefault(server.ClientSubnet))
- } else if dnsOptions.ClientSubnet != nil {
- clientSubnet = netip.Prefix(common.PtrValueOrDefault(dnsOptions.ClientSubnet))
- }
- if serverProtocol == "" {
- serverProtocol = "transport"
- }
- transport, err := dns.CreateTransport(dns.TransportOptions{
- Context: ctx,
- Logger: logFactory.NewLogger(F.ToString("dns/", serverProtocol, "[", tag, "]")),
- Name: tag,
- Dialer: detour,
- Address: server.Address,
- ClientSubnet: clientSubnet,
- })
- if err != nil {
- return nil, E.Cause(err, "parse dns server[", tag, "]")
- }
- transports[i] = transport
- dummyTransportMap[tag] = transport
- if server.Tag != "" {
- transportMap[server.Tag] = transport
- }
- strategy := dns.DomainStrategy(server.Strategy)
- if strategy != dns.DomainStrategyAsIS {
- transportDomainStrategy[transport] = strategy
- }
- }
- if len(transports) == len(dummyTransportMap) {
- break
- }
- if lastLen != len(dummyTransportMap) {
- continue
- }
- unresolvedTags := common.MapIndexed(common.FilterIndexed(dnsOptions.Servers, func(index int, server option.DNSServerOptions) bool {
- _, exists := dummyTransportMap[transportTags[index]]
- return !exists
- }), func(index int, server option.DNSServerOptions) string {
- return transportTags[index]
- })
- if len(unresolvedTags) == 0 {
- panic(F.ToString("unexpected unresolved dns servers: ", len(transports), " ", len(dummyTransportMap), " ", len(transportMap)))
- }
- return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " "))
- }
- var defaultTransport dns.Transport
- if dnsOptions.Final != "" {
- defaultTransport = dummyTransportMap[dnsOptions.Final]
- if defaultTransport == nil {
- return nil, E.New("default dns server not found: ", dnsOptions.Final)
- }
- }
- if defaultTransport == nil {
- if len(transports) == 0 {
- transports = append(transports, common.Must1(dns.CreateTransport(dns.TransportOptions{
- Context: ctx,
- Name: "local",
- Address: "local",
- Dialer: common.Must1(dialer.NewDefault(router.networkManager, option.DialerOptions{})),
- })))
- }
- defaultTransport = transports[0]
- }
- if _, isFakeIP := defaultTransport.(adapter.FakeIPTransport); isFakeIP {
- return nil, E.New("default DNS server cannot be fakeip")
- }
- router.defaultTransport = defaultTransport
- router.transports = transports
- router.transportMap = transportMap
- router.transportDomainStrategy = transportDomainStrategy
- if dnsOptions.ReverseMapping {
- router.dnsReverseMapping = NewDNSReverseMapping()
- }
- if fakeIPOptions := dnsOptions.FakeIP; fakeIPOptions != nil && dnsOptions.FakeIP.Enabled {
- var inet4Range netip.Prefix
- var inet6Range netip.Prefix
- if fakeIPOptions.Inet4Range != nil {
- inet4Range = *fakeIPOptions.Inet4Range
- }
- if fakeIPOptions.Inet6Range != nil {
- inet6Range = *fakeIPOptions.Inet6Range
- }
- router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range)
- }
- return router, nil
- }
- func (r *Router) Start(stage adapter.StartStage) error {
- monitor := taskmonitor.New(r.logger, C.StartTimeout)
- switch stage {
- case adapter.StartStateInitialize:
- if r.fakeIPStore != nil {
- monitor.Start("initialize fakeip store")
- err := r.fakeIPStore.Start()
- monitor.Finish()
- if err != nil {
- return err
- }
- }
- case adapter.StartStateStart:
- if r.needGeoIPDatabase {
- monitor.Start("initialize geoip database")
- err := r.prepareGeoIPDatabase()
- monitor.Finish()
- if err != nil {
- return err
- }
- }
- if r.needGeositeDatabase {
- monitor.Start("initialize geosite database")
- err := r.prepareGeositeDatabase()
- monitor.Finish()
- if err != nil {
- return err
- }
- }
- if r.needGeositeDatabase {
- for _, rule := range r.rules {
- err := rule.UpdateGeosite()
- if err != nil {
- r.logger.Error("failed to initialize geosite: ", err)
- }
- }
- for _, rule := range r.dnsRules {
- err := rule.UpdateGeosite()
- if err != nil {
- r.logger.Error("failed to initialize geosite: ", err)
- }
- }
- err := common.Close(r.geositeReader)
- if err != nil {
- return err
- }
- r.geositeCache = nil
- r.geositeReader = nil
- }
- monitor.Start("initialize DNS client")
- r.dnsClient.Start()
- monitor.Finish()
- for i, rule := range r.dnsRules {
- monitor.Start("initialize DNS rule[", i, "]")
- err := rule.Start()
- monitor.Finish()
- if err != nil {
- return E.Cause(err, "initialize DNS rule[", i, "]")
- }
- }
- for i, transport := range r.transports {
- monitor.Start("initialize DNS transport[", i, "]")
- err := transport.Start()
- monitor.Finish()
- if err != nil {
- return E.Cause(err, "initialize DNS server[", i, "]")
- }
- }
- case adapter.StartStatePostStart:
- var cacheContext *adapter.HTTPStartContext
- if len(r.ruleSets) > 0 {
- monitor.Start("initialize rule-set")
- cacheContext = adapter.NewHTTPStartContext()
- var ruleSetStartGroup task.Group
- for i, ruleSet := range r.ruleSets {
- ruleSetInPlace := ruleSet
- ruleSetStartGroup.Append0(func(ctx context.Context) error {
- err := ruleSetInPlace.StartContext(ctx, cacheContext)
- if err != nil {
- return E.Cause(err, "initialize rule-set[", i, "]")
- }
- return nil
- })
- }
- ruleSetStartGroup.Concurrency(5)
- ruleSetStartGroup.FastFail()
- err := ruleSetStartGroup.Run(r.ctx)
- monitor.Finish()
- if err != nil {
- return err
- }
- }
- if cacheContext != nil {
- cacheContext.Close()
- }
- needFindProcess := r.needFindProcess
- for _, ruleSet := range r.ruleSets {
- metadata := ruleSet.Metadata()
- if metadata.ContainsProcessRule {
- needFindProcess = true
- }
- if metadata.ContainsWIFIRule {
- r.needWIFIState = true
- }
- }
- if needFindProcess {
- if r.platformInterface != nil {
- r.processSearcher = r.platformInterface
- } else {
- monitor.Start("initialize process searcher")
- searcher, err := process.NewSearcher(process.Config{
- Logger: r.logger,
- PackageManager: r.networkManager.PackageManager(),
- })
- monitor.Finish()
- if err != nil {
- if err != os.ErrInvalid {
- r.logger.Warn(E.Cause(err, "create process searcher"))
- }
- } else {
- r.processSearcher = searcher
- }
- }
- }
- for i, rule := range r.rules {
- monitor.Start("initialize rule[", i, "]")
- err := rule.Start()
- monitor.Finish()
- if err != nil {
- return E.Cause(err, "initialize rule[", i, "]")
- }
- }
- for _, ruleSet := range r.ruleSets {
- monitor.Start("post start rule_set[", ruleSet.Name(), "]")
- err := ruleSet.PostStart()
- monitor.Finish()
- if err != nil {
- return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]")
- }
- }
- r.started = true
- return nil
- case adapter.StartStateStarted:
- for _, ruleSet := range r.ruleSetMap {
- ruleSet.Cleanup()
- }
- runtime.GC()
- }
- return nil
- }
- func (r *Router) Close() error {
- monitor := taskmonitor.New(r.logger, C.StopTimeout)
- var err error
- 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()
- }
- for i, rule := range r.dnsRules {
- monitor.Start("close dns rule[", i, "]")
- err = E.Append(err, rule.Close(), func(err error) error {
- return E.Cause(err, "close dns rule[", i, "]")
- })
- monitor.Finish()
- }
- for i, transport := range r.transports {
- monitor.Start("close dns transport[", i, "]")
- err = E.Append(err, transport.Close(), func(err error) error {
- return E.Cause(err, "close dns transport[", i, "]")
- })
- monitor.Finish()
- }
- if r.geoIPReader != nil {
- monitor.Start("close geoip reader")
- err = E.Append(err, r.geoIPReader.Close(), func(err error) error {
- return E.Cause(err, "close geoip reader")
- })
- monitor.Finish()
- }
- if r.fakeIPStore != nil {
- monitor.Start("close fakeip store")
- err = E.Append(err, r.fakeIPStore.Close(), func(err error) error {
- return E.Cause(err, "close fakeip store")
- })
- monitor.Finish()
- }
- return err
- }
- func (r *Router) FakeIPStore() adapter.FakeIPStore {
- return r.fakeIPStore
- }
- func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {
- ruleSet, loaded := r.ruleSetMap[tag]
- return ruleSet, loaded
- }
- func (r *Router) NeedWIFIState() bool {
- return r.needWIFIState
- }
- func (r *Router) Rules() []adapter.Rule {
- return r.rules
- }
- func (r *Router) SetTracker(tracker adapter.ConnectionTracker) {
- r.tracker = tracker
- }
- func (r *Router) ResetNetwork() {
- r.networkManager.ResetNetwork()
- for _, transport := range r.transports {
- transport.Reset()
- }
- }
|