mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-11-04 10:11:18 +01:00 
			
		
		
		
	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>
		
			
				
	
	
		
			127 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			127 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) Tailscale Inc & AUTHORS
 | 
						|
// SPDX-License-Identifier: BSD-3-Clause
 | 
						|
 | 
						|
package dns
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"os"
 | 
						|
 | 
						|
	"go4.org/mem"
 | 
						|
	"tailscale.com/control/controlknobs"
 | 
						|
	"tailscale.com/health"
 | 
						|
	"tailscale.com/types/logger"
 | 
						|
	"tailscale.com/util/mak"
 | 
						|
)
 | 
						|
 | 
						|
// NewOSConfigurator creates a new OS configurator.
 | 
						|
//
 | 
						|
// The health tracker and the knobs may be nil and are ignored on this platform.
 | 
						|
func NewOSConfigurator(logf logger.Logf, _ *health.Tracker, _ *controlknobs.Knobs, ifName string) (OSConfigurator, error) {
 | 
						|
	return &darwinConfigurator{logf: logf, ifName: ifName}, nil
 | 
						|
}
 | 
						|
 | 
						|
// darwinConfigurator is the tailscaled-on-macOS DNS OS configurator that
 | 
						|
// maintains the Split DNS nameserver entries pointing MagicDNS DNS suffixes
 | 
						|
// to 100.100.100.100 using the macOS /etc/resolver/$SUFFIX files.
 | 
						|
type darwinConfigurator struct {
 | 
						|
	logf   logger.Logf
 | 
						|
	ifName string
 | 
						|
}
 | 
						|
 | 
						|
func (c *darwinConfigurator) Close() error {
 | 
						|
	c.removeResolverFiles(func(domain string) bool { return true })
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (c *darwinConfigurator) SupportsSplitDNS() bool {
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (c *darwinConfigurator) SetDNS(cfg OSConfig) error {
 | 
						|
	var buf bytes.Buffer
 | 
						|
	buf.WriteString(macResolverFileHeader)
 | 
						|
	for _, ip := range cfg.Nameservers {
 | 
						|
		buf.WriteString("nameserver ")
 | 
						|
		buf.WriteString(ip.String())
 | 
						|
		buf.WriteString("\n")
 | 
						|
	}
 | 
						|
 | 
						|
	if err := os.MkdirAll("/etc/resolver", 0755); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	var keep map[string]bool
 | 
						|
 | 
						|
	// Add a dummy file to /etc/resolver with a "search ..." directive if we have
 | 
						|
	// search suffixes to add.
 | 
						|
	if len(cfg.SearchDomains) > 0 {
 | 
						|
		const searchFile = "search.tailscale" // fake DNS suffix+TLD to put our search
 | 
						|
		mak.Set(&keep, searchFile, true)
 | 
						|
		var sbuf bytes.Buffer
 | 
						|
		sbuf.WriteString(macResolverFileHeader)
 | 
						|
		sbuf.WriteString("search")
 | 
						|
		for _, d := range cfg.SearchDomains {
 | 
						|
			sbuf.WriteString(" ")
 | 
						|
			sbuf.WriteString(string(d.WithoutTrailingDot()))
 | 
						|
		}
 | 
						|
		sbuf.WriteString("\n")
 | 
						|
		if err := os.WriteFile("/etc/resolver/"+searchFile, sbuf.Bytes(), 0644); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, d := range cfg.MatchDomains {
 | 
						|
		fileBase := string(d.WithoutTrailingDot())
 | 
						|
		mak.Set(&keep, fileBase, true)
 | 
						|
		fullPath := "/etc/resolver/" + fileBase
 | 
						|
 | 
						|
		if err := os.WriteFile(fullPath, buf.Bytes(), 0644); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return c.removeResolverFiles(func(domain string) bool { return !keep[domain] })
 | 
						|
}
 | 
						|
 | 
						|
func (c *darwinConfigurator) GetBaseConfig() (OSConfig, error) {
 | 
						|
	return OSConfig{}, ErrGetBaseConfigNotSupported
 | 
						|
}
 | 
						|
 | 
						|
const macResolverFileHeader = "# Added by tailscaled\n"
 | 
						|
 | 
						|
// removeResolverFiles deletes all files in /etc/resolver for which the shouldDelete
 | 
						|
// func returns true.
 | 
						|
func (c *darwinConfigurator) removeResolverFiles(shouldDelete func(domain string) bool) error {
 | 
						|
	dents, err := os.ReadDir("/etc/resolver")
 | 
						|
	if os.IsNotExist(err) {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	for _, de := range dents {
 | 
						|
		if !de.Type().IsRegular() {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		name := de.Name()
 | 
						|
		if !shouldDelete(name) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		fullPath := "/etc/resolver/" + name
 | 
						|
		contents, err := os.ReadFile(fullPath)
 | 
						|
		if err != nil {
 | 
						|
			if os.IsNotExist(err) { // race?
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if !mem.HasPrefix(mem.B(contents), mem.S(macResolverFileHeader)) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if err := os.Remove(fullPath); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |