mirror of
https://github.com/moby/moby.git
synced 2026-07-07 23:28:37 +00:00
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>
248 lines
6.1 KiB
Go
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)
|
|
}
|