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