123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- //go:build !windows
- package libnetwork
- import (
- "context"
- "io/fs"
- "net/netip"
- "os"
- "path/filepath"
- "strings"
- "github.com/containerd/log"
- "github.com/docker/docker/errdefs"
- "github.com/docker/docker/libnetwork/etchosts"
- "github.com/docker/docker/libnetwork/internal/resolvconf"
- "github.com/docker/docker/libnetwork/types"
- "github.com/pkg/errors"
- )
- const (
- defaultPrefix = "/var/lib/docker/network/files"
- dirPerm = 0o755
- filePerm = 0o644
- resolverIPSandbox = "127.0.0.11"
- )
- // finishInitDNS is to be called after the container namespace has been created,
- // before it the user process is started. The container's support for IPv6 can be
- // determined at this point.
- func (sb *Sandbox) finishInitDNS() error {
- if err := sb.buildHostsFile(); err != nil {
- return errdefs.System(err)
- }
- for _, ep := range sb.Endpoints() {
- if err := sb.updateHostsFile(ep.getEtcHostsAddrs()); err != nil {
- return errdefs.System(err)
- }
- }
- return nil
- }
- func (sb *Sandbox) startResolver(restore bool) {
- sb.resolverOnce.Do(func() {
- var err error
- // The embedded resolver is always started with proxyDNS set as true, even when the sandbox is only attached to
- // an internal network. This way, it's the driver responsibility to make sure `connect` syscall fails fast when
- // no external connectivity is available (eg. by not setting a default gateway).
- sb.resolver = NewResolver(resolverIPSandbox, true, sb)
- defer func() {
- if err != nil {
- sb.resolver = nil
- }
- }()
- // In the case of live restore container is already running with
- // right resolv.conf contents created before. Just update the
- // external DNS servers from the restored sandbox for embedded
- // server to use.
- if !restore {
- err = sb.rebuildDNS()
- if err != nil {
- log.G(context.TODO()).Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err)
- return
- }
- }
- sb.resolver.SetExtServers(sb.extDNS)
- if err = sb.osSbox.InvokeFunc(sb.resolver.SetupFunc(0)); err != nil {
- log.G(context.TODO()).Errorf("Resolver Setup function failed for container %s, %q", sb.ContainerID(), err)
- return
- }
- if err = sb.resolver.Start(); err != nil {
- log.G(context.TODO()).Errorf("Resolver Start failed for container %s, %q", sb.ContainerID(), err)
- }
- })
- }
- func (sb *Sandbox) setupResolutionFiles() error {
- // Create a hosts file that can be mounted during container setup. For most
- // networking modes (not host networking) it will be re-created before the
- // container start, once its support for IPv6 is known.
- if sb.config.hostsPath == "" {
- sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts"
- }
- dir, _ := filepath.Split(sb.config.hostsPath)
- if err := createBasePath(dir); err != nil {
- return err
- }
- if err := sb.buildHostsFile(); err != nil {
- return err
- }
- return sb.setupDNS()
- }
- func (sb *Sandbox) buildHostsFile() error {
- sb.restoreHostsPath()
- dir, _ := filepath.Split(sb.config.hostsPath)
- if err := createBasePath(dir); err != nil {
- return err
- }
- // This is for the host mode networking
- if sb.config.useDefaultSandBox && len(sb.config.extraHosts) == 0 {
- // We are working under the assumption that the origin file option had been properly expressed by the upper layer
- // if not here we are going to error out
- if err := copyFile(sb.config.originHostsPath, sb.config.hostsPath); err != nil && !os.IsNotExist(err) {
- return types.InternalErrorf("could not copy source hosts file %s to %s: %v", sb.config.originHostsPath, sb.config.hostsPath, err)
- }
- return nil
- }
- extraContent := make([]etchosts.Record, 0, len(sb.config.extraHosts))
- for _, extraHost := range sb.config.extraHosts {
- extraContent = append(extraContent, etchosts.Record{Hosts: extraHost.name, IP: extraHost.IP})
- }
- // Assume IPv6 support, unless it's definitely disabled.
- buildf := etchosts.Build
- if en, ok := sb.ipv6Enabled(); ok && !en {
- buildf = etchosts.BuildNoIPv6
- }
- if err := buildf(sb.config.hostsPath, extraContent); err != nil {
- return err
- }
- return sb.updateParentHosts()
- }
- func (sb *Sandbox) updateHostsFile(ifaceIPs []string) error {
- if len(ifaceIPs) == 0 {
- return nil
- }
- if sb.config.originHostsPath != "" {
- return nil
- }
- // User might have provided a FQDN in hostname or split it across hostname
- // and domainname. We want the FQDN and the bare hostname.
- fqdn := sb.config.hostName
- if sb.config.domainName != "" {
- fqdn += "." + sb.config.domainName
- }
- hosts := fqdn
- if hostName, _, ok := strings.Cut(fqdn, "."); ok {
- hosts += " " + hostName
- }
- var extraContent []etchosts.Record
- for _, ip := range ifaceIPs {
- extraContent = append(extraContent, etchosts.Record{Hosts: hosts, IP: ip})
- }
- sb.addHostsEntries(extraContent)
- return nil
- }
- func (sb *Sandbox) addHostsEntries(recs []etchosts.Record) {
- // Assume IPv6 support, unless it's definitely disabled.
- if en, ok := sb.ipv6Enabled(); ok && !en {
- var filtered []etchosts.Record
- for _, rec := range recs {
- if addr, err := netip.ParseAddr(rec.IP); err == nil && !addr.Is6() {
- filtered = append(filtered, rec)
- }
- }
- recs = filtered
- }
- if err := etchosts.Add(sb.config.hostsPath, recs); err != nil {
- log.G(context.TODO()).Warnf("Failed adding service host entries to the running container: %v", err)
- }
- }
- func (sb *Sandbox) deleteHostsEntries(recs []etchosts.Record) {
- if err := etchosts.Delete(sb.config.hostsPath, recs); err != nil {
- log.G(context.TODO()).Warnf("Failed deleting service host entries to the running container: %v", err)
- }
- }
- func (sb *Sandbox) updateParentHosts() error {
- var pSb *Sandbox
- for _, update := range sb.config.parentUpdates {
- // 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.
- if s, _ := sb.controller.GetSandbox(update.cid); s != nil {
- pSb = s
- }
- if pSb == nil {
- continue
- }
- // TODO(robmry) - filter out IPv6 addresses here if !sb.ipv6Enabled() but...
- // - this is part of the implementation of '--link', which will be removed along
- // with the rest of legacy networking.
- // - IPv6 addresses shouldn't be allocated if IPv6 is not available in a container,
- // and that change will come along later.
- // - I think this may be dead code, it's not possible to start a parent container with
- // '--link child' unless the child has already started ("Error response from daemon:
- // Cannot link to a non running container"). So, when the child starts and this method
- // is called with updates for parents, the parents aren't running and GetSandbox()
- // returns nil.)
- if err := etchosts.Update(pSb.config.hostsPath, update.ip, update.name); err != nil {
- return err
- }
- }
- return nil
- }
- func (sb *Sandbox) restoreResolvConfPath() {
- if sb.config.resolvConfPath == "" {
- sb.config.resolvConfPath = defaultPrefix + "/" + sb.id + "/resolv.conf"
- }
- sb.config.resolvConfHashFile = sb.config.resolvConfPath + ".hash"
- }
- func (sb *Sandbox) restoreHostsPath() {
- if sb.config.hostsPath == "" {
- sb.config.hostsPath = defaultPrefix + "/" + sb.id + "/hosts"
- }
- }
- func (sb *Sandbox) setExternalResolvers(entries []resolvconf.ExtDNSEntry) {
- sb.extDNS = make([]extDNSEntry, 0, len(entries))
- for _, entry := range entries {
- sb.extDNS = append(sb.extDNS, extDNSEntry{
- IPStr: entry.Addr.String(),
- HostLoopback: entry.HostLoopback,
- })
- }
- }
- func (c *containerConfig) getOriginResolvConfPath() string {
- if c.originResolvConfPath != "" {
- return c.originResolvConfPath
- }
- // Fallback if not specified.
- return resolvconf.Path()
- }
- // loadResolvConf reads the resolv.conf file at path, and merges in overrides for
- // nameservers, options, and search domains.
- func (sb *Sandbox) loadResolvConf(path string) (*resolvconf.ResolvConf, error) {
- rc, err := resolvconf.Load(path)
- if err != nil && !errors.Is(err, fs.ErrNotExist) {
- return nil, err
- }
- // Proceed with rc, which might be zero-valued if path does not exist.
- rc.SetHeader(`# Generated by Docker Engine.
- # This file can be edited; Docker Engine will not make further changes once it
- # has been modified.`)
- if len(sb.config.dnsList) > 0 {
- var dnsAddrs []netip.Addr
- for _, ns := range sb.config.dnsList {
- addr, err := netip.ParseAddr(ns)
- if err != nil {
- return nil, errors.Wrapf(err, "bad nameserver address %s", ns)
- }
- dnsAddrs = append(dnsAddrs, addr)
- }
- rc.OverrideNameServers(dnsAddrs)
- }
- if len(sb.config.dnsSearchList) > 0 {
- rc.OverrideSearch(sb.config.dnsSearchList)
- }
- if len(sb.config.dnsOptionsList) > 0 {
- rc.OverrideOptions(sb.config.dnsOptionsList)
- }
- return &rc, nil
- }
- // For a new sandbox, write an initial version of the container's resolv.conf. It'll
- // be a copy of the host's file, with overrides for nameservers, options and search
- // domains applied.
- func (sb *Sandbox) setupDNS() error {
- // Make sure the directory exists.
- sb.restoreResolvConfPath()
- dir, _ := filepath.Split(sb.config.resolvConfPath)
- if err := createBasePath(dir); err != nil {
- return err
- }
- rc, err := sb.loadResolvConf(sb.config.getOriginResolvConfPath())
- if err != nil {
- return err
- }
- return rc.WriteFile(sb.config.resolvConfPath, sb.config.resolvConfHashFile, filePerm)
- }
- // Called when an endpoint has joined the sandbox.
- func (sb *Sandbox) updateDNS(ipv6Enabled bool) error {
- if mod, err := resolvconf.UserModified(sb.config.resolvConfPath, sb.config.resolvConfHashFile); err != nil || mod {
- return err
- }
- // Load the host's resolv.conf as a starting point.
- rc, err := sb.loadResolvConf(sb.config.getOriginResolvConfPath())
- if err != nil {
- return err
- }
- // For host-networking, no further change is needed.
- if !sb.config.useDefaultSandBox {
- // The legacy bridge network has no internal nameserver. So, strip localhost
- // nameservers from the host's config, then add default nameservers if there
- // are none remaining.
- rc.TransformForLegacyNw(ipv6Enabled)
- }
- return rc.WriteFile(sb.config.resolvConfPath, sb.config.resolvConfHashFile, filePerm)
- }
- // Embedded DNS server has to be enabled for this sandbox. Rebuild the container's resolv.conf.
- func (sb *Sandbox) rebuildDNS() error {
- // Don't touch the file if the user has modified it.
- if mod, err := resolvconf.UserModified(sb.config.resolvConfPath, sb.config.resolvConfHashFile); err != nil || mod {
- return err
- }
- // Load the host's resolv.conf as a starting point.
- rc, err := sb.loadResolvConf(sb.config.getOriginResolvConfPath())
- if err != nil {
- return err
- }
- // Check for IPv6 endpoints in this sandbox. If there are any, and the container has
- // IPv6 enabled, upstream requests from the internal DNS resolver can be made from
- // the container's namespace.
- // TODO(robmry) - this can only check networks connected when the resolver is set up,
- // the configuration won't be updated if the container gets an IPv6 address later.
- ipv6 := false
- for _, ep := range sb.endpoints {
- if ep.network.enableIPv6 {
- if en, ok := sb.ipv6Enabled(); ok {
- ipv6 = en
- }
- break
- }
- }
- intNS, err := netip.ParseAddr(sb.resolver.NameServer())
- if err != nil {
- return err
- }
- // Work out whether ndots has been set from host config or overrides.
- _, sb.ndotsSet = rc.Option("ndots")
- // Swap nameservers for the internal one, and make sure the required options are set.
- var extNameServers []resolvconf.ExtDNSEntry
- extNameServers, err = rc.TransformForIntNS(ipv6, intNS, sb.resolver.ResolverOptions())
- if err != nil {
- return err
- }
- // Extract the list of nameservers that just got swapped out, and store them as
- // upstream nameservers.
- sb.setExternalResolvers(extNameServers)
- // Write the file for the container - preserving old behaviour, not updating the
- // hash file (so, no further updates will be made).
- // TODO(robmry) - I think that's probably accidental, I can't find a reason for it,
- // and the old resolvconf.Build() function wrote the file but not the hash, which
- // is surprising. But, before fixing it, a guard/flag needs to be added to
- // sb.updateDNS() to make sure that when an endpoint joins a sandbox that already
- // has an internal resolver, the container's resolv.conf is still (re)configured
- // for an internal resolver.
- return rc.WriteFile(sb.config.resolvConfPath, "", filePerm)
- }
- func createBasePath(dir string) error {
- return os.MkdirAll(dir, dirPerm)
- }
- func copyFile(src, dst string) error {
- sBytes, err := os.ReadFile(src)
- if err != nil {
- return err
- }
- return os.WriteFile(dst, sBytes, filePerm)
- }
|