ipn/ipnlocal, net/dns*, util/cloudenv: add AWS DNS support

And remove the GCP special-casing from ipn/ipnlocal; do it only in the
forwarder for *.internal.

Fixes #4980
Fixes #4981

Change-Id: I5c481e96d91f3d51d274a80fbd37c38f16dfa5cb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2022-06-29 19:32:41 -07:00
committed by Brad Fitzpatrick
parent 88c2afd1e3
commit aa37aece9c
7 changed files with 103 additions and 82 deletions

View File

@@ -198,6 +198,16 @@ type forwarder struct {
// routes are per-suffix resolvers to use, with
// the most specific routes first.
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() {
@@ -297,18 +307,52 @@ func resolversWithDelays(resolvers []*dnstype.Resolver) []resolverAndDelay {
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
// Resolver.SetConfig on reconfig.
//
// The memory referenced by routesBySuffix should not be modified.
func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]*dnstype.Resolver) {
routes := make([]route, 0, len(routesBySuffix))
cloudHostFallback := cloudResolvers()
for suffix, rs := range routesBySuffix {
routes = append(routes, route{
Suffix: suffix,
Resolvers: resolversWithDelays(rs),
})
if suffix == "." && len(rs) == 0 && len(cloudHostFallback) > 0 {
routes = append(routes, route{
Suffix: suffix,
Resolvers: cloudHostFallback,
})
} else {
routes = append(routes, route{
Suffix: suffix,
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.Slice(routes, func(i, j int) bool {
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()
defer f.mu.Unlock()
f.routes = routes
f.cloudHostFallback = cloudHostFallback
}
var stdNetPacketListener packetListener = new(net.ListenConfig)
@@ -561,38 +606,18 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
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.
func (f *forwarder) resolvers(domain dnsname.FQDN) []resolverAndDelay {
f.mu.Lock()
routes := f.routes
cloudHostFallback := f.cloudHostFallback
f.mu.Unlock()
var ret []resolverAndDelay
var matchedSuffix dnsname.FQDN
for _, route := range routes {
if route.Suffix == "." || route.Suffix.Contains(domain) {
ret = route.Resolvers
matchedSuffix = route.Suffix
break
return route.Resolvers
}
}
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

View File

@@ -31,6 +31,7 @@ import (
"tailscale.com/types/dnstype"
"tailscale.com/types/logger"
"tailscale.com/util/clientmetric"
"tailscale.com/util/cloudenv"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine/monitor"
)
@@ -97,6 +98,9 @@ func (c *Config) WriteToBufioWriter(w *bufio.Writer) {
if arpa > 0 {
fmt.Fprintf(w, "+%darpa", arpa)
}
if c := cloudenv.Get(); c != "" {
fmt.Fprintf(w, ", cloud=%q", string(c))
}
w.WriteString("}")
}