ipcmode_linux_test.go 11 KB

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