github.com/jaegerpicker/docker@v0.7.7-0.20150325003727-22dba32b4dab/pkg/resolvconf/resolvconf.go (about) 1 // Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf 2 package resolvconf 3 4 import ( 5 "bytes" 6 "io/ioutil" 7 "regexp" 8 "strings" 9 "sync" 10 11 log "github.com/Sirupsen/logrus" 12 "github.com/docker/docker/utils" 13 ) 14 15 var ( 16 // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS 17 defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"} 18 defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"} 19 ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)` 20 ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock 21 // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also 22 // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants 23 // -- e.g. other link-local types -- either won't work in containers or are unnecessary. 24 // For readability and sufficiency for Docker purposes this seemed more reasonable than a 25 // 1000+ character regexp with exact and complete IPv6 validation 26 ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})` 27 ipLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))` 28 29 localhostIPRegexp = regexp.MustCompile(ipLocalhost) 30 localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`) 31 nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`) 32 nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`) 33 searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`) 34 ) 35 36 var lastModified struct { 37 sync.Mutex 38 sha256 string 39 contents []byte 40 } 41 42 // Get returns the contents of /etc/resolv.conf 43 func Get() ([]byte, error) { 44 resolv, err := ioutil.ReadFile("/etc/resolv.conf") 45 if err != nil { 46 return nil, err 47 } 48 return resolv, nil 49 } 50 51 // GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash 52 // and, if modified since last check, returns the bytes and new hash. 53 // This feature is used by the resolv.conf updater for containers 54 func GetIfChanged() ([]byte, string, error) { 55 lastModified.Lock() 56 defer lastModified.Unlock() 57 58 resolv, err := ioutil.ReadFile("/etc/resolv.conf") 59 if err != nil { 60 return nil, "", err 61 } 62 newHash, err := utils.HashData(bytes.NewReader(resolv)) 63 if err != nil { 64 return nil, "", err 65 } 66 if lastModified.sha256 != newHash { 67 lastModified.sha256 = newHash 68 lastModified.contents = resolv 69 return resolv, newHash, nil 70 } 71 // nothing changed, so return no data 72 return nil, "", nil 73 } 74 75 // GetLastModified retrieves the last used contents and hash of the host resolv.conf. 76 // Used by containers updating on restart 77 func GetLastModified() ([]byte, string) { 78 lastModified.Lock() 79 defer lastModified.Unlock() 80 81 return lastModified.contents, lastModified.sha256 82 } 83 84 // FilterResolvDns cleans up the config in resolvConf. It has two main jobs: 85 // 1. It looks for localhost (127.*|::1) entries in the provided 86 // resolv.conf, removing local nameserver entries, and, if the resulting 87 // cleaned config has no defined nameservers left, adds default DNS entries 88 // 2. Given the caller provides the enable/disable state of IPv6, the filter 89 // code will remove all IPv6 nameservers if it is not enabled for containers 90 // 91 // It returns a boolean to notify the caller if changes were made at all 92 func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) { 93 changed := false 94 cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) 95 // if IPv6 is not enabled, also clean out any IPv6 address nameserver 96 if !ipv6Enabled { 97 cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{}) 98 } 99 // if the resulting resolvConf has no more nameservers defined, add appropriate 100 // default DNS servers for IPv4 and (optionally) IPv6 101 if len(GetNameservers(cleanedResolvConf)) == 0 { 102 log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns) 103 dns := defaultIPv4Dns 104 if ipv6Enabled { 105 log.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns) 106 dns = append(dns, defaultIPv6Dns...) 107 } 108 cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) 109 } 110 if !bytes.Equal(resolvConf, cleanedResolvConf) { 111 changed = true 112 } 113 return cleanedResolvConf, changed 114 } 115 116 // getLines parses input into lines and strips away comments. 117 func getLines(input []byte, commentMarker []byte) [][]byte { 118 lines := bytes.Split(input, []byte("\n")) 119 var output [][]byte 120 for _, currentLine := range lines { 121 var commentIndex = bytes.Index(currentLine, commentMarker) 122 if commentIndex == -1 { 123 output = append(output, currentLine) 124 } else { 125 output = append(output, currentLine[:commentIndex]) 126 } 127 } 128 return output 129 } 130 131 // IsLocalhost returns true if ip matches the localhost IP regular expression. 132 // Used for determining if nameserver settings are being passed which are 133 // localhost addresses 134 func IsLocalhost(ip string) bool { 135 return localhostIPRegexp.MatchString(ip) 136 } 137 138 // GetNameservers returns nameservers (if any) listed in /etc/resolv.conf 139 func GetNameservers(resolvConf []byte) []string { 140 nameservers := []string{} 141 for _, line := range getLines(resolvConf, []byte("#")) { 142 var ns = nsRegexp.FindSubmatch(line) 143 if len(ns) > 0 { 144 nameservers = append(nameservers, string(ns[1])) 145 } 146 } 147 return nameservers 148 } 149 150 // GetNameserversAsCIDR returns nameservers (if any) listed in 151 // /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32") 152 // This function's output is intended for net.ParseCIDR 153 func GetNameserversAsCIDR(resolvConf []byte) []string { 154 nameservers := []string{} 155 for _, nameserver := range GetNameservers(resolvConf) { 156 nameservers = append(nameservers, nameserver+"/32") 157 } 158 return nameservers 159 } 160 161 // GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf 162 // If more than one search line is encountered, only the contents of the last 163 // one is returned. 164 func GetSearchDomains(resolvConf []byte) []string { 165 domains := []string{} 166 for _, line := range getLines(resolvConf, []byte("#")) { 167 match := searchRegexp.FindSubmatch(line) 168 if match == nil { 169 continue 170 } 171 domains = strings.Fields(string(match[1])) 172 } 173 return domains 174 } 175 176 // Build writes a configuration file to path containing a "nameserver" entry 177 // for every element in dns, and a "search" entry for every element in 178 // dnsSearch. 179 func Build(path string, dns, dnsSearch []string) error { 180 content := bytes.NewBuffer(nil) 181 for _, dns := range dns { 182 if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil { 183 return err 184 } 185 } 186 if len(dnsSearch) > 0 { 187 if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." { 188 if _, err := content.WriteString("search " + searchString + "\n"); err != nil { 189 return err 190 } 191 } 192 } 193 194 return ioutil.WriteFile(path, content.Bytes(), 0644) 195 }