Skip to content

Commit

Permalink
Windows DNS resolver forwarding
Browse files Browse the repository at this point in the history
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 <rob.murray@docker.com>
  • Loading branch information
robmry committed Mar 26, 2024
1 parent 784402c commit 2fbee5b
Show file tree
Hide file tree
Showing 16 changed files with 549 additions and 36 deletions.
7 changes: 7 additions & 0 deletions daemon/config/config.go
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"net/url"
"os"
"runtime"
"strings"

"dario.cat/mergo"
Expand Down Expand Up @@ -514,6 +515,12 @@ func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Con
return nil, err
}

if runtime.GOOS != "windows" {
if _, ok := config.Features["windows-no-dns-proxy"]; ok {
return nil, errors.New("feature option 'windows-no-dns-proxy' is only available on Windows")
}
}

return &config, nil
}

Expand Down
3 changes: 2 additions & 1 deletion daemon/container_operations.go
Expand Up @@ -54,7 +54,8 @@ func (daemon *Daemon) buildSandboxOptions(cfg *config.Config, container *contain
sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
}

if err := setupPathsAndSandboxOptions(container, cfg, &sboxOptions); err != nil {
// Add OS-specific Sandbox options.
if err := buildSandboxOptionsOS(container, cfg, &sboxOptions); err != nil {
return nil, err
}

Expand Down
3 changes: 2 additions & 1 deletion daemon/container_operations_unix.go
Expand Up @@ -417,7 +417,7 @@ func serviceDiscoveryOnDefaultNetwork() bool {
return false
}

func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
func buildSandboxOptionsOS(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
var err error
var originResolvConfPath string

Expand Down Expand Up @@ -481,6 +481,7 @@ func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Con
return err
}
*sboxOptions = append(*sboxOptions, libnetwork.OptionResolvConfPath(container.ResolvConfPath))

return nil
}

Expand Down
5 changes: 4 additions & 1 deletion daemon/container_operations_windows.go
Expand Up @@ -163,7 +163,10 @@ func serviceDiscoveryOnDefaultNetwork() bool {
return true
}

func setupPathsAndSandboxOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
func buildSandboxOptionsOS(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error {
if cfg.Features["windows-no-dns-proxy"] {
*sboxOptions = append(*sboxOptions, libnetwork.OptionDNSNoProxy())
}
return nil
}

Expand Down
21 changes: 21 additions & 0 deletions integration/networking/resolvconf_test.go
@@ -1,10 +1,12 @@
package networking

import (
"context"
"net"
"os"
"strings"
"testing"
"time"

containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
Expand Down Expand Up @@ -189,3 +191,22 @@ func TestInternalNetworkDNS(t *testing.T) {
assert.Check(t, is.Equal(res.ExitCode, 0))
assert.Check(t, is.Contains(res.Stdout(), dnsRespAddr))
}

// TestNslookupWindows checks that nslookup gets results from external DNS.
// Regression test for https://github.com/moby/moby/issues/46792
func TestNslookupWindows(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "windows")

ctx := setupTest(t)
c := testEnv.APIClient()

attachCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
res := container.RunAttach(attachCtx, t, c,
container.WithCmd("nslookup", "docker.com"),
)
defer c.ContainerRemove(ctx, res.ContainerID, containertypes.RemoveOptions{Force: true})

assert.Check(t, is.Equal(res.ExitCode, 0))
assert.Check(t, is.Contains(res.Stdout.String(), "Addresses:"))
}
9 changes: 9 additions & 0 deletions libnetwork/endpoint.go
Expand Up @@ -11,6 +11,7 @@ import (
"sync"

"github.com/containerd/log"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/internal/sliceutil"
"github.com/docker/docker/libnetwork/datastore"
"github.com/docker/docker/libnetwork/ipamapi"
Expand Down Expand Up @@ -543,6 +544,10 @@ func (ep *Endpoint) sbJoin(sb *Sandbox, options ...EndpointOption) (err error) {
return err
}

if err = addEpToResolver(context.TODO(), n.Name(), ep.Name(), &sb.config, ep.iface, n.Resolvers()); err != nil {
return errdefs.System(err)
}

if err = n.getController().updateToStore(ep); err != nil {
return err
}
Expand Down Expand Up @@ -745,6 +750,10 @@ func (ep *Endpoint) sbLeave(sb *Sandbox, force bool) error {
log.G(context.TODO()).Warnf("Failed to clean up service info on container %s disconnect: %v", ep.name, err)
}

if err := deleteEpFromResolver(ep.Name(), ep.iface, n.Resolvers()); err != nil {
log.G(context.TODO()).Warnf("Failed to clean up resolver info on container %s disconnect: %v", ep.name, err)
}

if err := sb.clearNetworkResources(ep); err != nil {
log.G(context.TODO()).Warnf("Failed to clean up network resources on container %s disconnect: %v", ep.name, err)
}
Expand Down
13 changes: 8 additions & 5 deletions libnetwork/network.go
Expand Up @@ -192,7 +192,6 @@ type Network struct {
dbExists bool
persist bool
drvOnce *sync.Once
resolverOnce sync.Once //nolint:nolintlint,unused // only used on windows
resolver []*Resolver
internal bool
attachable bool
Expand All @@ -204,6 +203,7 @@ type Network struct {
configFrom string
loadBalancerIP net.IP
loadBalancerMode string
platformNetwork //nolint:nolintlint,unused // only populated on windows
mu sync.Mutex
}

Expand Down Expand Up @@ -244,6 +244,13 @@ func (n *Network) Type() string {
return n.networkType
}

func (n *Network) Resolvers() []*Resolver {
n.mu.Lock()
defer n.mu.Unlock()

return n.resolver
}

func (n *Network) Key() []string {
n.mu.Lock()
defer n.mu.Unlock()
Expand Down Expand Up @@ -2097,10 +2104,6 @@ func (n *Network) ResolveService(ctx context.Context, name string) ([]*net.SRV,
return srv, ip
}

func (n *Network) ExecFunc(f func()) error {
return types.NotImplementedErrorf("ExecFunc not supported by network")
}

func (n *Network) NdotsSet() bool {
return false
}
Expand Down
22 changes: 21 additions & 1 deletion libnetwork/network_unix.go
Expand Up @@ -2,13 +2,33 @@

package libnetwork

import "github.com/docker/docker/libnetwork/ipamapi"
import (
"context"

"github.com/docker/docker/libnetwork/ipamapi"
)

type platformNetwork struct{} //nolint:nolintlint,unused // only populated on windows

// Stub implementations for DNS related functions

func (n *Network) startResolver() {
}

func addEpToResolver(
ctx context.Context,
netName, epName string,
config *containerConfig,
epIface *EndpointInterface,
resolvers []*Resolver,
) error {
return nil
}

func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers []*Resolver) error {
return nil
}

func defaultIpamForNetworkType(networkType string) string {
return ipamapi.DefaultIPAM
}

0 comments on commit 2fbee5b

Please sign in to comment.