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