resolvconf_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package networking
  2. import (
  3. "net"
  4. "os"
  5. "testing"
  6. containertypes "github.com/docker/docker/api/types/container"
  7. "github.com/docker/docker/integration/internal/container"
  8. "github.com/docker/docker/integration/internal/network"
  9. "github.com/docker/docker/testutil/daemon"
  10. "github.com/miekg/dns"
  11. "gotest.tools/v3/assert"
  12. is "gotest.tools/v3/assert/cmp"
  13. "gotest.tools/v3/skip"
  14. )
  15. // writeTempResolvConf writes a resolv.conf that only contains a single
  16. // nameserver line, with address addr.
  17. // It returns the name of the temp file.
  18. func writeTempResolvConf(t *testing.T, addr string) string {
  19. t.Helper()
  20. // Not using t.TempDir() here because in rootless mode, while the temporary
  21. // directory gets mode 0777, it's a subdir of an 0700 directory owned by root.
  22. // So, it's not accessible by the daemon.
  23. f, err := os.CreateTemp("", "resolv.conf")
  24. assert.NilError(t, err)
  25. t.Cleanup(func() { os.Remove(f.Name()) })
  26. err = f.Chmod(0644)
  27. assert.NilError(t, err)
  28. f.Write([]byte("nameserver " + addr + "\n"))
  29. return f.Name()
  30. }
  31. const dnsRespAddr = "10.11.12.13"
  32. // startDaftDNS starts and returns a really, really daft DNS server that only
  33. // responds to type-A requests, and always with address dnsRespAddr.
  34. func startDaftDNS(t *testing.T, addr string) *dns.Server {
  35. serveDNS := func(w dns.ResponseWriter, query *dns.Msg) {
  36. if query.Question[0].Qtype == dns.TypeA {
  37. resp := &dns.Msg{}
  38. resp.SetReply(query)
  39. answer := &dns.A{
  40. Hdr: dns.RR_Header{
  41. Name: query.Question[0].Name,
  42. Rrtype: dns.TypeA,
  43. Class: dns.ClassINET,
  44. Ttl: 600,
  45. },
  46. }
  47. answer.A = net.ParseIP(dnsRespAddr)
  48. resp.Answer = append(resp.Answer, answer)
  49. _ = w.WriteMsg(resp)
  50. }
  51. }
  52. conn, err := net.ListenUDP("udp", &net.UDPAddr{
  53. IP: net.ParseIP(addr),
  54. Port: 53,
  55. })
  56. assert.NilError(t, err)
  57. server := &dns.Server{Handler: dns.HandlerFunc(serveDNS), PacketConn: conn}
  58. go func() {
  59. _ = server.ActivateAndServe()
  60. }()
  61. return server
  62. }
  63. // Check that when a container is connected to an internal network, DNS
  64. // requests sent to daemon's internal DNS resolver are not forwarded to
  65. // an upstream resolver listening on a localhost address.
  66. // (Assumes the host does not already have a DNS server on 127.0.0.1.)
  67. func TestInternalNetworkDNS(t *testing.T) {
  68. skip.If(t, testEnv.DaemonInfo.OSType == "windows", "No resolv.conf on Windows")
  69. skip.If(t, testEnv.IsRootless, "Can't use resolver on host in rootless mode")
  70. ctx := setupTest(t)
  71. // Start a DNS server on the loopback interface.
  72. server := startDaftDNS(t, "127.0.0.1")
  73. defer server.Shutdown()
  74. // Set up a temp resolv.conf pointing at that DNS server, and a daemon using it.
  75. tmpFileName := writeTempResolvConf(t, "127.0.0.1")
  76. d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
  77. d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
  78. defer d.Stop(t)
  79. c := d.NewClientT(t)
  80. defer c.Close()
  81. intNetName := "intnet"
  82. network.CreateNoError(ctx, t, c, intNetName,
  83. network.WithDriver("bridge"),
  84. network.WithInternal(),
  85. )
  86. defer network.RemoveNoError(ctx, t, c, intNetName)
  87. extNetName := "extnet"
  88. network.CreateNoError(ctx, t, c, extNetName,
  89. network.WithDriver("bridge"),
  90. )
  91. defer network.RemoveNoError(ctx, t, c, extNetName)
  92. // Create a container, initially with external connectivity.
  93. // Expect the external DNS server to respond to a request from the container.
  94. ctrId := container.Run(ctx, t, c, container.WithNetworkMode(extNetName))
  95. defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
  96. res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  97. assert.NilError(t, err)
  98. assert.Check(t, is.Equal(res.ExitCode, 0))
  99. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  100. // Connect the container to the internal network as well.
  101. // External DNS should still be used.
  102. err = c.NetworkConnect(ctx, intNetName, ctrId, nil)
  103. assert.NilError(t, err)
  104. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  105. assert.NilError(t, err)
  106. assert.Check(t, is.Equal(res.ExitCode, 0))
  107. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  108. // Disconnect from the external network.
  109. // Expect no access to the external DNS.
  110. err = c.NetworkDisconnect(ctx, extNetName, ctrId, true)
  111. assert.NilError(t, err)
  112. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  113. assert.NilError(t, err)
  114. assert.Check(t, is.Equal(res.ExitCode, 1))
  115. assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL"))
  116. // Reconnect the external network.
  117. // Check that the external DNS server is used again.
  118. err = c.NetworkConnect(ctx, extNetName, ctrId, nil)
  119. assert.NilError(t, err)
  120. res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
  121. assert.NilError(t, err)
  122. assert.Check(t, is.Equal(res.ExitCode, 0))
  123. assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
  124. }