|
|
|
|
@ -7,7 +7,6 @@ package dns |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"os" |
|
|
|
|
@ -15,13 +14,12 @@ import ( |
|
|
|
|
"sync" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/godbus/dbus/v5" |
|
|
|
|
"tailscale.com/control/controlknobs" |
|
|
|
|
"tailscale.com/feature" |
|
|
|
|
"tailscale.com/health" |
|
|
|
|
"tailscale.com/net/netaddr" |
|
|
|
|
"tailscale.com/types/logger" |
|
|
|
|
"tailscale.com/util/clientmetric" |
|
|
|
|
"tailscale.com/util/cmpver" |
|
|
|
|
"tailscale.com/util/syspolicy/policyclient" |
|
|
|
|
"tailscale.com/version/distro" |
|
|
|
|
) |
|
|
|
|
@ -36,6 +34,31 @@ func (kv kv) String() string { |
|
|
|
|
|
|
|
|
|
var publishOnce sync.Once |
|
|
|
|
|
|
|
|
|
// reconfigTimeout is the time interval within which Manager.{Up,Down} should complete.
|
|
|
|
|
//
|
|
|
|
|
// This is particularly useful because certain conditions can cause indefinite hangs
|
|
|
|
|
// (such as improper dbus auth followed by contextless dbus.Object.Call).
|
|
|
|
|
// Such operations should be wrapped in a timeout context.
|
|
|
|
|
const reconfigTimeout = time.Second |
|
|
|
|
|
|
|
|
|
// Set unless ts_omit_networkmanager
|
|
|
|
|
var ( |
|
|
|
|
optNewNMManager feature.Hook[func(ifName string) (OSConfigurator, error)] |
|
|
|
|
optNMIsUsingResolved feature.Hook[func() error] |
|
|
|
|
optNMVersionBetween feature.Hook[func(v1, v2 string) (bool, error)] |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Set unless ts_omit_resolved
|
|
|
|
|
var ( |
|
|
|
|
optNewResolvedManager feature.Hook[func(logf logger.Logf, health *health.Tracker, interfaceName string) (OSConfigurator, error)] |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Set unless ts_omit_dbus
|
|
|
|
|
var ( |
|
|
|
|
optDBusPing feature.Hook[func(name, objectPath string) error] |
|
|
|
|
optDBusReadString feature.Hook[func(name, objectPath, iface, member string) (string, error)] |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// NewOSConfigurator created a new OS configurator.
|
|
|
|
|
//
|
|
|
|
|
// The health tracker may be nil; the knobs may be nil and are ignored on this platform.
|
|
|
|
|
@ -45,13 +68,25 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ policyclient. |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
env := newOSConfigEnv{ |
|
|
|
|
fs: directFS{}, |
|
|
|
|
dbusPing: dbusPing, |
|
|
|
|
dbusReadString: dbusReadString, |
|
|
|
|
nmIsUsingResolved: nmIsUsingResolved, |
|
|
|
|
nmVersionBetween: nmVersionBetween, |
|
|
|
|
resolvconfStyle: resolvconfStyle, |
|
|
|
|
fs: directFS{}, |
|
|
|
|
resolvconfStyle: resolvconfStyle, |
|
|
|
|
} |
|
|
|
|
if f, ok := optDBusPing.GetOk(); ok { |
|
|
|
|
env.dbusPing = f |
|
|
|
|
} else { |
|
|
|
|
env.dbusPing = func(_, _ string) error { return errors.ErrUnsupported } |
|
|
|
|
} |
|
|
|
|
if f, ok := optDBusReadString.GetOk(); ok { |
|
|
|
|
env.dbusReadString = f |
|
|
|
|
} else { |
|
|
|
|
env.dbusReadString = func(_, _, _, _ string) (string, error) { return "", errors.ErrUnsupported } |
|
|
|
|
} |
|
|
|
|
if f, ok := optNMIsUsingResolved.GetOk(); ok { |
|
|
|
|
env.nmIsUsingResolved = f |
|
|
|
|
} else { |
|
|
|
|
env.nmIsUsingResolved = func() error { return errors.ErrUnsupported } |
|
|
|
|
} |
|
|
|
|
env.nmVersionBetween, _ = optNMVersionBetween.GetOk() // GetOk to not panic if nil; unused if optNMIsUsingResolved returns an error
|
|
|
|
|
mode, err := dnsMode(logf, health, env) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
@ -66,17 +101,24 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ policyclient. |
|
|
|
|
case "direct": |
|
|
|
|
return newDirectManagerOnFS(logf, health, env.fs), nil |
|
|
|
|
case "systemd-resolved": |
|
|
|
|
return newResolvedManager(logf, health, interfaceName) |
|
|
|
|
if f, ok := optNewResolvedManager.GetOk(); ok { |
|
|
|
|
return f(logf, health, interfaceName) |
|
|
|
|
} |
|
|
|
|
return nil, fmt.Errorf("tailscaled was built without DNS %q support", mode) |
|
|
|
|
case "network-manager": |
|
|
|
|
return newNMManager(interfaceName) |
|
|
|
|
if f, ok := optNewNMManager.GetOk(); ok { |
|
|
|
|
return f(interfaceName) |
|
|
|
|
} |
|
|
|
|
return nil, fmt.Errorf("tailscaled was built without DNS %q support", mode) |
|
|
|
|
case "debian-resolvconf": |
|
|
|
|
return newDebianResolvconfManager(logf) |
|
|
|
|
case "openresolv": |
|
|
|
|
return newOpenresolvManager(logf) |
|
|
|
|
default: |
|
|
|
|
logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode) |
|
|
|
|
return newDirectManagerOnFS(logf, health, env.fs), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return newDirectManagerOnFS(logf, health, env.fs), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
|
|
|
|
|
@ -292,50 +334,6 @@ func dnsMode(logf logger.Logf, health *health.Tracker, env newOSConfigEnv) (ret |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func nmVersionBetween(first, last 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()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
outside := cmpver.Compare(version, first) < 0 || cmpver.Compare(version, last) > 0 |
|
|
|
|
return !outside, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func nmIsUsingResolved() error { |
|
|
|
|
conn, err := dbus.SystemBus() |
|
|
|
|
if err != nil { |
|
|
|
|
// DBus probably not running.
|
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nm := conn.Object("org.freedesktop.NetworkManager", dbus.ObjectPath("/org/freedesktop/NetworkManager/DnsManager")) |
|
|
|
|
v, err := nm.GetProperty("org.freedesktop.NetworkManager.DnsManager.Mode") |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("getting NM mode: %w", err) |
|
|
|
|
} |
|
|
|
|
mode, ok := v.Value().(string) |
|
|
|
|
if !ok { |
|
|
|
|
return fmt.Errorf("unexpected type %T for NM DNS mode", v.Value()) |
|
|
|
|
} |
|
|
|
|
if mode != "systemd-resolved" { |
|
|
|
|
return errors.New("NetworkManager is not using systemd-resolved for DNS") |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// resolvedIsActuallyResolver reports whether the system is using
|
|
|
|
|
// systemd-resolved as the resolver. There are two different ways to
|
|
|
|
|
// use systemd-resolved:
|
|
|
|
|
@ -396,44 +394,3 @@ func isLibnssResolveUsed(env newOSConfigEnv) error { |
|
|
|
|
} |
|
|
|
|
return fmt.Errorf("libnss_resolve not used") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func dbusPing(name, objectPath string) error { |
|
|
|
|
conn, err := dbus.SystemBus() |
|
|
|
|
if err != nil { |
|
|
|
|
// DBus probably not running.
|
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second) |
|
|
|
|
defer cancel() |
|
|
|
|
|
|
|
|
|
obj := conn.Object(name, dbus.ObjectPath(objectPath)) |
|
|
|
|
call := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0) |
|
|
|
|
return call.Err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// dbusReadString reads a string property from the provided name and object
|
|
|
|
|
// path. property must be in "interface.member" notation.
|
|
|
|
|
func dbusReadString(name, objectPath, iface, member string) (string, error) { |
|
|
|
|
conn, err := dbus.SystemBus() |
|
|
|
|
if err != nil { |
|
|
|
|
// DBus probably not running.
|
|
|
|
|
return "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second) |
|
|
|
|
defer cancel() |
|
|
|
|
|
|
|
|
|
obj := conn.Object(name, dbus.ObjectPath(objectPath)) |
|
|
|
|
|
|
|
|
|
var result dbus.Variant |
|
|
|
|
err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, iface, member).Store(&result) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if s, ok := result.Value().(string); ok { |
|
|
|
|
return s, nil |
|
|
|
|
} |
|
|
|
|
return result.String(), nil |
|
|
|
|
} |
|
|
|
|
|