tailscale/net/dns/manager_openbsd.go
Nick Khyl c32efd9118 various: create a catch-all NRPT rule when "Override local DNS" is enabled on Windows
Without this rule, Windows 8.1 and newer devices issue parallel DNS requests to DNS servers
associated with all network adapters, even when "Override local DNS" is enabled and/or
a Mullvad exit node is being used, resulting in DNS leaks.

This also adds "disable-local-dns-override-via-nrpt" nodeAttr that can be used to disable
the new behavior if needed.

Fixes tailscale/corp#20718

Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-06-14 14:41:50 -05:00

79 lines
1.8 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dns
import (
"bytes"
"fmt"
"os"
"tailscale.com/control/controlknobs"
"tailscale.com/health"
"tailscale.com/types/logger"
)
type kv struct {
k, v string
}
func (kv kv) String() string {
return fmt.Sprintf("%s=%s", kv.k, kv.v)
}
// NewOSConfigurator created a new OS configurator.
//
// The health tracker may be nil; the knobs may be nil and are ignored on this platform.
func NewOSConfigurator(logf logger.Logf, health *health.Tracker, _ *controlknobs.Knobs, interfaceName string) (OSConfigurator, error) {
return newOSConfigurator(logf, health, interfaceName,
newOSConfigEnv{
rcIsResolvd: rcIsResolvd,
fs: directFS{},
})
}
// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
type newOSConfigEnv struct {
fs directFS
rcIsResolvd func(resolvConfContents []byte) bool
}
func newOSConfigurator(logf logger.Logf, health *health.Tracker, interfaceName string, env newOSConfigEnv) (ret OSConfigurator, err error) {
var debug []kv
dbg := func(k, v string) {
debug = append(debug, kv{k, v})
}
defer func() {
if ret != nil {
dbg("ret", fmt.Sprintf("%T", ret))
}
logf("dns: %v", debug)
}()
bs, err := env.fs.ReadFile(resolvConf)
if os.IsNotExist(err) {
dbg("rc", "missing")
return newDirectManager(logf, health), nil
}
if err != nil {
return nil, fmt.Errorf("reading /etc/resolv.conf: %w", err)
}
if env.rcIsResolvd(bs) {
dbg("resolvd", "yes")
return newResolvdManager(logf, interfaceName)
}
dbg("resolvd", "missing")
return newDirectManager(logf, health), nil
}
func rcIsResolvd(resolvConfContents []byte) bool {
// If we have the string "# resolvd:" in resolv.conf resolvd(8) is
// managing things.
if bytes.Contains(resolvConfContents, []byte("# resolvd:")) {
return true
}
return false
}