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