Browse code

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 <rob.murray@docker.com>

Rob Murray authored on 2024/04/17 23:52:37
Showing 3 changed files
... ...
@@ -47,6 +47,13 @@ func WithNetworkMode(mode string) func(*TestContainerConfig) {
47 47
 	}
48 48
 }
49 49
 
50
+// WithDNS sets external DNS servers for the container
51
+func WithDNS(dns []string) func(*TestContainerConfig) {
52
+	return func(c *TestContainerConfig) {
53
+		c.HostConfig.DNS = append([]string(nil), dns...)
54
+	}
55
+}
56
+
50 57
 // WithSysctls sets sysctl options for the container
51 58
 func WithSysctls(sysctls map[string]string) func(*TestContainerConfig) {
52 59
 	return func(c *TestContainerConfig) {
... ...
@@ -9,6 +9,8 @@ import (
9 9
 	"github.com/docker/docker/integration/internal/network"
10 10
 	"github.com/docker/docker/testutil"
11 11
 	"github.com/docker/docker/testutil/daemon"
12
+	"gotest.tools/v3/assert"
13
+	is "gotest.tools/v3/assert/cmp"
12 14
 	"gotest.tools/v3/poll"
13 15
 	"gotest.tools/v3/skip"
14 16
 )
... ...
@@ -33,3 +35,59 @@ func TestDaemonDNSFallback(t *testing.T) {
33 33
 
34 34
 	poll.WaitOn(t, container.IsSuccessful(ctx, c, cid), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(10*time.Second))
35 35
 }
36
+
37
+// Check that, when the internal DNS server's address is supplied as an external
38
+// DNS server, the daemon doesn't start talking to itself.
39
+func TestIntDNSAsExtDNS(t *testing.T) {
40
+	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "cannot start daemon on Windows test run")
41
+	skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run")
42
+
43
+	ctx := setupTest(t)
44
+
45
+	d := daemon.New(t)
46
+	d.StartWithBusybox(ctx, t)
47
+	defer d.Stop(t)
48
+
49
+	c := d.NewClientT(t)
50
+	defer c.Close()
51
+
52
+	testcases := []struct {
53
+		name        string
54
+		extServers  []string
55
+		expExitCode int
56
+		expStdout   string
57
+	}{
58
+		{
59
+			name:        "only self",
60
+			extServers:  []string{"127.0.0.11"},
61
+			expExitCode: 1,
62
+			expStdout:   "SERVFAIL",
63
+		},
64
+		{
65
+			name:        "self then ext",
66
+			extServers:  []string{"127.0.0.11", "8.8.8.8"},
67
+			expExitCode: 0,
68
+			expStdout:   "Non-authoritative answer",
69
+		},
70
+	}
71
+
72
+	for _, tc := range testcases {
73
+		t.Run(tc.name, func(t *testing.T) {
74
+			ctx := testutil.StartSpan(ctx, t)
75
+
76
+			const netName = "testnet"
77
+			network.CreateNoError(ctx, t, c, netName)
78
+			defer network.RemoveNoError(ctx, t, c, netName)
79
+
80
+			res := container.RunAttach(ctx, t, c,
81
+				container.WithNetworkMode(netName),
82
+				container.WithDNS(tc.extServers),
83
+				container.WithCmd("nslookup", "docker.com"),
84
+			)
85
+			defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true})
86
+
87
+			assert.Check(t, is.Equal(res.ExitCode, tc.expExitCode))
88
+			assert.Check(t, is.Contains(res.Stdout.String(), tc.expStdout))
89
+		})
90
+	}
91
+}
... ...
@@ -224,13 +224,7 @@ func (r *Resolver) Stop() {
224 224
 // when forwarding queries, unless SetExtServersForSrc has configured servers
225 225
 // for the DNS client making the request.
226 226
 func (r *Resolver) SetExtServers(extDNS []extDNSEntry) {
227
-	l := len(extDNS)
228
-	if l > maxExtDNS {
229
-		l = maxExtDNS
230
-	}
231
-	for i := 0; i < l; i++ {
232
-		r.extDNSList[i] = extDNS[i]
233
-	}
227
+	copy(r.extDNSList[:], r.filterExtServers(extDNS))
234 228
 }
235 229
 
236 230
 // 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) {
244 244
 // in preference to servers set by SetExtServers. Supplying a nil or empty extDNS
245 245
 // deletes nameservers for srcAddr.
246 246
 func (r *Resolver) SetExtServersForSrc(srcAddr netip.Addr, extDNS []extDNSEntry) error {
247
-	r.ipToExtDNS.set(srcAddr, extDNS)
247
+	r.ipToExtDNS.set(srcAddr, r.filterExtServers(extDNS))
248 248
 	return nil
249 249
 }
250 250
 
... ...
@@ -258,6 +252,23 @@ func (r *Resolver) ResolverOptions() []string {
258 258
 	return []string{"ndots:0"}
259 259
 }
260 260
 
261
+// filterExtServers removes the resolver's own address from extDNS if present,
262
+// and returns the result.
263
+func (r *Resolver) filterExtServers(extDNS []extDNSEntry) []extDNSEntry {
264
+	result := make([]extDNSEntry, 0, len(extDNS))
265
+	for _, e := range extDNS {
266
+		if !e.HostLoopback {
267
+			if ra, _ := netip.ParseAddr(e.IPStr); ra == r.listenAddress {
268
+				log.G(context.TODO()).Infof("[resolver] not using own address (%s) as an external DNS server",
269
+					r.listenAddress)
270
+				continue
271
+			}
272
+		}
273
+		result = append(result, e)
274
+	}
275
+	return result
276
+}
277
+
261 278
 //nolint:gosec // The RNG is not used in a security-sensitive context.
262 279
 var (
263 280
 	shuffleRNG   = rand.New(rand.NewSource(time.Now().Unix()))