diff --git a/net/dns/resolvd.go b/net/dns/resolvd.go index ad1a99c11..bfd9a2b6e 100644 --- a/net/dns/resolvd.go +++ b/net/dns/resolvd.go @@ -38,11 +38,31 @@ func (m *resolvdManager) SetDNS(config OSConfig) error { m.ifName, } - origResolv, err := m.readAndCopy(resolvConf, backupConf, 0644) + // first, read in the resolvConf + origResolv, err := m.fs.ReadFile(resolvConf) if err != nil { return err } - newResolvConf := removeSearchLines(origResolv) + + // search for our sentinel in resolvConf, if it's not present + // it's our first call to SetDNS, and we should make backupConf + resolvConfOwned := bytes.Contains(origResolv, []byte("generated by tailscale")) + if !resolvConfOwned { + _, err = m.fs.Stat(backupConf) + if err != nil && os.IsNotExist(err) { + _, err = m.readAndCopy(resolvConf, backupConf, 0644) + if err != nil { + return err + } + } + } + + // remove resolvd nameservers and search lines + origResolv = removeSearchLines(origResolv) + origResolv = removeResolvdNameserverLines(origResolv) + // and clean up whitespace + removeBlanks := regexp.MustCompile(`(?m)^\s*\n`) + origResolv = removeBlanks.ReplaceAll(origResolv, []byte("")) for _, ns := range config.Nameservers { args = append(args, ns.String()) @@ -55,6 +75,15 @@ func (m *resolvdManager) SetDNS(config OSConfig) error { newSearch = append(newSearch, s.WithoutTrailingDot()) } + var resolvConfHeader = []byte("") + if !resolvConfOwned { + // if we don't have a header, insert one + resolvConfHeader = []byte( + "# resolv.conf(5) file generated by tailscale\n" + + "# For more info, see https://tailscale.com/s/resolvconf-overwrite\n" + + "# DO NOT EDIT THIS FILE BY HAND -- CHANGES WILL BE OVERWRITTEN\n\n") + } + newResolvConf := append(resolvConfHeader, origResolv...) if len(newSearch) > 1 { newResolvConf = append(newResolvConf, []byte(strings.Join(newSearch, " "))...) newResolvConf = append(newResolvConf, '\n') @@ -123,6 +152,11 @@ func (m resolvdManager) readResolvConf() (config OSConfig, err error) { }, nil } +func removeResolvdNameserverLines(orig []byte) []byte { + re := regexp.MustCompile(`(?m)^nameserver\s+[^\s]+\s+#\s+resolvd:.*$`) + return re.ReplaceAll(orig, []byte("")) +} + func removeSearchLines(orig []byte) []byte { re := regexp.MustCompile(`(?ms)^search\s+.+$`) return re.ReplaceAll(orig, []byte(""))