From 4771fed64f2372c1ccb0411f3cf095fff9309f68 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Wed, 24 Jan 2024 16:47:26 +0100 Subject: [PATCH] Support disabled resolved stub server mode (#1493) In the case of disabled stub listeren the list of name servers is unordered. The solution is to configure the resolv.conf file directly instead of dbus API. Because third-party services also can manipulate the DNS settings the agent watch the resolv.conf file and keep it up to date. - apply file type DNS manager if in the name server list does not exist the 127.0.0.53 address - watching the resolv.conf file with inotify service and overwrite all the time if the configuration has changed and it invalid - fix resolv.conf generation algorithm --- client/internal/dns/file_linux.go | 138 ++++++++-------- client/internal/dns/file_linux_test.go | 65 +++++++- client/internal/dns/file_parser_linux.go | 105 ++++++++++++ client/internal/dns/file_parser_linux_test.go | 149 +++++++++++++++++ client/internal/dns/file_repair_linux.go | 151 ++++++++++++++++++ client/internal/dns/file_repair_linux_test.go | 130 +++++++++++++++ client/internal/dns/host_linux.go | 27 +++- client/internal/dns/resolvconf_linux.go | 8 +- go.mod | 2 +- 9 files changed, 692 insertions(+), 83 deletions(-) create mode 100644 client/internal/dns/file_parser_linux.go create mode 100644 client/internal/dns/file_parser_linux_test.go create mode 100644 client/internal/dns/file_repair_linux.go create mode 100644 client/internal/dns/file_repair_linux_test.go diff --git a/client/internal/dns/file_linux.go b/client/internal/dns/file_linux.go index f49e9fb93..acb31b7f0 100644 --- a/client/internal/dns/file_linux.go +++ b/client/internal/dns/file_linux.go @@ -3,7 +3,6 @@ package dns import ( - "bufio" "bytes" "fmt" "os" @@ -24,11 +23,15 @@ const ( ) type fileConfigurator struct { + repair *repair + originalPerms os.FileMode } func newFileConfigurator() (hostManager, error) { - return &fileConfigurator{}, nil + fc := &fileConfigurator{} + fc.repair = newRepair(defaultResolvConfPath, fc.updateConfig) + return fc, nil } func (f *fileConfigurator) supportCustomPort() bool { @@ -59,22 +62,35 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error { } } - searchDomainList := searchDomains(config) + nbSearchDomains := searchDomains(config) + nbNameserverIP := config.ServerIP - originalSearchDomains, nameServers, others, err := originalDNSConfigs(fileDefaultResolvConfBackupLocation) + resolvConf, err := parseBackupResolvConf() if err != nil { log.Error(err) } - searchDomainList = mergeSearchDomains(searchDomainList, originalSearchDomains) + f.repair.stopWatchFileChanges() + + err = f.updateConfig(nbSearchDomains, nbNameserverIP, resolvConf) + if err != nil { + return err + } + f.repair.watchFileChanges(nbSearchDomains, nbNameserverIP) + return nil +} + +func (f *fileConfigurator) updateConfig(nbSearchDomains []string, nbNameserverIP string, cfg *resolvConf) error { + searchDomainList := mergeSearchDomains(nbSearchDomains, cfg.searchDomains) + nameServers := generateNsList(nbNameserverIP, cfg) buf := prepareResolvConfContent( searchDomainList, - append([]string{config.ServerIP}, nameServers...), - others) + nameServers, + cfg.others) log.Debugf("creating managed file %s", defaultResolvConfPath) - err = os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms) + err := os.WriteFile(defaultResolvConfPath, buf.Bytes(), f.originalPerms) if err != nil { restoreErr := f.restore() if restoreErr != nil { @@ -88,6 +104,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error { } func (f *fileConfigurator) restoreHostDNS() error { + f.repair.stopWatchFileChanges() return f.restore() } @@ -115,6 +132,18 @@ func (f *fileConfigurator) restore() error { return os.RemoveAll(fileDefaultResolvConfBackupLocation) } +// generateNsList generates a list of nameservers from the config and adds the primary nameserver to the beginning of the list +func generateNsList(nbNameserverIP string, cfg *resolvConf) []string { + ns := make([]string, 1, len(cfg.nameServers)+1) + ns[0] = nbNameserverIP + for _, cfgNs := range cfg.nameServers { + if nbNameserverIP != cfgNs { + ns = append(ns, cfgNs) + } + } + return ns +} + func prepareResolvConfContent(searchDomains, nameServers, others []string) bytes.Buffer { var buf bytes.Buffer buf.WriteString(fileGeneratedResolvConfContentHeaderNextLine) @@ -150,70 +179,6 @@ func searchDomains(config HostDNSConfig) []string { return listOfDomains } -func originalDNSConfigs(resolvconfFile string) (searchDomains, nameServers, others []string, err error) { - file, err := os.Open(resolvconfFile) - if err != nil { - err = fmt.Errorf(`could not read existing resolv.conf`) - return - } - defer file.Close() - - reader := bufio.NewReader(file) - - for { - lineBytes, isPrefix, readErr := reader.ReadLine() - if readErr != nil { - break - } - - if isPrefix { - err = fmt.Errorf(`resolv.conf line too long`) - return - } - - line := strings.TrimSpace(string(lineBytes)) - - if strings.HasPrefix(line, "#") { - continue - } - - if strings.HasPrefix(line, "domain") { - continue - } - - if strings.HasPrefix(line, "options") && strings.Contains(line, "rotate") { - line = strings.ReplaceAll(line, "rotate", "") - splitLines := strings.Fields(line) - if len(splitLines) == 1 { - continue - } - line = strings.Join(splitLines, " ") - } - - if strings.HasPrefix(line, "search") { - splitLines := strings.Fields(line) - if len(splitLines) < 2 { - continue - } - - searchDomains = splitLines[1:] - continue - } - - if strings.HasPrefix(line, "nameserver") { - splitLines := strings.Fields(line) - if len(splitLines) != 2 { - continue - } - nameServers = append(nameServers, splitLines[1]) - continue - } - - others = append(others, line) - } - return -} - // merge search Domains lists and cut off the list if it is too long func mergeSearchDomains(searchDomains []string, originalSearchDomains []string) []string { lineSize := len("search") @@ -230,6 +195,19 @@ func mergeSearchDomains(searchDomains []string, originalSearchDomains []string) // return with the number of characters in the searchDomains line func validateAndFillSearchDomains(initialLineChars int, s *[]string, vs []string) int { for _, sd := range vs { + duplicated := false + for _, fs := range *s { + if fs == sd { + duplicated = true + break + } + + } + + if duplicated { + continue + } + tmpCharsNumber := initialLineChars + 1 + len(sd) if tmpCharsNumber > fileMaxLineCharsLimit { // lets log all skipped Domains @@ -246,6 +224,7 @@ func validateAndFillSearchDomains(initialLineChars int, s *[]string, vs []string } *s = append(*s, sd) } + return initialLineChars } @@ -266,3 +245,18 @@ func copyFile(src, dest string) error { } return nil } + +func isContains(subList []string, list []string) bool { + for _, sl := range subList { + var found bool + for _, l := range list { + if sl == l { + found = true + } + } + if !found { + return false + } + } + return true +} diff --git a/client/internal/dns/file_linux_test.go b/client/internal/dns/file_linux_test.go index 369a47ef4..902791b36 100644 --- a/client/internal/dns/file_linux_test.go +++ b/client/internal/dns/file_linux_test.go @@ -1,3 +1,5 @@ +//go:build !android + package dns import ( @@ -7,7 +9,7 @@ import ( func Test_mergeSearchDomains(t *testing.T) { searchDomains := []string{"a", "b"} - originDomains := []string{"a", "b"} + originDomains := []string{"c", "d"} mergedDomains := mergeSearchDomains(searchDomains, originDomains) if len(mergedDomains) != 4 { t.Errorf("invalid len of result domains: %d, want: %d", len(mergedDomains), 4) @@ -49,6 +51,67 @@ func Test_mergeSearchTooLongDomain(t *testing.T) { } } +func Test_isContains(t *testing.T) { + type args struct { + subList []string + list []string + } + tests := []struct { + args args + want bool + }{ + { + args: args{ + subList: []string{"a", "b", "c"}, + list: []string{"a", "b", "c"}, + }, + want: true, + }, + { + args: args{ + subList: []string{"a"}, + list: []string{"a", "b", "c"}, + }, + want: true, + }, + { + args: args{ + subList: []string{"d"}, + list: []string{"a", "b", "c"}, + }, + want: false, + }, + { + args: args{ + subList: []string{"a"}, + list: []string{}, + }, + want: false, + }, + { + args: args{ + subList: []string{}, + list: []string{"b"}, + }, + want: true, + }, + { + args: args{ + subList: []string{}, + list: []string{}, + }, + want: true, + }, + } + for _, tt := range tests { + t.Run("list check test", func(t *testing.T) { + if got := isContains(tt.args.subList, tt.args.list); got != tt.want { + t.Errorf("isContains() = %v, want %v", got, tt.want) + } + }) + } +} + func getLongLine() string { x := "search " for { diff --git a/client/internal/dns/file_parser_linux.go b/client/internal/dns/file_parser_linux.go new file mode 100644 index 000000000..a0115ecd3 --- /dev/null +++ b/client/internal/dns/file_parser_linux.go @@ -0,0 +1,105 @@ +//go:build !android + +package dns + +import ( + "fmt" + "os" + "strings" + + log "github.com/sirupsen/logrus" +) + +const ( + defaultResolvConfPath = "/etc/resolv.conf" +) + +type resolvConf struct { + nameServers []string + searchDomains []string + others []string +} + +func (r *resolvConf) String() string { + return fmt.Sprintf("search domains: %v, name servers: %v, others: %s", r.searchDomains, r.nameServers, r.others) +} + +func parseDefaultResolvConf() (*resolvConf, error) { + return parseResolvConfFile(defaultResolvConfPath) +} + +func parseBackupResolvConf() (*resolvConf, error) { + return parseResolvConfFile(fileDefaultResolvConfBackupLocation) +} + +func parseResolvConfFile(resolvConfFile string) (*resolvConf, error) { + file, err := os.Open(resolvConfFile) + if err != nil { + return nil, fmt.Errorf("failed to open %s file: %w", resolvConfFile, err) + } + defer func() { + if err := file.Close(); err != nil { + log.Errorf("failed closing %s: %s", resolvConfFile, err) + } + }() + + cur, err := os.ReadFile(resolvConfFile) + if err != nil { + return nil, fmt.Errorf("failed to read %s file: %w", resolvConfFile, err) + } + + if len(cur) == 0 { + return nil, fmt.Errorf("file is empty") + } + + rconf := &resolvConf{ + searchDomains: make([]string, 0), + nameServers: make([]string, 0), + others: make([]string, 0), + } + + for _, line := range strings.Split(string(cur), "\n") { + line = strings.TrimSpace(line) + + if strings.HasPrefix(line, "#") { + continue + } + + if strings.HasPrefix(line, "domain") { + continue + } + + if strings.HasPrefix(line, "options") && strings.Contains(line, "rotate") { + line = strings.ReplaceAll(line, "rotate", "") + splitLines := strings.Fields(line) + if len(splitLines) == 1 { + continue + } + line = strings.Join(splitLines, " ") + } + + if strings.HasPrefix(line, "search") { + splitLines := strings.Fields(line) + if len(splitLines) < 2 { + continue + } + + rconf.searchDomains = splitLines[1:] + continue + } + + if strings.HasPrefix(line, "nameserver") { + splitLines := strings.Fields(line) + if len(splitLines) != 2 { + continue + } + rconf.nameServers = append(rconf.nameServers, splitLines[1]) + continue + } + + if line != "" { + rconf.others = append(rconf.others, line) + } + } + return rconf, nil +} diff --git a/client/internal/dns/file_parser_linux_test.go b/client/internal/dns/file_parser_linux_test.go new file mode 100644 index 000000000..8eedc141f --- /dev/null +++ b/client/internal/dns/file_parser_linux_test.go @@ -0,0 +1,149 @@ +//go:build !android + +package dns + +import ( + "fmt" + "os" + "testing" +) + +func Test_parseResolvConf(t *testing.T) { + testCases := []struct { + input string + expectedSearch []string + expectedNS []string + expectedOther []string + }{ + { + input: `domain chello.hu +search chello.hu +nameserver 192.168.0.1 +`, + expectedSearch: []string{"chello.hu"}, + expectedNS: []string{"192.168.0.1"}, + expectedOther: []string{}, + }, + { + input: `# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8). +# Do not edit. +# +# This file might be symlinked as /etc/resolv.conf. If you're looking at +# /etc/resolv.conf and seeing this text, you have followed the symlink. +# +# This is a dynamic resolv.conf file for connecting local clients directly to +# all known uplink DNS servers. This file lists all configured search domains. +# +# Third party programs should typically not access this file directly, but only +# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a +# different way, replace this symlink by a static file or a different symlink. +# +# See man:systemd-resolved.service(8) for details about the supported modes of +# operation for /etc/resolv.conf. + +nameserver 192.168.2.1 +nameserver 100.81.99.197 +search netbird.cloud +`, + expectedSearch: []string{"netbird.cloud"}, + expectedNS: []string{"192.168.2.1", "100.81.99.197"}, + expectedOther: []string{}, + }, + { + input: `# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8). +# Do not edit. +# +# This file might be symlinked as /etc/resolv.conf. If you're looking at +# /etc/resolv.conf and seeing this text, you have followed the symlink. +# +# This is a dynamic resolv.conf file for connecting local clients directly to +# all known uplink DNS servers. This file lists all configured search domains. +# +# Third party programs should typically not access this file directly, but only +# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a +# different way, replace this symlink by a static file or a different symlink. +# +# See man:systemd-resolved.service(8) for details about the supported modes of +# operation for /etc/resolv.conf. + +nameserver 192.168.2.1 +nameserver 100.81.99.197 +search netbird.cloud +options debug +`, + expectedSearch: []string{"netbird.cloud"}, + expectedNS: []string{"192.168.2.1", "100.81.99.197"}, + expectedOther: []string{"options debug"}, + }, + { + input: `# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8). +# Do not edit. +# +# This file might be symlinked as /etc/resolv.conf. If you're looking at +# /etc/resolv.conf and seeing this text, you have followed the symlink. +# +# This is a dynamic resolv.conf file for connecting local clients directly to +# all known uplink DNS servers. This file lists all configured search domains. +# +# Third party programs should typically not access this file directly, but only +# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a +# different way, replace this symlink by a static file or a different symlink. +# +# See man:systemd-resolved.service(8) for details about the supported modes of +# operation for /etc/resolv.conf. + +nameserver 192.168.2.1 +nameserver 100.81.99.197 +search netbird.cloud +options debug +options edns0 trust-ad +`, + expectedSearch: []string{"netbird.cloud"}, + expectedNS: []string{"192.168.2.1", "100.81.99.197"}, + expectedOther: []string{"options debug", "options edns0 trust-ad"}, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run("test", func(t *testing.T) { + t.Parallel() + tmpResolvConf := fmt.Sprintf("%s/%s", t.TempDir(), "resolv.conf") + err := os.WriteFile(tmpResolvConf, []byte(testCase.input), 0644) + if err != nil { + t.Fatal(err) + } + cfg, err := parseResolvConfFile(tmpResolvConf) + if err != nil { + t.Fatal(err) + } + ok := compareLists(cfg.searchDomains, testCase.expectedSearch) + if !ok { + t.Errorf("invalid parse result for search domains, expected: %v, got: %v", testCase.expectedSearch, cfg.searchDomains) + } + + ok = compareLists(cfg.nameServers, testCase.expectedNS) + if !ok { + t.Errorf("invalid parse result for ns domains, expected: %v, got: %v", testCase.expectedNS, cfg.nameServers) + } + + ok = compareLists(cfg.others, testCase.expectedOther) + if !ok { + t.Errorf("invalid parse result for others, expected: %v, got: %v", testCase.expectedOther, cfg.others) + } + }) + } + +} + +func compareLists(search []string, search2 []string) bool { + if len(search) != len(search2) { + return false + } + for i, v := range search { + if v != search2[i] { + return false + } + } + return true +} diff --git a/client/internal/dns/file_repair_linux.go b/client/internal/dns/file_repair_linux.go new file mode 100644 index 000000000..60c89abe6 --- /dev/null +++ b/client/internal/dns/file_repair_linux.go @@ -0,0 +1,151 @@ +//go:build !android + +package dns + +import ( + "fmt" + "path" + "sync" + + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" +) + +var ( + eventTypes = []fsnotify.Op{ + fsnotify.Create, + fsnotify.Write, + fsnotify.Remove, + fsnotify.Rename, + } +) + +type repairConfFn func([]string, string, *resolvConf) error + +type repair struct { + operationFile string + updateFn repairConfFn + watchDir string + + inotify *fsnotify.Watcher + inotifyWg sync.WaitGroup +} + +func newRepair(operationFile string, updateFn repairConfFn) *repair { + return &repair{ + operationFile: operationFile, + watchDir: path.Dir(operationFile), + updateFn: updateFn, + } +} + +func (f *repair) watchFileChanges(nbSearchDomains []string, nbNameserverIP string) { + if f.inotify != nil { + return + } + + log.Infof("start to watch resolv.conf") + inotify, err := fsnotify.NewWatcher() + if err != nil { + log.Errorf("failed to start inotify watcher for resolv.conf: %s", err) + return + } + f.inotify = inotify + + f.inotifyWg.Add(1) + go func() { + defer f.inotifyWg.Done() + for event := range f.inotify.Events { + if !f.isEventRelevant(event) { + continue + } + + log.Tracef("resolv.conf changed, check if it is broken") + + rConf, err := parseResolvConfFile(f.operationFile) + if err != nil { + log.Warnf("failed to parse resolv conf: %s", err) + continue + } + + log.Debugf("check resolv.conf parameters: %s", rConf) + if !isNbParamsMissing(nbSearchDomains, nbNameserverIP, rConf) { + log.Tracef("resolv.conf still correct, skip the update") + continue + } + log.Info("broken params in resolv.conf, repairing it...") + + err = f.inotify.Remove(f.watchDir) + if err != nil { + log.Errorf("failed to rm inotify watch for resolv.conf: %s", err) + } + + err = f.updateFn(nbSearchDomains, nbNameserverIP, rConf) + if err != nil { + log.Errorf("failed to repair resolv.conf: %v", err) + } + + err = f.inotify.Add(f.watchDir) + if err != nil { + log.Errorf("failed to readd inotify watch for resolv.conf: %s", err) + return + } + } + }() + + err = f.inotify.Add(f.watchDir) + if err != nil { + log.Errorf("failed to add inotify watch for resolv.conf: %s", err) + return + } +} + +func (f *repair) stopWatchFileChanges() { + if f.inotify == nil { + return + } + err := f.inotify.Close() + if err != nil { + log.Warnf("failed to close resolv.conf inotify: %v", err) + } + f.inotifyWg.Wait() + f.inotify = nil +} + +func (f *repair) isEventRelevant(event fsnotify.Event) bool { + var ok bool + for _, et := range eventTypes { + if event.Has(et) { + ok = true + break + } + } + if !ok { + return false + } + + operationFileSymlink := fmt.Sprintf("%s~", f.operationFile) + if event.Name == f.operationFile || event.Name == operationFileSymlink { + return true + } + return false +} + +// nbParamsAreMissing checks if the resolv.conf file contains all the parameters that NetBird needs +// check the NetBird related nameserver IP at the first place +// check the NetBird related search domains in the search domains list +func isNbParamsMissing(nbSearchDomains []string, nbNameserverIP string, rConf *resolvConf) bool { + if !isContains(nbSearchDomains, rConf.searchDomains) { + return true + } + + if len(rConf.nameServers) == 0 { + return true + } + + if rConf.nameServers[0] != nbNameserverIP { + return true + } + + return false +} diff --git a/client/internal/dns/file_repair_linux_test.go b/client/internal/dns/file_repair_linux_test.go new file mode 100644 index 000000000..6c20db716 --- /dev/null +++ b/client/internal/dns/file_repair_linux_test.go @@ -0,0 +1,130 @@ +//go:build !android + +package dns + +import ( + "context" + "os" + "testing" + "time" + + "github.com/netbirdio/netbird/util" +) + +func TestMain(m *testing.M) { + _ = util.InitLog("debug", "console") + code := m.Run() + os.Exit(code) +} + +func Test_newRepairtmp(t *testing.T) { + type args struct { + resolvConfContent string + touchedConfContent string + wantChange bool + } + tests := []args{ + { + resolvConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + + touchedConfContent: ` +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + wantChange: true, + }, + { + resolvConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + + touchedConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something somethingelse`, + wantChange: false, + }, + { + resolvConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + + touchedConfContent: ` +nameserver 10.0.0.1 +searchdomain netbird.cloud something`, + wantChange: false, + }, + { + resolvConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + + touchedConfContent: ` +searchdomain something`, + wantChange: true, + }, + { + resolvConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + + touchedConfContent: ` +nameserver 10.0.0.1`, + wantChange: true, + }, + { + resolvConfContent: ` +nameserver 10.0.0.1 +nameserver 8.8.8.8 +searchdomain netbird.cloud something`, + + touchedConfContent: ` +nameserver 8.8.8.8`, + wantChange: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run("test", func(t *testing.T) { + t.Parallel() + workDir := t.TempDir() + operationFile := workDir + "/resolv.conf" + err := os.WriteFile(operationFile, []byte(tt.resolvConfContent), 0755) + if err != nil { + t.Fatalf("failed to write out resolv.conf: %s", err) + } + + var changed bool + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + updateFn := func([]string, string, *resolvConf) error { + changed = true + cancel() + return nil + } + + r := newRepair(operationFile, updateFn) + r.watchFileChanges([]string{"netbird.cloud"}, "10.0.0.1") + + err = os.WriteFile(operationFile, []byte(tt.touchedConfContent), 0755) + if err != nil { + t.Fatalf("failed to write out resolv.conf: %s", err) + } + + <-ctx.Done() + + r.stopWatchFileChanges() + + if changed != tt.wantChange { + t.Errorf("unexpected result: want: %v, got: %v", tt.wantChange, changed) + } + }) + } + +} diff --git a/client/internal/dns/host_linux.go b/client/internal/dns/host_linux.go index a6557f121..931cda28d 100644 --- a/client/internal/dns/host_linux.go +++ b/client/internal/dns/host_linux.go @@ -11,10 +11,6 @@ import ( log "github.com/sirupsen/logrus" ) -const ( - defaultResolvConfPath = "/etc/resolv.conf" -) - const ( netbirdManager osManagerType = iota fileManager @@ -85,7 +81,11 @@ func getOSDNSManagerType() (osManagerType, error) { return networkManager, nil } if strings.Contains(text, "systemd-resolved") && isDbusListenerRunning(systemdResolvedDest, systemdDbusObjectNode) { - return systemdManager, nil + if checkStub() { + return systemdManager, nil + } else { + return fileManager, nil + } } if strings.Contains(text, "resolvconf") { if isDbusListenerRunning(systemdResolvedDest, systemdDbusObjectNode) { @@ -103,3 +103,20 @@ func getOSDNSManagerType() (osManagerType, error) { } return fileManager, nil } + +// checkStub checks if the stub resolver is disabled in systemd-resolved. If it is disabled, we fall back to file manager. +func checkStub() bool { + rConf, err := parseDefaultResolvConf() + if err != nil { + log.Warnf("failed to parse resolv conf: %s", err) + return true + } + + for _, ns := range rConf.nameServers { + if ns == "127.0.0.53" { + return true + } + } + + return false +} diff --git a/client/internal/dns/resolvconf_linux.go b/client/internal/dns/resolvconf_linux.go index 54bdeae12..1dad7627a 100644 --- a/client/internal/dns/resolvconf_linux.go +++ b/client/internal/dns/resolvconf_linux.go @@ -22,16 +22,16 @@ type resolvconf struct { // supported "openresolv" only func newResolvConfConfigurator(wgInterface WGIface) (hostManager, error) { - originalSearchDomains, nameServers, others, err := originalDNSConfigs("/etc/resolv.conf") + resolvConfEntries, err := parseDefaultResolvConf() if err != nil { log.Error(err) } return &resolvconf{ ifaceName: wgInterface.Name(), - originalSearchDomains: originalSearchDomains, - originalNameServers: nameServers, - othersConfigs: others, + originalSearchDomains: resolvConfEntries.searchDomains, + originalNameServers: resolvConfEntries.nameServers, + othersConfigs: resolvConfEntries.others, }, nil } diff --git a/go.mod b/go.mod index 42d82f7a7..1bdf78a3a 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/coreos/go-iptables v0.7.0 github.com/creack/pty v1.1.18 github.com/eko/gocache/v3 v3.1.1 + github.com/fsnotify/fsnotify v1.6.0 github.com/getlantern/systray v1.2.1 github.com/gliderlabs/ssh v0.3.4 github.com/godbus/dbus/v5 v5.1.0 @@ -99,7 +100,6 @@ require ( github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect