From 997ec342e0d6484a1b05d68f9eb64fa8e2008eb2 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Fri, 10 Feb 2023 21:50:08 +0700 Subject: [PATCH] cmd/ctrld,internal/dns: support systemd-networkd dbus For interface managed by systemd-networkd, systemd-resolved can not reset DNS. To fix this, attempting to check before the run loop and set the suitable manager for the system. Updates #55 --- cmd/ctrld/os_linux.go | 20 ++++--- internal/dns/resolved.go | 111 +++++++++++++++++++++++++++++++-------- 2 files changed, 102 insertions(+), 29 deletions(-) diff --git a/cmd/ctrld/os_linux.go b/cmd/ctrld/os_linux.go index 22a469e..970de78 100644 --- a/cmd/ctrld/os_linux.go +++ b/cmd/ctrld/os_linux.go @@ -80,16 +80,20 @@ func setDNS(iface *net.Interface, nameservers []string) error { return nil } -func resetDNS(iface *net.Interface) error { - if r, err := dns.NewOSConfigurator(logf, iface.Name); err == nil { - if err := r.Close(); err != nil { - mainLog.Error().Err(err).Msg("failed to rollback DNS setting") - return err +func resetDNS(iface *net.Interface) (err error) { + defer func() { + if err == nil { + return } - if r.Mode() == "direct" { - return nil + if r, oerr := dns.NewOSConfigurator(logf, iface.Name); oerr == nil { + _ = r.SetDNS(dns.OSConfig{}) + if err := r.Close(); err != nil { + mainLog.Error().Err(err).Msg("failed to rollback DNS setting") + return + } + err = nil } - } + }() var ns []string c, err := nclient4.New(iface.Name) diff --git a/internal/dns/resolved.go b/internal/dns/resolved.go index 6c0b1de..8d03249 100644 --- a/internal/dns/resolved.go +++ b/internal/dns/resolved.go @@ -37,14 +37,45 @@ const reconfigTimeout = time.Second // Clients connect to the bus and walk that same hierarchy to invoke // RPCs, get/set properties, or listen for signals. const ( - dbusResolvedObject = "org.freedesktop.resolve1" - dbusResolvedPath dbus.ObjectPath = "/org/freedesktop/resolve1" - dbusResolvedInterface = "org.freedesktop.resolve1.Manager" - dbusPath dbus.ObjectPath = "/org/freedesktop/DBus" - dbusInterface = "org.freedesktop.DBus" - dbusOwnerSignal = "NameOwnerChanged" // broadcast when a well-known name's owning process changes. + dbusResolvedObject = "org.freedesktop.resolve1" + dbusNetworkdObject = "org.freedesktop.network1" + dbusResolvedPath dbus.ObjectPath = "/org/freedesktop/resolve1" + dbusNetworkdPath dbus.ObjectPath = "/org/freedesktop/network1" + dbusResolvedInterface = "org.freedesktop.resolve1.Manager" + dbusNetworkdInterface = "org.freedesktop.network1.Manager" + dbusPath dbus.ObjectPath = "/org/freedesktop/DBus" + dbusInterface = "org.freedesktop.DBus" + dbusOwnerSignal = "NameOwnerChanged" // broadcast when a well-known name's owning process changes. + dbusResolvedErrorLinkBusy = "org.freedesktop.resolve1.LinkBusy" ) +var ( + dbusSetLinkDNS string + dbusSetLinkDomains string + dbusSetLinkDefaultRoute string + dbusSetLinkLLMNR string + dbusSetLinkMulticastDNS string + dbusSetLinkDNSSEC string + dbusSetLinkDNSOverTLS string + dbusFlushCaches string + dbusRevertLink string +) + +func setDbusMethods(dbusInterface string) { + dbusSetLinkDNS = dbusInterface + ".SetLinkDNS" + dbusSetLinkDomains = dbusInterface + ".SetLinkDomains" + dbusSetLinkDefaultRoute = dbusInterface + ".SetLinkDefaultRoute" + dbusSetLinkLLMNR = dbusInterface + ".SetLinkLLMNR" + dbusSetLinkMulticastDNS = dbusInterface + ".SetLinkMulticastDNS" + dbusSetLinkDNSSEC = dbusInterface + ".SetLinkDNSSEC" + dbusSetLinkDNSOverTLS = dbusInterface + ".SetLinkDNSOverTLS" + dbusFlushCaches = dbusInterface + ".FlushCaches" + dbusRevertLink = dbusInterface + ".RevertLink" + if dbusInterface == dbusNetworkdInterface { + dbusRevertLink = dbusInterface + ".RevertLinkDNS" + } +} + type resolvedLinkNameserver struct { Family int32 Address []byte @@ -69,7 +100,9 @@ type resolvedManager struct { logf logger.Logf ifidx int - configCR chan changeRequest // tracks OSConfigs changes and error responses + configCR chan changeRequest // tracks OSConfigs changes and error responses + revertCh chan struct{} + newManager func(conn *dbus.Conn) dbus.BusObject } func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManager, error) { @@ -89,6 +122,7 @@ func newResolvedManager(logf logger.Logf, interfaceName string) (*resolvedManage ifidx: iface.Index, configCR: make(chan changeRequest), + revertCh: make(chan struct{}), } go mgr.run(ctx) @@ -117,6 +151,16 @@ func (m *resolvedManager) SetDNS(config OSConfig) error { } } +func newResolvedObject(conn *dbus.Conn) dbus.BusObject { + setDbusMethods(dbusResolvedInterface) + return conn.Object(dbusResolvedObject, dbusResolvedPath) +} + +func newNetworkdObject(conn *dbus.Conn) dbus.BusObject { + setDbusMethods(dbusNetworkdInterface) + return conn.Object(dbusNetworkdObject, dbusNetworkdPath) +} + func (m *resolvedManager) run(ctx context.Context) { var ( conn *dbus.Conn @@ -131,6 +175,22 @@ func (m *resolvedManager) run(ctx context.Context) { } }() + newManager := newResolvedObject + func() { + conn, err := dbus.SystemBus() + if err != nil { + m.logf("dbus connection error: %v", err) + return + } + rManager = newManager(conn) + if call := rManager.CallWithContext(ctx, dbusRevertLink, 0, m.ifidx); call.Err != nil { + if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbusResolvedErrorLinkBusy { + m.logf("[v1] Using %s as manager", dbusNetworkdObject) + newManager = newNetworkdObject + } + } + }() + // Reconnect the systemBus if disconnected. reconnect := func() error { var err error @@ -151,7 +211,7 @@ func (m *resolvedManager) run(ctx context.Context) { return err } - rManager = conn.Object(dbusResolvedObject, dbus.ObjectPath(dbusResolvedPath)) + rManager = newManager(conn) // Only receive the DBus signals we need to resync our config on // resolved restart. Failure to set filters isn't a fatal error, @@ -160,6 +220,9 @@ func (m *resolvedManager) run(ctx context.Context) { if err = conn.AddMatchSignal(dbus.WithMatchObjectPath(dbusPath), dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(dbusOwnerSignal), dbus.WithMatchArg(0, dbusResolvedObject)); err != nil { m.logf("[v1] Setting DBus signal filter failed: %v", err) } + if err = conn.AddMatchSignal(dbus.WithMatchObjectPath(dbusPath), dbus.WithMatchInterface(dbusInterface), dbus.WithMatchMember(dbusOwnerSignal), dbus.WithMatchArg(0, dbusNetworkdObject)); err != nil { + m.logf("[v1] Setting DBus signal filter failed: %v", err) + } conn.Signal(signals) // Reset backoff and SetNSOSHealth after successful on reconnect. @@ -179,13 +242,15 @@ func (m *resolvedManager) run(ctx context.Context) { if rManager == nil { return } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // RevertLink resets all per-interface settings on systemd-resolved to defaults. // When ctx goes away systemd-resolved auto reverts. // Keeping for potential use in future refactor. - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".RevertLink", 0, m.ifidx); call.Err != nil { + if call := rManager.CallWithContext(ctx, dbusRevertLink, 0, m.ifidx); call.Err != nil { m.logf("[v1] RevertLink: %v", call.Err) - return } + cancel() + close(m.revertCh) return case configCR := <-m.configCR: // Track and update sync with latest config change. @@ -223,7 +288,7 @@ func (m *resolvedManager) run(ctx context.Context) { if len(signal.Body) != 3 { m.logf("[unexpected] DBus NameOwnerChanged len(Body) = %d, want 3") } - if name, ok := signal.Body[0].(string); !ok || name != dbusResolvedObject { + if name, ok := signal.Body[0].(string); !ok || (name != dbusResolvedObject && name != dbusNetworkdObject) { continue } newOwner, ok := signal.Body[2].(string) @@ -271,7 +336,7 @@ func (m *resolvedManager) setConfigOverDBus(ctx context.Context, rManager dbus.B } } err := rManager.CallWithContext( - ctx, dbusResolvedInterface+".SetLinkDNS", 0, + ctx, dbusSetLinkDNS, 0, m.ifidx, linkNameservers, ).Store() if err != nil { @@ -311,14 +376,14 @@ func (m *resolvedManager) setConfigOverDBus(ctx context.Context, rManager dbus.B } err = rManager.CallWithContext( - ctx, dbusResolvedInterface+".SetLinkDomains", 0, + ctx, dbusSetLinkDomains, 0, m.ifidx, linkDomains, ).Store() if err != nil && err.Error() == "Argument list too long" { // TODO: better error match // Issue 3188: older systemd-resolved had argument length limits. // Trim out the *.arpa. entries and try again. err = rManager.CallWithContext( - ctx, dbusResolvedInterface+".SetLinkDomains", 0, + ctx, dbusSetLinkDomains, 0, m.ifidx, linkDomainsWithoutReverseDNS(linkDomains), ).Store() } @@ -326,7 +391,7 @@ func (m *resolvedManager) setConfigOverDBus(ctx context.Context, rManager dbus.B return fmt.Errorf("setLinkDomains: %w", err) } - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDefaultRoute", 0, m.ifidx, len(config.MatchDomains) == 0); call.Err != nil { + if call := rManager.CallWithContext(ctx, dbusSetLinkDefaultRoute, 0, m.ifidx, len(config.MatchDomains) == 0); call.Err != nil { if dbusErr, ok := call.Err.(dbus.Error); ok && dbusErr.Name == dbus.ErrMsgUnknownMethod.Name { // on some older systems like Kubuntu 18.04.6 with systemd 237 method SetLinkDefaultRoute is absent, // but otherwise it's working good @@ -341,33 +406,37 @@ func (m *resolvedManager) setConfigOverDBus(ctx context.Context, rManager dbus.B // or something). // Disable LLMNR, we don't do multicast. - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkLLMNR", 0, m.ifidx, "no"); call.Err != nil { + if call := rManager.CallWithContext(ctx, dbusSetLinkLLMNR, 0, m.ifidx, "no"); call.Err != nil { m.logf("[v1] failed to disable LLMNR: %v", call.Err) } // Disable mdns. - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkMulticastDNS", 0, m.ifidx, "no"); call.Err != nil { + if call := rManager.CallWithContext(ctx, dbusSetLinkMulticastDNS, 0, m.ifidx, "no"); call.Err != nil { m.logf("[v1] failed to disable mdns: %v", call.Err) } // We don't support dnssec consistently right now, force it off to // avoid partial failures when we split DNS internally. - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSSEC", 0, m.ifidx, "no"); call.Err != nil { + if call := rManager.CallWithContext(ctx, dbusSetLinkDNSSEC, 0, m.ifidx, "no"); call.Err != nil { m.logf("[v1] failed to disable DNSSEC: %v", call.Err) } - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".SetLinkDNSOverTLS", 0, m.ifidx, "no"); call.Err != nil { + if call := rManager.CallWithContext(ctx, dbusSetLinkDNSOverTLS, 0, m.ifidx, "no"); call.Err != nil { m.logf("[v1] failed to disable DoT: %v", call.Err) } - if call := rManager.CallWithContext(ctx, dbusResolvedInterface+".FlushCaches", 0); call.Err != nil { - m.logf("failed to flush resolved DNS cache: %v", call.Err) + if rManager.Path() == dbusResolvedPath { + if call := rManager.CallWithContext(ctx, dbusFlushCaches, 0); call.Err != nil { + m.logf("failed to flush resolved DNS cache: %v", call.Err) + } } + return nil } func (m *resolvedManager) Close() error { m.cancel() // stops the 'run' method goroutine + <-m.revertCh return nil }