diff --git a/dns/transport/local/local.go b/dns/transport/local/local.go index ddb8683569..98c8bdfdc2 100644 --- a/dns/transport/local/local.go +++ b/dns/transport/local/local.go @@ -8,7 +8,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" - "github.com/sagernet/sing-box/dns/transport/hosts" "github.com/sagernet/sing-box/dns/transport/mdns" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -33,21 +32,18 @@ var ( type Transport struct { dns.TransportAdapter - ctx context.Context - logger logger.ContextLogger - hosts *hosts.File - dialer N.Dialer - preferGo bool - fallback bool - resolved ResolvedResolver - mdnsTransport adapter.DNSTransport - dhcpTransport dhcpTransport - system systemResolver - serverSet atomic.Pointer[localServerSet] - serverSetAccess sync.Mutex - - neighborResolver adapter.NeighborResolver - neighborSuffixes []string + ctx context.Context + logger logger.ContextLogger + preferredResolver *PreferredDomainResolver + dialer N.Dialer + preferGo bool + fallback bool + resolved ResolvedResolver + mdnsTransport adapter.DNSTransport + dhcpTransport dhcpTransport + system systemResolver + serverSet atomic.Pointer[localServerSet] + serverSetAccess sync.Mutex } type dhcpTransport interface { @@ -60,29 +56,24 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt if err != nil { return nil, err } - suffixes, err := buildNeighborMatchers(options.NeighborDomain) + preferredResolver, err := NewPreferredDomainResolver(ctx, logger, options) if err != nil { return nil, err } return &Transport{ - TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), - ctx: ctx, - logger: logger, - dialer: transportDialer, - preferGo: options.PreferGo, - neighborSuffixes: suffixes, + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + ctx: ctx, + logger: logger, + preferredResolver: preferredResolver, + dialer: transportDialer, + preferGo: options.PreferGo, }, nil } func (t *Transport) Start(stage adapter.StartStage) error { + t.preferredResolver.Start(stage) switch stage { case adapter.StartStateInitialize: - defaultHosts, err := hosts.NewDefault() - if err != nil { - t.logger.Warn(err) - } else { - t.hosts = defaultHosts - } if !t.preferGo && isSystemdResolvedManaged() { resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger) if err == nil { @@ -109,10 +100,6 @@ func (t *Transport) Start(stage adapter.StartStage) error { } else { t.mdnsTransport = mdns.NewRawTransport(t.TransportAdapter, t.ctx, t.logger) } - router := service.FromContext[adapter.Router](t.ctx) - if router != nil { - t.neighborResolver = router.NeighborResolver() - } fallthrough default: if t.dhcpTransport != nil { @@ -160,12 +147,7 @@ func (t *Transport) Reset() { } func (t *Transport) PreferredDomain(domain string) bool { - if t.hosts != nil { - if len(t.hosts.Lookup(dns.FqdnToDomain(domain))) > 0 { - return true - } - } - return t.hasNeighborHost(domain) || mdns.IsLocalDomain(domain) + return t.preferredResolver.PreferredDomain(domain) } func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { @@ -185,14 +167,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, func (t *Transport) ExchangeAsync(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) { question := message.Question[0] - if t.hosts != nil && (question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA) { - addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name)) - if len(addresses) > 0 { - callback(dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil) - return - } - } - response := t.lookupNeighbor(message) + response := t.preferredResolver.Lookup(message) if response != nil { callback(response, nil) return diff --git a/dns/transport/local/local_neighbor.go b/dns/transport/local/local_neighbor.go index 3dd394a8b6..c313abdc49 100644 --- a/dns/transport/local/local_neighbor.go +++ b/dns/transport/local/local_neighbor.go @@ -24,34 +24,34 @@ func buildNeighborMatchers(domains []string) ([]string, error) { return suffixes, nil } -func (t *Transport) lookupNeighbor(message *mDNS.Msg) *mDNS.Msg { - if t.neighborResolver == nil { +func (r *PreferredDomainResolver) lookupNeighbor(message *mDNS.Msg) *mDNS.Msg { + if r.neighborResolver == nil { return nil } question := message.Question[0] if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA { return nil } - host := extractNeighborHost(mDNS.CanonicalName(question.Name), t.neighborSuffixes) + host := extractNeighborHost(mDNS.CanonicalName(question.Name), r.neighborSuffixes) if host == "" { return nil } - addresses := t.neighborResolver.LookupAddresses(host) + addresses := r.neighborResolver.LookupAddresses(host) if len(addresses) == 0 { return nil } return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL) } -func (t *Transport) hasNeighborHost(domain string) bool { - if t.neighborResolver == nil { +func (r *PreferredDomainResolver) hasNeighborHost(domain string) bool { + if r.neighborResolver == nil { return false } - host := extractNeighborHost(domain, t.neighborSuffixes) + host := extractNeighborHost(domain, r.neighborSuffixes) if host == "" { return false } - return len(t.neighborResolver.LookupAddresses(host)) > 0 + return len(r.neighborResolver.LookupAddresses(host)) > 0 } func extractNeighborHost(canonical string, suffixes []string) string { diff --git a/dns/transport/local/local_preferred.go b/dns/transport/local/local_preferred.go new file mode 100644 index 0000000000..a9027caf29 --- /dev/null +++ b/dns/transport/local/local_preferred.go @@ -0,0 +1,76 @@ +package local + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/hosts" + "github.com/sagernet/sing-box/dns/transport/mdns" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/service" + + mDNS "github.com/miekg/dns" +) + +type PreferredDomainResolver struct { + ctx context.Context + logger logger.ContextLogger + hosts *hosts.File + neighborResolver adapter.NeighborResolver + neighborSuffixes []string +} + +func NewPreferredDomainResolver(ctx context.Context, contextLogger logger.ContextLogger, options option.LocalDNSServerOptions) (*PreferredDomainResolver, error) { + suffixes, err := buildNeighborMatchers(options.NeighborDomain) + if err != nil { + return nil, err + } + return &PreferredDomainResolver{ + ctx: ctx, + logger: contextLogger, + neighborSuffixes: suffixes, + }, nil +} + +func (r *PreferredDomainResolver) Start(stage adapter.StartStage) { + switch stage { + case adapter.StartStateInitialize: + defaultHosts, err := hosts.NewDefault() + if err != nil { + r.logger.Warn(err) + } else { + r.hosts = defaultHosts + } + case adapter.StartStateStart: + router := service.FromContext[adapter.Router](r.ctx) + if router != nil { + r.neighborResolver = router.NeighborResolver() + } + } +} + +func (r *PreferredDomainResolver) PreferredDomain(domain string) bool { + if r.hosts != nil { + if len(r.hosts.Lookup(dns.FqdnToDomain(domain))) > 0 { + return true + } + } + return r.hasNeighborHost(domain) || mdns.IsLocalDomain(domain) +} + +func (r *PreferredDomainResolver) Lookup(message *mDNS.Msg) *mDNS.Msg { + question := message.Question[0] + if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA { + return nil + } + if r.hosts != nil { + addresses := r.hosts.Lookup(dns.FqdnToDomain(question.Name)) + if len(addresses) > 0 { + return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL) + } + } + return r.lookupNeighbor(message) +} diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 41793b9296..bc05eaff03 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -30,7 +30,7 @@ func baseContext(platformInterface PlatformInterface) context.Context { if platformInterface != nil { if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil { dns.RegisterTransport[option.LocalDNSServerOptions](dnsRegistry, C.DNSTypeLocal, func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) { - return newPlatformTransport(localTransport, tag, options), nil + return newPlatformTransport(ctx, logger, localTransport, tag, options) }) } } diff --git a/experimental/libbox/dns.go b/experimental/libbox/dns.go index 7a3b8a47dc..38e6874088 100644 --- a/experimental/libbox/dns.go +++ b/experimental/libbox/dns.go @@ -9,6 +9,8 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" + "github.com/sagernet/sing-box/dns/transport/local" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -23,21 +25,26 @@ type LocalDNSTransport interface { Exchange(ctx *ExchangeContext, message []byte) error } -var _ adapter.DNSTransport = (*platformTransport)(nil) - type platformTransport struct { dns.TransportAdapter - iif LocalDNSTransport + iif LocalDNSTransport + preferredResolver *local.PreferredDomainResolver } -func newPlatformTransport(iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) *platformTransport { - return &platformTransport{ - TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), - iif: iif, +func newPlatformTransport(ctx context.Context, logger log.ContextLogger, iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) (*platformTransport, error) { + preferredResolver, err := local.NewPreferredDomainResolver(ctx, logger, options) + if err != nil { + return nil, err } + return &platformTransport{ + TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options), + iif: iif, + preferredResolver: preferredResolver, + }, nil } func (p *platformTransport) Start(stage adapter.StartStage) error { + p.preferredResolver.Start(stage) return nil } @@ -48,7 +55,15 @@ func (p *platformTransport) Close() error { func (p *platformTransport) Reset() { } +func (p *platformTransport) PreferredDomain(domain string) bool { + return p.preferredResolver.PreferredDomain(domain) +} + func (p *platformTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + localResponse := p.preferredResolver.Lookup(message) + if localResponse != nil { + return localResponse, nil + } response := &ExchangeContext{ context: ctx, } @@ -151,3 +166,8 @@ func (c *ExchangeContext) ErrorCode(code int32) { func (c *ExchangeContext) ErrnoCode(code int32) { c.error = syscall.Errno(code) } + +var ( + _ adapter.DNSTransport = (*platformTransport)(nil) + _ adapter.DNSTransportWithPreferredDomain = (*platformTransport)(nil) +)