github.com/moby/docker@v26.1.3+incompatible/daemon/daemon_linux_test.go (about)

     1  //go:build linux
     2  
     3  package daemon // import "github.com/docker/docker/daemon"
     4  
     5  import (
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	containertypes "github.com/docker/docker/api/types/container"
    13  	"github.com/docker/docker/internal/testutils/netnsutils"
    14  	"github.com/docker/docker/libnetwork/types"
    15  	"github.com/google/go-cmp/cmp/cmpopts"
    16  	"github.com/moby/sys/mount"
    17  	"github.com/moby/sys/mountinfo"
    18  	"github.com/vishvananda/netlink"
    19  	"gotest.tools/v3/assert"
    20  	is "gotest.tools/v3/assert/cmp"
    21  )
    22  
    23  //nolint:dupword
    24  const mountsFixture = `142 78 0:38 / / rw,relatime - aufs none rw,si=573b861da0b3a05b,dio
    25  143 142 0:60 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
    26  144 142 0:67 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755
    27  145 144 0:78 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
    28  146 144 0:49 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
    29  147 142 0:84 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
    30  148 147 0:86 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
    31  149 148 0:22 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset
    32  150 148 0:25 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu
    33  151 148 0:27 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct
    34  152 148 0:28 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
    35  153 148 0:29 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
    36  154 148 0:30 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
    37  155 148 0:31 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
    38  156 148 0:32 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event
    39  157 148 0:33 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb
    40  158 148 0:35 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup systemd rw,name=systemd
    41  159 142 8:4 /home/mlaventure/gopath /home/mlaventure/gopath rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    42  160 142 8:4 /var/lib/docker/volumes/9a428b651ee4c538130143cad8d87f603a4bf31b928afe7ff3ecd65480692b35/_data /var/lib/docker rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    43  164 142 8:4 /home/mlaventure/gopath/src/github.com/docker/docker /go/src/github.com/docker/docker rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    44  165 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    45  166 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/hostname /etc/hostname rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    46  167 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/hosts /etc/hosts rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    47  168 144 0:39 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
    48  169 144 0:12 /14 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
    49  83 147 0:10 / /sys/kernel/security rw,relatime - securityfs none rw
    50  89 142 0:87 / /tmp rw,relatime - tmpfs none rw
    51  97 142 0:60 / /run/docker/netns/default rw,nosuid,nodev,noexec,relatime - proc proc rw
    52  100 160 8:4 /var/lib/docker/volumes/9a428b651ee4c538130143cad8d87f603a4bf31b928afe7ff3ecd65480692b35/_data/aufs /var/lib/docker/aufs rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
    53  115 100 0:102 / /var/lib/docker/aufs/mnt/0ecda1c63e5b58b3d89ff380bf646c95cc980252cf0b52466d43619aec7c8432 rw,relatime - aufs none rw,si=573b861dbc01905b,dio
    54  116 160 0:107 / /var/lib/docker/containers/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
    55  118 142 0:102 / /run/docker/libcontainerd/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/rootfs rw,relatime - aufs none rw,si=573b861dbc01905b,dio
    56  242 142 0:60 / /run/docker/netns/c3664df2a0f7 rw,nosuid,nodev,noexec,relatime - proc proc rw
    57  120 100 0:122 / /var/lib/docker/aufs/mnt/03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d rw,relatime - aufs none rw,si=573b861eb147805b,dio
    58  171 142 0:122 / /run/docker/libcontainerd/e406ff6f3e18516d50e03dbca4de54767a69a403a6f7ec1edc2762812824521e/rootfs rw,relatime - aufs none rw,si=573b861eb147805b,dio
    59  310 142 0:60 / /run/docker/netns/71a18572176b rw,nosuid,nodev,noexec,relatime - proc proc rw
    60  `
    61  
    62  //nolint:dupword
    63  const mountsFixtureOverlay2 = `23 28 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw
    64  24 28 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
    65  25 28 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=491380k,nr_inodes=122845,mode=755
    66  26 25 0:23 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
    67  27 28 0:24 / /run rw,nosuid,noexec,relatime shared:5 - tmpfs tmpfs rw,size=100884k,mode=755
    68  28 0 252:1 / / rw,relatime shared:1 - ext4 /dev/vda1 rw,data=ordered
    69  29 23 0:7 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:8 - securityfs securityfs rw
    70  30 25 0:25 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
    71  31 27 0:26 / /run/lock rw,nosuid,nodev,noexec,relatime shared:6 - tmpfs tmpfs rw,size=5120k
    72  32 23 0:27 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755
    73  33 32 0:28 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw
    74  34 32 0:29 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,xattr,name=systemd
    75  35 23 0:30 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:12 - pstore pstore rw
    76  36 32 0:31 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,blkio
    77  37 32 0:32 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,memory
    78  38 32 0:33 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,hugetlb
    79  39 32 0:34 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,freezer
    80  40 32 0:35 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,perf_event
    81  41 32 0:36 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,pids
    82  42 32 0:37 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,cpuset
    83  43 32 0:38 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,cpu,cpuacct
    84  44 32 0:39 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,rdma
    85  45 32 0:40 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,devices
    86  46 32 0:41 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,net_cls,net_prio
    87  47 24 0:42 / /proc/sys/fs/binfmt_misc rw,relatime shared:25 - autofs systemd-1 rw,fd=33,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=11725
    88  48 23 0:8 / /sys/kernel/debug rw,relatime shared:26 - debugfs debugfs rw
    89  49 25 0:19 / /dev/mqueue rw,relatime shared:27 - mqueue mqueue rw
    90  50 25 0:43 / /dev/hugepages rw,relatime shared:28 - hugetlbfs hugetlbfs rw,pagesize=2M
    91  80 23 0:20 / /sys/kernel/config rw,relatime shared:29 - configfs configfs rw
    92  82 23 0:44 / /sys/fs/fuse/connections rw,relatime shared:30 - fusectl fusectl rw
    93  84 28 252:15 / /boot/efi rw,relatime shared:31 - vfat /dev/vda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
    94  391 28 0:49 / /var/lib/lxcfs rw,nosuid,nodev,relatime shared:208 - fuse.lxcfs lxcfs rw,user_id=0,group_id=0,allow_other
    95  401 48 0:11 / /sys/kernel/debug/tracing rw,relatime shared:213 - tracefs tracefs rw
    96  421 47 0:93 / /proc/sys/fs/binfmt_misc rw,relatime shared:223 - binfmt_misc binfmt_misc rw
    97  510 27 0:3 net:[4026531993] /run/docker/netns/default rw shared:255 - nsfs nsfs rw
    98  60 27 0:3 net:[4026532265] /run/docker/netns/ingress_sbox rw shared:40 - nsfs nsfs rw
    99  162 27 0:3 net:[4026532331] /run/docker/netns/1-bj0aarwy1n rw shared:41 - nsfs nsfs rw
   100  450 28 0:51 / /var/lib/docker/overlay2/3a4b807fcb98c208573f368c5654a6568545a7f92404a07d0045eb5c85acaf67/merged rw,relatime shared:231 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/E6KNVZ2QUCIXY5VT7E5LO3PVCA:/var/lib/docker/overlay2/l/64XI57TRGG6QS4K6DCSREZXBN2:/var/lib/docker/overlay2/l/TWXZ4ANJR6BDLDZMWZ4Y6AICAR:/var/lib/docker/overlay2/l/VRLSNSG3PKZELC5O66TVTQ7EH5:/var/lib/docker/overlay2/l/HOLV4F57X56TRLVACMRLFVW7YD:/var/lib/docker/overlay2/l/JJQFBBBT6LWLQS35XBADV6BLAM:/var/lib/docker/overlay2/l/FZTPKHZGP2Z6DBPFEEL2IK3I5Y,upperdir=/var/lib/docker/overlay2/3a4b807fcb98c208573f368c5654a6568545a7f92404a07d0045eb5c85acaf67/diff,workdir=/var/lib/docker/overlay2/3a4b807fcb98c208573f368c5654a6568545a7f92404a07d0045eb5c85acaf67/work
   101  569 27 0:3 net:[4026532353] /run/docker/netns/7de1071d0d8b rw shared:245 - nsfs nsfs rw
   102  245 27 0:50 / /run/user/0 rw,nosuid,nodev,relatime shared:160 - tmpfs tmpfs rw,size=100880k,mode=700
   103  482 28 0:69 / /var/lib/docker/overlay2/df4ee7b0bac7bda30e6e3d24a1153b288ebda50ffe68aae7ae0f38bc9286a01a/merged rw,relatime shared:250 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/CNZ3ATGGHMUTPPJBBU2OL4GLL6:/var/lib/docker/overlay2/l/64XI57TRGG6QS4K6DCSREZXBN2:/var/lib/docker/overlay2/l/TWXZ4ANJR6BDLDZMWZ4Y6AICAR:/var/lib/docker/overlay2/l/VRLSNSG3PKZELC5O66TVTQ7EH5:/var/lib/docker/overlay2/l/HOLV4F57X56TRLVACMRLFVW7YD:/var/lib/docker/overlay2/l/JJQFBBBT6LWLQS35XBADV6BLAM:/var/lib/docker/overlay2/l/FZTPKHZGP2Z6DBPFEEL2IK3I5Y,upperdir=/var/lib/docker/overlay2/df4ee7b0bac7bda30e6e3d24a1153b288ebda50ffe68aae7ae0f38bc9286a01a/diff,workdir=/var/lib/docker/overlay2/df4ee7b0bac7bda30e6e3d24a1153b288ebda50ffe68aae7ae0f38bc9286a01a/work
   104  528 28 0:77 / /var/lib/docker/containers/404a7f860e600bfc144f7b5d9140d80bf3072fbb97659f98bc47039fd73d2695/mounts/shm rw,nosuid,nodev,noexec,relatime shared:260 - tmpfs shm rw,size=65536k
   105  649 27 0:3 net:[4026532429] /run/docker/netns/7f85bc5ef3ba rw shared:265 - nsfs nsfs rw
   106  `
   107  
   108  func TestCleanupMounts(t *testing.T) {
   109  	d := &Daemon{
   110  		root: "/var/lib/docker/",
   111  	}
   112  
   113  	t.Run("aufs", func(t *testing.T) {
   114  		expected := "/var/lib/docker/containers/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/shm"
   115  		var unmounted int
   116  		unmount := func(target string) error {
   117  			if target == expected {
   118  				unmounted++
   119  			}
   120  			return nil
   121  		}
   122  
   123  		err := d.cleanupMountsFromReaderByID(strings.NewReader(mountsFixture), "", unmount)
   124  		assert.NilError(t, err)
   125  		assert.Equal(t, unmounted, 1, "Expected to unmount the shm (and the shm only)")
   126  	})
   127  
   128  	t.Run("overlay2", func(t *testing.T) {
   129  		expected := "/var/lib/docker/containers/404a7f860e600bfc144f7b5d9140d80bf3072fbb97659f98bc47039fd73d2695/mounts/shm"
   130  		var unmounted int
   131  		unmount := func(target string) error {
   132  			if target == expected {
   133  				unmounted++
   134  			}
   135  			return nil
   136  		}
   137  
   138  		err := d.cleanupMountsFromReaderByID(strings.NewReader(mountsFixtureOverlay2), "", unmount)
   139  		assert.NilError(t, err)
   140  		assert.Equal(t, unmounted, 1, "Expected to unmount the shm (and the shm only)")
   141  	})
   142  }
   143  
   144  func TestCleanupMountsByID(t *testing.T) {
   145  	d := &Daemon{
   146  		root: "/var/lib/docker/",
   147  	}
   148  	t.Run("overlay2", func(t *testing.T) {
   149  		expected := "/var/lib/docker/overlay2/3a4b807fcb98c208573f368c5654a6568545a7f92404a07d0045eb5c85acaf67/merged"
   150  		var unmounted int
   151  		unmount := func(target string) error {
   152  			if target == expected {
   153  				unmounted++
   154  			}
   155  			return nil
   156  		}
   157  
   158  		err := d.cleanupMountsFromReaderByID(strings.NewReader(mountsFixtureOverlay2), "3a4b807fcb98c208573f368c5654a6568545a7f92404a07d0045eb5c85acaf67", unmount)
   159  		assert.NilError(t, err)
   160  		assert.Equal(t, unmounted, 1, "Expected to unmount the root (and that only)")
   161  	})
   162  }
   163  
   164  func TestNotCleanupMounts(t *testing.T) {
   165  	d := &Daemon{
   166  		repository: "",
   167  	}
   168  	var unmounted bool
   169  	unmount := func(target string) error {
   170  		unmounted = true
   171  		return nil
   172  	}
   173  	mountInfo := `234 232 0:59 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k`
   174  	err := d.cleanupMountsFromReaderByID(strings.NewReader(mountInfo), "", unmount)
   175  	assert.NilError(t, err)
   176  	assert.Equal(t, unmounted, false, "Expected not to clean up /dev/shm")
   177  }
   178  
   179  func TestValidateContainerIsolationLinux(t *testing.T) {
   180  	d := Daemon{}
   181  
   182  	_, err := d.verifyContainerSettings(&configStore{}, &containertypes.HostConfig{Isolation: containertypes.IsolationHyperV}, nil, false)
   183  	assert.Check(t, is.Error(err, "invalid isolation 'hyperv' on linux"))
   184  }
   185  
   186  func TestShouldUnmountRoot(t *testing.T) {
   187  	for _, test := range []struct {
   188  		desc   string
   189  		root   string
   190  		info   *mountinfo.Info
   191  		expect bool
   192  	}{
   193  		{
   194  			desc:   "root is at /",
   195  			root:   "/docker",
   196  			info:   &mountinfo.Info{Root: "/docker", Mountpoint: "/docker"},
   197  			expect: true,
   198  		},
   199  		{
   200  			desc:   "root is at in a submount from `/`",
   201  			root:   "/foo/docker",
   202  			info:   &mountinfo.Info{Root: "/docker", Mountpoint: "/foo/docker"},
   203  			expect: true,
   204  		},
   205  		{
   206  			desc:   "root is mounted in from a parent mount namespace same root dir", // dind is an example of this
   207  			root:   "/docker",
   208  			info:   &mountinfo.Info{Root: "/docker/volumes/1234657/_data", Mountpoint: "/docker"},
   209  			expect: false,
   210  		},
   211  	} {
   212  		t.Run(test.desc, func(t *testing.T) {
   213  			for _, options := range []struct {
   214  				desc     string
   215  				Optional string
   216  				expect   bool
   217  			}{
   218  				{desc: "shared", Optional: "shared:", expect: true},
   219  				{desc: "slave", Optional: "slave:", expect: false},
   220  				{desc: "private", Optional: "private:", expect: false},
   221  			} {
   222  				t.Run(options.desc, func(t *testing.T) {
   223  					expect := options.expect
   224  					if expect {
   225  						expect = test.expect
   226  					}
   227  					if test.info != nil {
   228  						test.info.Optional = options.Optional
   229  					}
   230  					assert.Check(t, is.Equal(expect, shouldUnmountRoot(test.root, test.info)))
   231  				})
   232  			}
   233  		})
   234  	}
   235  }
   236  
   237  func checkMounted(t *testing.T, p string, expect bool) {
   238  	t.Helper()
   239  	mounted, err := mountinfo.Mounted(p)
   240  	assert.Check(t, err)
   241  	assert.Check(t, mounted == expect, "expected %v, actual %v", expect, mounted)
   242  }
   243  
   244  func TestRootMountCleanup(t *testing.T) {
   245  	if os.Getuid() != 0 {
   246  		t.Skip("root required")
   247  	}
   248  
   249  	t.Parallel()
   250  
   251  	testRoot, err := os.MkdirTemp("", t.Name())
   252  	assert.NilError(t, err)
   253  	defer os.RemoveAll(testRoot)
   254  	cfg := &configStore{}
   255  
   256  	err = mount.MakePrivate(testRoot)
   257  	assert.NilError(t, err)
   258  	defer mount.Unmount(testRoot)
   259  
   260  	cfg.ExecRoot = filepath.Join(testRoot, "exec")
   261  	cfg.Root = filepath.Join(testRoot, "daemon")
   262  
   263  	err = os.Mkdir(cfg.ExecRoot, 0o755)
   264  	assert.NilError(t, err)
   265  	err = os.Mkdir(cfg.Root, 0o755)
   266  	assert.NilError(t, err)
   267  
   268  	d := &Daemon{root: cfg.Root}
   269  	d.configStore.Store(cfg)
   270  	unmountFile := getUnmountOnShutdownPath(&cfg.Config)
   271  
   272  	t.Run("regular dir no mountpoint", func(t *testing.T) {
   273  		err = setupDaemonRootPropagation(&cfg.Config)
   274  		assert.NilError(t, err)
   275  		_, err = os.Stat(unmountFile)
   276  		assert.NilError(t, err)
   277  		checkMounted(t, cfg.Root, true)
   278  
   279  		assert.Assert(t, d.cleanupMounts(&cfg.Config))
   280  		checkMounted(t, cfg.Root, false)
   281  
   282  		_, err = os.Stat(unmountFile)
   283  		assert.Assert(t, os.IsNotExist(err))
   284  	})
   285  
   286  	t.Run("root is a private mountpoint", func(t *testing.T) {
   287  		err = mount.MakePrivate(cfg.Root)
   288  		assert.NilError(t, err)
   289  		defer mount.Unmount(cfg.Root)
   290  
   291  		err = setupDaemonRootPropagation(&cfg.Config)
   292  		assert.NilError(t, err)
   293  		assert.Check(t, ensureShared(cfg.Root))
   294  
   295  		_, err = os.Stat(unmountFile)
   296  		assert.Assert(t, os.IsNotExist(err))
   297  		assert.Assert(t, d.cleanupMounts(&cfg.Config))
   298  		checkMounted(t, cfg.Root, true)
   299  	})
   300  
   301  	// mount is pre-configured with a shared mount
   302  	t.Run("root is a shared mountpoint", func(t *testing.T) {
   303  		err = mount.MakeShared(cfg.Root)
   304  		assert.NilError(t, err)
   305  		defer mount.Unmount(cfg.Root)
   306  
   307  		err = setupDaemonRootPropagation(&cfg.Config)
   308  		assert.NilError(t, err)
   309  
   310  		if _, err := os.Stat(unmountFile); err == nil {
   311  			t.Fatal("unmount file should not exist")
   312  		}
   313  
   314  		assert.Assert(t, d.cleanupMounts(&cfg.Config))
   315  		checkMounted(t, cfg.Root, true)
   316  		assert.Assert(t, mount.Unmount(cfg.Root))
   317  	})
   318  
   319  	// does not need mount but unmount file exists from previous run
   320  	t.Run("old mount file is cleaned up on setup if not needed", func(t *testing.T) {
   321  		err = mount.MakeShared(testRoot)
   322  		assert.NilError(t, err)
   323  		defer mount.MakePrivate(testRoot)
   324  		err = os.WriteFile(unmountFile, nil, 0o644)
   325  		assert.NilError(t, err)
   326  
   327  		err = setupDaemonRootPropagation(&cfg.Config)
   328  		assert.NilError(t, err)
   329  
   330  		_, err = os.Stat(unmountFile)
   331  		assert.Check(t, os.IsNotExist(err), err)
   332  		checkMounted(t, cfg.Root, false)
   333  		assert.Assert(t, d.cleanupMounts(&cfg.Config))
   334  	})
   335  }
   336  
   337  func TestIfaceAddrs(t *testing.T) {
   338  	CIDR := func(cidr string) *net.IPNet {
   339  		t.Helper()
   340  		nw, err := types.ParseCIDR(cidr)
   341  		assert.NilError(t, err)
   342  		return nw
   343  	}
   344  
   345  	for _, tt := range []struct {
   346  		name string
   347  		nws  []*net.IPNet
   348  	}{
   349  		{
   350  			name: "Single",
   351  			nws:  []*net.IPNet{CIDR("172.101.202.254/16")},
   352  		},
   353  		{
   354  			name: "Multiple",
   355  			nws: []*net.IPNet{
   356  				CIDR("172.101.202.254/16"),
   357  				CIDR("172.102.202.254/16"),
   358  			},
   359  		},
   360  	} {
   361  		t.Run(tt.name, func(t *testing.T) {
   362  			defer netnsutils.SetupTestOSContext(t)()
   363  
   364  			createBridge(t, "test", tt.nws...)
   365  
   366  			ipv4Nw, ipv6Nw, err := ifaceAddrs("test")
   367  			if err != nil {
   368  				t.Fatal(err)
   369  			}
   370  
   371  			assert.Check(t, is.DeepEqual(tt.nws, ipv4Nw,
   372  				cmpopts.SortSlices(func(a, b *net.IPNet) bool { return a.String() < b.String() })))
   373  			// IPv6 link-local address
   374  			assert.Check(t, is.Len(ipv6Nw, 1))
   375  		})
   376  	}
   377  }
   378  
   379  func createBridge(t *testing.T, name string, bips ...*net.IPNet) {
   380  	t.Helper()
   381  
   382  	link := &netlink.Bridge{
   383  		LinkAttrs: netlink.LinkAttrs{
   384  			Name: name,
   385  		},
   386  	}
   387  	if err := netlink.LinkAdd(link); err != nil {
   388  		t.Fatalf("Failed to create interface via netlink: %v", err)
   389  	}
   390  	for _, bip := range bips {
   391  		if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: bip}); err != nil {
   392  			t.Fatal(err)
   393  		}
   394  	}
   395  	if err := netlink.LinkSetUp(link); err != nil {
   396  		t.Fatal(err)
   397  	}
   398  }