github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/libnetwork/sandbox_dns_unix.go (about) 1 //go:build !windows 2 3 package libnetwork 4 5 import ( 6 "context" 7 "io/fs" 8 "net/netip" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/containerd/log" 14 "github.com/docker/docker/errdefs" 15 "github.com/docker/docker/libnetwork/etchosts" 16 "github.com/docker/docker/libnetwork/internal/resolvconf" 17 "github.com/docker/docker/libnetwork/types" 18 "github.com/pkg/errors" 19 ) 20 21 const ( 22 defaultPrefix = "/var/lib/docker/network/files" 23 dirPerm = 0o755 24 filePerm = 0o644 25 26 resolverIPSandbox = "127.0.0.11" 27 ) 28 29 // finishInitDNS is to be called after the container namespace has been created, 30 // before it the user process is started. The container's support for IPv6 can be 31 // determined at this point. 32 func (sb *Sandbox) finishInitDNS() error { 33 if err := sb.buildHostsFile(); err != nil { 34 return errdefs.System(err) 35 } 36 for _, ep := range sb.Endpoints() { 37 if err := sb.updateHostsFile(ep.getEtcHostsAddrs()); err != nil { 38 return errdefs.System(err) 39 } 40 } 41 return nil 42 } 43 44 func (sb *Sandbox) startResolver(restore bool) { 45 sb.resolverOnce.Do(func() { 46 var err error 47 // The resolver is started with proxyDNS=false if the sandbox does not currently 48 // have a gateway. So, if the Sandbox is only connected to an 'internal' network, 49 // it will not forward DNS requests to external resolvers. The resolver's 50 // proxyDNS setting is then updated as network Endpoints are added/removed. 51 sb.resolver = NewResolver(resolverIPSandbox, sb.getGatewayEndpoint() != nil, sb) 52 defer func() { 53 if err != nil { 54 sb.resolver = nil 55 } 56 }() 57 58 // In the case of live restore container is already running with 59 // right resolv.conf contents created before. Just update the 60 // external DNS servers from the restored sandbox for embedded 61 // server to use. 62 if !restore { 63 err = sb.rebuildDNS() 64 if err != nil { 65 log.G(context.TODO()).Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err) 66 return 67 } 68 } 69 sb.resolver.SetExtServers(sb.extDNS) 70 71 if err = sb.osSbox.InvokeFunc(sb.resolver.SetupFunc(0)); err != nil { 72 log.G(context.TODO()).Errorf("Resolver Setup function failed for container %s, %q", sb.ContainerID(), err) 73 return 74 } 75 76 if err = sb.resolver.Start(); err != nil { 77 log.G(context.TODO()).Errorf("Resolver Start failed for container %s, %q", sb.ContainerID(), err) 78 } 79 }) 80 } 81 82 func (sb *Sandbox) setupResolutionFiles() error { 83 // Create a hosts file that can be mounted during container setup. For most 84 // networking modes (not host networking) it will be re-created before the 85 // container start, once its support for IPv6 is known. 86 if sb.config.hostsPath == "" { 87 sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts" 88 } 89 dir, _ := filepath.Split(sb.config.hostsPath) 90 if err := createBasePath(dir); err != nil { 91 return err 92 } 93 if err := sb.buildHostsFile(); err != nil { 94 return err 95 } 96 97 return sb.setupDNS() 98 } 99 100 func (sb *Sandbox) buildHostsFile() error { 101 sb.restoreHostsPath() 102 103 dir, _ := filepath.Split(sb.config.hostsPath) 104 if err := createBasePath(dir); err != nil { 105 return err 106 } 107 108 // This is for the host mode networking 109 if sb.config.useDefaultSandBox && len(sb.config.extraHosts) == 0 { 110 // We are working under the assumption that the origin file option had been properly expressed by the upper layer 111 // if not here we are going to error out 112 if err := copyFile(sb.config.originHostsPath, sb.config.hostsPath); err != nil && !os.IsNotExist(err) { 113 return types.InternalErrorf("could not copy source hosts file %s to %s: %v", sb.config.originHostsPath, sb.config.hostsPath, err) 114 } 115 return nil 116 } 117 118 extraContent := make([]etchosts.Record, 0, len(sb.config.extraHosts)) 119 for _, extraHost := range sb.config.extraHosts { 120 extraContent = append(extraContent, etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP}) 121 } 122 123 // Assume IPv6 support, unless it's definitely disabled. 124 buildf := etchosts.Build 125 if en, ok := sb.ipv6Enabled(); ok && !en { 126 buildf = etchosts.BuildNoIPv6 127 } 128 if err := buildf(sb.config.hostsPath, extraContent); err != nil { 129 return err 130 } 131 132 return sb.updateParentHosts() 133 } 134 135 func (sb *Sandbox) updateHostsFile(ifaceIPs []string) error { 136 if len(ifaceIPs) == 0 { 137 return nil 138 } 139 140 if sb.config.originHostsPath != "" { 141 return nil 142 } 143 144 // User might have provided a FQDN in hostname or split it across hostname 145 // and domainname. We want the FQDN and the bare hostname. 146 fqdn := sb.config.hostName 147 if sb.config.domainName != "" { 148 fqdn += "." + sb.config.domainName 149 } 150 hosts := fqdn 151 152 if hostName, _, ok := strings.Cut(fqdn, "."); ok { 153 hosts += " " + hostName 154 } 155 156 var extraContent []etchosts.Record 157 for _, ip := range ifaceIPs { 158 extraContent = append(extraContent, etchosts.Record{Hosts: hosts, IP: ip}) 159 } 160 161 sb.addHostsEntries(extraContent) 162 return nil 163 } 164 165 func (sb *Sandbox) addHostsEntries(recs []etchosts.Record) { 166 // Assume IPv6 support, unless it's definitely disabled. 167 if en, ok := sb.ipv6Enabled(); ok && !en { 168 var filtered []etchosts.Record 169 for _, rec := range recs { 170 if addr, err := netip.ParseAddr(rec.IP); err == nil && !addr.Is6() { 171 filtered = append(filtered, rec) 172 } 173 } 174 recs = filtered 175 } 176 if err := etchosts.Add(sb.config.hostsPath, recs); err != nil { 177 log.G(context.TODO()).Warnf("Failed adding service host entries to the running container: %v", err) 178 } 179 } 180 181 func (sb *Sandbox) deleteHostsEntries(recs []etchosts.Record) { 182 if err := etchosts.Delete(sb.config.hostsPath, recs); err != nil { 183 log.G(context.TODO()).Warnf("Failed deleting service host entries to the running container: %v", err) 184 } 185 } 186 187 func (sb *Sandbox) updateParentHosts() error { 188 var pSb *Sandbox 189 190 for _, update := range sb.config.parentUpdates { 191 // TODO(thaJeztah): was it intentional for this loop to re-use prior results of pSB? If not, we should make pSb local and always replace here. 192 if s, _ := sb.controller.GetSandbox(update.cid); s != nil { 193 pSb = s 194 } 195 if pSb == nil { 196 continue 197 } 198 // TODO(robmry) - filter out IPv6 addresses here if !sb.ipv6Enabled() but... 199 // - this is part of the implementation of '--link', which will be removed along 200 // with the rest of legacy networking. 201 // - IPv6 addresses shouldn't be allocated if IPv6 is not available in a container, 202 // and that change will come along later. 203 // - I think this may be dead code, it's not possible to start a parent container with 204 // '--link child' unless the child has already started ("Error response from daemon: 205 // Cannot link to a non running container"). So, when the child starts and this method 206 // is called with updates for parents, the parents aren't running and GetSandbox() 207 // returns nil.) 208 if err := etchosts.Update(pSb.config.hostsPath, update.ip, update.name); err != nil { 209 return err 210 } 211 } 212 213 return nil 214 } 215 216 func (sb *Sandbox) restoreResolvConfPath() { 217 if sb.config.resolvConfPath == "" { 218 sb.config.resolvConfPath = defaultPrefix + "/" + sb.id + "/resolv.conf" 219 } 220 sb.config.resolvConfHashFile = sb.config.resolvConfPath + ".hash" 221 } 222 223 func (sb *Sandbox) restoreHostsPath() { 224 if sb.config.hostsPath == "" { 225 sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts" 226 } 227 } 228 229 func (sb *Sandbox) setExternalResolvers(entries []resolvconf.ExtDNSEntry) { 230 sb.extDNS = make([]extDNSEntry, 0, len(entries)) 231 for _, entry := range entries { 232 sb.extDNS = append(sb.extDNS, extDNSEntry{ 233 IPStr: entry.Addr.String(), 234 HostLoopback: entry.HostLoopback, 235 }) 236 } 237 } 238 239 func (c *containerConfig) getOriginResolvConfPath() string { 240 if c.originResolvConfPath != "" { 241 return c.originResolvConfPath 242 } 243 // Fallback if not specified. 244 return resolvconf.Path() 245 } 246 247 // loadResolvConf reads the resolv.conf file at path, and merges in overrides for 248 // nameservers, options, and search domains. 249 func (sb *Sandbox) loadResolvConf(path string) (*resolvconf.ResolvConf, error) { 250 rc, err := resolvconf.Load(path) 251 if err != nil && !errors.Is(err, fs.ErrNotExist) { 252 return nil, err 253 } 254 // Proceed with rc, which might be zero-valued if path does not exist. 255 256 rc.SetHeader(`# Generated by Docker Engine. 257 # This file can be edited; Docker Engine will not make further changes once it 258 # has been modified.`) 259 if len(sb.config.dnsList) > 0 { 260 var dnsAddrs []netip.Addr 261 for _, ns := range sb.config.dnsList { 262 addr, err := netip.ParseAddr(ns) 263 if err != nil { 264 return nil, errors.Wrapf(err, "bad nameserver address %s", ns) 265 } 266 dnsAddrs = append(dnsAddrs, addr) 267 } 268 rc.OverrideNameServers(dnsAddrs) 269 } 270 if len(sb.config.dnsSearchList) > 0 { 271 rc.OverrideSearch(sb.config.dnsSearchList) 272 } 273 if len(sb.config.dnsOptionsList) > 0 { 274 rc.OverrideOptions(sb.config.dnsOptionsList) 275 } 276 return &rc, nil 277 } 278 279 // For a new sandbox, write an initial version of the container's resolv.conf. It'll 280 // be a copy of the host's file, with overrides for nameservers, options and search 281 // domains applied. 282 func (sb *Sandbox) setupDNS() error { 283 // Make sure the directory exists. 284 sb.restoreResolvConfPath() 285 dir, _ := filepath.Split(sb.config.resolvConfPath) 286 if err := createBasePath(dir); err != nil { 287 return err 288 } 289 290 rc, err := sb.loadResolvConf(sb.config.getOriginResolvConfPath()) 291 if err != nil { 292 return err 293 } 294 return rc.WriteFile(sb.config.resolvConfPath, sb.config.resolvConfHashFile, filePerm) 295 } 296 297 // Called when an endpoint has joined the sandbox. 298 func (sb *Sandbox) updateDNS(ipv6Enabled bool) error { 299 if mod, err := resolvconf.UserModified(sb.config.resolvConfPath, sb.config.resolvConfHashFile); err != nil || mod { 300 return err 301 } 302 303 // Load the host's resolv.conf as a starting point. 304 rc, err := sb.loadResolvConf(sb.config.getOriginResolvConfPath()) 305 if err != nil { 306 return err 307 } 308 // For host-networking, no further change is needed. 309 if !sb.config.useDefaultSandBox { 310 // The legacy bridge network has no internal nameserver. So, strip localhost 311 // nameservers from the host's config, then add default nameservers if there 312 // are none remaining. 313 rc.TransformForLegacyNw(ipv6Enabled) 314 } 315 return rc.WriteFile(sb.config.resolvConfPath, sb.config.resolvConfHashFile, filePerm) 316 } 317 318 // Embedded DNS server has to be enabled for this sandbox. Rebuild the container's resolv.conf. 319 func (sb *Sandbox) rebuildDNS() error { 320 // Don't touch the file if the user has modified it. 321 if mod, err := resolvconf.UserModified(sb.config.resolvConfPath, sb.config.resolvConfHashFile); err != nil || mod { 322 return err 323 } 324 325 // Load the host's resolv.conf as a starting point. 326 rc, err := sb.loadResolvConf(sb.config.getOriginResolvConfPath()) 327 if err != nil { 328 return err 329 } 330 331 // Check for IPv6 endpoints in this sandbox. If there are any, and the container has 332 // IPv6 enabled, upstream requests from the internal DNS resolver can be made from 333 // the container's namespace. 334 // TODO(robmry) - this can only check networks connected when the resolver is set up, 335 // the configuration won't be updated if the container gets an IPv6 address later. 336 ipv6 := false 337 for _, ep := range sb.endpoints { 338 if ep.network.enableIPv6 { 339 if en, ok := sb.ipv6Enabled(); ok { 340 ipv6 = en 341 } 342 break 343 } 344 } 345 346 intNS, err := netip.ParseAddr(sb.resolver.NameServer()) 347 if err != nil { 348 return err 349 } 350 351 // Work out whether ndots has been set from host config or overrides. 352 _, sb.ndotsSet = rc.Option("ndots") 353 // Swap nameservers for the internal one, and make sure the required options are set. 354 var extNameServers []resolvconf.ExtDNSEntry 355 extNameServers, err = rc.TransformForIntNS(ipv6, intNS, sb.resolver.ResolverOptions()) 356 if err != nil { 357 return err 358 } 359 // Extract the list of nameservers that just got swapped out, and store them as 360 // upstream nameservers. 361 sb.setExternalResolvers(extNameServers) 362 363 // Write the file for the container - preserving old behaviour, not updating the 364 // hash file (so, no further updates will be made). 365 // TODO(robmry) - I think that's probably accidental, I can't find a reason for it, 366 // and the old resolvconf.Build() function wrote the file but not the hash, which 367 // is surprising. But, before fixing it, a guard/flag needs to be added to 368 // sb.updateDNS() to make sure that when an endpoint joins a sandbox that already 369 // has an internal resolver, the container's resolv.conf is still (re)configured 370 // for an internal resolver. 371 return rc.WriteFile(sb.config.resolvConfPath, "", filePerm) 372 } 373 374 func createBasePath(dir string) error { 375 return os.MkdirAll(dir, dirPerm) 376 } 377 378 func copyFile(src, dst string) error { 379 sBytes, err := os.ReadFile(src) 380 if err != nil { 381 return err 382 } 383 return os.WriteFile(dst, sBytes, filePerm) 384 }