diff --git a/client/internal/dns/host.go b/client/internal/dns/host.go index 3070763a6..e55a07055 100644 --- a/client/internal/dns/host.go +++ b/client/internal/dns/host.go @@ -15,6 +15,12 @@ type hostManager interface { restoreUncleanShutdownDNS(storedDNSAddress *netip.Addr) error } +type SystemDNSSettings struct { + Domains []string + ServerIP string + ServerPort int +} + type HostDNSConfig struct { Domains []DomainConfig `json:"domains"` RouteAll bool `json:"routeAll"` diff --git a/client/internal/dns/host_darwin.go b/client/internal/dns/host_darwin.go index 5ae84fb91..70d2443ef 100644 --- a/client/internal/dns/host_darwin.go +++ b/client/internal/dns/host_darwin.go @@ -7,6 +7,7 @@ import ( "bytes" "fmt" "io" + "net" "net/netip" "os/exec" "strconv" @@ -18,7 +19,7 @@ import ( const ( netbirdDNSStateKeyFormat = "State:/Network/Service/NetBird-%s/DNS" globalIPv4State = "State:/Network/Global/IPv4" - primaryServiceSetupKeyFormat = "Setup:/Network/Service/%s/DNS" + primaryServiceStateKeyFormat = "State:/Network/Service/%s/DNS" keySupplementalMatchDomains = "SupplementalMatchDomains" keySupplementalMatchDomainsNoSearch = "SupplementalMatchDomainsNoSearch" keyServerAddresses = "ServerAddresses" @@ -28,12 +29,12 @@ const ( scutilPath = "/usr/sbin/scutil" searchSuffix = "Search" matchSuffix = "Match" + localSuffix = "Local" ) type systemConfigurator struct { - // primaryServiceID primary interface in the system. AKA the interface with the default route - primaryServiceID string - createdKeys map[string]struct{} + createdKeys map[string]struct{} + systemDNSSettings SystemDNSSettings } func newHostManager() (hostManager, error) { @@ -49,20 +50,6 @@ func (s *systemConfigurator) supportCustomPort() bool { func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error { var err error - if config.RouteAll { - err = s.addDNSSetupForAll(config.ServerIP, config.ServerPort) - if err != nil { - return fmt.Errorf("add dns setup for all: %w", err) - } - } else if s.primaryServiceID != "" { - err = s.removeKeyFromSystemConfig(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID)) - if err != nil { - return fmt.Errorf("remote key from system config: %w", err) - } - s.primaryServiceID = "" - log.Infof("removed %s:%d as main DNS resolver for this peer", config.ServerIP, config.ServerPort) - } - // create a file for unclean shutdown detection if err := createUncleanShutdownIndicator(); err != nil { log.Errorf("failed to create unclean shutdown file: %s", err) @@ -73,6 +60,19 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error { matchDomains []string ) + err = s.recordSystemDNSSettings(true) + if err != nil { + log.Errorf("unable to update record of System's DNS config: %s", err.Error()) + } + + if config.RouteAll { + searchDomains = append(searchDomains, "\"\"") + err = s.addLocalDNS() + if err != nil { + log.Infof("failed to enable split DNS") + } + } + for _, dConf := range config.Domains { if dConf.Disabled { continue @@ -119,10 +119,6 @@ func (s *systemConfigurator) restoreHostDNS() error { } log.Infof("removing %s domains from system", keyType) } - if s.primaryServiceID != "" { - lines += buildRemoveKeyOperation(getKeyWithInput(primaryServiceSetupKeyFormat, s.primaryServiceID)) - log.Infof("restoring DNS resolver configuration for system") - } _, err := runSystemConfigCommand(wrapCommand(lines)) if err != nil { log.Errorf("got an error while cleaning the system configuration: %s", err) @@ -148,6 +144,97 @@ func (s *systemConfigurator) removeKeyFromSystemConfig(key string) error { return nil } +func (s *systemConfigurator) addLocalDNS() error { + if s.systemDNSSettings.ServerIP == "" || len(s.systemDNSSettings.Domains) == 0 { + err := s.recordSystemDNSSettings(true) + log.Errorf("Unable to get system DNS configuration") + return err + } + localKey := getKeyWithInput(netbirdDNSStateKeyFormat, localSuffix) + if s.systemDNSSettings.ServerIP != "" && len(s.systemDNSSettings.Domains) != 0 { + err := s.addSearchDomains(localKey, strings.Join(s.systemDNSSettings.Domains, " "), s.systemDNSSettings.ServerIP, s.systemDNSSettings.ServerPort) + if err != nil { + return fmt.Errorf("couldn't add local network DNS conf: %w", err) + } + } else { + log.Info("Not enabling local DNS server") + } + + return nil +} + +func (s *systemConfigurator) recordSystemDNSSettings(force bool) error { + if s.systemDNSSettings.ServerIP != "" && len(s.systemDNSSettings.Domains) != 0 && !force { + return nil + } + + systemDNSSettings, err := s.getSystemDNSSettings() + if err != nil { + return fmt.Errorf("couldn't get current DNS config: %w", err) + } + s.systemDNSSettings = systemDNSSettings + + return nil +} + +func (s *systemConfigurator) getSystemDNSSettings() (SystemDNSSettings, error) { + primaryServiceKey, _, err := s.getPrimaryService() + if err != nil || primaryServiceKey == "" { + return SystemDNSSettings{}, fmt.Errorf("couldn't find the primary service key: %w", err) + } + dnsServiceKey := getKeyWithInput(primaryServiceStateKeyFormat, primaryServiceKey) + line := buildCommandLine("show", dnsServiceKey, "") + stdinCommands := wrapCommand(line) + + b, err := runSystemConfigCommand(stdinCommands) + if err != nil { + return SystemDNSSettings{}, fmt.Errorf("sending the command: %w", err) + } + + var dnsSettings SystemDNSSettings + inSearchDomainsArray := false + inServerAddressesArray := false + + scanner := bufio.NewScanner(bytes.NewReader(b)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + switch { + case strings.HasPrefix(line, "DomainName :"): + domainName := strings.TrimSpace(strings.Split(line, ":")[1]) + dnsSettings.Domains = append(dnsSettings.Domains, domainName) + case line == "SearchDomains : {": + inSearchDomainsArray = true + continue + case line == "ServerAddresses : {": + inServerAddressesArray = true + continue + case line == "}": + inSearchDomainsArray = false + inServerAddressesArray = false + } + + if inSearchDomainsArray { + searchDomains := strings.Split(line, " : ") + dnsSettings.Domains = append(dnsSettings.Domains, searchDomains...) + } else if inServerAddressesArray { + address := strings.Split(line, " : ")[1] + if ip := net.ParseIP(address); ip != nil && ip.To4() != nil { + dnsSettings.ServerIP = address + inServerAddressesArray = false // Stop reading after finding the first IPv4 address + } + } + } + + if err := scanner.Err(); err != nil { + return dnsSettings, err + } + + // default to 53 port + dnsSettings.ServerPort = 53 + + return dnsSettings, nil +} + func (s *systemConfigurator) addSearchDomains(key, domains string, ip string, port int) error { err := s.addDNSState(key, domains, ip, port, true) if err != nil { @@ -194,23 +281,6 @@ func (s *systemConfigurator) addDNSState(state, domains, dnsServer string, port return nil } -func (s *systemConfigurator) addDNSSetupForAll(dnsServer string, port int) error { - primaryServiceKey, existingNameserver, err := s.getPrimaryService() - if err != nil || primaryServiceKey == "" { - return fmt.Errorf("couldn't find the primary service key: %w", err) - } - - err = s.addDNSSetup(getKeyWithInput(primaryServiceSetupKeyFormat, primaryServiceKey), dnsServer, port, existingNameserver) - if err != nil { - return fmt.Errorf("add dns setup: %w", err) - } - - log.Infof("configured %s:%d as main DNS resolver for this peer", dnsServer, port) - s.primaryServiceID = primaryServiceKey - - return nil -} - func (s *systemConfigurator) getPrimaryService() (string, string, error) { line := buildCommandLine("show", globalIPv4State, "") stdinCommands := wrapCommand(line) @@ -239,19 +309,6 @@ func (s *systemConfigurator) getPrimaryService() (string, string, error) { return primaryService, router, nil } -func (s *systemConfigurator) addDNSSetup(setupKey, dnsServer string, port int, existingDNSServer string) error { - lines := buildAddCommandLine(keySupplementalMatchDomainsNoSearch, digitSymbol+strconv.Itoa(0)) - lines += buildAddCommandLine(keyServerAddresses, arraySymbol+dnsServer+" "+existingDNSServer) - lines += buildAddCommandLine(keyServerPort, digitSymbol+strconv.Itoa(port)) - addDomainCommand := buildCreateStateWithOperation(setupKey, lines) - stdinCommands := wrapCommand(addDomainCommand) - _, err := runSystemConfigCommand(stdinCommands) - if err != nil { - return fmt.Errorf("applying dns setup, error: %w", err) - } - return nil -} - func (s *systemConfigurator) restoreUncleanShutdownDNS(*netip.Addr) error { if err := s.restoreHostDNS(); err != nil { return fmt.Errorf("restoring dns via scutil: %w", err)