| package networking | |
| import ( | |
| "context" | |
| "net/netip" | |
| "os" | |
| "path" | |
| "strings" | |
| "testing" | |
| "time" | |
| "github.com/moby/moby/api/types/mount" | |
| "github.com/moby/moby/client" | |
| "github.com/moby/moby/v2/integration/internal/container" | |
| "github.com/moby/moby/v2/integration/internal/network" | |
| "github.com/moby/moby/v2/internal/testutil/daemon" | |
| "gotest.tools/v3/assert" | |
| is "gotest.tools/v3/assert/cmp" | |
| "gotest.tools/v3/skip" | |
| ) | |
| // 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) | |
| d := daemon.New(t, daemon.WithResolvConf(network.GenResolvConf("127.0.0.53"))) | |
| d.StartWithBusybox(ctx, t) | |
| 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, client.ContainerRemoveOptions{ | |
| Force: true, | |
| }) | |
| output := strings.ReplaceAll(result.Stdout.String(), d.ResolvConfPathOverride, "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 | |
| `)) | |
| } | |
| // 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. | |
| network.StartDaftDNS(t, "127.0.0.1") | |
| // Set up a temp resolv.conf pointing at that DNS server, and a daemon using it. | |
| d := daemon.New(t, daemon.WithResolvConf(network.GenResolvConf("127.0.0.1"))) | |
| d.StartWithBusybox(ctx, t) | |
| 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, client.ContainerRemoveOptions{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(), network.DNSRespAddr)) | |
| // Connect the container to the internal network as well. | |
| // External DNS should still be used. | |
| _, err = c.NetworkConnect(ctx, intNetName, client.NetworkConnectOptions{ | |
| Container: ctrId, | |
| }) | |
| 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(), network.DNSRespAddr)) | |
| // Disconnect from the external network. | |
| // Expect no access to the external DNS. | |
| _, err = c.NetworkDisconnect(ctx, extNetName, client.NetworkDisconnectOptions{Container: ctrId, Force: 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, client.NetworkConnectOptions{ | |
| Container: ctrId, | |
| }) | |
| 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(), network.DNSRespAddr)) | |
| } | |
| // Check that '--dns' can be used to name a server inside a '--internal' network. | |
| // Regression test for https://github.com/moby/moby/issues/47822 | |
| func TestInternalNetworkLocalDNS(t *testing.T) { | |
| skip.If(t, testEnv.DaemonInfo.OSType == "windows", "No internal networks on Windows") | |
| skip.If(t, testEnv.IsRootless, "Can't write an accessible dnsd.conf in rootless mode") | |
| ctx := setupTest(t) | |
| d := daemon.New(t) | |
| d.StartWithBusybox(ctx, t) | |
| 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) | |
| // Write a config file for busybox's dnsd. | |
| td := t.TempDir() | |
| fname := path.Join(td, "dnsd.conf") | |
| err := os.WriteFile(fname, []byte("foo.example 192.0.2.42\n"), 0o644) | |
| assert.NilError(t, err) | |
| // Start a DNS server on the internal network. | |
| serverId := container.Run(ctx, t, c, | |
| container.WithNetworkMode(intNetName), | |
| container.WithMount(mount.Mount{ | |
| Type: mount.TypeBind, | |
| Source: fname, | |
| Target: "/etc/dnsd.conf", | |
| }), | |
| container.WithCmd("dnsd"), | |
| ) | |
| defer c.ContainerRemove(ctx, serverId, client.ContainerRemoveOptions{Force: true}) | |
| // Get the DNS server's address. | |
| inspect := container.Inspect(ctx, t, c, serverId) | |
| serverIP := inspect.NetworkSettings.Networks[intNetName].IPAddress | |
| // Query the internal network's DNS server (via the daemon's internal DNS server). | |
| res := container.RunAttach(ctx, t, c, | |
| container.WithNetworkMode(intNetName), | |
| container.WithDNS([]netip.Addr{serverIP}), | |
| container.WithCmd("nslookup", "-type=A", "foo.example"), | |
| ) | |
| defer c.ContainerRemove(ctx, res.ContainerID, client.ContainerRemoveOptions{Force: true}) | |
| assert.Check(t, is.Contains(res.Stdout.String(), "192.0.2.42")) | |
| } | |
| // TestNslookupWindows checks that nslookup gets results from external DNS. | |
| // Regression test for https://github.com/moby/moby/issues/46792 | |
| func TestNslookupWindows(t *testing.T) { | |
| skip.If(t, testEnv.DaemonInfo.OSType != "windows") | |
| ctx := setupTest(t) | |
| c := testEnv.APIClient() | |
| attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second) | |
| defer cancel() | |
| res := container.RunAttach(attachCtx, t, c, | |
| container.WithCmd("nslookup", "docker.com"), | |
| ) | |
| defer c.ContainerRemove(ctx, res.ContainerID, client.ContainerRemoveOptions{Force: true}) | |
| assert.Check(t, is.Equal(res.ExitCode, 0)) | |
| // Current default is to forward requests to external servers, which | |
| // can only be changed in daemon.json using feature flag "windows-dns-proxy". | |
| assert.Check(t, is.Contains(res.Stdout.String(), "Addresses:")) | |
| } |