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>
251 lines
7.5 KiB
Go
251 lines
7.5 KiB
Go
//go:build windows
|
|
|
|
package libnetwork
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Microsoft/hcsshim"
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/libnetwork/drivers/windows"
|
|
"github.com/docker/docker/libnetwork/ipamapi"
|
|
"github.com/docker/docker/libnetwork/ipams/windowsipam"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type platformNetwork struct {
|
|
resolverOnce sync.Once
|
|
dnsCompartment uint32
|
|
}
|
|
|
|
func executeInCompartment(compartmentID uint32, x func()) {
|
|
runtime.LockOSThread()
|
|
|
|
if err := hcsshim.SetCurrentThreadCompartmentId(compartmentID); err != nil {
|
|
log.G(context.TODO()).Error(err)
|
|
}
|
|
defer func() {
|
|
hcsshim.SetCurrentThreadCompartmentId(0)
|
|
runtime.UnlockOSThread()
|
|
}()
|
|
|
|
x()
|
|
}
|
|
|
|
func (n *Network) ExecFunc(f func()) error {
|
|
executeInCompartment(n.dnsCompartment, f)
|
|
return nil
|
|
}
|
|
|
|
func (n *Network) startResolver() {
|
|
if n.networkType == "ics" {
|
|
return
|
|
}
|
|
n.resolverOnce.Do(func() {
|
|
log.G(context.TODO()).Debugf("Launching DNS server for network %q", n.Name())
|
|
hnsid := n.DriverOptions()[windows.HNSID]
|
|
if hnsid == "" {
|
|
return
|
|
}
|
|
|
|
hnsresponse, err := hcsshim.HNSNetworkRequest("GET", hnsid, "")
|
|
if err != nil {
|
|
log.G(context.TODO()).Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
|
|
return
|
|
}
|
|
|
|
for _, subnet := range hnsresponse.Subnets {
|
|
if subnet.GatewayAddress != "" {
|
|
for i := 0; i < 3; i++ {
|
|
resolver := NewResolver(subnet.GatewayAddress, true, n)
|
|
log.G(context.TODO()).Debugf("Binding a resolver on network %s gateway %s", n.Name(), subnet.GatewayAddress)
|
|
n.dnsCompartment = hnsresponse.DNSServerCompartment
|
|
n.ExecFunc(resolver.SetupFunc(53))
|
|
|
|
if err = resolver.Start(); err != nil {
|
|
log.G(context.TODO()).Errorf("Resolver Setup/Start failed for container %s, %q", n.Name(), err)
|
|
time.Sleep(1 * time.Second)
|
|
} else {
|
|
log.G(context.TODO()).Debugf("Resolver bound successfully for network %s", n.Name())
|
|
n.resolver = append(n.resolver, resolver)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// addEpToResolver configures the internal DNS resolver for an endpoint.
|
|
//
|
|
// Windows resolvers don't consistently fall back to a secondary server if they
|
|
// get a SERVFAIL from our resolver. So, our resolver needs to forward the query
|
|
// upstream.
|
|
//
|
|
// To retrieve the list of DNS Servers to use for requests originating from an
|
|
// endpoint, this method finds the HNSEndpoint represented by the endpoint. If
|
|
// HNSEndpoint's list of DNS servers includes the HNSEndpoint's gateway address,
|
|
// it's the Resolver running at that address. Other DNS servers in the
|
|
// list have either come from config ('--dns') or have been set up by HNS as
|
|
// external resolvers, these are the external servers the Resolver should
|
|
// use for DNS requests from that endpoint.
|
|
func addEpToResolver(
|
|
ctx context.Context,
|
|
netName, epName string,
|
|
config *containerConfig,
|
|
epIface *EndpointInterface,
|
|
resolvers []*Resolver,
|
|
) error {
|
|
if config.dnsNoProxy {
|
|
return nil
|
|
}
|
|
hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return addEpToResolverImpl(ctx, netName, epName, epIface, resolvers, hnsEndpoints)
|
|
}
|
|
|
|
func addEpToResolverImpl(
|
|
ctx context.Context,
|
|
netName, epName string,
|
|
epIface *EndpointInterface,
|
|
resolvers []*Resolver,
|
|
hnsEndpoints []hcsshim.HNSEndpoint,
|
|
) error {
|
|
// Find the HNSEndpoint represented by ep, matching on endpoint address.
|
|
hnsEp := findHNSEp(epIface.addr, epIface.addrv6, hnsEndpoints)
|
|
if hnsEp == nil || !hnsEp.EnableInternalDNS {
|
|
return nil
|
|
}
|
|
|
|
// Find the resolver for that HNSEndpoint, matching on gateway address.
|
|
resolver := findResolver(resolvers, hnsEp.GatewayAddress, hnsEp.GatewayAddressV6)
|
|
if resolver == nil {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"network": netName,
|
|
"endpoint": epName,
|
|
}).Debug("No internal DNS resolver to configure")
|
|
return nil
|
|
}
|
|
|
|
// Get the list of DNS servers HNS has set up for this Endpoint.
|
|
var dnsList []extDNSEntry
|
|
dnsServers := strings.Split(hnsEp.DNSServerList, ",")
|
|
|
|
// Create an extDNSEntry for each DNS server, apart from 'resolver' itself.
|
|
var foundSelf bool
|
|
hnsGw4, _ := netip.ParseAddr(hnsEp.GatewayAddress)
|
|
hnsGw6, _ := netip.ParseAddr(hnsEp.GatewayAddressV6)
|
|
for _, dnsServer := range dnsServers {
|
|
dnsAddr, _ := netip.ParseAddr(dnsServer)
|
|
if dnsAddr.IsValid() && (dnsAddr == hnsGw4 || dnsAddr == hnsGw6) {
|
|
foundSelf = true
|
|
} else {
|
|
dnsList = append(dnsList, extDNSEntry{IPStr: dnsServer})
|
|
}
|
|
}
|
|
if !foundSelf {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"network": netName,
|
|
"endpoint": epName,
|
|
}).Debug("Endpoint is not configured to use internal DNS resolver")
|
|
return nil
|
|
}
|
|
|
|
// If the internal resolver is configured as one of this endpoint's DNS servers,
|
|
// tell it which ext servers to use for requests from this endpoint's addresses.
|
|
log.G(ctx).Infof("External DNS servers for '%s': %v", epName, dnsList)
|
|
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPAddress); ok {
|
|
if err := resolver.SetExtServersForSrc(srcAddr.Unmap(), dnsList); err != nil {
|
|
return errors.Wrapf(err, "failed to set external DNS servers for %s address %s",
|
|
epName, hnsEp.IPAddress)
|
|
}
|
|
}
|
|
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPv6Address); ok {
|
|
if err := resolver.SetExtServersForSrc(srcAddr, dnsList); err != nil {
|
|
return errors.Wrapf(err, "failed to set external DNS servers for %s address %s",
|
|
epName, hnsEp.IPv6Address)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func deleteEpFromResolver(epName string, epIface *EndpointInterface, resolvers []*Resolver) error {
|
|
hnsEndpoints, err := hcsshim.HNSListEndpointRequest()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return deleteEpFromResolverImpl(epName, epIface, resolvers, hnsEndpoints)
|
|
}
|
|
|
|
func deleteEpFromResolverImpl(
|
|
epName string,
|
|
epIface *EndpointInterface,
|
|
resolvers []*Resolver,
|
|
hnsEndpoints []hcsshim.HNSEndpoint,
|
|
) error {
|
|
// Find the HNSEndpoint represented by ep, matching on endpoint address.
|
|
hnsEp := findHNSEp(epIface.addr, epIface.addrv6, hnsEndpoints)
|
|
if hnsEp == nil {
|
|
return fmt.Errorf("no HNS endpoint for %s", epName)
|
|
}
|
|
|
|
// Find the resolver for that HNSEndpoint, matching on gateway address.
|
|
resolver := findResolver(resolvers, hnsEp.GatewayAddress, hnsEp.GatewayAddressV6)
|
|
if resolver == nil {
|
|
return nil
|
|
}
|
|
|
|
// Delete external DNS servers for the endpoint's IP addresses.
|
|
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPAddress); ok {
|
|
if err := resolver.SetExtServersForSrc(srcAddr.Unmap(), nil); err != nil {
|
|
return errors.Wrapf(err, "failed to delete external DNS servers for %s address %s",
|
|
epName, hnsEp.IPv6Address)
|
|
}
|
|
}
|
|
if srcAddr, ok := netip.AddrFromSlice(hnsEp.IPv6Address); ok {
|
|
if err := resolver.SetExtServersForSrc(srcAddr, nil); err != nil {
|
|
return errors.Wrapf(err, "failed to delete external DNS servers for %s address %s",
|
|
epName, hnsEp.IPv6Address)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func findHNSEp(ip4, ip6 *net.IPNet, hnsEndpoints []hcsshim.HNSEndpoint) *hcsshim.HNSEndpoint {
|
|
for _, hnsEp := range hnsEndpoints {
|
|
if (hnsEp.IPAddress != nil && hnsEp.IPAddress.Equal(ip4.IP)) ||
|
|
(hnsEp.IPv6Address != nil && hnsEp.IPv6Address.Equal(ip6.IP)) {
|
|
return &hnsEp
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func findResolver(resolvers []*Resolver, gw4, gw6 string) *Resolver {
|
|
gw4addr, _ := netip.ParseAddr(gw4)
|
|
gw6addr, _ := netip.ParseAddr(gw6)
|
|
for _, resolver := range resolvers {
|
|
ns := resolver.NameServer()
|
|
if ns.IsValid() && (ns == gw4addr || ns == gw6addr) {
|
|
return resolver
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func defaultIpamForNetworkType(networkType string) string {
|
|
if windows.IsBuiltinLocalDriver(networkType) {
|
|
return windowsipam.DefaultIPAM
|
|
}
|
|
return ipamapi.DefaultIPAM
|
|
}
|