github.com/khulnasoft-lab/khulnasoft@v26.0.1-0.20240328202558-330a6f959fe0+incompatible/integration/networking/resolvconf_test.go (about)

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