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