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  }