@ -198,6 +198,16 @@ type forwarder struct {
// routes are per-suffix resolvers to use, with
// routes are per-suffix resolvers to use, with
// the most specific routes first.
// the most specific routes first.
routes [ ] route
routes [ ] route
// cloudHostFallback are last resort resolvers to use if no per-suffix
// resolver matches. These are only populated on cloud hosts where the
// platform provides a well-known recursive resolver.
//
// That is, if we're running on GCP or AWS where there's always a well-known
// IP of a recursive resolver, return that rather than having callers return
// errNoUpstreams. This fixes both normal 100.100.100.100 resolution when
// /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS stub
// resolver lookup.
cloudHostFallback [ ] resolverAndDelay
}
}
func init ( ) {
func init ( ) {
@ -297,18 +307,52 @@ func resolversWithDelays(resolvers []*dnstype.Resolver) []resolverAndDelay {
return rr
return rr
}
}
var (
cloudResolversOnce sync . Once
cloudResolversLazy [ ] resolverAndDelay
)
func cloudResolvers ( ) [ ] resolverAndDelay {
cloudResolversOnce . Do ( func ( ) {
if ip := cloudenv . Get ( ) . ResolverIP ( ) ; ip != "" {
cloudResolver := [ ] * dnstype . Resolver { { Addr : ip } }
cloudResolversLazy = resolversWithDelays ( cloudResolver )
}
} )
return cloudResolversLazy
}
// setRoutes sets the routes to use for DNS forwarding. It's called by
// setRoutes sets the routes to use for DNS forwarding. It's called by
// Resolver.SetConfig on reconfig.
// Resolver.SetConfig on reconfig.
//
//
// The memory referenced by routesBySuffix should not be modified.
// The memory referenced by routesBySuffix should not be modified.
func ( f * forwarder ) setRoutes ( routesBySuffix map [ dnsname . FQDN ] [ ] * dnstype . Resolver ) {
func ( f * forwarder ) setRoutes ( routesBySuffix map [ dnsname . FQDN ] [ ] * dnstype . Resolver ) {
routes := make ( [ ] route , 0 , len ( routesBySuffix ) )
routes := make ( [ ] route , 0 , len ( routesBySuffix ) )
cloudHostFallback := cloudResolvers ( )
for suffix , rs := range routesBySuffix {
for suffix , rs := range routesBySuffix {
if suffix == "." && len ( rs ) == 0 && len ( cloudHostFallback ) > 0 {
routes = append ( routes , route {
Suffix : suffix ,
Resolvers : cloudHostFallback ,
} )
} else {
routes = append ( routes , route {
routes = append ( routes , route {
Suffix : suffix ,
Suffix : suffix ,
Resolvers : resolversWithDelays ( rs ) ,
Resolvers : resolversWithDelays ( rs ) ,
} )
} )
}
}
}
if cloudenv . Get ( ) . HasInternalTLD ( ) && len ( cloudHostFallback ) > 0 {
if _ , ok := routesBySuffix [ "internal." ] ; ! ok {
routes = append ( routes , route {
Suffix : "internal." ,
Resolvers : cloudHostFallback ,
} )
}
}
// Sort from longest prefix to shortest.
// Sort from longest prefix to shortest.
sort . Slice ( routes , func ( i , j int ) bool {
sort . Slice ( routes , func ( i , j int ) bool {
return routes [ i ] . Suffix . NumLabels ( ) > routes [ j ] . Suffix . NumLabels ( )
return routes [ i ] . Suffix . NumLabels ( ) > routes [ j ] . Suffix . NumLabels ( )
@ -317,6 +361,7 @@ func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolve
f . mu . Lock ( )
f . mu . Lock ( )
defer f . mu . Unlock ( )
defer f . mu . Unlock ( )
f . routes = routes
f . routes = routes
f . cloudHostFallback = cloudHostFallback
}
}
var stdNetPacketListener packetListener = new ( net . ListenConfig )
var stdNetPacketListener packetListener = new ( net . ListenConfig )
@ -561,38 +606,18 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
return out , nil
return out , nil
}
}
// gcpResolverFallback is the fallback resolver for Google Cloud.
var gcpResolverFallback = [ ] resolverAndDelay { { name : & dnstype . Resolver { Addr : cloudenv . GoogleMetadataAndDNSIP } } }
// resolvers returns the resolvers to use for domain.
// resolvers returns the resolvers to use for domain.
func ( f * forwarder ) resolvers ( domain dnsname . FQDN ) [ ] resolverAndDelay {
func ( f * forwarder ) resolvers ( domain dnsname . FQDN ) [ ] resolverAndDelay {
f . mu . Lock ( )
f . mu . Lock ( )
routes := f . routes
routes := f . routes
cloudHostFallback := f . cloudHostFallback
f . mu . Unlock ( )
f . mu . Unlock ( )
var ret [ ] resolverAndDelay
var matchedSuffix dnsname . FQDN
for _ , route := range routes {
for _ , route := range routes {
if route . Suffix == "." || route . Suffix . Contains ( domain ) {
if route . Suffix == "." || route . Suffix . Contains ( domain ) {
ret = route . Resolvers
return route . Resolvers
matchedSuffix = route . Suffix
break
}
}
}
if len ( ret ) == 0 && cloudenv . Get ( ) == cloudenv . GCP && ( matchedSuffix == "" || matchedSuffix == "." ) {
// If we're running on GCP where there's always a well-known IP of a
// recursive resolver, return that rather than having callers return
// errNoUpstreams. This fixes both normal 100.100.100.100 resolution
// when /etc/resolv.conf is missing/corrupt, and the peerapi ExitDNS
// stub resolver lookup.
//
// But we only do this if no route matched (matchedSuffix == "") or
// if we had no resolvers for the top-level route (matchedSuffix == ".").
// If they had an explicit empty route that we matched, don't do the auto
// fallback in that case.
ret = gcpResolverFallback
}
}
return ret
return cloudHostFallback // or nil if no fallback
}
}
// forwardQuery is information and state about a forwarded DNS query that's
// forwardQuery is information and state about a forwarded DNS query that's