refactor: DNS

This commit is contained in:
世界 2025-03-16 14:50:44 +08:00
parent 572ee775b1
commit 6c9e61a0a0
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
89 changed files with 4794 additions and 1733 deletions

View file

@ -2,17 +2,10 @@ 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"
@ -20,13 +13,7 @@ import (
"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"
@ -35,334 +22,71 @@ import (
var _ adapter.Router = (*Router)(nil)
type Router struct {
ctx context.Context
logger log.ContextLogger
dnsLogger log.ContextLogger
inbound adapter.InboundManager
outbound adapter.OutboundManager
connection adapter.ConnectionManager
network 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
ctx context.Context
logger log.ContextLogger
inbound adapter.InboundManager
outbound adapter.OutboundManager
dns adapter.DNSRouter
dnsTransport adapter.DNSTransportManager
connection adapter.ConnectionManager
network adapter.NetworkManager
rules []adapter.Rule
needFindProcess bool
ruleSets []adapter.RuleSet
ruleSetMap map[string]adapter.RuleSet
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"),
inbound: service.FromContext[adapter.InboundManager](ctx),
outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
network: 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,
CacheCapacity: dnsOptions.DNSClientOptions.CacheCapacity,
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
func NewRouter(ctx context.Context, logFactory log.Factory, options option.RouteOptions, dnsOptions option.DNSOptions) *Router {
return &Router{
ctx: ctx,
logger: logFactory.NewLogger("router"),
inbound: service.FromContext[adapter.InboundManager](ctx),
outbound: service.FromContext[adapter.OutboundManager](ctx),
dns: service.FromContext[adapter.DNSRouter](ctx),
dnsTransport: service.FromContext[adapter.DNSTransportManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
network: service.FromContext[adapter.NetworkManager](ctx),
rules: make([]adapter.Rule, 0, len(options.Rules)),
ruleSetMap: make(map[string]adapter.RuleSet),
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
pauseManager: service.FromContext[pause.Manager](ctx),
platformInterface: service.FromContext[platform.Interface](ctx),
needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule),
}
}
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)
func (r *Router) Initialize(rules []option.Rule, ruleSets []option.RuleSet) error {
for i, options := range rules {
rule, err := R.NewRule(r.ctx, r.logger, options, false)
if err != nil {
return E.Cause(err, "parse rule[", i, "]")
}
if transportTagMap[tag] {
return nil, E.New("duplicate dns server tag: ", tag)
}
transportTags[i] = tag
transportTagMap[tag] = true
r.rules = append(r.rules, rule)
}
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
}
for i, options := range ruleSets {
if _, exists := r.ruleSetMap[options.Tag]; exists {
return E.New("duplicate rule-set tag: ", options.Tag)
}
if len(transports) == len(dummyTransportMap) {
break
ruleSet, err := R.NewRuleSet(r.ctx, r.logger, options)
if err != nil {
return E.Cause(err, "parse rule-set[", i, "]")
}
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, " "))
r.ruleSets = append(r.ruleSets, ruleSet)
r.ruleSetMap[options.Tag] = ruleSet
}
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(ctx, 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
return 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, "]")
}
}
var cacheContext *adapter.HTTPStartContext
if len(r.ruleSets) > 0 {
monitor.Start("initialize rule-set")
@ -438,7 +162,7 @@ func (r *Router) Start(stage adapter.StartStage) error {
r.started = true
return nil
case adapter.StartStateStarted:
for _, ruleSet := range r.ruleSetMap {
for _, ruleSet := range r.ruleSets {
ruleSet.Cleanup()
}
runtime.GC()
@ -456,34 +180,6 @@ func (r *Router) Close() error {
})
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()
}
for i, ruleSet := range r.ruleSets {
monitor.Start("close rule-set[", i, "]")
err = E.Append(err, ruleSet.Close(), func(err error) error {
@ -494,10 +190,6 @@ func (r *Router) Close() error {
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
@ -517,7 +209,5 @@ func (r *Router) SetTracker(tracker adapter.ConnectionTracker) {
func (r *Router) ResetNetwork() {
r.network.ResetNetwork()
for _, transport := range r.transports {
transport.Reset()
}
r.dns.ResetNetwork()
}