Skip to content

Commit 6c68be2

Browse files
committed
Windows DNS resolver forwarding
Make the internal DNS resolver for Windows containers forward requests to upsteam DNS servers when it cannot respond itself, rather than returning SERVFAIL. Windows containers are normally configured with the internal resolver first for service discovery (container name lookup), then external resolvers from '--dns' or the host's networking configuration. When a tool like ping gets a SERVFAIL from the internal resolver, it tries the other nameservers. But, nslookup does not, and with this change it does not need to. The internal resolver learns external server addresses from the container's HNSEndpoint configuration, so it will use the same DNS servers as processes in the container. The internal resolver for Windows containers listens on the network's gateway address, and each container may have a different set of external DNS servers. So, the resolver uses the source address of the DNS request to select external resolvers. On Windows, daemon.json feature option 'windows-no-dns-proxy' can be used to prevent the internal resolver from forwarding requests (restoring the old behaviour). Signed-off-by: Rob Murray <[email protected]>
1 parent f07644e commit 6c68be2

16 files changed

Lines changed: 553 additions & 38 deletions

daemon/config/config_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ func (conf *Config) ValidatePlatformConfig() error {
196196
return errors.Wrap(err, "invalid fixed-cidr-v6")
197197
}
198198

199+
if _, ok := conf.Features["windows-dns-proxy"]; ok {
200+
return errors.New("feature option 'windows-dns-proxy' is only available on Windows")
201+
}
202+
199203
return verifyDefaultCgroupNsMode(conf.CgroupNamespaceMode)
200204
}
201205

daemon/container_operations.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ func (daemon *Daemon) buildSandboxOptions(cfg *config.Config, container *contain
5454
sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
5555
}
5656

57-
if err := setupPathsAndSandboxOptions(container, cfg, &sboxOptions); err != nil {
57+
// Add platform-specific Sandbox options.
58+
if err := buildSandboxPlatformOptions(container, cfg, &sboxOptions); err != nil {
5859
return nil, err
5960
}
6061

daemon/container_operations_unix.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ func serviceDiscoveryOnDefaultNetwork() bool {
417417
return false
418418
}
419419

420-
func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
420+
func buildSandboxPlatformOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
421421
var err error
422422
var originResolvConfPath string
423423

@@ -481,6 +481,7 @@ func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Con
481481
return err
482482
}
483483
*sboxOptions = append(*sboxOptions, libnetwork.OptionResolvConfPath(container.ResolvConfPath))
484+
484485
return nil
485486
}
486487

daemon/container_operations_windows.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,13 @@ func serviceDiscoveryOnDefaultNetwork() bool {
163163
return true
164164
}
165165

166-
func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
166+
func buildSandboxPlatformOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
167+
// By default, the Windows internal resolver does not forward requests to
168+
// external resolvers - but forwarding can be enabled using feature flag
169+
// "windows-dns-proxy":true.
170+
if doproxy, exists := cfg.Features["windows-dns-proxy"]; !exists || !doproxy {
171+
*sboxOptions = append(*sboxOptions, libnetwork.OptionDNSNoProxy())
172+
}
167173
return nil
168174
}
169175

integration/networking/resolvconf_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package networking
22

33
import (
4+
"context"
45
"strings"
56
"testing"
7+
"time"
68

79
containertypes "github.com/docker/docker/api/types/container"
810
"github.com/docker/docker/integration/internal/container"
@@ -131,3 +133,27 @@ func TestInternalNetworkDNS(t *testing.T) {
131133
assert.Check(t, is.Equal(res.ExitCode, 0))
132134
assert.Check(t, is.Contains(res.Stdout(), network.DNSRespAddr))
133135
}
136+
137+
// TestNslookupWindows checks that nslookup gets results from external DNS.
138+
// Regression test for https://github.com/moby/moby/issues/46792
139+
func TestNslookupWindows(t *testing.T) {
140+
skip.If(t, testEnv.DaemonInfo.OSType != "windows")
141+
142+
ctx := setupTest(t)
143+
c := testEnv.APIClient()
144+
145+
attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
146+
defer cancel()
147+
res := container.RunAttach(attachCtx, t, c,
148+
container.WithCmd("nslookup", "docker.com"),
149+
)
150+
defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true})
151+
152+
assert.Check(t, is.Equal(res.ExitCode, 0))
153+
// Current default is to not-forward requests to external servers, which
154+
// can only be changed in daemon.json using feature flag "windows-dns-proxy".
155+
// So, expect the lookup to fail...
156+
assert.Check(t, is.Contains(res.Stderr.String(), "Server failed"))
157+
// When the default behaviour is changed, nslookup should succeed...
158+
//assert.Check(t, is.Contains(res.Stdout.String(), "Addresses:"))
159+
}

