Files
sing-box-extended-mirror/dns/transport/local/local_resolved_linux.go
T

834 lines
25 KiB
Go

package local
import (
"bufio"
"context"
"errors"
"net/netip"
"os"
"slices"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
dnsTransport "github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
"github.com/godbus/dbus/v5"
mDNS "github.com/miekg/dns"
)
func isSystemdResolvedManaged() bool {
resolvContent, err := os.Open("/etc/resolv.conf")
if err != nil {
return false
}
defer resolvContent.Close()
scanner := bufio.NewScanner(resolvContent)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || line[0] != '#' {
return false
}
if strings.Contains(line, "systemd-resolved") {
return true
}
}
return false
}
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
updateCancel context.CancelFunc
updateRunAccess sync.Mutex
closed bool
closeOnce sync.Once
}
type resolvedServerSet struct {
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) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
interfaceMonitor := networkManager.InterfaceMonitor()
if interfaceMonitor == nil {
return nil, os.ErrInvalid
}
systemBus, err := dbus.SystemBus()
if err != nil {
return nil, err
}
return &DBusResolvedResolver{
ctx: ctx,
logger: logger,
interfaceFinder: networkManager.InterfaceFinder(),
interfaceMonitor: interfaceMonitor,
networkMonitor: networkManager.NetworkMonitor(),
systemBus: systemBus,
}, nil
}
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",
dbus.WithMatchSender("org.freedesktop.DBus"),
dbus.WithMatchArg(0, "org.freedesktop.resolve1"),
).Err
if err != nil {
return E.Cause(err, "configure resolved restart listener")
}
err = t.systemBus.BusObject().AddMatchSignal(
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
dbus.WithMatchSender("org.freedesktop.resolve1"),
dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"),
).Err
if err != nil {
return E.Cause(err, "configure resolved properties listener")
}
go t.loopUpdateStatus()
return nil
}
func (t *DBusResolvedResolver) Close() error {
var closeErr error
t.closeOnce.Do(func() {
t.updateAccess.Lock()
updateCancel := t.updateCancel
t.updateCancel = nil
t.updateAccess.Unlock()
if updateCancel != nil {
updateCancel()
}
t.updateRunAccess.Lock()
t.closed = true
serverSet := t.savedServerSet.Swap(nil)
t.updateRunAccess.Unlock()
if serverSet != nil {
closeErr = serverSet.Close()
}
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()
}
})
return closeErr
}
func (t *DBusResolvedResolver) Reset() {
serverSet := t.savedServerSet.Load()
if serverSet == nil {
return
}
for _, scope := range serverSet.scopes {
for _, server := range scope.servers {
server.primaryTransport.Reset()
if server.fallbackTransport != nil {
server.fallbackTransport.Reset()
}
}
}
}
func (t *DBusResolvedResolver) Environment() []string {
serverSet := t.savedServerSet.Load()
if serverSet == nil {
return nil
}
return serverSet.signature
}
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() {
err := t.updateStatus(t.ctx)
if err != nil {
callback(nil, err)
return
}
t.exchangeServerSet(ctx, message, t.savedServerSet.Load(), callback)
}()
return
}
t.exchangeServerSet(ctx, message, serverSet, func(response *mDNS.Msg, err error) {
if err == nil {
callback(response, nil)
return
}
go func() {
t.updateStatus(t.ctx)
refreshedServerSet := t.savedServerSet.Load()
if refreshedServerSet == nil || refreshedServerSet == serverSet {
callback(nil, err)
return
}
t.exchangeServerSet(ctx, message, refreshedServerSet, callback)
}()
})
}
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
}
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.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, err)
})
})
}
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() {
signalChan := make(chan *dbus.Signal, 1)
t.systemBus.Signal(signalChan)
for signal := range signalChan {
switch signal.Name {
case "org.freedesktop.DBus.NameOwnerChanged":
if len(signal.Body) != 3 {
continue
}
newOwner, loaded := signal.Body[2].(string)
if !loaded || newOwner == "" {
continue
}
t.postUpdateStatus()
case "org.freedesktop.DBus.Properties.PropertiesChanged":
if !shouldUpdateResolvedServerSet(signal) {
continue
}
t.postUpdateStatus()
}
}
}
func (t *DBusResolvedResolver) postUpdateStatus() {
updateContext, updateCancel := context.WithCancel(t.ctx)
t.updateAccess.Lock()
previousCancel := t.updateCancel
t.updateCancel = updateCancel
t.updateAccess.Unlock()
if previousCancel != nil {
previousCancel()
}
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) error {
t.updateRunAccess.Lock()
defer t.updateRunAccess.Unlock()
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()
}
if t.closed {
return os.ErrClosed
}
return ctx.Err()
}
oldServerSet := t.savedServerSet.Swap(serverSet)
if oldServerSet != nil {
_ = oldServerSet.Close()
}
if err != nil {
var dbusErr dbus.Error
if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwner" {
t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable"))
}
if oldServerSet != nil {
t.logger.Debug("systemd-resolved service is gone")
}
return err
} else if oldServerSet == nil {
t.logger.Debug("using systemd-resolved service as resolver")
}
return nil
}
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServerSet, error) {
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
}
defaultInterface := t.interfaceMonitor.DefaultInterface()
if defaultInterface == nil {
return nil, E.New("missing default interface")
}
managerProperties, err := loadResolvedProperties(ctx, managerObject, "org.freedesktop.resolve1.Manager")
if err != nil {
return nil, err
}
servers, err := loadResolvedManagerServers(managerProperties, "DNS")
if err != nil {
return nil, err
}
var managerDomains []resolvedManagerDomain
err = storeResolvedProperty(managerProperties, "Domains", &managerDomains)
if err != nil {
return nil, err
}
var dnsOverTLSMode string
err = storeResolvedProperty(managerProperties, "DNSOverTLS", &dnsOverTLSMode)
if err != nil {
return nil, err
}
globalScope := resolvedScopeSpecification{
interfaceName: defaultInterface.Name,
dnsOverTLSMode: dnsOverTLSMode,
defaultRoute: true,
}
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)
}
}
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)
}
}
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: scopeSpecification.interfaceName,
UDPFragmentDefault: true,
},
})
if err != nil {
return nil, err
}
for _, serverSpecification := range scopeSpecification.servers {
server, createErr := t.createResolvedServer(serverDialer, scopeSpecification.dnsOverTLSMode, serverSpecification)
if createErr != nil {
_ = scope.Close()
return nil, createErr
}
scope.servers = append(scope.servers, server)
}
return scope, nil
}
func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) {
if dnsOverTLSMode == "yes" {
primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true)
if err != nil {
return resolvedServer{}, err
}
return resolvedServer{
primaryTransport: primaryTransport,
}, nil
}
if dnsOverTLSMode == "opportunistic" {
primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true)
if err != nil {
return resolvedServer{}, err
}
fallbackTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false)
if err != nil {
_ = primaryTransport.Close()
return resolvedServer{}, err
}
return resolvedServer{
primaryTransport: primaryTransport,
fallbackTransport: fallbackTransport,
}, nil
}
primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false)
if err != nil {
return resolvedServer{}, err
}
return resolvedServer{
primaryTransport: primaryTransport,
}, nil
}
func (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, serverSpecification resolvedServerSpecification, useTLS bool) (adapter.DNSTransport, error) {
serverAddress := M.SocksaddrFrom(serverSpecification.address, resolvedServerPort(serverSpecification.port, useTLS))
if useTLS {
tlsAddress := serverSpecification.address
if tlsAddress.Zone() != "" {
tlsAddress = tlsAddress.WithZone("")
}
serverName := serverSpecification.serverName
if serverName == "" {
serverName = tlsAddress.String()
}
tlsConfig, err := tls.NewClient(t.ctx, t.logger, tlsAddress.String(), option.OutboundTLSOptions{
Enabled: true,
ServerName: serverName,
})
if err != nil {
return nil, err
}
serverTransport := dnsTransport.NewTLSRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeTLS, "", nil), serverDialer, serverAddress, tlsConfig)
err = serverTransport.Start(adapter.StartStateStart)
if err != nil {
_ = serverTransport.Close()
return nil, err
}
return serverTransport, nil
}
serverTransport := dnsTransport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, "", nil), serverDialer, serverAddress)
err := serverTransport.Start(adapter.StartStateStart)
if err != nil {
_ = serverTransport.Close()
return nil, err
}
return serverTransport, nil
}
func buildResolvedServerSpecification(interfaceName string, server resolvedManagerServer) (resolvedServerSpecification, bool) {
address, loaded := netip.AddrFromSlice(server.address)
if !loaded {
return resolvedServerSpecification{}, false
}
if address.Is6() && address.IsLinkLocalUnicast() && address.Zone() == "" {
address = address.WithZone(interfaceName)
}
return resolvedServerSpecification{
address: address,
port: server.port,
serverName: server.serverName,
}, true
}
func resolvedServerPort(port uint16, useTLS bool) uint16 {
if port > 0 {
return port
}
if useTLS {
return 853
}
return 53
}
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 {
return nil, E.Cause(err, "load ", interfaceName, " properties")
}
return properties, nil
}
func storeResolvedProperty(properties map[string]dbus.Variant, name string, value any) error {
property, loaded := properties[name]
if !loaded {
return nil
}
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 common.Map(servers, func(it resolvedManagerDNS) resolvedManagerServer {
return resolvedManagerServer{
interfaceIndex: it.InterfaceIndex,
address: it.Address,
}
}), nil
}
func shouldUpdateResolvedServerSet(signal *dbus.Signal) bool {
if len(signal.Body) != 3 {
return true
}
changedProperties, loaded := signal.Body[1].(map[string]dbus.Variant)
if !loaded {
return true
}
for propertyName := range changedProperties {
switch propertyName {
case "DNS", "DNSEx", "DNSOverTLS":
return true
}
}
invalidatedProperties, loaded := signal.Body[2].([]string)
if !loaded {
return true
}
for _, propertyName := range invalidatedProperties {
switch propertyName {
case "DNS", "DNSEx", "DNSOverTLS":
return true
}
}
return false
}
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
t.postUpdateStatus()
}