// Build writes a configuration file to path containing a "nameserver" entry // for every element in dns, a "search" entry for every element in // dnsSearch, and an "options" entry for every element in dnsOptions. func Build(path string, dns, dnsSearch, dnsOptions []string) (*File, error) { content := bytes.NewBuffer(nil) if len(dnsSearch) > 0 { if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { if _, err := content.WriteString("search " + searchString + "\n"); err != nil { return nil, err } } } for _, dns := range dns { if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { return nil, err } } if len(dnsOptions) > 0 { if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" { if _, err := content.WriteString("options " + optsString + "\n"); err != nil { return nil, err } } } hash, err := ioutils.HashData(bytes.NewReader(content.Bytes())) if err != nil { return nil, err } return &File{Content: content.Bytes(), Hash: hash}, ioutil.WriteFile(path, content.Bytes(), 0644) }
// GetSpecific returns the contents of the user specified resolv.conf file and its hash func GetSpecific(path string) (*File, error) { resolv, err := ioutil.ReadFile(path) if err != nil { return nil, err } hash, err := ioutils.HashData(bytes.NewReader(resolv)) if err != nil { return nil, err } return &File{Content: resolv, Hash: hash}, nil }
// Get returns the contents of /etc/resolv.conf and its hash func Get() (*File, error) { resolv, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { return nil, err } hash, err := ioutils.HashData(bytes.NewReader(resolv)) if err != nil { return nil, err } return &File{Content: resolv, Hash: hash}, nil }
// set up the watch on the host's /etc/resolv.conf so that we can update container's // live resolv.conf when the network changes on the host func (daemon *Daemon) setupResolvconfWatcher() error { watcher, err := fsnotify.NewWatcher() if err != nil { return err } //this goroutine listens for the events on the watch we add //on the resolv.conf file on the host go func() { for { select { case event := <-watcher.Events: if event.Name == "/etc/resolv.conf" && (event.Op&(fsnotify.Write|fsnotify.Create) != 0) { // verify a real change happened before we go further--a file write may have happened // without an actual change to the file updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged() if err != nil { logrus.Debugf("Error retrieving updated host resolv.conf: %v", err) } else if updatedResolvConf != nil { // because the new host resolv.conf might have localhost nameservers.. updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during localhost cleanup: generate an updated hash newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { logrus.Debugf("Error generating hash of new resolv.conf: %v", err) } else { newResolvConfHash = newHash } } logrus.Debug("host network resolv.conf changed--walking container list for updates") contList := daemon.containers.List() for _, container := range contList { if err := container.updateResolvConf(updatedResolvConf, newResolvConfHash); err != nil { logrus.Debugf("Error on resolv.conf update check for container ID: %s: %v", container.ID, err) } } } } case err := <-watcher.Errors: logrus.Debugf("host resolv.conf notify error: %v", err) } } }() if err := watcher.Add("/etc"); err != nil { return err } return nil }
func TestGet(t *testing.T) { resolvConfUtils, err := Get() if err != nil { t.Fatal(err) } resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { t.Fatal(err) } if string(resolvConfUtils.Content) != string(resolvConfSystem) { t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.") } hashSystem, err := ioutils.HashData(bytes.NewReader(resolvConfSystem)) if err != nil { t.Fatal(err) } if resolvConfUtils.Hash != hashSystem { t.Fatalf("/etc/resolv.conf and GetResolvConf have different hashes.") } }
// GetIfChanged 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 := ioutils.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 }
// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs: // 1. It looks for localhost (127.*|::1) 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 // 2. Given the caller provides the enable/disable state of IPv6, the filter // code will remove all IPv6 nameservers if it is not enabled for containers // func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) { cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) // if IPv6 is not enabled, also clean out any IPv6 address nameserver if !ipv6Enabled { cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{}) } // if the resulting resolvConf has no more nameservers defined, add appropriate // default DNS servers for IPv4 and (optionally) IPv6 if len(GetNameservers(cleanedResolvConf, types.IP)) == 0 { logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v", defaultIPv4Dns) dns := defaultIPv4Dns if ipv6Enabled { logrus.Infof("IPv6 enabled; Adding default IPv6 external servers: %v", defaultIPv6Dns) dns = append(dns, defaultIPv6Dns...) } cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) } hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf)) if err != nil { return nil, err } return &File{Content: cleanedResolvConf, Hash: hash}, nil }
// called when the host's resolv.conf changes to check whether container's resolv.conf // is unchanged by the container "user" since container start: if unchanged, the // container's resolv.conf will be updated to match the host's new resolv.conf func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolvHash string) error { if container.ResolvConfPath == "" { return nil } if container.Running { //set a marker in the hostConfig to update on next start/restart container.UpdateDns = true return nil } resolvHashFile := container.ResolvConfPath + ".hash" //read the container's current resolv.conf and compute the hash resolvBytes, err := ioutil.ReadFile(container.ResolvConfPath) if err != nil { return err } curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes)) if err != nil { return err } //read the hash from the last time we wrote resolv.conf in the container hashBytes, err := ioutil.ReadFile(resolvHashFile) if err != nil { if !os.IsNotExist(err) { return err } // backwards compat: if no hash file exists, this container pre-existed from // a Docker daemon that didn't contain this update feature. Given we can't know // if the user has modified the resolv.conf since container start time, safer // to just never update the container's resolv.conf during it's lifetime which // we can control by setting hashBytes to an empty string hashBytes = []byte("") } //if the user has not modified the resolv.conf of the container since we wrote it last //we will replace it with the updated resolv.conf from the host if string(hashBytes) == curHash { logrus.Debugf("replacing %q with updated host resolv.conf", container.ResolvConfPath) // for atomic updates to these files, use temporary files with os.Rename: dir := path.Dir(container.ResolvConfPath) tmpHashFile, err := ioutil.TempFile(dir, "hash") if err != nil { return err } tmpResolvFile, err := ioutil.TempFile(dir, "resolv") if err != nil { return err } // write the updates to the temp files if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newResolvHash), 0644); err != nil { return err } if err = ioutil.WriteFile(tmpResolvFile.Name(), updatedResolvConf, 0644); err != nil { return err } // rename the temp files for atomic replace if err = os.Rename(tmpHashFile.Name(), resolvHashFile); err != nil { return err } return os.Rename(tmpResolvFile.Name(), container.ResolvConfPath) } return nil }
func (container *Container) setupContainerDns() error { if container.ResolvConfPath != "" { // check if this is an existing container that needs DNS update: if container.UpdateDns { // read the host's resolv.conf, get the hash and call updateResolvConf logrus.Debugf("Check container (%s) for update to resolv.conf - UpdateDns flag was set", container.ID) latestResolvConf, latestHash := resolvconf.GetLastModified() // clean container resolv.conf re: localhost nameservers and IPv6 NS (if IPv6 disabled) updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during resolv.conf localhost cleanup: generate an updated hash newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { return err } latestHash = newHash } if err := container.updateResolvConf(updatedResolvConf, latestHash); err != nil { return err } // successful update of the restarting container; set the flag off container.UpdateDns = false } return nil } var ( config = container.hostConfig daemon = container.daemon ) resolvConf, err := resolvconf.Get() if err != nil { return err } container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") if err != nil { return err } if config.NetworkMode != "host" { // check configurations for any container/daemon dns settings if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 { var ( dns = resolvconf.GetNameservers(resolvConf) dnsSearch = resolvconf.GetSearchDomains(resolvConf) ) if len(config.Dns) > 0 { dns = config.Dns } else if len(daemon.config.Dns) > 0 { dns = daemon.config.Dns } if len(config.DnsSearch) > 0 { dnsSearch = config.DnsSearch } else if len(daemon.config.DnsSearch) > 0 { dnsSearch = daemon.config.DnsSearch } return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch) } // replace any localhost/127.*, and remove IPv6 nameservers if IPv6 disabled in daemon resolvConf, _ = resolvconf.FilterResolvDns(resolvConf, daemon.config.Bridge.EnableIPv6) } //get a sha256 hash of the resolv conf at this point so we can check //for changes when the host resolv.conf changes (e.g. network update) resolvHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) if err != nil { return err } resolvHashFile := container.ResolvConfPath + ".hash" if err = ioutil.WriteFile(resolvHashFile, []byte(resolvHash), 0644); err != nil { return err } return ioutil.WriteFile(container.ResolvConfPath, resolvConf, 0644) }
func (sb *sandbox) updateDNS(ipv6Enabled bool) error { var oldHash []byte hashFile := sb.config.resolvConfHashFile resolvConf, err := ioutil.ReadFile(sb.config.resolvConfPath) if err != nil { if !os.IsNotExist(err) { return err } } else { oldHash, err = ioutil.ReadFile(hashFile) if err != nil { if !os.IsNotExist(err) { return err } oldHash = []byte{} } } curHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) if err != nil { return err } if string(oldHash) != "" && curHash != string(oldHash) { // Seems the user has changed the container resolv.conf since the last time // we checked so return without doing anything. log.Infof("Skipping update of resolv.conf file with ipv6Enabled: %t because file was touched by user", ipv6Enabled) return nil } // replace any localhost/127.* and remove IPv6 nameservers if IPv6 disabled. resolvConf, _ = resolvconf.FilterResolvDNS(resolvConf, ipv6Enabled) newHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) if err != nil { return err } // for atomic updates to these files, use temporary files with os.Rename: dir := path.Dir(sb.config.resolvConfPath) tmpHashFile, err := ioutil.TempFile(dir, "hash") if err != nil { return err } tmpResolvFile, err := ioutil.TempFile(dir, "resolv") if err != nil { return err } // Change the perms to filePerm (0644) since ioutil.TempFile creates it by default as 0600 if err := os.Chmod(tmpResolvFile.Name(), filePerm); err != nil { return err } // write the updates to the temp files if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newHash), filePerm); err != nil { return err } if err = ioutil.WriteFile(tmpResolvFile.Name(), resolvConf, filePerm); err != nil { return err } // rename the temp files for atomic replace if err = os.Rename(tmpHashFile.Name(), hashFile); err != nil { return err } return os.Rename(tmpResolvFile.Name(), sb.config.resolvConfPath) }
func (ep *endpoint) updateDNS(resolvConf []byte) error { ep.Lock() container := ep.container network := ep.network ep.Unlock() if container == nil { return ErrNoContainer{} } oldHash := []byte{} hashFile := container.config.resolvConfPath + ".hash" resolvBytes, err := ioutil.ReadFile(container.config.resolvConfPath) if err != nil { if !os.IsNotExist(err) { return err } } else { oldHash, err = ioutil.ReadFile(hashFile) if err != nil { if !os.IsNotExist(err) { return err } oldHash = []byte{} } } curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes)) if err != nil { return err } if string(oldHash) != "" && curHash != string(oldHash) { // Seems the user has changed the container resolv.conf since the last time // we checked so return without doing anything. return nil } // replace any localhost/127.* and remove IPv6 nameservers if IPv6 disabled. resolvConf, _ = resolvconf.FilterResolvDNS(resolvConf, network.enableIPv6) newHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) if err != nil { return err } // for atomic updates to these files, use temporary files with os.Rename: dir := path.Dir(container.config.resolvConfPath) tmpHashFile, err := ioutil.TempFile(dir, "hash") if err != nil { return err } tmpResolvFile, err := ioutil.TempFile(dir, "resolv") if err != nil { return err } // Change the perms to 0644 since ioutil.TempFile creates it by default as 0600 if err := os.Chmod(tmpResolvFile.Name(), 0644); err != nil { return err } // write the updates to the temp files if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newHash), 0644); err != nil { return err } if err = ioutil.WriteFile(tmpResolvFile.Name(), resolvConf, 0644); err != nil { return err } // rename the temp files for atomic replace if err = os.Rename(tmpHashFile.Name(), hashFile); err != nil { return err } return os.Rename(tmpResolvFile.Name(), container.config.resolvConfPath) }