|
|
|
|
@ -5,7 +5,6 @@ |
|
|
|
|
package dns |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
@ -79,6 +78,18 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat |
|
|
|
|
// "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.
|
|
|
|
|
//
|
|
|
|
|
// In a perfect world, we'd avoid this by replacing
|
|
|
|
|
// configuration out from under NM entirely (e.g. using
|
|
|
|
|
// directManager to overwrite resolv.conf), but in a world
|
|
|
|
|
// where resolved runs, we need to get correct configuration
|
|
|
|
|
// into resolved regardless of what's in resolv.conf (because
|
|
|
|
|
// resolved can also be queried over dbus, or via an NSS
|
|
|
|
|
// module that bypasses /etc/resolv.conf). Given that we must
|
|
|
|
|
// get correct configuration into resolved, we have no choice
|
|
|
|
|
// but to use NM, and accept the loss of IPv6 configuration
|
|
|
|
|
// that comes with it (see
|
|
|
|
|
// https://github.com/tailscale/tailscale/issues/1699)
|
|
|
|
|
old, err := nmVersionOlderThan("1.26.6") |
|
|
|
|
if err != nil { |
|
|
|
|
// Failed to figure out NM's version, can't make a correct
|
|
|
|
|
@ -93,26 +104,6 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat |
|
|
|
|
return newResolvedManager(logf, interfaceName) |
|
|
|
|
case "resolvconf": |
|
|
|
|
dbg("rc", "resolvconf") |
|
|
|
|
if err := resolvconfSourceIsNM(bs); err == nil { |
|
|
|
|
dbg("src-is-nm", "yes") |
|
|
|
|
if err := dbusPing("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/DnsManager"); err == nil { |
|
|
|
|
dbg("nm", "yes") |
|
|
|
|
old, err := nmVersionOlderThan("1.26.6") |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("checking NetworkManager version: %v", err) |
|
|
|
|
} |
|
|
|
|
if old { |
|
|
|
|
dbg("nm-old", "yes") |
|
|
|
|
return newNMManager(interfaceName) |
|
|
|
|
} else { |
|
|
|
|
dbg("nm-old", "no") |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
dbg("nm", "no") |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
dbg("src-is-nm", "no") |
|
|
|
|
} |
|
|
|
|
if _, err := exec.LookPath("resolvconf"); err != nil { |
|
|
|
|
dbg("resolvconf", "no") |
|
|
|
|
return newDirectManager() |
|
|
|
|
@ -120,21 +111,19 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat |
|
|
|
|
dbg("resolvconf", "yes") |
|
|
|
|
return newResolvconfManager(logf) |
|
|
|
|
case "NetworkManager": |
|
|
|
|
// You'd think we would use newNMManager somewhere in
|
|
|
|
|
// here. However, as explained in
|
|
|
|
|
// https://github.com/tailscale/tailscale/issues/1699 , using
|
|
|
|
|
// NetworkManager for DNS configuration carries with it the
|
|
|
|
|
// cost of losing IPv6 configuration on the Tailscale network
|
|
|
|
|
// interface. So, when we can avoid it, we bypass
|
|
|
|
|
// NetworkManager by replacing resolv.conf directly.
|
|
|
|
|
//
|
|
|
|
|
// If you ever try to put NMManager back here, keep in mind
|
|
|
|
|
// that versions >=1.26.6 will ignore DNS configuration
|
|
|
|
|
// anyway, so you still need a fallback path that uses
|
|
|
|
|
// directManager.
|
|
|
|
|
dbg("rc", "nm") |
|
|
|
|
if err := dbusPing("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/DnsManager"); err != nil { |
|
|
|
|
dbg("nm", "no") |
|
|
|
|
return newDirectManager() |
|
|
|
|
} |
|
|
|
|
dbg("nm", "yes") |
|
|
|
|
old, err := nmVersionOlderThan("1.26.6") |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("checking NetworkManager version: %v", err) |
|
|
|
|
} |
|
|
|
|
if old { |
|
|
|
|
dbg("nm-old", "yes") |
|
|
|
|
return newNMManager(interfaceName) |
|
|
|
|
} |
|
|
|
|
dbg("nm-old", "no") |
|
|
|
|
return newDirectManager() |
|
|
|
|
default: |
|
|
|
|
dbg("rc", "unknown") |
|
|
|
|
@ -142,45 +131,6 @@ func NewOSConfigurator(logf logger.Logf, interfaceName string) (ret OSConfigurat |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func resolvconfSourceIsNM(resolvDotConf []byte) error { |
|
|
|
|
b := bytes.NewBuffer(resolvDotConf) |
|
|
|
|
cfg, err := readResolv(b) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("parsing /etc/resolv.conf: %w", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
paths = []string{ |
|
|
|
|
"/etc/resolvconf/run/interface/NetworkManager", |
|
|
|
|
"/run/resolvconf/interface/NetworkManager", |
|
|
|
|
"/var/run/resolvconf/interface/NetworkManager", |
|
|
|
|
"/run/resolvconf/interfaces/NetworkManager", |
|
|
|
|
"/var/run/resolvconf/interfaces/NetworkManager", |
|
|
|
|
} |
|
|
|
|
nmCfg OSConfig |
|
|
|
|
found bool |
|
|
|
|
) |
|
|
|
|
for _, path := range paths { |
|
|
|
|
nmCfg, err = readResolvFile(path) |
|
|
|
|
if os.IsNotExist(err) { |
|
|
|
|
continue |
|
|
|
|
} else if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
found = true |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
if !found { |
|
|
|
|
return errors.New("NetworkManager resolvconf snippet not found") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !nmCfg.Equal(cfg) { |
|
|
|
|
return errors.New("NetworkManager config not applied by resolvconf") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func nmVersionOlderThan(want string) (bool, error) { |
|
|
|
|
conn, err := dbus.SystemBus() |
|
|
|
|
if err != nil { |
|
|
|
|
|