libnetwork/endpoint.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sync"
1212

1313
"github.com/containerd/log"
14+
"github.com/docker/docker/errdefs"
1415
"github.com/docker/docker/internal/sliceutil"
1516
"github.com/docker/docker/libnetwork/datastore"
1617
"github.com/docker/docker/libnetwork/ipamapi"
@@ -543,6 +544,10 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) {
543544
return err
544545
}
545546

547+
if err = addEpToResolver(context.TODO(), n.Name(), ep.Name(), &sb.config, ep.iface, n.Resolvers()); err != nil {
548+
return errdefs.System(err)
549+
}
550+
546551
if err = n.getController().updateToStore(ep); err != nil {
547552
return err
548553
}
@@ -745,6 +750,10 @@ func (ep *Endpoint) sbLeave(sb *Sandbox, force bool) error {
745750
log.G(context.TODO()).Warnf("Failed to clean up service info on container %s disconnect: %v", ep.name, err)
746751
}
747752

753+
if err := deleteEpFromResolver(ep.Name(), ep.iface, n.Resolvers()); err != nil {
754+
log.G(context.TODO()).Warnf("Failed to clean up resolver info on container %s disconnect: %v", ep.name, err)
755+
}
756+
748757
if err := sb.clearNetworkResources(ep); err != nil {
749758
log.G(context.TODO()).Warnf("Failed to clean up network resources on container %s disconnect: %v", ep.name, err)
750759
}

libnetwork/network.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ type Network struct {
192192
dbExists bool
193193
persist bool
194194
drvOnce *sync.Once
195-
resolverOnce sync.Once //nolint:nolintlint,unused // only used on windows
196195
resolver []*Resolver
197196
internal bool
198197
attachable bool
@@ -204,6 +203,7 @@ type Network struct {
204203
configFrom string
205204
loadBalancerIP net.IP
206205
loadBalancerMode string
206+
platformNetwork //nolint:nolintlint,unused // only populated on windows
207207
mu sync.Mutex
208208
}
209209

@@ -244,6 +244,13 @@ func (n *Network) Type() string {
244244
return n.networkType
245245
}
246246

247+
func (n *Network) Resolvers() []*Resolver {
248+
n.mu.Lock()
249+
defer n.mu.Unlock()
250+
251+
return n.resolver
252+
}
253+
247254
func (n *Network) Key() []string {
248255
n.mu.Lock()
249256
defer n.mu.Unlock()
@@ -2097,10 +2104,6 @@ func (n *Network) ResolveService(ctx context.Context, name string) ([]*net.SRV,
20972104
return srv, ip
20982105
}
20992106

2100-
func (n *Network) ExecFunc(f func()) error {
2101-
return types.NotImplementedErrorf("ExecFunc not supported by network")
2102-
}
2103-
21042107
func (n *Network) NdotsSet() bool {
21052108
return false
21062109
}

libnetwork/network_unix.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,33 @@
22

33
package libnetwork
44

5-
import "github.com/docker/docker/libnetwork/ipamapi"
5+
import (
6+
"context"
7+
8+
"github.com/docker/docker/libnetwork/ipamapi"
9+
)
10+
11+
type platformNetwork struct{} //nolint:nolintlint,unused // only populated on windows
612

713
// Stub implementations for DNS related functions
814

915
func (n *Network) startResolver() {
1016
}
1117

18+
func addEpToResolver(
19+
ctx context.Context,
20+
netName, epName string,
21+
config *containerConfig,
22+
epIface *EndpointInterface,
23+
resolvers []*Resolver,
24+
) error {
25+
return nil
26+
}
27+
28+
func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers []*Resolver) error {
29+
return nil
30+
}
31+
1232
func defaultIpamForNetworkType(networkType string) string {
1333
return ipamapi.DefaultIPAM
1434
}

0 commit comments

Comments
 (0)