diff --git a/integration/internal/container/ops.go b/integration/internal/container/ops.go index b2d35ca8a7be0..893696bf23f7b 100644 --- a/integration/internal/container/ops.go +++ b/integration/internal/container/ops.go @@ -47,6 +47,13 @@ func WithNetworkMode(mode string) func(*TestContainerConfig) { } } +// WithDNS sets external DNS servers for the container +func WithDNS(dns []string) func(*TestContainerConfig) { + return func(c *TestContainerConfig) { + c.HostConfig.DNS = append([]string(nil), dns...) + } +} + // WithSysctls sets sysctl options for the container func WithSysctls(sysctls map[string]string) func(*TestContainerConfig) { return func(c *TestContainerConfig) { diff --git a/integration/network/dns_test.go b/integration/network/dns_test.go index 1231051e7ba92..52641c5e1bd93 100644 --- a/integration/network/dns_test.go +++ b/integration/network/dns_test.go @@ -9,6 +9,8 @@ import ( "github.com/docker/docker/integration/internal/network" "github.com/docker/docker/testutil" "github.com/docker/docker/testutil/daemon" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/poll" "gotest.tools/v3/skip" ) @@ -33,3 +35,59 @@ func TestDaemonDNSFallback(t *testing.T) { poll.WaitOn(t, container.IsSuccessful(ctx, c, cid), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(10*time.Second)) } + +// Check that, when the internal DNS server's address is supplied as an external +// DNS server, the daemon doesn't start talking to itself. +func TestIntDNSAsExtDNS(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType == "windows", "cannot start daemon on Windows test run") + skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run") + + ctx := setupTest(t) + + d := daemon.New(t) + d.StartWithBusybox(ctx, t) + defer d.Stop(t) + + c := d.NewClientT(t) + defer c.Close() + + testcases := []struct { + name string + extServers []string + expExitCode int + expStdout string + }{ + { + name: "only self", + extServers: []string{"127.0.0.11"}, + expExitCode: 1, + expStdout: "SERVFAIL", + }, + { + name: "self then ext", + extServers: []string{"127.0.0.11", "8.8.8.8"}, + expExitCode: 0, + expStdout: "Non-authoritative answer", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := testutil.StartSpan(ctx, t) + + const netName = "testnet" + network.CreateNoError(ctx, t, c, netName) + defer network.RemoveNoError(ctx, t, c, netName) + + res := container.RunAttach(ctx, t, c, + container.WithNetworkMode(netName), + container.WithDNS(tc.extServers), + container.WithCmd("nslookup", "docker.com"), + ) + defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true}) + + assert.Check(t, is.Equal(res.ExitCode, tc.expExitCode)) + assert.Check(t, is.Contains(res.Stdout.String(), tc.expStdout)) + }) + } +} diff --git a/libnetwork/resolver.go b/libnetwork/resolver.go index 3ce1c4b81f05e..ee430d17a39e3 100644 --- a/libnetwork/resolver.go +++ b/libnetwork/resolver.go @@ -224,13 +224,7 @@ func (r *Resolver) Stop() { // when forwarding queries, unless SetExtServersForSrc has configured servers // for the DNS client making the request. func (r *Resolver) SetExtServers(extDNS []extDNSEntry) { - l := len(extDNS) - if l > maxExtDNS { - l = maxExtDNS - } - for i := 0; i < l; i++ { - r.extDNSList[i] = extDNS[i] - } + copy(r.extDNSList[:], r.filterExtServers(extDNS)) } // SetForwardingPolicy re-configures the embedded DNS resolver to either enable or disable forwarding DNS queries to @@ -244,7 +238,7 @@ func (r *Resolver) SetForwardingPolicy(policy bool) { // in preference to servers set by SetExtServers. Supplying a nil or empty extDNS // deletes nameservers for srcAddr. func (r *Resolver) SetExtServersForSrc(srcAddr netip.Addr, extDNS []extDNSEntry) error { - r.ipToExtDNS.set(srcAddr, extDNS) + r.ipToExtDNS.set(srcAddr, r.filterExtServers(extDNS)) return nil } @@ -258,6 +252,23 @@ func (r *Resolver) ResolverOptions() []string { return []string{"ndots:0"} } +// filterExtServers removes the resolver's own address from extDNS if present, +// and returns the result. +func (r *Resolver) filterExtServers(extDNS []extDNSEntry) []extDNSEntry { + result := make([]extDNSEntry, 0, len(extDNS)) + for _, e := range extDNS { + if !e.HostLoopback { + if ra, _ := netip.ParseAddr(e.IPStr); ra == r.listenAddress { + log.G(context.TODO()).Infof("[resolver] not using own address (%s) as an external DNS server", + r.listenAddress) + continue + } + } + result = append(result, e) + } + return result +} + //nolint:gosec // The RNG is not used in a security-sensitive context. var ( shuffleRNG = rand.New(rand.NewSource(time.Now().Unix()))