From 87506142d88fa3c0e10963144bef0651b60cfafc Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Wed, 17 Apr 2024 15:52:37 +0100 Subject: [PATCH] Do not forward DNS requests to self. If a container is configured with the internal DNS resolver's own address as an external server, try the next ext server rather than recursing (return SERVFAIL if there are no other servers). Signed-off-by: Rob Murray --- integration/internal/container/ops.go | 7 ++++ integration/network/dns_test.go | 58 +++++++++++++++++++++++++++ libnetwork/resolver.go | 27 +++++++++---- 3 files changed, 84 insertions(+), 8 deletions(-) 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()))