1// +build !windows 2 3package libnetwork 4 5import ( 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path" 10 "path/filepath" 11 "strconv" 12 "strings" 13 14 "github.com/docker/libnetwork/etchosts" 15 "github.com/docker/libnetwork/resolvconf" 16 "github.com/docker/libnetwork/resolvconf/dns" 17 "github.com/docker/libnetwork/types" 18 "github.com/sirupsen/logrus" 19) 20 21const ( 22 defaultPrefix = "/var/lib/docker/network/files" 23 dirPerm = 0755 24 filePerm = 0644 25) 26 27func (sb *sandbox) startResolver(restore bool) { 28 sb.resolverOnce.Do(func() { 29 var err error 30 sb.resolver = NewResolver(resolverIPSandbox, true, sb.Key(), sb) 31 defer func() { 32 if err != nil { 33 sb.resolver = nil 34 } 35 }() 36 37 // In the case of live restore container is already running with 38 // right resolv.conf contents created before. Just update the 39 // external DNS servers from the restored sandbox for embedded 40 // server to use. 41 if !restore { 42 err = sb.rebuildDNS() 43 if err != nil { 44 logrus.Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err) 45 return 46 } 47 } 48 sb.resolver.SetExtServers(sb.extDNS) 49 50 if err = sb.osSbox.InvokeFunc(sb.resolver.SetupFunc(0)); err != nil { 51 logrus.Errorf("Resolver Setup function failed for container %s, %q", sb.ContainerID(), err) 52 return 53 } 54 55 if err = sb.resolver.Start(); err != nil { 56 logrus.Errorf("Resolver Start failed for container %s, %q", sb.ContainerID(), err) 57 } 58 }) 59} 60 61func (sb *sandbox) setupResolutionFiles() error { 62 if err := sb.buildHostsFile(); err != nil { 63 return err 64 } 65 66 if err := sb.updateParentHosts(); err != nil { 67 return err 68 } 69 70 return sb.setupDNS() 71} 72 73func (sb *sandbox) buildHostsFile() error { 74 if sb.config.hostsPath == "" { 75 sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts" 76 } 77 78 dir, _ := filepath.Split(sb.config.hostsPath) 79 if err := createBasePath(dir); err != nil { 80 return err 81 } 82 83 // This is for the host mode networking 84 if sb.config.useDefaultSandBox && len(sb.config.extraHosts) == 0 { 85 // We are working under the assumption that the origin file option had been properly expressed by the upper layer 86 // if not here we are going to error out 87 if err := copyFile(sb.config.originHostsPath, sb.config.hostsPath); err != nil && !os.IsNotExist(err) { 88 return types.InternalErrorf("could not copy source hosts file %s to %s: %v", sb.config.originHostsPath, sb.config.hostsPath, err) 89 } 90 return nil 91 } 92 93 extraContent := make([]etchosts.Record, 0, len(sb.config.extraHosts)) 94 for _, extraHost := range sb.config.extraHosts { 95 extraContent = append(extraContent, etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP}) 96 } 97 98 return etchosts.Build(sb.config.hostsPath, "", sb.config.hostName, sb.config.domainName, extraContent) 99} 100 101func (sb *sandbox) updateHostsFile(ifaceIP string) error { 102 if ifaceIP == "" { 103 return nil 104 } 105 106 if sb.config.originHostsPath != "" { 107 return nil 108 } 109 110 // User might have provided a FQDN in hostname or split it across hostname 111 // and domainname. We want the FQDN and the bare hostname. 112 fqdn := sb.config.hostName 113 mhost := sb.config.hostName 114 if sb.config.domainName != "" { 115 fqdn = fmt.Sprintf("%s.%s", fqdn, sb.config.domainName) 116 } 117 118 parts := strings.SplitN(fqdn, ".", 2) 119 if len(parts) == 2 { 120 mhost = fmt.Sprintf("%s %s", fqdn, parts[0]) 121 } 122 123 extraContent := []etchosts.Record{{Hosts: mhost, IP: ifaceIP}} 124 125 sb.addHostsEntries(extraContent) 126 return nil 127} 128 129func (sb *sandbox) addHostsEntries(recs []etchosts.Record) { 130 if err := etchosts.Add(sb.config.hostsPath, recs); err != nil { 131 logrus.Warnf("Failed adding service host entries to the running container: %v", err) 132 } 133} 134 135func (sb *sandbox) deleteHostsEntries(recs []etchosts.Record) { 136 if err := etchosts.Delete(sb.config.hostsPath, recs); err != nil { 137 logrus.Warnf("Failed deleting service host entries to the running container: %v", err) 138 } 139} 140 141func (sb *sandbox) updateParentHosts() error { 142 var pSb Sandbox 143 144 for _, update := range sb.config.parentUpdates { 145 sb.controller.WalkSandboxes(SandboxContainerWalker(&pSb, update.cid)) 146 if pSb == nil { 147 continue 148 } 149 if err := etchosts.Update(pSb.(*sandbox).config.hostsPath, update.ip, update.name); err != nil { 150 return err 151 } 152 } 153 154 return nil 155} 156 157func (sb *sandbox) restorePath() { 158 if sb.config.resolvConfPath == "" { 159 sb.config.resolvConfPath = defaultPrefix + "/" + sb.id + "/resolv.conf" 160 } 161 sb.config.resolvConfHashFile = sb.config.resolvConfPath + ".hash" 162 if sb.config.hostsPath == "" { 163 sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts" 164 } 165} 166 167func (sb *sandbox) setExternalResolvers(content []byte, addrType int, checkLoopback bool) { 168 servers := resolvconf.GetNameservers(content, addrType) 169 for _, ip := range servers { 170 hostLoopback := false 171 if checkLoopback { 172 hostLoopback = dns.IsIPv4Localhost(ip) 173 } 174 sb.extDNS = append(sb.extDNS, extDNSEntry{ 175 IPStr: ip, 176 HostLoopback: hostLoopback, 177 }) 178 } 179} 180 181func (sb *sandbox) setupDNS() error { 182 var newRC *resolvconf.File 183 184 if sb.config.resolvConfPath == "" { 185 sb.config.resolvConfPath = defaultPrefix + "/" + sb.id + "/resolv.conf" 186 } 187 188 sb.config.resolvConfHashFile = sb.config.resolvConfPath + ".hash" 189 190 dir, _ := filepath.Split(sb.config.resolvConfPath) 191 if err := createBasePath(dir); err != nil { 192 return err 193 } 194 195 // When the user specify a conainter in the host namespace and do no have any dns option specified 196 // we just copy the host resolv.conf from the host itself 197 if sb.config.useDefaultSandBox && 198 len(sb.config.dnsList) == 0 && len(sb.config.dnsSearchList) == 0 && len(sb.config.dnsOptionsList) == 0 { 199 200 // We are working under the assumption that the origin file option had been properly expressed by the upper layer 201 // if not here we are going to error out 202 if err := copyFile(sb.config.originResolvConfPath, sb.config.resolvConfPath); err != nil { 203 if !os.IsNotExist(err) { 204 return fmt.Errorf("could not copy source resolv.conf file %s to %s: %v", sb.config.originResolvConfPath, sb.config.resolvConfPath, err) 205 } 206 logrus.Infof("%s does not exist, we create an empty resolv.conf for container", sb.config.originResolvConfPath) 207 if err := createFile(sb.config.resolvConfPath); err != nil { 208 return err 209 } 210 } 211 return nil 212 } 213 214 originResolvConfPath := sb.config.originResolvConfPath 215 if originResolvConfPath == "" { 216 // if not specified fallback to default /etc/resolv.conf 217 originResolvConfPath = resolvconf.DefaultResolvConf 218 } 219 currRC, err := resolvconf.GetSpecific(originResolvConfPath) 220 if err != nil { 221 if !os.IsNotExist(err) { 222 return err 223 } 224 // it's ok to continue if /etc/resolv.conf doesn't exist, default resolvers (Google's Public DNS) 225 // will be used 226 currRC = &resolvconf.File{} 227 logrus.Infof("/etc/resolv.conf does not exist") 228 } 229 230 if len(sb.config.dnsList) > 0 || len(sb.config.dnsSearchList) > 0 || len(sb.config.dnsOptionsList) > 0 { 231 var ( 232 err error 233 dnsList = resolvconf.GetNameservers(currRC.Content, types.IP) 234 dnsSearchList = resolvconf.GetSearchDomains(currRC.Content) 235 dnsOptionsList = resolvconf.GetOptions(currRC.Content) 236 ) 237 if len(sb.config.dnsList) > 0 { 238 dnsList = sb.config.dnsList 239 } 240 if len(sb.config.dnsSearchList) > 0 { 241 dnsSearchList = sb.config.dnsSearchList 242 } 243 if len(sb.config.dnsOptionsList) > 0 { 244 dnsOptionsList = sb.config.dnsOptionsList 245 } 246 newRC, err = resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList, dnsOptionsList) 247 if err != nil { 248 return err 249 } 250 // After building the resolv.conf from the user config save the 251 // external resolvers in the sandbox. Note that --dns 127.0.0.x 252 // config refers to the loopback in the container namespace 253 sb.setExternalResolvers(newRC.Content, types.IPv4, false) 254 } else { 255 // If the host resolv.conf file has 127.0.0.x container should 256 // use the host resolver for queries. This is supported by the 257 // docker embedded DNS server. Hence save the external resolvers 258 // before filtering it out. 259 sb.setExternalResolvers(currRC.Content, types.IPv4, true) 260 261 // Replace any localhost/127.* (at this point we have no info about ipv6, pass it as true) 262 if newRC, err = resolvconf.FilterResolvDNS(currRC.Content, true); err != nil { 263 return err 264 } 265 // No contention on container resolv.conf file at sandbox creation 266 if err := ioutil.WriteFile(sb.config.resolvConfPath, newRC.Content, filePerm); err != nil { 267 return types.InternalErrorf("failed to write unhaltered resolv.conf file content when setting up dns for sandbox %s: %v", sb.ID(), err) 268 } 269 } 270 271 // Write hash 272 if err := ioutil.WriteFile(sb.config.resolvConfHashFile, []byte(newRC.Hash), filePerm); err != nil { 273 return types.InternalErrorf("failed to write resolv.conf hash file when setting up dns for sandbox %s: %v", sb.ID(), err) 274 } 275 276 return nil 277} 278 279func (sb *sandbox) updateDNS(ipv6Enabled bool) error { 280 var ( 281 currHash string 282 hashFile = sb.config.resolvConfHashFile 283 ) 284 285 // This is for the host mode networking 286 if sb.config.useDefaultSandBox { 287 return nil 288 } 289 290 if len(sb.config.dnsList) > 0 || len(sb.config.dnsSearchList) > 0 || len(sb.config.dnsOptionsList) > 0 { 291 return nil 292 } 293 294 currRC, err := resolvconf.GetSpecific(sb.config.resolvConfPath) 295 if err != nil { 296 if !os.IsNotExist(err) { 297 return err 298 } 299 } else { 300 h, err := ioutil.ReadFile(hashFile) 301 if err != nil { 302 if !os.IsNotExist(err) { 303 return err 304 } 305 } else { 306 currHash = string(h) 307 } 308 } 309 310 if currHash != "" && currHash != currRC.Hash { 311 // Seems the user has changed the container resolv.conf since the last time 312 // we checked so return without doing anything. 313 //logrus.Infof("Skipping update of resolv.conf file with ipv6Enabled: %t because file was touched by user", ipv6Enabled) 314 return nil 315 } 316 317 // replace any localhost/127.* and remove IPv6 nameservers if IPv6 disabled. 318 newRC, err := resolvconf.FilterResolvDNS(currRC.Content, ipv6Enabled) 319 if err != nil { 320 return err 321 } 322 err = ioutil.WriteFile(sb.config.resolvConfPath, newRC.Content, 0644) 323 if err != nil { 324 return err 325 } 326 327 // write the new hash in a temp file and rename it to make the update atomic 328 dir := path.Dir(sb.config.resolvConfPath) 329 tmpHashFile, err := ioutil.TempFile(dir, "hash") 330 if err != nil { 331 return err 332 } 333 if err = tmpHashFile.Chmod(filePerm); err != nil { 334 tmpHashFile.Close() 335 return err 336 } 337 _, err = tmpHashFile.Write([]byte(newRC.Hash)) 338 if err1 := tmpHashFile.Close(); err == nil { 339 err = err1 340 } 341 if err != nil { 342 return err 343 } 344 return os.Rename(tmpHashFile.Name(), hashFile) 345} 346 347// Embedded DNS server has to be enabled for this sandbox. Rebuild the container's 348// resolv.conf by doing the following 349// - Add only the embedded server's IP to container's resolv.conf 350// - If the embedded server needs any resolv.conf options add it to the current list 351func (sb *sandbox) rebuildDNS() error { 352 currRC, err := resolvconf.GetSpecific(sb.config.resolvConfPath) 353 if err != nil { 354 return err 355 } 356 357 if len(sb.extDNS) == 0 { 358 sb.setExternalResolvers(currRC.Content, types.IPv4, false) 359 } 360 var ( 361 dnsList = []string{sb.resolver.NameServer()} 362 dnsOptionsList = resolvconf.GetOptions(currRC.Content) 363 dnsSearchList = resolvconf.GetSearchDomains(currRC.Content) 364 ) 365 366 // external v6 DNS servers has to be listed in resolv.conf 367 dnsList = append(dnsList, resolvconf.GetNameservers(currRC.Content, types.IPv6)...) 368 369 // If the user config and embedded DNS server both have ndots option set, 370 // remember the user's config so that unqualified names not in the docker 371 // domain can be dropped. 372 resOptions := sb.resolver.ResolverOptions() 373 374dnsOpt: 375 for _, resOpt := range resOptions { 376 if strings.Contains(resOpt, "ndots") { 377 for _, option := range dnsOptionsList { 378 if strings.Contains(option, "ndots") { 379 parts := strings.Split(option, ":") 380 if len(parts) != 2 { 381 return fmt.Errorf("invalid ndots option %v", option) 382 } 383 if num, err := strconv.Atoi(parts[1]); err != nil { 384 return fmt.Errorf("invalid number for ndots option: %v", parts[1]) 385 } else if num >= 0 { 386 // if the user sets ndots, use the user setting 387 sb.ndotsSet = true 388 break dnsOpt 389 } else { 390 return fmt.Errorf("invalid number for ndots option: %v", num) 391 } 392 } 393 } 394 } 395 } 396 397 if !sb.ndotsSet { 398 // if the user did not set the ndots, set it to 0 to prioritize the service name resolution 399 // Ref: https://linux.die.net/man/5/resolv.conf 400 dnsOptionsList = append(dnsOptionsList, resOptions...) 401 } 402 403 _, err = resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList, dnsOptionsList) 404 return err 405} 406 407func createBasePath(dir string) error { 408 return os.MkdirAll(dir, dirPerm) 409} 410 411func createFile(path string) error { 412 var f *os.File 413 414 dir, _ := filepath.Split(path) 415 err := createBasePath(dir) 416 if err != nil { 417 return err 418 } 419 420 f, err = os.Create(path) 421 if err == nil { 422 f.Close() 423 } 424 425 return err 426} 427 428func copyFile(src, dst string) error { 429 sBytes, err := ioutil.ReadFile(src) 430 if err != nil { 431 return err 432 } 433 return ioutil.WriteFile(dst, sBytes, filePerm) 434} 435