mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-20 16:24:25 +00:00
Fix local DNS server ignoring systemd-resolved global DNS servers
This commit is contained in:
@@ -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 := ¶llelExchange{
|
||||
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"))
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user