net/dns: detect when libnss_resolve is used

Having `127.0.0.53` is not the only way to use `systemd-resolved`. An
alternative way is to enable `libnss_resolve` module, which seems to now
be used by default on Debian 12 bookworm.

Fixes #8549

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
pull/8657/head
Anton Tolchanov 1 year ago committed by Anton Tolchanov
parent efd6d90dd7
commit 388b124513

@ -139,7 +139,7 @@ func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) {
// header, but doesn't actually point to resolved. We mustn't // header, but doesn't actually point to resolved. We mustn't
// try to program resolved in that case. // try to program resolved in that case.
// https://github.com/tailscale/tailscale/issues/2136 // https://github.com/tailscale/tailscale/issues/2136
if err := resolvedIsActuallyResolver(bs); err != nil { if err := resolvedIsActuallyResolver(logf, env, dbg, bs); err != nil {
logf("dns: resolvedIsActuallyResolver error: %v", err) logf("dns: resolvedIsActuallyResolver error: %v", err)
dbg("resolved", "not-in-use") dbg("resolved", "not-in-use")
return "direct", nil return "direct", nil
@ -225,7 +225,7 @@ func dnsMode(logf logger.Logf, env newOSConfigEnv) (ret string, err error) {
dbg("rc", "nm") dbg("rc", "nm")
// Sometimes, NetworkManager owns the configuration but points // Sometimes, NetworkManager owns the configuration but points
// it at systemd-resolved. // it at systemd-resolved.
if err := resolvedIsActuallyResolver(bs); err != nil { if err := resolvedIsActuallyResolver(logf, env, dbg, bs); err != nil {
logf("dns: resolvedIsActuallyResolver error: %v", err) logf("dns: resolvedIsActuallyResolver error: %v", err)
dbg("resolved", "not-in-use") dbg("resolved", "not-in-use")
// You'd think we would use newNMManager here. However, as // You'd think we would use newNMManager here. However, as
@ -318,14 +318,23 @@ func nmIsUsingResolved() error {
return nil return nil
} }
// resolvedIsActuallyResolver reports whether the given resolv.conf // resolvedIsActuallyResolver reports whether the system is using
// bytes describe a configuration where systemd-resolved (127.0.0.53) // systemd-resolved as the resolver. There are two different ways to
// is the only configured nameserver. // use systemd-resolved:
// - libnss_resolve, which requires adding `resolve` to the "hosts:"
// line in /etc/nsswitch.conf
// - setting the only nameserver configured in `resolv.conf` to
// systemd-resolved IP (127.0.0.53)
// //
// Returns an error if the configuration is something other than // Returns an error if the configuration is something other than
// exclusively systemd-resolved, or nil if the config is only // exclusively systemd-resolved, or nil if the config is only
// systemd-resolved. // systemd-resolved.
func resolvedIsActuallyResolver(bs []byte) error { func resolvedIsActuallyResolver(logf logger.Logf, env newOSConfigEnv, dbg func(k, v string), bs []byte) error {
if err := isLibnssResolveUsed(env); err == nil {
dbg("resolved", "nss")
return nil
}
cfg, err := readResolv(bytes.NewBuffer(bs)) cfg, err := readResolv(bytes.NewBuffer(bs))
if err != nil { if err != nil {
return err return err
@ -342,9 +351,34 @@ func resolvedIsActuallyResolver(bs []byte) error {
return fmt.Errorf("resolv.conf doesn't point to systemd-resolved; points to %v", cfg.Nameservers) return fmt.Errorf("resolv.conf doesn't point to systemd-resolved; points to %v", cfg.Nameservers)
} }
} }
dbg("resolved", "file")
return nil return nil
} }
// isLibnssResolveUsed reports whether libnss_resolve is used
// for resolving names. Returns nil if it is, and an error otherwise.
func isLibnssResolveUsed(env newOSConfigEnv) error {
bs, err := env.fs.ReadFile("/etc/nsswitch.conf")
if err != nil {
return fmt.Errorf("reading /etc/resolv.conf: %w", err)
}
for _, line := range strings.Split(string(bs), "\n") {
fields := strings.Fields(line)
if len(fields) < 2 || fields[0] != "hosts:" {
continue
}
for _, module := range fields[1:] {
if module == "dns" {
return fmt.Errorf("dns with a higher priority than libnss_resolve")
}
if module == "resolve" {
return nil
}
}
}
return fmt.Errorf("libnss_resolve not used")
}
func dbusPing(name, objectPath string) error { func dbusPing(name, objectPath string) error {
conn, err := dbus.SystemBus() conn, err := dbus.SystemBus()
if err != nil { if err != nil {

@ -70,7 +70,7 @@ func TestLinuxDNSMode(t *testing.T) {
{ {
name: "resolved_alone_without_ping", name: "resolved_alone_without_ping",
env: env(resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53")), env: env(resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53")),
wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=resolved nm=no resolv-conf-mode=error ret=systemd-resolved]", wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=resolved resolved=file nm=no resolv-conf-mode=error ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -78,16 +78,46 @@ func TestLinuxDNSMode(t *testing.T) {
env: env( env: env(
resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"),
resolvedRunning()), resolvedRunning()),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{
name: "resolved_and_nsswitch_resolve",
env: env(
resolvDotConf("# Managed by systemd-resolved", "nameserver 1.1.1.1"),
resolvedRunning(),
nsswitchDotConf("hosts: files resolve [!UNAVAIL=return] dns"),
),
wantLog: "dns: [resolved-ping=yes rc=resolved resolved=nss nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved",
},
{
name: "resolved_and_nsswitch_dns",
env: env(
resolvDotConf("# Managed by systemd-resolved", "nameserver 1.1.1.1"),
resolvedRunning(),
nsswitchDotConf("hosts: files dns resolve [!UNAVAIL=return]"),
),
wantLog: "dns: resolvedIsActuallyResolver error: resolv.conf doesn't point to systemd-resolved; points to [1.1.1.1]\ndns: [resolved-ping=yes rc=resolved resolved=not-in-use ret=direct]",
want: "direct",
},
{
name: "resolved_and_nsswitch_none",
env: env(
resolvDotConf("# Managed by systemd-resolved", "nameserver 1.1.1.1"),
resolvedRunning(),
nsswitchDotConf("hosts:"),
),
wantLog: "dns: resolvedIsActuallyResolver error: resolv.conf doesn't point to systemd-resolved; points to [1.1.1.1]\ndns: [resolved-ping=yes rc=resolved resolved=not-in-use ret=direct]",
want: "direct",
},
{ {
name: "resolved_and_networkmanager_not_using_resolved", name: "resolved_and_networkmanager_not_using_resolved",
env: env( env: env(
resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"),
resolvedRunning(), resolvedRunning(),
nmRunning("1.2.3", false)), nmRunning("1.2.3", false)),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=yes nm-resolved=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -96,7 +126,7 @@ func TestLinuxDNSMode(t *testing.T) {
resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"),
resolvedRunning(), resolvedRunning(),
nmRunning("1.26.2", true)), nmRunning("1.26.2", true)),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=yes ret=network-manager]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=yes nm-resolved=yes nm-safe=yes ret=network-manager]",
want: "network-manager", want: "network-manager",
}, },
{ {
@ -105,7 +135,7 @@ func TestLinuxDNSMode(t *testing.T) {
resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"),
resolvedRunning(), resolvedRunning(),
nmRunning("1.27.0", true)), nmRunning("1.27.0", true)),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=yes nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -114,7 +144,7 @@ func TestLinuxDNSMode(t *testing.T) {
resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"),
resolvedRunning(), resolvedRunning(),
nmRunning("1.22.0", true)), nmRunning("1.22.0", true)),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=yes nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=yes nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
// Regression tests for extreme corner cases below. // Regression tests for extreme corner cases below.
@ -140,7 +170,7 @@ func TestLinuxDNSMode(t *testing.T) {
"nameserver 127.0.0.53", "nameserver 127.0.0.53",
"nameserver 127.0.0.53"), "nameserver 127.0.0.53"),
resolvedRunning()), resolvedRunning()),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -155,7 +185,7 @@ func TestLinuxDNSMode(t *testing.T) {
"# run \"systemd-resolve --status\" to see details about the actual nameservers.", "# run \"systemd-resolve --status\" to see details about the actual nameservers.",
"nameserver 127.0.0.53"), "nameserver 127.0.0.53"),
resolvedRunning()), resolvedRunning()),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -170,7 +200,7 @@ func TestLinuxDNSMode(t *testing.T) {
"# 127.0.0.53 is the systemd-resolved stub resolver.", "# 127.0.0.53 is the systemd-resolved stub resolver.",
"# run \"systemd-resolve --status\" to see details about the actual nameservers.", "# run \"systemd-resolve --status\" to see details about the actual nameservers.",
"nameserver 127.0.0.53")), "nameserver 127.0.0.53")),
wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=resolved nm=no resolv-conf-mode=error ret=systemd-resolved]", wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=resolved resolved=file nm=no resolv-conf-mode=error ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -182,7 +212,7 @@ func TestLinuxDNSMode(t *testing.T) {
"options edns0 trust-ad"), "options edns0 trust-ad"),
resolvedRunning(), resolvedRunning(),
nmRunning("1.32.12", true)), nmRunning("1.32.12", true)),
wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=nm resolved=file nm-resolved=yes nm-safe=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -193,7 +223,7 @@ func TestLinuxDNSMode(t *testing.T) {
"nameserver 127.0.0.53", "nameserver 127.0.0.53",
"options edns0 trust-ad"), "options edns0 trust-ad"),
nmRunning("1.32.12", true)), nmRunning("1.32.12", true)),
wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=nm nm-resolved=yes nm-safe=no resolv-conf-mode=error ret=systemd-resolved]", wantLog: "dns: ResolvConfMode error: dbus property not found\ndns: [rc=nm resolved=file nm-resolved=yes nm-safe=no resolv-conf-mode=error ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -205,7 +235,7 @@ func TestLinuxDNSMode(t *testing.T) {
"options edns0 trust-ad"), "options edns0 trust-ad"),
resolvedRunning(), resolvedRunning(),
nmRunning("1.26.3", true)), nmRunning("1.26.3", true)),
wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm-safe=yes ret=network-manager]", wantLog: "dns: [resolved-ping=yes rc=nm resolved=file nm-resolved=yes nm-safe=yes ret=network-manager]",
want: "network-manager", want: "network-manager",
}, },
{ {
@ -216,7 +246,7 @@ func TestLinuxDNSMode(t *testing.T) {
"nameserver 127.0.0.53", "nameserver 127.0.0.53",
"options edns0 trust-ad"), "options edns0 trust-ad"),
resolvedRunning()), resolvedRunning()),
wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=nm resolved=file nm-resolved=yes nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -227,7 +257,7 @@ func TestLinuxDNSMode(t *testing.T) {
"search lan", "search lan",
"nameserver 127.0.0.53"), "nameserver 127.0.0.53"),
resolvedRunning()), resolvedRunning()),
wantLog: "dns: [resolved-ping=yes rc=nm nm-resolved=yes nm=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=nm resolved=file nm-resolved=yes nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
{ {
@ -237,7 +267,7 @@ func TestLinuxDNSMode(t *testing.T) {
resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"), resolvDotConf("# Managed by systemd-resolved", "nameserver 127.0.0.53"),
resolvedDbusProperty(), resolvedDbusProperty(),
)), )),
wantLog: "dns: [resolved-ping=yes rc=resolved nm=no resolv-conf-mode=fortests ret=systemd-resolved]", wantLog: "dns: [resolved-ping=yes rc=resolved resolved=file nm=no resolv-conf-mode=fortests ret=systemd-resolved]",
want: "systemd-resolved", want: "systemd-resolved",
}, },
} }
@ -380,6 +410,12 @@ func resolvDotConf(ss ...string) envOption {
}) })
} }
func nsswitchDotConf(ss ...string) envOption {
return envOpt(func(b *envBuilder) {
b.fs["/etc/nsswitch.conf"] = strings.Join(ss, "\n")
})
}
// resolvedRunning returns an option that makes resolved reply to a dbusPing // resolvedRunning returns an option that makes resolved reply to a dbusPing
// and the ResolvConfMode property. // and the ResolvConfMode property.
func resolvedRunning() envOption { func resolvedRunning() envOption {

Loading…
Cancel
Save