Fix local DNS server ignoring systemd-resolved global DNS servers

This commit is contained in:
世界
2026-10-02 18:00:08 +08:00
parent 4537a1ac00
commit ddbf34639a
3 changed files with 466 additions and 192 deletions
+64
View File
@@ -82,6 +82,70 @@ type sequentialCallState struct {
continued bool
}
func ExchangeParallel(ctx context.Context, exchangers []AsyncExchanger, accept func(response *mDNS.Msg, err error) bool, callback func(response *mDNS.Msg, err error)) {
if len(exchangers) == 0 {
callback(nil, E.New("missing exchangers"))
return
}
exchangeCtx, cancel := context.WithCancel(ctx)
parallel := &parallelExchange{
accept: accept,
callback: callback,
cancel: cancel,
responses: make([]*mDNS.Msg, len(exchangers)),
errors: make([]error, len(exchangers)),
pending: len(exchangers),
}
for i, exchanger := range exchangers {
exchanger(exchangeCtx, func(response *mDNS.Msg, err error) {
parallel.complete(i, response, err)
})
}
}
type parallelExchange struct {
access sync.Mutex
accept func(response *mDNS.Msg, err error) bool
callback func(response *mDNS.Msg, err error)
cancel context.CancelFunc
responses []*mDNS.Msg
errors []error
pending int
completed bool
}
func (p *parallelExchange) complete(index int, response *mDNS.Msg, err error) {
p.access.Lock()
if p.completed {
p.access.Unlock()
return
}
accepted := p.accept(response, err)
if !accepted {
p.responses[index] = response
p.errors[index] = err
p.pending--
if p.pending > 0 {
p.access.Unlock()
return
}
}
p.completed = true
p.access.Unlock()
p.cancel()
if accepted {
p.callback(response, err)
return
}
for i, finalResponse := range p.responses {
if p.errors[i] == nil {
p.callback(finalResponse, nil)
return
}
}
p.callback(nil, E.Errors(p.errors...))
}
func ExchangeNames(ctx context.Context, names []string, question mDNS.Question, exchangerFor func(fqdn string) AsyncExchanger, callback func(response *mDNS.Msg, err error)) {
if len(names) == 0 {
callback(nil, E.New("missing name candidates"))
-1
View File
@@ -11,6 +11,5 @@ type ResolvedResolver interface {
Close() error
Reset()
Environment() []string
Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
ExchangeAsync(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error))
}
+402 -191
View File
@@ -6,9 +6,11 @@ import (
"errors"
"net/netip"
"os"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
@@ -17,7 +19,6 @@ import (
"github.com/sagernet/sing-box/dns"
dnsTransport "github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/service/resolved"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
@@ -54,8 +55,11 @@ func isSystemdResolvedManaged() bool {
type DBusResolvedResolver struct {
ctx context.Context
logger logger.ContextLogger
interfaceFinder control.InterfaceFinder
interfaceMonitor tun.DefaultInterfaceMonitor
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
networkMonitor tun.NetworkUpdateMonitor
networkCallback *list.Element[tun.NetworkUpdateCallback]
systemBus *dbus.Conn
savedServerSet atomic.Pointer[resolvedServerSet]
updateAccess sync.Mutex
@@ -66,23 +70,88 @@ type DBusResolvedResolver struct {
}
type resolvedServerSet struct {
servers []resolvedServer
scopes []*resolvedScope
signature []string
}
// Match levels of dns_scope_good_domain() in systemd-resolved: a routing or search
// domain match yields its label count, and only scopes at the best level are queried.
const (
resolvedScopeNoMatch = -3
resolvedScopeLastResort = -2
resolvedScopeMaybe = -1
)
// systemd-resolved only signals changes of the DNS server list, while clients such as
// NetworkManager and systemd-networkd configure domains, default route and DNS over TLS
// of a link in separate calls right after it; link state changes are not signaled at all.
const resolvedUpdateDelay = time.Second
// SD_RESOLVED_DNS bit of org.freedesktop.resolve1.Link.ScopesMask, set when the link has a unicast DNS scope.
const resolvedScopesMaskDNS = 1 << 0
// TRANSACTION_UDP_TIMEOUT_USEC in systemd-resolved (resolved-timeouts.h), after which a transaction
// is resent to the next server of the scope.
const resolvedServerTimeout = 5 * time.Second
type resolvedScope struct {
domains []string
defaultRoute bool
fallback bool
servers []resolvedServer
currentServer atomic.Uint32
}
type resolvedServer struct {
primaryTransport adapter.DNSTransport
fallbackTransport adapter.DNSTransport
}
type resolvedScopeSpecification struct {
interfaceName string
dnsOverTLSMode string
domains []string
defaultRoute bool
fallback bool
servers []resolvedServerSpecification
}
type resolvedServerSpecification struct {
address netip.Addr
port uint16
serverName string
}
type resolvedManagerDNS struct {
InterfaceIndex int32
Family int32
Address []byte
}
type resolvedManagerDNSEx struct {
InterfaceIndex int32
Family int32
Address []byte
Port uint16
Name string
}
type resolvedManagerDomain struct {
InterfaceIndex int32
Domain string
RoutingOnly bool
}
type resolvedManagerServer struct {
interfaceIndex int32
address []byte
port uint16
serverName string
}
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
networkManager := service.FromContext[adapter.NetworkManager](ctx)
interfaceMonitor := networkManager.InterfaceMonitor()
if interfaceMonitor == nil {
return nil, os.ErrInvalid
}
@@ -93,7 +162,9 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso
return &DBusResolvedResolver{
ctx: ctx,
logger: logger,
interfaceFinder: networkManager.InterfaceFinder(),
interfaceMonitor: interfaceMonitor,
networkMonitor: networkManager.NetworkMonitor(),
systemBus: systemBus,
}, nil
}
@@ -101,6 +172,9 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso
func (t *DBusResolvedResolver) Start() error {
t.updateStatus(t.ctx)
t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface)
if t.networkMonitor != nil {
t.networkCallback = t.networkMonitor.RegisterCallback(t.postUpdateStatus)
}
err := t.systemBus.BusObject().AddMatchSignal(
"org.freedesktop.DBus",
"NameOwnerChanged",
@@ -143,6 +217,9 @@ func (t *DBusResolvedResolver) Close() error {
if t.interfaceCallback != nil {
t.interfaceMonitor.UnregisterCallback(t.interfaceCallback)
}
if t.networkCallback != nil {
t.networkMonitor.UnregisterCallback(t.networkCallback)
}
if t.systemBus != nil {
_ = t.systemBus.Close()
}
@@ -155,10 +232,12 @@ func (t *DBusResolvedResolver) Reset() {
if serverSet == nil {
return
}
for _, server := range serverSet.servers {
server.primaryTransport.Reset()
if server.fallbackTransport != nil {
server.fallbackTransport.Reset()
for _, scope := range serverSet.scopes {
for _, server := range scope.servers {
server.primaryTransport.Reset()
if server.fallbackTransport != nil {
server.fallbackTransport.Reset()
}
}
}
}
@@ -171,40 +250,20 @@ func (t *DBusResolvedResolver) Environment() []string {
return serverSet.signature
}
func (t *DBusResolvedResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
serverSet := t.savedServerSet.Load()
if serverSet == nil {
var err error
serverSet, err = t.checkResolved(context.Background())
if err != nil {
return nil, err
}
previousServerSet := t.savedServerSet.Swap(serverSet)
if previousServerSet != nil {
_ = previousServerSet.Close()
}
}
response, err := t.exchangeServerSet(ctx, message, serverSet)
if err == nil {
return response, nil
}
t.updateStatus(t.ctx)
refreshedServerSet := t.savedServerSet.Load()
if refreshedServerSet == nil || refreshedServerSet == serverSet {
return nil, err
}
return t.exchangeServerSet(ctx, message, refreshedServerSet)
}
func (t *DBusResolvedResolver) ExchangeAsync(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) {
serverSet := t.savedServerSet.Load()
if serverSet == nil {
go func() {
callback(t.Exchange(ctx, message))
err := t.updateStatus(t.ctx)
if err != nil {
callback(nil, err)
return
}
t.exchangeServerSet(ctx, message, t.savedServerSet.Load(), callback)
}()
return
}
t.exchangeServerSetAsync(ctx, message, serverSet, func(response *mDNS.Msg, err error) {
t.exchangeServerSet(ctx, message, serverSet, func(response *mDNS.Msg, err error) {
if err == nil {
callback(response, nil)
return
@@ -216,29 +275,129 @@ func (t *DBusResolvedResolver) ExchangeAsync(ctx context.Context, message *mDNS.
callback(nil, err)
return
}
t.exchangeServerSetAsync(ctx, message, refreshedServerSet, callback)
t.exchangeServerSet(ctx, message, refreshedServerSet, callback)
}()
})
}
func (t *DBusResolvedResolver) exchangeServerSetAsync(ctx context.Context, message *mDNS.Msg, serverSet *resolvedServerSet, callback func(response *mDNS.Msg, err error)) {
if len(serverSet.servers) == 0 {
callback(nil, E.New("link has no DNS servers configured"))
func (t *DBusResolvedResolver) exchangeServerSet(ctx context.Context, message *mDNS.Msg, serverSet *resolvedServerSet, callback func(response *mDNS.Msg, err error)) {
if serverSet == nil {
callback(nil, os.ErrClosed)
return
}
serverExchangers := make([]dnsTransport.AsyncExchanger, 0, len(serverSet.servers))
for _, server := range serverSet.servers {
scopes := serverSet.selectScopes(message.Question[0].Name)
if len(scopes) == 0 {
callback(nil, E.New("no appropriate name servers or networks for name found"))
return
}
scopeExchangers := common.Map(scopes, func(scope *resolvedScope) dnsTransport.AsyncExchanger {
return func(exchangeCtx context.Context, exchangeCallback func(response *mDNS.Msg, err error)) {
scope.exchange(exchangeCtx, message, exchangeCallback)
}
})
dnsTransport.ExchangeParallel(ctx, scopeExchangers, acceptResolvedScopeResponse, callback)
}
func (s *resolvedScope) exchange(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) {
serverCount := uint32(len(s.servers))
currentServer := s.currentServer.Load()
serverExchangers := make([]dnsTransport.AsyncExchanger, 0, serverCount)
for i := range serverCount {
serverIndex := (currentServer + i) % serverCount
server := s.servers[serverIndex]
serverExchangers = append(serverExchangers, func(exchangeCtx context.Context, exchangeCallback func(response *mDNS.Msg, err error)) {
server.primaryTransport.ExchangeAsync(exchangeCtx, message, func(response *mDNS.Msg, exchangeErr error) {
if exchangeErr != nil && server.fallbackTransport != nil {
server.fallbackTransport.ExchangeAsync(exchangeCtx, message, exchangeCallback)
return
server.exchange(exchangeCtx, message, func(response *mDNS.Msg, err error) {
if exchangeCtx.Err() == nil && !acceptResolvedServerResponse(response, err) {
s.currentServer.CompareAndSwap(serverIndex, (serverIndex+1)%serverCount)
}
exchangeCallback(response, exchangeErr)
exchangeCallback(response, err)
})
})
}
dnsTransport.ExchangeSequential(ctx, serverExchangers, nil, callback)
dnsTransport.ExchangeSequential(ctx, serverExchangers, acceptResolvedServerResponse, callback)
}
func (s *resolvedServer) exchange(ctx context.Context, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) {
exchangeResolvedTransport(ctx, s.primaryTransport, message, func(response *mDNS.Msg, err error) {
if err != nil && s.fallbackTransport != nil {
exchangeResolvedTransport(ctx, s.fallbackTransport, message, callback)
return
}
callback(response, err)
})
}
func exchangeResolvedTransport(ctx context.Context, transport adapter.DNSTransport, message *mDNS.Msg, callback func(response *mDNS.Msg, err error)) {
exchangeCtx, cancel := context.WithTimeout(ctx, resolvedServerTimeout)
transport.ExchangeAsync(exchangeCtx, message, func(response *mDNS.Msg, err error) {
cancel()
callback(response, err)
})
}
// systemd-resolved starts a query on every scope at the best match level at once
// (resolved-dns-query.c dns_query_go_scopes) and completes it with the first positive answer
// (dns_query_ready). Within a scope, SERVFAIL and timeouts move the scope to its next server, which
// later queries start from (resolved-dns-transaction.c dns_transaction_retry).
func acceptResolvedServerResponse(response *mDNS.Msg, err error) bool {
return err == nil && response.Rcode != mDNS.RcodeServerFailure
}
func acceptResolvedScopeResponse(response *mDNS.Msg, err error) bool {
return err == nil && response.Rcode == mDNS.RcodeSuccess
}
func (s *resolvedServerSet) selectScopes(name string) []*resolvedScope {
var (
bestMatch = resolvedScopeNoMatch
selectedScopes []*resolvedScope
)
for _, scope := range s.scopes {
match := scope.match(name)
if match == resolvedScopeNoMatch || match < bestMatch {
continue
}
if match > bestMatch {
bestMatch = match
selectedScopes = selectedScopes[:0]
}
selectedScopes = append(selectedScopes, scope)
}
return selectedScopes
}
func (s *resolvedScope) match(name string) int {
bestLabels := -1
for _, domain := range s.domains {
if mDNS.IsSubDomain(domain, name) {
bestLabels = max(bestLabels, mDNS.CountLabel(domain))
}
}
if bestLabels >= 0 {
return bestLabels
}
if !s.defaultRoute {
return resolvedScopeNoMatch
}
if s.fallback {
return resolvedScopeLastResort
}
return resolvedScopeMaybe
}
func (s *resolvedServerSet) Close() error {
return E.Errors(common.Map(s.scopes, (*resolvedScope).Close)...)
}
func (s *resolvedScope) Close() error {
var errors []error
for _, server := range s.servers {
errors = append(errors, server.primaryTransport.Close())
if server.fallbackTransport != nil {
errors = append(errors, server.fallbackTransport.Close())
}
}
return E.Errors(errors...)
}
func (t *DBusResolvedResolver) loopUpdateStatus() {
@@ -275,22 +434,35 @@ func (t *DBusResolvedResolver) postUpdateStatus() {
}
go func() {
defer updateCancel()
timer := time.NewTimer(resolvedUpdateDelay)
defer timer.Stop()
select {
case <-timer.C:
case <-updateContext.Done():
return
}
t.updateStatus(updateContext)
}()
}
func (t *DBusResolvedResolver) updateStatus(ctx context.Context) {
func (t *DBusResolvedResolver) updateStatus(ctx context.Context) error {
t.updateRunAccess.Lock()
defer t.updateRunAccess.Unlock()
if t.closed || ctx.Err() != nil {
return
if t.closed {
return os.ErrClosed
}
if ctx.Err() != nil {
return ctx.Err()
}
serverSet, err := t.checkResolved(ctx)
if t.closed || ctx.Err() != nil {
if serverSet != nil {
_ = serverSet.Close()
}
return
if t.closed {
return os.ErrClosed
}
return ctx.Err()
}
oldServerSet := t.savedServerSet.Swap(serverSet)
if oldServerSet != nil {
@@ -304,34 +476,16 @@ func (t *DBusResolvedResolver) updateStatus(ctx context.Context) {
if oldServerSet != nil {
t.logger.Debug("systemd-resolved service is gone")
}
return
return err
} else if oldServerSet == nil {
t.logger.Debug("using systemd-resolved service as resolver")
}
}
func (t *DBusResolvedResolver) exchangeServerSet(ctx context.Context, message *mDNS.Msg, serverSet *resolvedServerSet) (*mDNS.Msg, error) {
if serverSet == nil || len(serverSet.servers) == 0 {
return nil, E.New("link has no DNS servers configured")
}
var lastError error
for _, server := range serverSet.servers {
response, err := server.primaryTransport.Exchange(ctx, message)
if err != nil && server.fallbackTransport != nil {
response, err = server.fallbackTransport.Exchange(ctx, message)
}
if err != nil {
lastError = err
continue
}
return response, nil
}
return nil, lastError
return nil
}
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServerSet, error) {
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
err := dbusObject.(*dbus.Object).CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Err
managerObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
err := managerObject.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Err
if err != nil {
return nil, err
}
@@ -339,97 +493,167 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServ
if defaultInterface == nil {
return nil, E.New("missing default interface")
}
call := dbusObject.(*dbus.Object).CallWithContext(
ctx,
"org.freedesktop.resolve1.Manager.GetLink",
0,
int32(defaultInterface.Index),
)
if call.Err != nil {
return nil, call.Err
}
var linkPath dbus.ObjectPath
err = call.Store(&linkPath)
managerProperties, err := loadResolvedProperties(ctx, managerObject, "org.freedesktop.resolve1.Manager")
if err != nil {
return nil, err
}
linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath)
if linkObject == nil {
return nil, E.New("missing link object for default interface")
}
dnsOverTLSMode, err := loadResolvedLinkDNSOverTLS(linkObject)
servers, err := loadResolvedManagerServers(managerProperties, "DNS")
if err != nil {
return nil, err
}
err = ctx.Err()
var managerDomains []resolvedManagerDomain
err = storeResolvedProperty(managerProperties, "Domains", &managerDomains)
if err != nil {
return nil, err
}
linkDNSEx, err := loadResolvedLinkDNSEx(linkObject)
var dnsOverTLSMode string
err = storeResolvedProperty(managerProperties, "DNSOverTLS", &dnsOverTLSMode)
if err != nil {
return nil, err
}
err = ctx.Err()
if err != nil {
return nil, err
globalScope := resolvedScopeSpecification{
interfaceName: defaultInterface.Name,
dnsOverTLSMode: dnsOverTLSMode,
defaultRoute: true,
}
linkDNS, err := loadResolvedLinkDNS(linkObject)
if err != nil {
return nil, err
var linkScopes []resolvedScopeSpecification
linkScopeIndexes := make(map[int32]int)
for _, server := range servers {
if server.interfaceIndex == 0 {
serverSpecification, loaded := buildResolvedServerSpecification(globalScope.interfaceName, server)
if loaded {
globalScope.servers = append(globalScope.servers, serverSpecification)
}
continue
}
scopeIndex, loaded := linkScopeIndexes[server.interfaceIndex]
if !loaded {
linkScope, linkLoaded, linkErr := t.loadResolvedLinkScope(ctx, managerObject, server.interfaceIndex)
if linkErr != nil {
return nil, linkErr
}
if !linkLoaded {
linkScopeIndexes[server.interfaceIndex] = -1
continue
}
scopeIndex = len(linkScopes)
linkScopeIndexes[server.interfaceIndex] = scopeIndex
linkScopes = append(linkScopes, linkScope)
} else if scopeIndex < 0 {
continue
}
serverSpecification, loaded := buildResolvedServerSpecification(linkScopes[scopeIndex].interfaceName, server)
if loaded {
linkScopes[scopeIndex].servers = append(linkScopes[scopeIndex].servers, serverSpecification)
}
}
if len(linkDNSEx) == 0 && len(linkDNS) == 0 {
for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() {
if inbound.Type() == C.TypeTun {
return nil, E.New("No appropriate name servers or networks for name found")
for _, domain := range managerDomains {
domainName := mDNS.Fqdn(domain.Domain)
if domain.InterfaceIndex == 0 {
globalScope.domains = append(globalScope.domains, domainName)
continue
}
scopeIndex, loaded := linkScopeIndexes[domain.InterfaceIndex]
if loaded && scopeIndex >= 0 {
linkScopes[scopeIndex].domains = append(linkScopes[scopeIndex].domains, domainName)
}
}
if len(globalScope.servers) == 0 && !slices.ContainsFunc(linkScopes, func(it resolvedScopeSpecification) bool {
return it.defaultRoute && len(it.servers) > 0
}) {
fallbackServers, fallbackErr := loadResolvedManagerServers(managerProperties, "FallbackDNS")
if fallbackErr != nil {
return nil, fallbackErr
}
for _, server := range fallbackServers {
serverSpecification, loaded := buildResolvedServerSpecification(globalScope.interfaceName, server)
if loaded {
globalScope.servers = append(globalScope.servers, serverSpecification)
}
}
return nil, E.New("link has no DNS servers configured")
globalScope.fallback = true
}
scopeSpecifications := common.Filter(append([]resolvedScopeSpecification{globalScope}, linkScopes...), func(it resolvedScopeSpecification) bool {
return len(it.servers) > 0
})
if len(scopeSpecifications) == 0 {
return nil, E.New("no DNS servers configured")
}
serverSet := &resolvedServerSet{}
for _, scopeSpecification := range scopeSpecifications {
scope, createErr := t.createResolvedScope(scopeSpecification)
if createErr != nil {
_ = serverSet.Close()
return nil, createErr
}
serverSet.scopes = append(serverSet.scopes, scope)
for _, serverSpecification := range scopeSpecification.servers {
serverSet.signature = append(serverSet.signature, scopeSpecification.interfaceName+"/"+M.SocksaddrFrom(serverSpecification.address, serverSpecification.port).String())
}
}
return serverSet, nil
}
func (t *DBusResolvedResolver) loadResolvedLinkScope(ctx context.Context, managerObject dbus.BusObject, interfaceIndex int32) (resolvedScopeSpecification, bool, error) {
linkInterface, err := t.interfaceFinder.ByIndex(int(interfaceIndex))
if err != nil || slices.Contains(t.interfaceMonitor.MyInterfaces(), linkInterface.Name) {
return resolvedScopeSpecification{}, false, nil
}
var linkPath dbus.ObjectPath
err = managerObject.CallWithContext(ctx, "org.freedesktop.resolve1.Manager.GetLink", 0, interfaceIndex).Store(&linkPath)
if err != nil {
return resolvedScopeSpecification{}, false, err
}
linkProperties, err := loadResolvedProperties(ctx, t.systemBus.Object("org.freedesktop.resolve1", linkPath), "org.freedesktop.resolve1.Link")
if err != nil {
return resolvedScopeSpecification{}, false, err
}
var scopesMask uint64
err = storeResolvedProperty(linkProperties, "ScopesMask", &scopesMask)
if err != nil {
return resolvedScopeSpecification{}, false, err
}
if scopesMask&resolvedScopesMaskDNS == 0 {
return resolvedScopeSpecification{}, false, nil
}
linkScope := resolvedScopeSpecification{
interfaceName: linkInterface.Name,
}
err = storeResolvedProperty(linkProperties, "DNSOverTLS", &linkScope.dnsOverTLSMode)
if err != nil {
return resolvedScopeSpecification{}, false, err
}
err = storeResolvedProperty(linkProperties, "DefaultRoute", &linkScope.defaultRoute)
if err != nil {
return resolvedScopeSpecification{}, false, err
}
return linkScope, true, nil
}
func (t *DBusResolvedResolver) createResolvedScope(scopeSpecification resolvedScopeSpecification) (*resolvedScope, error) {
scope := &resolvedScope{
domains: scopeSpecification.domains,
defaultRoute: scopeSpecification.defaultRoute,
fallback: scopeSpecification.fallback,
}
serverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{
AbstractDialerOptions: option.AbstractDialerOptions{
BindInterface: defaultInterface.Name,
BindInterface: scopeSpecification.interfaceName,
UDPFragmentDefault: true,
},
})
if err != nil {
return nil, err
}
var serverSpecifications []resolvedServerSpecification
if len(linkDNSEx) > 0 {
for _, entry := range linkDNSEx {
serverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, entry.Port, entry.Name)
if !loaded {
continue
}
serverSpecifications = append(serverSpecifications, serverSpecification)
}
} else {
for _, entry := range linkDNS {
serverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, 0, "")
if !loaded {
continue
}
serverSpecifications = append(serverSpecifications, serverSpecification)
}
}
if len(serverSpecifications) == 0 {
return nil, E.New("no valid DNS servers on link")
}
serverSet := &resolvedServerSet{
servers: make([]resolvedServer, 0, len(serverSpecifications)),
signature: common.Map(serverSpecifications, func(it resolvedServerSpecification) string {
return M.SocksaddrFrom(it.address, it.port).String()
}),
}
for _, serverSpecification := range serverSpecifications {
server, createErr := t.createResolvedServer(serverDialer, dnsOverTLSMode, serverSpecification)
for _, serverSpecification := range scopeSpecification.servers {
server, createErr := t.createResolvedServer(serverDialer, scopeSpecification.dnsOverTLSMode, serverSpecification)
if createErr != nil {
_ = serverSet.Close()
_ = scope.Close()
return nil, createErr
}
serverSet.servers = append(serverSet.servers, server)
scope.servers = append(scope.servers, server)
}
return serverSet, nil
return scope, nil
}
func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) {
@@ -501,19 +725,8 @@ func (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, se
return serverTransport, nil
}
func (s *resolvedServerSet) Close() error {
var errors []error
for _, server := range s.servers {
errors = append(errors, server.primaryTransport.Close())
if server.fallbackTransport != nil {
errors = append(errors, server.fallbackTransport.Close())
}
}
return E.Errors(errors...)
}
func buildResolvedServerSpecification(interfaceName string, rawAddress []byte, port uint16, serverName string) (resolvedServerSpecification, bool) {
address, loaded := netip.AddrFromSlice(rawAddress)
func buildResolvedServerSpecification(interfaceName string, server resolvedManagerServer) (resolvedServerSpecification, bool) {
address, loaded := netip.AddrFromSlice(server.address)
if !loaded {
return resolvedServerSpecification{}, false
}
@@ -522,8 +735,8 @@ func buildResolvedServerSpecification(interfaceName string, rawAddress []byte, p
}
return resolvedServerSpecification{
address: address,
port: port,
serverName: serverName,
port: server.port,
serverName: server.serverName,
}, true
}
@@ -537,57 +750,55 @@ func resolvedServerPort(port uint16, useTLS bool) uint16 {
return 53
}
func loadResolvedLinkDNS(linkObject dbus.BusObject) ([]resolved.LinkDNS, error) {
dnsProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS")
func loadResolvedProperties(ctx context.Context, object dbus.BusObject, interfaceName string) (map[string]dbus.Variant, error) {
var properties map[string]dbus.Variant
err := object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.GetAll", 0, interfaceName).Store(&properties)
if err != nil {
if isResolvedUnknownPropertyError(err) {
return nil, nil
}
return nil, err
return nil, E.Cause(err, "load ", interfaceName, " properties")
}
var linkDNS []resolved.LinkDNS
err = dnsProperty.Store(&linkDNS)
if err != nil {
return nil, err
}
return linkDNS, nil
return properties, nil
}
func loadResolvedLinkDNSEx(linkObject dbus.BusObject) ([]resolved.LinkDNSEx, error) {
dnsProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNSEx")
if err != nil {
if isResolvedUnknownPropertyError(err) {
return nil, nil
}
return nil, err
func storeResolvedProperty(properties map[string]dbus.Variant, name string, value any) error {
property, loaded := properties[name]
if !loaded {
return nil
}
var linkDNSEx []resolved.LinkDNSEx
err = dnsProperty.Store(&linkDNSEx)
err := property.Store(value)
if err != nil {
return E.Cause(err, "parse resolved property ", name)
}
return nil
}
func loadResolvedManagerServers(properties map[string]dbus.Variant, name string) ([]resolvedManagerServer, error) {
_, loaded := properties[name+"Ex"]
if loaded {
var serversEx []resolvedManagerDNSEx
err := storeResolvedProperty(properties, name+"Ex", &serversEx)
if err != nil {
return nil, err
}
return common.Map(serversEx, func(it resolvedManagerDNSEx) resolvedManagerServer {
return resolvedManagerServer{
interfaceIndex: it.InterfaceIndex,
address: it.Address,
port: it.Port,
serverName: it.Name,
}
}), nil
}
var servers []resolvedManagerDNS
err := storeResolvedProperty(properties, name, &servers)
if err != nil {
return nil, err
}
return linkDNSEx, nil
}
func loadResolvedLinkDNSOverTLS(linkObject dbus.BusObject) (string, error) {
dnsOverTLSProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNSOverTLS")
if err != nil {
if isResolvedUnknownPropertyError(err) {
return "", nil
return common.Map(servers, func(it resolvedManagerDNS) resolvedManagerServer {
return resolvedManagerServer{
interfaceIndex: it.InterfaceIndex,
address: it.Address,
}
return "", err
}
var dnsOverTLSMode string
err = dnsOverTLSProperty.Store(&dnsOverTLSMode)
if err != nil {
return "", err
}
return dnsOverTLSMode, nil
}
func isResolvedUnknownPropertyError(err error) bool {
var dbusError dbus.Error
return errors.As(err, &dbusError) && dbusError.Name == "org.freedesktop.DBus.Error.UnknownProperty"
}), nil
}
func shouldUpdateResolvedServerSet(signal *dbus.Signal) bool {