|
|
|
|
@ -12,12 +12,12 @@ import ( |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"os/exec" |
|
|
|
|
|
|
|
|
|
"github.com/godbus/dbus/v5" |
|
|
|
|
"golang.org/x/sys/unix" |
|
|
|
|
"inet.af/netaddr" |
|
|
|
|
"tailscale.com/net/interfaces" |
|
|
|
|
"tailscale.com/util/dnsname" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// resolvedListenAddr is the listen address of the resolved stub resolver.
|
|
|
|
|
@ -51,15 +51,20 @@ type resolvedLinkDomain struct { |
|
|
|
|
|
|
|
|
|
// isResolvedActive determines if resolved is currently managing system DNS settings.
|
|
|
|
|
func isResolvedActive() bool { |
|
|
|
|
// systemd-resolved is never installed without systemd.
|
|
|
|
|
_, err := exec.LookPath("systemctl") |
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), reconfigTimeout) |
|
|
|
|
defer cancel() |
|
|
|
|
|
|
|
|
|
conn, err := dbus.SystemBus() |
|
|
|
|
if err != nil { |
|
|
|
|
// Probably no DBus on the system, or we're not allowed to use
|
|
|
|
|
// it. Cannot control resolved.
|
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// is-active exits with code 3 if the service is not active.
|
|
|
|
|
err = exec.Command("systemctl", "is-active", "systemd-resolved").Run() |
|
|
|
|
if err != nil { |
|
|
|
|
rd := conn.Object("org.freedesktop.resolve1", dbus.ObjectPath("/org/freedesktop/resolve1")) |
|
|
|
|
call := rd.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0) |
|
|
|
|
if call.Err != nil { |
|
|
|
|
// Can't talk to resolved.
|
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -134,12 +139,37 @@ func (m resolvedManager) SetDNS(config OSConfig) error { |
|
|
|
|
return fmt.Errorf("setLinkDNS: %w", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var linkDomains = make([]resolvedLinkDomain, len(config.SearchDomains)) |
|
|
|
|
for i, domain := range config.SearchDomains { |
|
|
|
|
linkDomains[i] = resolvedLinkDomain{ |
|
|
|
|
Domain: domain.WithoutTrailingDot(), |
|
|
|
|
linkDomains := make([]resolvedLinkDomain, 0, len(config.SearchDomains)+len(config.MatchDomains)) |
|
|
|
|
seenDomains := map[dnsname.FQDN]bool{} |
|
|
|
|
for _, domain := range config.SearchDomains { |
|
|
|
|
if seenDomains[domain] { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
seenDomains[domain] = true |
|
|
|
|
linkDomains = append(linkDomains, resolvedLinkDomain{ |
|
|
|
|
Domain: domain.WithTrailingDot(), |
|
|
|
|
RoutingOnly: false, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
for _, domain := range config.MatchDomains { |
|
|
|
|
if seenDomains[domain] { |
|
|
|
|
// Search domains act as both search and match in
|
|
|
|
|
// resolved, so it's correct to skip.
|
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
seenDomains[domain] = true |
|
|
|
|
linkDomains = append(linkDomains, resolvedLinkDomain{ |
|
|
|
|
Domain: domain.WithTrailingDot(), |
|
|
|
|
RoutingOnly: true, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
if len(config.MatchDomains) == 0 { |
|
|
|
|
// Caller requested full DNS interception, install a
|
|
|
|
|
// routing-only root domain.
|
|
|
|
|
linkDomains = append(linkDomains, resolvedLinkDomain{ |
|
|
|
|
Domain: ".", |
|
|
|
|
RoutingOnly: true, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = resolved.CallWithContext( |
|
|
|
|
@ -150,11 +180,41 @@ func (m resolvedManager) SetDNS(config OSConfig) error { |
|
|
|
|
return fmt.Errorf("setLinkDomains: %w", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Some best-effort setting of things, but resolved should do the
|
|
|
|
|
// right thing if these fail (e.g. a really old resolved version
|
|
|
|
|
// or something).
|
|
|
|
|
|
|
|
|
|
// Disable LLMNR, we don't do multicast.
|
|
|
|
|
if call := resolved.CallWithContext(ctx, "org.freedesktop.resolve1.Manager.SetLinkLLMNR", 0, iface.Index, "no"); call.Err != nil { |
|
|
|
|
// TODO: log
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Disable mdns.
|
|
|
|
|
if call := resolved.CallWithContext(ctx, "org.freedesktop.resolve1.Manager.SetLinkMulticastDNS", 0, iface.Index, "no"); call.Err != nil { |
|
|
|
|
// TODO: log
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// We don't support dnssec consistently right now, force it off to
|
|
|
|
|
// avoid partial failures when we split DNS internally.
|
|
|
|
|
if call := resolved.CallWithContext(ctx, "org.freedesktop.resolve1.Manager.SetLinkDNSSEC", 0, iface.Index, "no"); call.Err != nil { |
|
|
|
|
// TODO: log
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if call := resolved.CallWithContext(ctx, "org.freedesktop.resolve1.Manager.SetLinkDNSOverTLS", 0, iface.Index, "no"); call.Err != nil { |
|
|
|
|
// TODO: log
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = resolved.CallWithContext( |
|
|
|
|
ctx, "org.freedesktop.resolve1.Manager.FlushCaches", 0).Store() |
|
|
|
|
if err != nil { |
|
|
|
|
// TODO: log
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (m resolvedManager) SupportsSplitDNS() bool { |
|
|
|
|
return false |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (m resolvedManager) GetBaseConfig() (OSConfig, error) { |
|
|
|
|
|