diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index e51b75dec..4f00dcd20 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -131,6 +131,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ tailscale.com/types/wgkey from tailscale.com/control/controlclient+ + L tailscale.com/util/cmpver from tailscale.com/net/dns tailscale.com/util/dnsname from tailscale.com/ipn/ipnstate+ LW tailscale.com/util/endian from tailscale.com/net/netns+ L tailscale.com/util/lineread from tailscale.com/control/controlclient+ diff --git a/net/dns/manager_linux.go b/net/dns/manager_linux.go index f55b2b8cd..47ce5c1ca 100644 --- a/net/dns/manager_linux.go +++ b/net/dns/manager_linux.go @@ -16,6 +16,7 @@ import ( "github.com/godbus/dbus/v5" "tailscale.com/types/logger" + "tailscale.com/util/cmpver" ) type kv struct { @@ -64,7 +65,32 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat return newResolvedManager(logf) } dbg("nm-resolved", "yes") - return newNMManager(interfaceName) + + // Version of NetworkManager before 1.26.6 programmed resolved + // incorrectly, such that NM's settings would always take + // precedence over other settings set by other resolved + // clients. + // + // If we're dealing with such a version, we have to set our + // DNS settings through NM to have them take. + // + // However, versions 1.26.6 later both fixed the resolved + // programming issue _and_ started ignoring DNS settings for + // "unmanaged" interfaces - meaning NM 1.26.6 and later + // actively ignore DNS configuration we give it. So, for those + // NM versions, we can and must use resolved directly. + old, err := nmVersionOlderThan("1.26.6") + if err != nil { + // Failed to figure out NM's version, can't make a correct + // decision. + return nil, fmt.Errorf("checking NetworkManager version: %v", err) + } + if old { + dbg("nm-old", "yes") + return newNMManager(interfaceName) + } + dbg("nm-old", "no") + return newResolvedManager(logf) case "resolvconf": dbg("rc", "resolvconf") if err := resolvconfSourceIsNM(bs); err == nil { @@ -135,6 +161,27 @@ func resolvconfSourceIsNM(resolvDotConf []byte) error { return nil } +func nmVersionOlderThan(want string) (bool, error) { + conn, err := dbus.SystemBus() + if err != nil { + // DBus probably not running. + return false, err + } + + nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager")) + v, err := nm.GetProperty("org.freedesktop.NetworkManager.Version") + if err != nil { + return false, err + } + + version, ok := v.Value().(string) + if !ok { + return false, fmt.Errorf("unexpected type %T for NM version", v.Value()) + } + + return cmpver.Compare(version, want) < 0, nil +} + func nmIsUsingResolved() error { conn, err := dbus.SystemBus() if err != nil {