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>
| ... | ... |
@@ -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())) |