123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package networking
- import (
- "net"
- "os"
- "strings"
- "testing"
- containertypes "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/integration/internal/container"
- "github.com/docker/docker/integration/internal/network"
- "github.com/docker/docker/testutil/daemon"
- "github.com/miekg/dns"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- "gotest.tools/v3/skip"
- )
- // writeTempResolvConf writes a resolv.conf that only contains a single
- // nameserver line, with address addr.
- // It returns the name of the temp file.
- func writeTempResolvConf(t *testing.T, addr string) string {
- t.Helper()
- // Not using t.TempDir() here because in rootless mode, while the temporary
- // directory gets mode 0777, it's a subdir of an 0700 directory owned by root.
- // So, it's not accessible by the daemon.
- f, err := os.CreateTemp("", "resolv.conf")
- assert.NilError(t, err)
- t.Cleanup(func() { os.Remove(f.Name()) })
- err = f.Chmod(0644)
- assert.NilError(t, err)
- f.Write([]byte("nameserver " + addr + "\n"))
- return f.Name()
- }
- // Regression test for https://github.com/moby/moby/issues/46968
- func TestResolvConfLocalhostIPv6(t *testing.T) {
- // No "/etc/resolv.conf" on Windows.
- skip.If(t, testEnv.DaemonInfo.OSType == "windows")
- ctx := setupTest(t)
- tmpFileName := writeTempResolvConf(t, "127.0.0.53")
- d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
- d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
- defer d.Stop(t)
- c := d.NewClientT(t)
- defer c.Close()
- netName := "nnn"
- network.CreateNoError(ctx, t, c, netName,
- network.WithDriver("bridge"),
- network.WithIPv6(),
- network.WithIPAM("fd49:b5ef:36d9::/64", "fd49:b5ef:36d9::1"),
- )
- defer network.RemoveNoError(ctx, t, c, netName)
- result := container.RunAttach(ctx, t, c,
- container.WithImage("busybox:latest"),
- container.WithNetworkMode(netName),
- container.WithCmd("cat", "/etc/resolv.conf"),
- )
- defer c.ContainerRemove(ctx, result.ContainerID, containertypes.RemoveOptions{
- Force: true,
- })
- output := strings.ReplaceAll(result.Stdout.String(), tmpFileName, "RESOLV.CONF")
- assert.Check(t, is.Equal(output, `# Generated by Docker Engine.
- # This file can be edited; Docker Engine will not make further changes once it
- # has been modified.
- nameserver 127.0.0.11
- options ndots:0
- # Based on host file: 'RESOLV.CONF' (internal resolver)
- # ExtServers: [host(127.0.0.53)]
- # Overrides: []
- # Option ndots from: internal
- `))
- }
- const dnsRespAddr = "10.11.12.13"
- // startDaftDNS starts and returns a really, really daft DNS server that only
- // responds to type-A requests, and always with address dnsRespAddr.
- func startDaftDNS(t *testing.T, addr string) *dns.Server {
- serveDNS := func(w dns.ResponseWriter, query *dns.Msg) {
- if query.Question[0].Qtype == dns.TypeA {
- resp := &dns.Msg{}
- resp.SetReply(query)
- answer := &dns.A{
- Hdr: dns.RR_Header{
- Name: query.Question[0].Name,
- Rrtype: dns.TypeA,
- Class: dns.ClassINET,
- Ttl: 600,
- },
- }
- answer.A = net.ParseIP(dnsRespAddr)
- resp.Answer = append(resp.Answer, answer)
- _ = w.WriteMsg(resp)
- }
- }
- conn, err := net.ListenUDP("udp", &net.UDPAddr{
- IP: net.ParseIP(addr),
- Port: 53,
- })
- assert.NilError(t, err)
- server := &dns.Server{Handler: dns.HandlerFunc(serveDNS), PacketConn: conn}
- go func() {
- _ = server.ActivateAndServe()
- }()
- return server
- }
- // Check that when a container is connected to an internal network, DNS
- // requests sent to daemon's internal DNS resolver are not forwarded to
- // an upstream resolver listening on a localhost address.
- // (Assumes the host does not already have a DNS server on 127.0.0.1.)
- func TestInternalNetworkDNS(t *testing.T) {
- skip.If(t, testEnv.DaemonInfo.OSType == "windows", "No resolv.conf on Windows")
- skip.If(t, testEnv.IsRootless, "Can't use resolver on host in rootless mode")
- ctx := setupTest(t)
- // Start a DNS server on the loopback interface.
- server := startDaftDNS(t, "127.0.0.1")
- defer server.Shutdown()
- // Set up a temp resolv.conf pointing at that DNS server, and a daemon using it.
- tmpFileName := writeTempResolvConf(t, "127.0.0.1")
- d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_RESOLV_CONF_PATH="+tmpFileName))
- d.StartWithBusybox(ctx, t, "--experimental", "--ip6tables")
- defer d.Stop(t)
- c := d.NewClientT(t)
- defer c.Close()
- intNetName := "intnet"
- network.CreateNoError(ctx, t, c, intNetName,
- network.WithDriver("bridge"),
- network.WithInternal(),
- )
- defer network.RemoveNoError(ctx, t, c, intNetName)
- extNetName := "extnet"
- network.CreateNoError(ctx, t, c, extNetName,
- network.WithDriver("bridge"),
- )
- defer network.RemoveNoError(ctx, t, c, extNetName)
- // Create a container, initially with external connectivity.
- // Expect the external DNS server to respond to a request from the container.
- ctrId := container.Run(ctx, t, c, container.WithNetworkMode(extNetName))
- defer c.ContainerRemove(ctx, ctrId, containertypes.RemoveOptions{Force: true})
- res, err := container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.ExitCode, 0))
- assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
- // Connect the container to the internal network as well.
- // External DNS should still be used.
- err = c.NetworkConnect(ctx, intNetName, ctrId, nil)
- assert.NilError(t, err)
- res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.ExitCode, 0))
- assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
- // Disconnect from the external network.
- // Expect no access to the external DNS.
- err = c.NetworkDisconnect(ctx, extNetName, ctrId, true)
- assert.NilError(t, err)
- res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.ExitCode, 1))
- assert.Check(t, is.Contains(res.Stdout(), "SERVFAIL"))
- // Reconnect the external network.
- // Check that the external DNS server is used again.
- err = c.NetworkConnect(ctx, extNetName, ctrId, nil)
- assert.NilError(t, err)
- res, err = container.Exec(ctx, c, ctrId, []string{"nslookup", "test.example"})
- assert.NilError(t, err)
- assert.Check(t, is.Equal(res.ExitCode, 0))
- assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
- }
|