From 9cbd9b3e44e359a646a0bbc863577c46be81b2f0 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 18 Sep 2024 20:00:37 +0700 Subject: [PATCH] cmd/cli: use powershell to set/reset DNS on Windows Using netsh command will emit unexpected SOA queries, do not use it. While at it, also ensure that local ipv6 will be added to nameservers list on systems that require ipv6 local listener. --- cmd/cli/cli.go | 1 + cmd/cli/os_windows.go | 91 ++++++++++++++----------------------------- cmd/cli/prog.go | 12 +++++- 3 files changed, 41 insertions(+), 63 deletions(-) diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go index 987a470..4b5fbdf 100644 --- a/cmd/cli/cli.go +++ b/cmd/cli/cli.go @@ -2661,6 +2661,7 @@ func resetDnsTask(p *prog, s service.Service) task { iface = runningIface(s) } if isCtrldInstalled { + mainLog.Load().Debug().Msg("restore system DNS settings") resetDnsNoLog(p) } iface = oldIface diff --git a/cmd/cli/os_windows.go b/cmd/cli/os_windows.go index e5ac1d2..234764f 100644 --- a/cmd/cli/os_windows.go +++ b/cmd/cli/os_windows.go @@ -5,7 +5,7 @@ import ( "fmt" "net" "os" - "os/exec" + "slices" "strconv" "strings" "sync" @@ -30,6 +30,14 @@ func setDnsIgnoreUnusableInterface(iface *net.Interface, nameservers []string) e return setDNS(iface, nameservers) } +func setDnsPowershellCmd(iface *net.Interface, nameservers []string) string { + nss := make([]string, 0, len(nameservers)) + for _, ns := range nameservers { + nss = append(nss, strconv.Quote(ns)) + } + return fmt.Sprintf("Set-DnsClientServerAddress -InterfaceIndex %d -ServerAddresses (%s)", iface.Index, strings.Join(nss, ",")) +} + // setDNS sets the dns server for the provided network interface func setDNS(iface *net.Interface, nameservers []string) error { if len(nameservers) == 0 { @@ -41,22 +49,25 @@ func setDNS(iface *net.Interface, nameservers []string) error { if windowsHasLocalDnsServerRunning() { file := absHomeDir(windowsForwardersFilename) oldForwardersContent, _ := os.ReadFile(file) - if err := os.WriteFile(file, []byte(strings.Join(nameservers, ",")), 0600); err != nil { + hasLocalIPv6Listener := needLocalIPv6Listener() + forwarders := slices.DeleteFunc(slices.Clone(nameservers), func(s string) bool { + if !hasLocalIPv6Listener { + return false + } + return s == "::1" + }) + if err := os.WriteFile(file, []byte(strings.Join(forwarders, ",")), 0600); err != nil { mainLog.Load().Warn().Err(err).Msg("could not save forwarders settings") } oldForwarders := strings.Split(string(oldForwardersContent), ",") - if err := addDnsServerForwarders(nameservers, oldForwarders); err != nil { + if err := addDnsServerForwarders(forwarders, oldForwarders); err != nil { mainLog.Load().Warn().Err(err).Msg("could not set forwarders settings") } } }) - primaryDNS := nameservers[0] - if err := setPrimaryDNS(iface, primaryDNS, true); err != nil { - return err - } - if len(nameservers) > 1 { - secondaryDNS := nameservers[1] - _ = addSecondaryDNS(iface, secondaryDNS) + out, err := powershell(setDnsPowershellCmd(iface, nameservers)) + if err != nil { + return fmt.Errorf("%w: %s", err, string(out)) } return nil } @@ -85,17 +96,13 @@ func resetDNS(iface *net.Interface) error { } }) - // Restoring ipv6 first. - if ctrldnet.SupportsIPv6ListenLocal() { - if output, err := netsh("interface", "ipv6", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp"); err != nil { - mainLog.Load().Warn().Err(err).Msgf("failed to reset ipv6 DNS: %s", string(output)) - } - } - // Restoring ipv4 DHCP. - output, err := netsh("interface", "ipv4", "set", "dnsserver", strconv.Itoa(iface.Index), "dhcp") + // Restoring DHCP settings. + cmd := fmt.Sprintf("Set-DnsClientServerAddress -InterfaceIndex %d -ResetServerAddresses", iface.Index) + out, err := powershell(cmd) if err != nil { - return fmt.Errorf("%s: %w", string(output), err) + return fmt.Errorf("%w: %s", err, string(out)) } + // If there's static DNS saved, restoring it. if nss := savedStaticNameservers(iface); len(nss) > 0 { v4ns := make([]string, 0, 2) @@ -112,54 +119,14 @@ func resetDNS(iface *net.Interface) error { if len(ns) == 0 { continue } - primaryDNS := ns[0] - if err := setPrimaryDNS(iface, primaryDNS, false); err != nil { + if err := setDNS(iface, ns); err != nil { return err } - if len(ns) > 1 { - secondaryDNS := ns[1] - _ = addSecondaryDNS(iface, secondaryDNS) - } } } return nil } -func setPrimaryDNS(iface *net.Interface, dns string, disablev6 bool) error { - ipVer := "ipv4" - if ctrldnet.IsIPv6(dns) { - ipVer = "ipv6" - } - idx := strconv.Itoa(iface.Index) - output, err := netsh("interface", ipVer, "set", "dnsserver", idx, "static", dns) - if err != nil { - mainLog.Load().Error().Err(err).Msgf("failed to set primary DNS: %s", string(output)) - return err - } - if disablev6 && ipVer == "ipv4" && ctrldnet.SupportsIPv6ListenLocal() { - // Disable IPv6 DNS, so the query will be fallback to IPv4. - _, _ = netsh("interface", "ipv6", "set", "dnsserver", idx, "static", "::1", "primary") - } - - return nil -} - -func addSecondaryDNS(iface *net.Interface, dns string) error { - ipVer := "ipv4" - if ctrldnet.IsIPv6(dns) { - ipVer = "ipv6" - } - output, err := netsh("interface", ipVer, "add", "dns", strconv.Itoa(iface.Index), dns, "index=2") - if err != nil { - mainLog.Load().Warn().Err(err).Msgf("failed to add secondary DNS: %s", string(output)) - } - return nil -} - -func netsh(args ...string) ([]byte, error) { - return exec.Command("netsh", args...).Output() -} - func currentDNS(iface *net.Interface) []string { luid, err := winipcfg.LUIDFromIndex(uint32(iface.Index)) if err != nil { @@ -200,7 +167,9 @@ func currentStaticDNS(iface *net.Interface) ([]string, error) { out, err := powershell(cmd) if err == nil && len(out) > 0 { found = true - ns = append(ns, strings.Split(string(out), ",")...) + for _, e := range strings.Split(string(out), ",") { + ns = append(ns, strings.TrimRight(e, "\x00")) + } } } } diff --git a/cmd/cli/prog.go b/cmd/cli/prog.go index 82daa24..6018efb 100644 --- a/cmd/cli/prog.go +++ b/cmd/cli/prog.go @@ -588,6 +588,10 @@ func (p *prog) setDNS() { if needRFC1918Listeners(lc) { nameservers = append(nameservers, ctrld.Rfc1918Addresses()...) } + if needLocalIPv6Listener() { + nameservers = append(nameservers, "::1") + } + slices.Sort(nameservers) if err := setDNS(netIface, nameservers); err != nil { logger.Error().Err(err).Msgf("could not set DNS for interface") return @@ -665,7 +669,7 @@ func (p *prog) dnsWatchdog(iface *net.Interface, nameservers []string, allIfaces } } if allIfaces { - withEachPhysicalInterfaces(iface.Name, "re-applying DNS", func(i *net.Interface) error { + withEachPhysicalInterfaces(iface.Name, "", func(i *net.Interface) error { if dnsChanged(i, ns) { if err := setDnsIgnoreUnusableInterface(i, nameservers); err != nil { mainLog.Load().Error().Err(err).Str("iface", i.Name).Msgf("could not re-apply DNS settings") @@ -977,7 +981,11 @@ func savedStaticNameservers(iface *net.Interface) []string { func dnsChanged(iface *net.Interface, nameservers []string) bool { curNameservers, _ := currentStaticDNS(iface) slices.Sort(curNameservers) - return !slices.Equal(curNameservers, nameservers) + if !slices.Equal(curNameservers, nameservers) { + mainLog.Load().Debug().Msgf("interface %q current DNS settings: %v, expected: %v", iface.Name, curNameservers, nameservers) + return true + } + return false } // selfUninstallCheck checks if the error dues to controld.InvalidConfigCode, perform self-uninstall then.