6c68be24a2
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>
201 lines
5.3 KiB
Go
201 lines
5.3 KiB
Go
package libnetwork
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"github.com/Microsoft/hcsshim"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
func TestAddEpToResolver(t *testing.T) {
|
|
const (
|
|
ep1v4 = "192.0.2.11"
|
|
ep2v4 = "192.0.2.12"
|
|
epFiveDNS = "192.0.2.13"
|
|
epNoIntDNS = "192.0.2.14"
|
|
ep1v6 = "2001:db8:aaaa::2"
|
|
gw1v4 = "192.0.2.1"
|
|
gw2v4 = "192.0.2.2"
|
|
gw1v6 = "2001:db8:aaaa::1"
|
|
dns1v4 = "198.51.100.1"
|
|
dns2v4 = "198.51.100.2"
|
|
dns3v4 = "198.51.100.3"
|
|
)
|
|
hnsEndpoints := map[string]hcsshim.HNSEndpoint{
|
|
ep1v4: {
|
|
IPAddress: net.ParseIP(ep1v4),
|
|
GatewayAddress: gw1v4,
|
|
DNSServerList: gw1v4 + "," + dns1v4,
|
|
EnableInternalDNS: true,
|
|
},
|
|
ep2v4: {
|
|
IPAddress: net.ParseIP(ep2v4),
|
|
GatewayAddress: gw1v4,
|
|
DNSServerList: gw1v4 + "," + dns2v4,
|
|
EnableInternalDNS: true,
|
|
},
|
|
epFiveDNS: {
|
|
IPAddress: net.ParseIP(epFiveDNS),
|
|
GatewayAddress: gw1v4,
|
|
DNSServerList: gw1v4 + "," + dns1v4 + "," + dns2v4 + "," + dns3v4 + ",198.51.100.4",
|
|
EnableInternalDNS: true,
|
|
},
|
|
epNoIntDNS: {
|
|
IPAddress: net.ParseIP(epNoIntDNS),
|
|
GatewayAddress: gw1v4,
|
|
DNSServerList: gw1v4 + "," + dns1v4,
|
|
//EnableInternalDNS: false,
|
|
},
|
|
ep1v6: {
|
|
IPv6Address: net.ParseIP(ep1v6),
|
|
GatewayAddressV6: gw1v6,
|
|
DNSServerList: gw1v6 + "," + dns1v4,
|
|
EnableInternalDNS: true,
|
|
},
|
|
}
|
|
|
|
makeIPNet := func(addr, netmask string) *net.IPNet {
|
|
t.Helper()
|
|
ip, ipnet, err := net.ParseCIDR(addr + "/" + netmask)
|
|
assert.NilError(t, err)
|
|
return &net.IPNet{IP: ip, Mask: ipnet.Mask}
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
epToAdd *EndpointInterface
|
|
hnsEndpoints []hcsshim.HNSEndpoint
|
|
resolverLAs []string
|
|
expIPToExtDNS map[netip.Addr][maxExtDNS]extDNSEntry
|
|
expResolverIdx int
|
|
}{
|
|
{
|
|
name: "ipv4",
|
|
epToAdd: &EndpointInterface{
|
|
addr: makeIPNet(ep1v4, "32"),
|
|
},
|
|
hnsEndpoints: []hcsshim.HNSEndpoint{
|
|
hnsEndpoints[ep1v4],
|
|
},
|
|
resolverLAs: []string{gw1v4},
|
|
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
|
|
netip.MustParseAddr(ep1v4): {{IPStr: dns1v4}},
|
|
},
|
|
},
|
|
{
|
|
name: "limit of three dns servers",
|
|
epToAdd: &EndpointInterface{
|
|
addr: makeIPNet(epFiveDNS, "32"),
|
|
},
|
|
hnsEndpoints: []hcsshim.HNSEndpoint{
|
|
hnsEndpoints[epFiveDNS],
|
|
},
|
|
resolverLAs: []string{gw1v4},
|
|
// Expect the internal resolver to keep the first three ext-servers.
|
|
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
|
|
netip.MustParseAddr(epFiveDNS): {
|
|
{IPStr: dns1v4},
|
|
{IPStr: dns2v4},
|
|
{IPStr: dns3v4},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "disabled internal resolver",
|
|
epToAdd: &EndpointInterface{
|
|
addr: makeIPNet(epNoIntDNS, "32"),
|
|
},
|
|
hnsEndpoints: []hcsshim.HNSEndpoint{
|
|
hnsEndpoints[epNoIntDNS],
|
|
hnsEndpoints[ep2v4],
|
|
},
|
|
resolverLAs: []string{gw1v4},
|
|
},
|
|
{
|
|
name: "missing internal resolver",
|
|
epToAdd: &EndpointInterface{
|
|
addr: makeIPNet(ep1v4, "32"),
|
|
},
|
|
hnsEndpoints: []hcsshim.HNSEndpoint{
|
|
hnsEndpoints[ep1v4],
|
|
},
|
|
// The only resolver is for the gateway on a different network.
|
|
resolverLAs: []string{gw2v4},
|
|
},
|
|
{
|
|
name: "multiple resolvers and endpoints",
|
|
epToAdd: &EndpointInterface{
|
|
addr: makeIPNet(ep2v4, "32"),
|
|
},
|
|
hnsEndpoints: []hcsshim.HNSEndpoint{
|
|
hnsEndpoints[ep1v4],
|
|
hnsEndpoints[ep2v4],
|
|
},
|
|
// Put the internal resolver for this network second in the list.
|
|
expResolverIdx: 1,
|
|
resolverLAs: []string{gw2v4, gw1v4},
|
|
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
|
|
netip.MustParseAddr(ep2v4): {{IPStr: dns2v4}},
|
|
},
|
|
},
|
|
{
|
|
name: "ipv6",
|
|
epToAdd: &EndpointInterface{
|
|
addrv6: makeIPNet(ep1v6, "80"),
|
|
},
|
|
hnsEndpoints: []hcsshim.HNSEndpoint{
|
|
hnsEndpoints[ep1v6],
|
|
},
|
|
resolverLAs: []string{gw1v6},
|
|
expIPToExtDNS: map[netip.Addr][maxExtDNS]extDNSEntry{
|
|
netip.MustParseAddr(ep1v6): {{IPStr: dns1v4}},
|
|
},
|
|
},
|
|
}
|
|
|
|
eMapCmpOpts := []cmp.Option{
|
|
cmpopts.EquateEmpty(),
|
|
cmpopts.EquateComparable(netip.Addr{}),
|
|
cmpopts.IgnoreUnexported(extDNSEntry{}),
|
|
}
|
|
emptyEMap := map[netip.Addr][maxExtDNS]extDNSEntry{}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Set up resolvers with the required listen-addresses.
|
|
var resolvers []*Resolver
|
|
for _, la := range tc.resolverLAs {
|
|
resolvers = append(resolvers, NewResolver(la, true, nil))
|
|
}
|
|
|
|
// Add the endpoint and check expected results.
|
|
err := addEpToResolverImpl(context.TODO(),
|
|
"netname", "epname", tc.epToAdd, resolvers, tc.hnsEndpoints)
|
|
assert.Check(t, err)
|
|
for i, resolver := range resolvers {
|
|
if i == tc.expResolverIdx {
|
|
assert.Check(t, is.DeepEqual(resolver.ipToExtDNS.eMap, tc.expIPToExtDNS,
|
|
eMapCmpOpts...), fmt.Sprintf("resolveridx=%d", i))
|
|
} else {
|
|
assert.Check(t, is.DeepEqual(resolver.ipToExtDNS.eMap, emptyEMap,
|
|
eMapCmpOpts...), fmt.Sprintf("resolveridx=%d", i))
|
|
}
|
|
}
|
|
|
|
// Delete the endpoint, check nothing got left behind.
|
|
err = deleteEpFromResolverImpl("epname", tc.epToAdd, resolvers, tc.hnsEndpoints)
|
|
assert.Check(t, err)
|
|
for i, resolver := range resolvers {
|
|
assert.Check(t, is.DeepEqual(resolver.ipToExtDNS.eMap, emptyEMap,
|
|
eMapCmpOpts...), fmt.Sprintf("resolveridx=%d", i))
|
|
}
|
|
})
|
|
}
|
|
}
|