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