diff --git a/networkfs/resolvconf/resolvconf.go b/networkfs/resolvconf/resolvconf.go index 9165cae..a43daa5 100644 --- a/networkfs/resolvconf/resolvconf.go +++ b/networkfs/resolvconf/resolvconf.go @@ -5,13 +5,25 @@ import ( "io/ioutil" "regexp" "strings" + "sync" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/utils" ) var ( - nsRegexp = regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`) - searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) + defaultDns = []string{"8.8.8.8", "8.8.4.4"} + localHostRegexp = regexp.MustCompile(`(?m)^nameserver 127[^\n]+\n*`) + nsRegexp = regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`) + searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) ) +var lastModified struct { + sync.Mutex + sha256 string + contents []byte +} + func Get() ([]byte, error) { resolv, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { @@ -20,6 +32,57 @@ func Get() ([]byte, error) { return resolv, nil } +// Retrieves the host /etc/resolv.conf file, checks against the last hash +// and, if modified since last check, returns the bytes and new hash. +// This feature is used by the resolv.conf updater for containers +func GetIfChanged() ([]byte, string, error) { + lastModified.Lock() + defer lastModified.Unlock() + + resolv, err := ioutil.ReadFile("/etc/resolv.conf") + if err != nil { + return nil, "", err + } + newHash, err := utils.HashData(bytes.NewReader(resolv)) + if err != nil { + return nil, "", err + } + if lastModified.sha256 != newHash { + lastModified.sha256 = newHash + lastModified.contents = resolv + return resolv, newHash, nil + } + // nothing changed, so return no data + return nil, "", nil +} + +// retrieve the last used contents and hash of the host resolv.conf +// Used by containers updating on restart +func GetLastModified() ([]byte, string) { + lastModified.Lock() + defer lastModified.Unlock() + + return lastModified.contents, lastModified.sha256 +} + +// RemoveReplaceLocalDns looks for localhost (127.*) entries in the provided +// resolv.conf, removing local nameserver entries, and, if the resulting +// cleaned config has no defined nameservers left, adds default DNS entries +// It also returns a boolean to notify the caller if changes were made at all +func RemoveReplaceLocalDns(resolvConf []byte) ([]byte, bool) { + changed := false + cleanedResolvConf := localHostRegexp.ReplaceAll(resolvConf, []byte{}) + // if the resulting resolvConf is empty, use defaultDns + if !bytes.Contains(cleanedResolvConf, []byte("nameserver")) { + log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultDns) + cleanedResolvConf = append(cleanedResolvConf, []byte("\nnameserver "+strings.Join(defaultDns, "\nnameserver "))...) + } + if !bytes.Equal(resolvConf, cleanedResolvConf) { + changed = true + } + return cleanedResolvConf, changed +} + // getLines parses input into lines and strips away comments. func getLines(input []byte, commentMarker []byte) [][]byte { lines := bytes.Split(input, []byte("\n")) diff --git a/networkfs/resolvconf/resolvconf_test.go b/networkfs/resolvconf/resolvconf_test.go index 6187acb..2432ea5 100644 --- a/networkfs/resolvconf/resolvconf_test.go +++ b/networkfs/resolvconf/resolvconf_test.go @@ -156,3 +156,34 @@ func TestBuildWithZeroLengthDomainSearch(t *testing.T) { t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content) } } + +func TestRemoveReplaceLocalDns(t *testing.T) { + ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n" + + if result, _ := RemoveReplaceLocalDns([]byte(ns0)); result != nil { + if ns0 != string(result) { + t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) + } + } + + ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n" + if result, _ := RemoveReplaceLocalDns([]byte(ns1)); result != nil { + if ns0 != string(result) { + t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) + } + } + + ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n" + if result, _ := RemoveReplaceLocalDns([]byte(ns1)); result != nil { + if ns0 != string(result) { + t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) + } + } + + ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n" + if result, _ := RemoveReplaceLocalDns([]byte(ns1)); result != nil { + if ns0 != string(result) { + t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result)) + } + } +}