github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/container/ipcmode_linux_test.go (about) 1 package container // import "github.com/Prakhar-Agarwal-byte/moby/integration/container" 2 3 import ( 4 "bufio" 5 "os" 6 "regexp" 7 "strings" 8 "testing" 9 10 containertypes "github.com/Prakhar-Agarwal-byte/moby/api/types/container" 11 "github.com/Prakhar-Agarwal-byte/moby/api/types/versions" 12 "github.com/Prakhar-Agarwal-byte/moby/client" 13 "github.com/Prakhar-Agarwal-byte/moby/integration/internal/container" 14 "github.com/Prakhar-Agarwal-byte/moby/testutil" 15 "github.com/Prakhar-Agarwal-byte/moby/testutil/daemon" 16 "github.com/Prakhar-Agarwal-byte/moby/testutil/request" 17 "gotest.tools/v3/assert" 18 is "gotest.tools/v3/assert/cmp" 19 "gotest.tools/v3/fs" 20 "gotest.tools/v3/skip" 21 ) 22 23 // testIpcCheckDevExists checks whether a given mount (identified by its 24 // major:minor pair from /proc/self/mountinfo) exists on the host system. 25 // 26 // The format of /proc/self/mountinfo is like: 27 // 28 // 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw 29 // ^^^^\ 30 // - this is the minor:major we look for 31 func testIpcCheckDevExists(mm string) (bool, error) { 32 f, err := os.Open("/proc/self/mountinfo") 33 if err != nil { 34 return false, err 35 } 36 defer f.Close() 37 38 s := bufio.NewScanner(f) 39 for s.Scan() { 40 fields := strings.Fields(s.Text()) 41 if len(fields) < 7 { 42 continue 43 } 44 if fields[2] == mm { 45 return true, nil 46 } 47 } 48 49 return false, s.Err() 50 } 51 52 // testIpcNonePrivateShareable is a helper function to test "none", 53 // "private" and "shareable" modes. 54 func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) { 55 ctx := setupTest(t) 56 57 cfg := containertypes.Config{ 58 Image: "busybox", 59 Cmd: []string{"top"}, 60 } 61 hostCfg := containertypes.HostConfig{ 62 IpcMode: containertypes.IpcMode(mode), 63 } 64 apiClient := testEnv.APIClient() 65 66 resp, err := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") 67 assert.NilError(t, err) 68 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 69 70 err = apiClient.ContainerStart(ctx, resp.ID, containertypes.StartOptions{}) 71 assert.NilError(t, err) 72 73 // get major:minor pair for /dev/shm from container's /proc/self/mountinfo 74 cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" 75 result, err := container.Exec(ctx, apiClient, resp.ID, []string{"sh", "-c", cmd}) 76 assert.NilError(t, err) 77 mm := result.Combined() 78 if !mustBeMounted { 79 assert.Check(t, is.Equal(mm, "")) 80 // no more checks to perform 81 return 82 } 83 assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm))) 84 85 shared, err := testIpcCheckDevExists(mm) 86 assert.NilError(t, err) 87 t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared) 88 assert.Check(t, is.Equal(shared, mustBeShared)) 89 } 90 91 // TestIpcModeNone checks the container "none" IPC mode 92 // (--ipc none) works as expected. It makes sure there is no 93 // /dev/shm mount inside the container. 94 func TestIpcModeNone(t *testing.T) { 95 skip.If(t, testEnv.IsRemoteDaemon) 96 97 testIpcNonePrivateShareable(t, "none", false, false) 98 } 99 100 // TestAPIIpcModePrivate checks the container private IPC mode 101 // (--ipc private) works as expected. It gets the minor:major pair 102 // of /dev/shm mount from the container, and makes sure there is no 103 // such pair on the host. 104 func TestIpcModePrivate(t *testing.T) { 105 skip.If(t, testEnv.IsRemoteDaemon) 106 107 testIpcNonePrivateShareable(t, "private", true, false) 108 } 109 110 // TestAPIIpcModeShareable checks the container shareable IPC mode 111 // (--ipc shareable) works as expected. It gets the minor:major pair 112 // of /dev/shm mount from the container, and makes sure such pair 113 // also exists on the host. 114 func TestIpcModeShareable(t *testing.T) { 115 skip.If(t, testEnv.IsRemoteDaemon) 116 skip.If(t, testEnv.IsRootless, "no support for --ipc=shareable in rootless") 117 118 testIpcNonePrivateShareable(t, "shareable", true, true) 119 } 120 121 // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios 122 func testIpcContainer(t *testing.T, donorMode string, mustWork bool) { 123 t.Helper() 124 125 ctx := setupTest(t) 126 127 cfg := containertypes.Config{ 128 Image: "busybox", 129 Cmd: []string{"top"}, 130 } 131 hostCfg := containertypes.HostConfig{ 132 IpcMode: containertypes.IpcMode(donorMode), 133 } 134 apiClient := testEnv.APIClient() 135 136 // create and start the "donor" container 137 resp, err := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") 138 assert.NilError(t, err) 139 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 140 name1 := resp.ID 141 142 err = apiClient.ContainerStart(ctx, name1, containertypes.StartOptions{}) 143 assert.NilError(t, err) 144 145 // create and start the second container 146 hostCfg.IpcMode = containertypes.IpcMode("container:" + name1) 147 resp, err = apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") 148 assert.NilError(t, err) 149 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 150 name2 := resp.ID 151 152 err = apiClient.ContainerStart(ctx, name2, containertypes.StartOptions{}) 153 if !mustWork { 154 // start should fail with a specific error 155 assert.Check(t, is.ErrorContains(err, "non-shareable IPC")) 156 // no more checks to perform here 157 return 158 } 159 160 // start should succeed 161 assert.NilError(t, err) 162 163 // check that IPC is shared 164 // 1. create a file in the first container 165 _, err = container.Exec(ctx, apiClient, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"}) 166 assert.NilError(t, err) 167 // 2. check it's the same file in the second one 168 result, err := container.Exec(ctx, apiClient, name2, []string{"cat", "/dev/shm/bar"}) 169 assert.NilError(t, err) 170 out := result.Combined() 171 assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out))) 172 } 173 174 // TestAPIIpcModeShareableAndPrivate checks that 175 // 1) a container created with --ipc container:ID can use IPC of another shareable container. 176 // 2) a container created with --ipc container:ID can NOT use IPC of another private container. 177 func TestAPIIpcModeShareableAndContainer(t *testing.T) { 178 skip.If(t, testEnv.IsRemoteDaemon) 179 180 testIpcContainer(t, "shareable", true) 181 182 testIpcContainer(t, "private", false) 183 } 184 185 /* TestAPIIpcModeHost checks that a container created with --ipc host 186 * can use IPC of the host system. 187 */ 188 func TestAPIIpcModeHost(t *testing.T) { 189 skip.If(t, testEnv.IsRemoteDaemon) 190 skip.If(t, testEnv.IsUserNamespace) 191 192 cfg := containertypes.Config{ 193 Image: "busybox", 194 Cmd: []string{"top"}, 195 } 196 hostCfg := containertypes.HostConfig{ 197 IpcMode: containertypes.IPCModeHost, 198 } 199 200 ctx := testutil.StartSpan(baseContext, t) 201 202 apiClient := testEnv.APIClient() 203 resp, err := apiClient.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "") 204 assert.NilError(t, err) 205 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 206 name := resp.ID 207 208 err = apiClient.ContainerStart(ctx, name, containertypes.StartOptions{}) 209 assert.NilError(t, err) 210 211 // check that IPC is shared 212 // 1. create a file inside container 213 _, err = container.Exec(ctx, apiClient, name, []string{"sh", "-c", "printf covfefe > /dev/shm/." + name}) 214 assert.NilError(t, err) 215 // 2. check it's the same on the host 216 bytes, err := os.ReadFile("/dev/shm/." + name) 217 assert.NilError(t, err) 218 assert.Check(t, is.Equal("covfefe", string(bytes))) 219 // 3. clean up 220 _, err = container.Exec(ctx, apiClient, name, []string{"rm", "-f", "/dev/shm/." + name}) 221 assert.NilError(t, err) 222 } 223 224 // testDaemonIpcPrivateShareable is a helper function to test "private" and "shareable" daemon default ipc modes. 225 func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...string) { 226 ctx := setupTest(t) 227 228 d := daemon.New(t) 229 d.StartWithBusybox(ctx, t, arg...) 230 defer d.Stop(t) 231 232 c := d.NewClientT(t) 233 234 cfg := containertypes.Config{ 235 Image: "busybox", 236 Cmd: []string{"top"}, 237 } 238 239 resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, nil, "") 240 assert.NilError(t, err) 241 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 242 243 err = c.ContainerStart(ctx, resp.ID, containertypes.StartOptions{}) 244 assert.NilError(t, err) 245 246 // get major:minor pair for /dev/shm from container's /proc/self/mountinfo 247 cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" 248 result, err := container.Exec(ctx, c, resp.ID, []string{"sh", "-c", cmd}) 249 assert.NilError(t, err) 250 mm := result.Combined() 251 assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm))) 252 253 shared, err := testIpcCheckDevExists(mm) 254 assert.NilError(t, err) 255 t.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, shared: %v, mustBeShared: %v\n", mm, shared, mustBeShared) 256 assert.Check(t, is.Equal(shared, mustBeShared)) 257 } 258 259 // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended. 260 func TestDaemonIpcModeShareable(t *testing.T) { 261 skip.If(t, testEnv.IsRemoteDaemon) 262 skip.If(t, testEnv.IsRootless, "no support for --ipc=shareable in rootless") 263 264 testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable") 265 } 266 267 // TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended. 268 func TestDaemonIpcModePrivate(t *testing.T) { 269 skip.If(t, testEnv.IsRemoteDaemon) 270 271 testDaemonIpcPrivateShareable(t, false, "--default-ipc-mode", "private") 272 } 273 274 // used to check if an IpcMode given in config works as intended 275 func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) { 276 config := `{"default-ipc-mode": "` + mode + `"}` 277 // WithMode is needed for rootless 278 file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config), fs.WithMode(0o644)) 279 defer file.Remove() 280 281 testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path()) 282 } 283 284 // TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended. 285 func TestDaemonIpcModePrivateFromConfig(t *testing.T) { 286 skip.If(t, testEnv.IsRemoteDaemon) 287 288 testDaemonIpcFromConfig(t, "private", false) 289 } 290 291 // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended. 292 func TestDaemonIpcModeShareableFromConfig(t *testing.T) { 293 skip.If(t, testEnv.IsRemoteDaemon) 294 skip.If(t, testEnv.IsRootless, "no support for --ipc=shareable in rootless") 295 296 testDaemonIpcFromConfig(t, "shareable", true) 297 } 298 299 // TestIpcModeOlderClient checks that older client gets shareable IPC mode 300 // by default, even when the daemon default is private. 301 func TestIpcModeOlderClient(t *testing.T) { 302 skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "requires a daemon with DefaultIpcMode: private") 303 apiClient := testEnv.APIClient() 304 skip.If(t, versions.LessThan(apiClient.ClientVersion(), "1.40"), "requires client API >= 1.40") 305 306 t.Parallel() 307 308 ctx := testutil.StartSpan(baseContext, t) 309 310 // pre-check: default ipc mode in daemon is private 311 cID := container.Create(ctx, t, apiClient, container.WithAutoRemove) 312 313 inspect, err := apiClient.ContainerInspect(ctx, cID) 314 assert.NilError(t, err) 315 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "private")) 316 317 // main check: using older client creates "shareable" container 318 apiClient = request.NewAPIClient(t, client.WithVersion("1.39")) 319 cID = container.Create(ctx, t, apiClient, container.WithAutoRemove) 320 321 inspect, err = apiClient.ContainerInspect(ctx, cID) 322 assert.NilError(t, err) 323 assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "shareable")) 324 }