From 1d72a6b30e01ff672747886db178af3f38e3cc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 21 Aug 2025 16:23:14 +0800 Subject: [PATCH] Fix resolve using resolved --- dns/transport/local/local_resolved_linux.go | 111 +++++++++++++++++--- 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/dns/transport/local/local_resolved_linux.go b/dns/transport/local/local_resolved_linux.go index 125a5d32..279f9c8e 100644 --- a/dns/transport/local/local_resolved_linux.go +++ b/dns/transport/local/local_resolved_linux.go @@ -2,15 +2,19 @@ package local import ( "context" + "errors" "os" "sync" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/service/resolved" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" + "github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/service" "github.com/godbus/dbus/v5" @@ -18,11 +22,18 @@ import ( ) type DBusResolvedResolver struct { - logger logger.ContextLogger - interfaceMonitor tun.DefaultInterfaceMonitor - systemBus *dbus.Conn - resoledObject atomic.TypedValue[dbus.BusObject] - closeOnce sync.Once + ctx context.Context + logger logger.ContextLogger + interfaceMonitor tun.DefaultInterfaceMonitor + interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback] + systemBus *dbus.Conn + resoledObject atomic.Pointer[ResolvedObject] + closeOnce sync.Once +} + +type ResolvedObject struct { + dbus.BusObject + InterfaceIndex int32 } func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) { @@ -35,6 +46,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso return nil, err } return &DBusResolvedResolver{ + ctx: ctx, logger: logger, interfaceMonitor: interfaceMonitor, systemBus: systemBus, @@ -43,6 +55,7 @@ func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (Reso func (t *DBusResolvedResolver) Start() error { t.updateStatus() + t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface) err := t.systemBus.BusObject().AddMatchSignal( "org.freedesktop.DBus", "NameOwnerChanged", @@ -58,6 +71,9 @@ func (t *DBusResolvedResolver) Start() error { func (t *DBusResolvedResolver) Close() error { t.closeOnce.Do(func() { + if t.interfaceCallback != nil { + t.interfaceMonitor.UnregisterCallback(t.interfaceCallback) + } if t.systemBus != nil { _ = t.systemBus.Close() } @@ -70,22 +86,23 @@ func (t *DBusResolvedResolver) Object() any { } func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { - defaultInterface := t.interfaceMonitor.DefaultInterface() - if defaultInterface == nil { - return nil, E.New("missing default interface") - } question := message.Question[0] - call := object.(*dbus.Object).CallWithContext( + resolvedObject := object.(*ResolvedObject) + call := resolvedObject.CallWithContext( ctx, "org.freedesktop.resolve1.Manager.ResolveRecord", 0, - int32(defaultInterface.Index), + resolvedObject.InterfaceIndex, question.Name, question.Qclass, question.Qtype, uint64(0), ) if call.Err != nil { + var dbusError dbus.Error + if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" { + t.updateStatus() + } return nil, E.Cause(call.Err, " resolve record via resolved") } var ( @@ -137,14 +154,76 @@ func (t *DBusResolvedResolver) loopUpdateStatus() { } func (t *DBusResolvedResolver) updateStatus() { - dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") - err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err + dbusObject, err := t.checkResolved(context.Background()) + oldValue := t.resoledObject.Swap(dbusObject) if err != nil { - if t.resoledObject.Swap(nil) != nil { + var dbusErr dbus.Error + if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" { + t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable")) + } + if oldValue != nil { t.logger.Debug("systemd-resolved service is gone") } return + } else if oldValue == nil { + t.logger.Debug("using systemd-resolved service as resolver") } - t.resoledObject.Store(dbusObject) - t.logger.Debug("using systemd-resolved service as resolver") +} + +func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) { + dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1") + err := dbusObject.Call("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") + } + call := dbusObject.(*dbus.Object).CallWithContext( + ctx, + "org.freedesktop.resolve1.Manager.GetLink", + 0, + int32(defaultInterface.Index), + ) + if call.Err != nil { + return nil, err + } + var linkPath dbus.ObjectPath + err = call.Store(&linkPath) + 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") + } + dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS") + if err != nil { + return nil, err + } + var linkDNS []resolved.LinkDNS + err = dnsProp.Store(&linkDNS) + if err != nil { + return nil, err + } + if 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") + } + } + return &ResolvedObject{ + BusObject: dbusObject, + }, nil + } else { + return &ResolvedObject{ + BusObject: dbusObject, + InterfaceIndex: int32(defaultInterface.Index), + }, nil + } +} + +func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) { + t.updateStatus() }