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
This commit is contained in:
Cuong Manh Le
2023-02-10 21:50:08 +07:00
committed by Cuong Manh Le
parent e385547461
commit 997ec342e0
2 changed files with 102 additions and 29 deletions

View File

@@ -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)

View File

@@ -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
}