resolvconf_test.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package networking
  2. import (
  3. "net"
  4. "os"
  5. "strings"
  6. "testing"
  7. containertypes "github.com/docker/docker/api/types/container"
  8. networktypes "github.com/docker/docker/api/types/network"
  9. "github.com/docker/docker/integration/internal/container"
  10. "github.com/docker/docker/integration/internal/network"
  11. "github.com/docker/docker/testutil/daemon"
  12. "github.com/miekg/dns"
  13. "gotest.tools/v3/assert"
  14. is "gotest.tools/v3/assert/cmp"
  15. "gotest.tools/v3/skip"
  16. )
  17. // writeTempResolvConf writes a resolv.conf that only contains a single
  18. // nameserver line, with address addr.
  19. // It returns the name of the temp file.
  20. func writeTempResolvConf(t *testing.T, addr string) string {
  21. t.Helper()
  22. // Not using t.TempDir() here because in rootless mode, while the temporary
  23. // directory gets mode 0777, it's a subdir of an 0700 directory owned by root.
  24. // So, it's not accessible by the daemon.
  25. f, err := os.CreateTemp("", "resolv.conf")
  26. assert.NilError(t, err)
  27. t.Cleanup(func() { os.Remove(f.Name()) })
  28. err = f.Chmod(0644)
  29. assert.NilError(t, err)
  30. f.Write([]byte("nameserver " + addr + "\n"))
  31. return f.Name()
  32. }
  33. // Regression test for https://github.com/moby/moby/issues/46968
  34. func TestResolvConfLocalhostIPv6(t *testing.T) {
  35. // No "/etc/resolv.conf" on Windows.
  36. skip.If(t, testEnv.DaemonInfo.OSType == "windows")
  37. ctx := setupTest(t)
  38. tmpFileName := writeTempResolvConf(t, "127.0.0.53")
  39. d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
  40. d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
  41. defer d.Stop(t)
  42. c := d.NewClientT(t)
  43. defer c.Close()
  44. netName := "nnn"
  45. network.CreateNoError(ctx, t, c, netName,
  46. network.WithDriver("bridge"),
  47. network.WithIPv6(),
  48. network.WithIPAM("fd49:b5ef:36d9::/64", "fd49:b5ef:36d9::1"),
  49. )
  50. defer network.RemoveNoError(ctx, t, c, netName)
  51. result := container.RunAttach(ctx, t, c,
  52. container.WithImage("busybox:latest"),
  53. container.WithNetworkMode(netName),
  54. container.WithCmd("cat", "/etc/resolv.conf"),
  55. )
  56. defer c.ContainerRemove(ctx, result.ContainerID, containertypes.RemoveOptions{
  57. Force: true,
  58. })
  59. output := strings.ReplaceAll(result.Stdout.String(), tmpFileName, "RESOLV.CONF")
  60. assert.Check(t, is.Equal(output, `# Generated by Docker Engine.
  61. # This file can be edited; Docker Engine will not make further changes once it
  62. # has been modified.
  63. nameserver 127.0.0.11
  64. options ndots:0
  65. # Based on host file: 'RESOLV.CONF' (internal resolver)
  66. # ExtServers: [host(127.0.0.53)]
  67. # Overrides: []
  68. # Option ndots from: internal
  69. `))
  70. }
  71. const dnsRespAddr = "10.11.12.13"
  72. // startDaftDNS starts and returns a really, really daft DNS server that only
  73. // responds to type-A requests, and always with address dnsRespAddr.
  74. func startDaftDNS(t *testing.T, addr string) *dns.Server {
  75. serveDNS := func(w dns.ResponseWriter, query *dns.Msg) {
  76. if query.Question[0].Qtype == dns.TypeA {
  77. resp := &dns.Msg{}
  78. resp.SetReply(query)
  79. answer := &dns.A{
  80. Hdr: dns.RR_Header{
  81. Name: query.Question[0].Name,
  82. Rrtype: dns.TypeA,
  83. Class: dns.ClassINET,
  84. Ttl: 600,
  85. },
  86. }
  87. answer.A = net.ParseIP(dnsRespAddr)
  88. resp.Answer = append(resp.Answer, answer)
  89. _ = w.WriteMsg(resp)
  90. }
  91. }
  92. conn, err := net.ListenUDP("udp", &net.UDPAddr{
  93. IP: net.ParseIP(addr),
  94. Port: 53,
  95. })
  96. assert.NilError(t, err)
  97. server := &dns.Server{Handler: dns.HandlerFunc(serveDNS), PacketConn: conn}
  98. go func() {
  99. _ = server.ActivateAndServe()
  100. }()
  101. return server
  102. }
  103. // Check that when a container is connected to an internal network, DNS
  104. // requests sent to daemon's internal DNS resolver are not forwarded to
  105. // an upstream resolver listening on a localhost address.
  106. // (Assumes the host does not already have a DNS server on 127.0.0.1.)
  107. func TestInternalNetworkDNS(t *testing.T) {
  108. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "No resolv.conf on Windows")
  109. skip.If(t, testEnv.IsRootless, "Can't use resolver on host in rootless mode")
  110. ctx := setupTest(t)
  111. // Start a DNS server on the loopback interface.
  112. server := startDaftDNS(t, "127.0.0.1")
  113. defer server.Shutdown()
  114. // Set up a temp resolv.conf pointing at that DNS server, and a daemon using it.
  115. tmpFileName := writeTempResolvConf(t, "127.0.0.1")
  116. d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
  117. d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
  118. defer d.Stop(t)
  119. c := d.NewClientT(t)
  120. defer c.Close()
  121. intNetName := "intnet"
  122. network.CreateNoError(ctx, t, c, intNetName,
  123. network.WithDriver("bridge"),
  124. network.WithInternal(),
  125. )
  126. defer network.RemoveNoError(ctx, t, c, intNetName)
  127. extNetName := "extnet"
  128. network.CreateNoError(ctx, t, c, extNetName,
  129. network.WithDriver("bridge"),
  130. )
  131. defer network.RemoveNoError(ctx, t, c, extNetName)
  132. // Create a container, initially with external connectivity.
  133. // Expect the external DNS server to respond to a request from the container.
  134. ctrId := container.Run(ctx, t, c, container.WithNetworkMode(extNetName))
  135. defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
  136. res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  137. assert.NilError(t, err)
  138. assert.Check(t, is.Equal(res.ExitCode, 0))
  139. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  140. // Connect the container to the internal network as well.
  141. // External DNS should still be used.
  142. err = c.NetworkConnect(ctx, intNetName, ctrId, nil)
  143. assert.NilError(t, err)
  144. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  145. assert.NilError(t, err)
  146. assert.Check(t, is.Equal(res.ExitCode, 0))
  147. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  148. // Disconnect from the external network.
  149. // Expect no access to the external DNS.
  150. err = c.NetworkDisconnect(ctx, extNetName, ctrId, true)
  151. assert.NilError(t, err)
  152. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  153. assert.NilError(t, err)
  154. assert.Check(t, is.Equal(res.ExitCode, 1))
  155. assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL"))
  156. // Reconnect the external network.
  157. // Check that the external DNS server is used again.
  158. err = c.NetworkConnect(ctx, extNetName, ctrId, nil)
  159. assert.NilError(t, err)
  160. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  161. assert.NilError(t, err)
  162. assert.Check(t, is.Equal(res.ExitCode, 0))
  163. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  164. }
  165. // Check that containers on the default bridge network can use a host's resolver
  166. // running on a loopback interface (via the internal resolver), but the internal
  167. // resolver is not populated with DNS names for containers (no service discovery
  168. // on the legacy/default bridge network).
  169. // (Assumes the host does not already have a DNS server on 127.0.0.1.)
  170. func TestDefaultBridgeDNS(t *testing.T) {
  171. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "No resolv.conf on Windows")
  172. skip.If(t, testEnv.IsRootless, "Can't use resolver on host in rootless mode")
  173. ctx := setupTest(t)
  174. // Start a DNS server on the loopback interface.
  175. server := startDaftDNS(t, "127.0.0.1")
  176. defer server.Shutdown()
  177. // Set up a temp resolv.conf pointing at that DNS server, and a daemon using it.
  178. tmpFileName := writeTempResolvConf(t, "127.0.0.1")
  179. d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
  180. d.StartWithBusybox(ctx, t)
  181. defer d.Stop(t)
  182. c := d.NewClientT(t)
  183. defer c.Close()
  184. // Create a container on the default bridge network.
  185. const ctrName = "ctrname"
  186. ctrId := container.Run(ctx, t, c, container.WithName(ctrName))
  187. defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
  188. // Expect the external DNS server to respond to a request from the container.
  189. res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  190. assert.NilError(t, err)
  191. assert.Check(t, is.Equal(res.ExitCode, 0))
  192. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  193. // Expect the external DNS server to respond to a request from the container
  194. // for the container's own name - it won't be recognised as a container name
  195. // because there's no service resolution on the default bridge.
  196. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", ctrName})
  197. assert.NilError(t, err)
  198. assert.Check(t, is.Equal(res.ExitCode, 0))
  199. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  200. // Check that inspect output has no DNSNames for the container.
  201. inspect := container.Inspect(ctx, t, c, ctrId)
  202. net, ok := inspect.NetworkSettings.Networks[networktypes.NetworkBridge]
  203. assert.Check(t, ok, "expected to find bridge network in inspect output")
  204. assert.Check(t, is.Nil(net.DNSNames))
  205. }