From 15599323a123dc438f1a5209b62aa3026605d999 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 10 Feb 2022 21:11:18 -0800 Subject: [PATCH] net/dns: fix systemd-resolved detection race at boot If systemd-resolved is enabled but not running (or not yet running, such as early boot) and resolv.conf is old/dangling, we weren't detecting systemd-resolved. This moves its ping earlier, which will trigger it to start up and write its file. Updates #3362 (likely fixes) Updates #3531 (likely fixes) Change-Id: I6392944ac59f600571c43b8f7a677df224f2beed Signed-off-by: Brad Fitzpatrick --- net/dns/manager_linux.go | 14 +++++++- net/dns/manager_linux_test.go | 61 +++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/net/dns/manager_linux.go b/net/dns/manager_linux.go index 337648298..b633d7fcf 100644 --- a/net/dns/manager_linux.go +++ b/net/dns/manager_linux.go @@ -77,6 +77,18 @@ func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) { logf("dns: %v", debug) }() + // Before we read /etc/resolv.conf (which might be in a broken + // or symlink-dangling state), try to ping the D-Bus service + // for systemd-resolved. If it's active on the machine, this + // will make it start up and write the /etc/resolv.conf file + // before it replies to the ping. (see how systemd's + // src/resolve/resolved.c calls manager_write_resolv_conf + // before the sd_event_loop starts) + resolvedUp := env.dbusPing("org.freedesktop.resolve1", "/org/freedesktop/resolve1") == nil + if resolvedUp { + dbg("resolved-ping", "yes") + } + bs, err := env.fs.ReadFile(resolvConf) if os.IsNotExist(err) { dbg("rc", "missing") @@ -99,7 +111,7 @@ func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) { dbg("resolved", "not-in-use") return "direct", nil } - if err := env.dbusPing("org.freedesktop.resolve1", "/org/freedesktop/resolve1"); err != nil { + if !resolvedUp { dbg("resolved", "no") return "direct", nil } diff --git a/net/dns/manager_linux_test.go b/net/dns/manager_linux_test.go index ed8c60e2b..9f6c1e0c0 100644 --- a/net/dns/manager_linux_test.go +++ b/net/dns/manager_linux_test.go @@ -79,7 +79,7 @@ func TestLinuxDNSMode(t *testing.T) { env: env( resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvedRunning()), - wantLog: "dns: [rc=resolved nm=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -88,7 +88,7 @@ func TestLinuxDNSMode(t *testing.T) { resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvedRunning(), nmRunning("1.2.3", false)), - wantLog: "dns: [rc=resolved nm=yes nm-resolved=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -97,7 +97,7 @@ func TestLinuxDNSMode(t *testing.T) { resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvedRunning(), nmRunning("1.26.2", true)), - wantLog: "dns: [rc=resolved nm=yes nm-resolved=yes nm-safe=yes ret=network-manager]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=yes ret=network-manager]", want: "network-manager", }, { @@ -106,7 +106,7 @@ func TestLinuxDNSMode(t *testing.T) { resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvedRunning(), nmRunning("1.27.0", true)), - wantLog: "dns: [rc=resolved nm=yes nm-resolved=yes nm-safe=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -115,7 +115,7 @@ func TestLinuxDNSMode(t *testing.T) { resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvedRunning(), nmRunning("1.22.0", true)), - wantLog: "dns: [rc=resolved nm=yes nm-resolved=yes nm-safe=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=no ret=systemd-resolved]", want: "systemd-resolved", }, // Regression tests for extreme corner cases below. @@ -141,7 +141,7 @@ func TestLinuxDNSMode(t *testing.T) { "nameserver 127.0.0.53", "nameserver 127.0.0.53"), resolvedRunning()), - wantLog: "dns: [rc=resolved nm=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -156,7 +156,7 @@ func TestLinuxDNSMode(t *testing.T) { "# run \"systemd-resolve --status\" to see details about the actual nameservers.", "nameserver 127.0.0.53"), resolvedRunning()), - wantLog: "dns: [rc=resolved nm=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -183,7 +183,7 @@ func TestLinuxDNSMode(t *testing.T) { "options edns0 trust-ad"), resolvedRunning(), nmRunning("1.32.12", true)), - wantLog: "dns: [rc=nm nm-resolved=yes nm-safe=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm-safe=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -206,7 +206,7 @@ func TestLinuxDNSMode(t *testing.T) { "options edns0 trust-ad"), resolvedRunning(), nmRunning("1.26.3", true)), - wantLog: "dns: [rc=nm nm-resolved=yes nm-safe=yes ret=network-manager]", + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm-safe=yes ret=network-manager]", want: "network-manager", }, { @@ -217,7 +217,7 @@ func TestLinuxDNSMode(t *testing.T) { "nameserver 127.0.0.53", "options edns0 trust-ad"), resolvedRunning()), - wantLog: "dns: [rc=nm nm-resolved=yes nm=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm=no ret=systemd-resolved]", want: "systemd-resolved", }, { @@ -228,7 +228,16 @@ func TestLinuxDNSMode(t *testing.T) { "search lan", "nameserver 127.0.0.53"), resolvedRunning()), - wantLog: "dns: [rc=nm nm-resolved=yes nm=no ret=systemd-resolved]", + wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm=no ret=systemd-resolved]", + want: "systemd-resolved", + }, + { + // Make sure that we ping systemd-resolved to let it start up and write its resolv.conf + // before we read its file. + env: env(resolvedStartOnPingAndThen( + resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), + )), + wantLog: "dns: [resolved-ping=yes rc=resolved nm=no ret=systemd-resolved]", want: "systemd-resolved", }, } @@ -292,9 +301,14 @@ func (m memFS) WriteFile(name string, contents []byte, perm os.FileMode) error { return nil } +type dbusService struct { + name, path string + hook func() // if non-nil, run on ping +} + type envBuilder struct { fs memFS - dbus []struct{ name, path string } + dbus []dbusService nmUsingResolved bool nmVersion string resolvconfStyle string @@ -323,6 +337,9 @@ func env(opts ...envOption) newOSConfigEnv { dbusPing: func(name, path string) error { for _, svc := range b.dbus { if svc.name == name && svc.path == path { + if svc.hook != nil { + svc.hook() + } return nil } } @@ -348,9 +365,25 @@ func resolvDotConf(ss ...string) envOption { }) } +// resolvedRunning returns an option that makes resolved reply to a dbusPing. func resolvedRunning() envOption { + return resolvedStartOnPingAndThen( /* nothing */ ) +} + +// resolvedStartOnPingAndThen returns an option that makes resolved be +// active but not yet running. On a dbus ping, it then applies the +// provided options. +func resolvedStartOnPingAndThen(opts ...envOption) envOption { return envOpt(func(b *envBuilder) { - b.dbus = append(b.dbus, struct{ name, path string }{"org.freedesktop.resolve1", "/org/freedesktop/resolve1"}) + b.dbus = append(b.dbus, dbusService{ + name: "org.freedesktop.resolve1", + path: "/org/freedesktop/resolve1", + hook: func() { + for _, opt := range opts { + opt.apply(b) + } + }, + }) }) } @@ -358,7 +391,7 @@ func nmRunning(version string, usingResolved bool) envOption { return envOpt(func(b *envBuilder) { b.nmUsingResolved = usingResolved b.nmVersion = version - b.dbus = append(b.dbus, struct{ name, path string }{"org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/DnsManager"}) + b.dbus = append(b.dbus, dbusService{name: "org.freedesktop.NetworkManager", path: "/org/freedesktop/NetworkManager/DnsManager"}) }) }