Files
moby/daemon/daemon_linux.go
Rob Murray 18f4f775ed Internal resolver for default bridge network
Until now, containers on the default bridge network have been configured
to talk directly to external DNS servers - their resolv.conf files have
either been populated with nameservers from the host's resolv.conf, or
with servers from '--dns' (or with Google's nameservers as a fallback).

This change makes the internal bridge more like other networks by using
the internal resolver.  But, the internal resolver is not populated with
container names or aliases - it's only for external DNS lookups.

Containers on the default network, on a host that has a loopback
resolver (like systemd's on 127.0.0.53) will now use that resolver
via the internal resolver. So, the logic used to find systemd's current
set of resolvers is no longer needed by the daemon.

Legacy links work just as they did before, using '/etc/hosts' and magic.

(Buildkit does not use libnetwork, so it can't use the internal resolver.
But it does use libnetwork/resolvconf's logic to configure resolv.conf.
So, code to set up resolv.conf for a legacy networking without an internal
resolver can't be removed yet.)

Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-06-05 20:27:24 +01:00

248 lines
6.1 KiB
Go

package daemon // import "github.com/docker/docker/daemon"
import (
"bufio"
"context"
"fmt"
"io"
"net"
"os"
"regexp"
"strings"
"sync"
"github.com/containerd/log"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/libnetwork/ns"
"github.com/moby/sys/mount"
"github.com/moby/sys/mountinfo"
"github.com/pkg/errors"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)
// On Linux, plugins use a static path for storing execution state,
// instead of deriving path from daemon's exec-root. This is because
// plugin socket files are created here and they cannot exceed max
// path length of 108 bytes.
func getPluginExecRoot(_ *config.Config) string {
return "/run/docker/plugins"
}
func (daemon *Daemon) cleanupMountsByID(id string) error {
log.G(context.TODO()).Debugf("Cleaning up old mountid %s: start.", id)
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return err
}
defer f.Close()
return daemon.cleanupMountsFromReaderByID(f, id, mount.Unmount)
}
func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, unmount func(target string) error) error {
if daemon.root == "" {
return nil
}
var errs []string
regexps := getCleanPatterns(id)
sc := bufio.NewScanner(reader)
for sc.Scan() {
if fields := strings.Fields(sc.Text()); len(fields) > 4 {
if mnt := fields[4]; strings.HasPrefix(mnt, daemon.root) {
for _, p := range regexps {
if p.MatchString(mnt) {
if err := unmount(mnt); err != nil {
log.G(context.TODO()).Error(err)
errs = append(errs, err.Error())
}
}
}
}
}
}
if err := sc.Err(); err != nil {
return err
}
if len(errs) > 0 {
return fmt.Errorf("Error cleaning up mounts:\n%v", strings.Join(errs, "\n"))
}
log.G(context.TODO()).Debugf("Cleaning up old mountid %v: done.", id)
return nil
}
// cleanupMounts umounts used by container resources and the daemon root mount
func (daemon *Daemon) cleanupMounts(cfg *config.Config) error {
if err := daemon.cleanupMountsByID(""); err != nil {
return err
}
info, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(daemon.root))
if err != nil {
return errors.Wrap(err, "error reading mount table for cleanup")
}
if len(info) < 1 {
// no mount found, we're done here
return nil
}
// `info.Root` here is the root mountpoint of the passed in path (`daemon.root`).
// The ony cases that need to be cleaned up is when the daemon has performed a
// `mount --bind /daemon/root /daemon/root && mount --make-shared /daemon/root`
// This is only done when the daemon is started up and `/daemon/root` is not
// already on a shared mountpoint.
if !shouldUnmountRoot(daemon.root, info[0]) {
return nil
}
unmountFile := getUnmountOnShutdownPath(cfg)
if _, err := os.Stat(unmountFile); err != nil {
return nil
}
log.G(context.TODO()).WithField("mountpoint", daemon.root).Debug("unmounting daemon root")
if err := mount.Unmount(daemon.root); err != nil {
return err
}
return os.Remove(unmountFile)
}
func getCleanPatterns(id string) (regexps []*regexp.Regexp) {
var patterns []string
if id == "" {
id = "[0-9a-f]{64}"
patterns = append(patterns, "containers/"+id+"/mounts/shm", "containers/"+id+"/shm")
}
patterns = append(patterns, "overlay2/"+id+"/merged$", "zfs/graph/"+id+"$")
for _, p := range patterns {
r, err := regexp.Compile(p)
if err == nil {
regexps = append(regexps, r)
}
}
return
}
func shouldUnmountRoot(root string, info *mountinfo.Info) bool {
if !strings.HasSuffix(root, info.Root) {
return false
}
return hasMountInfoOption(info.Optional, sharedPropagationOption)
}
// ifaceAddrs returns the IPv4 and IPv6 addresses assigned to the network
// interface with name linkName.
//
// No error is returned if the named interface does not exist.
func ifaceAddrs(linkName string) (v4, v6 []*net.IPNet, err error) {
nl := ns.NlHandle()
link, err := nl.LinkByName(linkName)
if err != nil {
if !errors.As(err, new(netlink.LinkNotFoundError)) {
return nil, nil, err
}
return nil, nil, nil
}
get := func(family int) ([]*net.IPNet, error) {
addrs, err := nl.AddrList(link, family)
if err != nil {
return nil, err
}
ipnets := make([]*net.IPNet, len(addrs))
for i := range addrs {
ipnets[i] = addrs[i].IPNet
}
return ipnets, nil
}
v4, err = get(netlink.FAMILY_V4)
if err != nil {
return nil, nil, err
}
v6, err = get(netlink.FAMILY_V6)
if err != nil {
return nil, nil, err
}
return v4, v6, nil
}
var (
kernelSupportsRROOnce sync.Once
kernelSupportsRROErr error
)
func kernelSupportsRecursivelyReadOnly() error {
fn := func() error {
tmpMnt, err := os.MkdirTemp("", "moby-detect-rro")
if err != nil {
return fmt.Errorf("failed to create a temp directory: %w", err)
}
for {
err = unix.Mount("", tmpMnt, "tmpfs", 0, "")
if !errors.Is(err, unix.EINTR) {
break
}
}
if err != nil {
return fmt.Errorf("failed to mount tmpfs on %q: %w", tmpMnt, err)
}
defer func() {
var umErr error
for {
umErr = unix.Unmount(tmpMnt, 0)
if !errors.Is(umErr, unix.EINTR) {
break
}
}
if umErr != nil {
log.G(context.TODO()).WithError(umErr).Warnf("Failed to unmount %q", tmpMnt)
}
}()
attr := &unix.MountAttr{
Attr_set: unix.MOUNT_ATTR_RDONLY,
}
for {
err = unix.MountSetattr(-1, tmpMnt, unix.AT_RECURSIVE, attr)
if !errors.Is(err, unix.EINTR) {
break
}
}
// ENOSYS on kernel < 5.12
if err != nil {
return fmt.Errorf("failed to call mount_setattr: %w", err)
}
return nil
}
kernelSupportsRROOnce.Do(func() {
kernelSupportsRROErr = fn()
})
return kernelSupportsRROErr
}
func supportsRecursivelyReadOnly(cfg *configStore, runtime string) error {
if err := kernelSupportsRecursivelyReadOnly(); err != nil {
return fmt.Errorf("rro is not supported: %w (kernel is older than 5.12?)", err)
}
if runtime == "" {
runtime = cfg.Runtimes.Default
}
features := cfg.Runtimes.Features(runtime)
if features == nil {
return fmt.Errorf("rro is not supported by runtime %q: OCI features struct is not available", runtime)
}
for _, s := range features.MountOptions {
if s == "rro" {
return nil
}
}
return fmt.Errorf("rro is not supported by runtime %q", runtime)
}