From 3ae6f898cfdb58fd0e30937147dd6ce28c6808dd Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 30 Nov 2021 15:19:18 -0800 Subject: [PATCH] ipn/ipnlocal, net/dns/resolver: use exit node's DoH proxy when available Updates #1713 Change-Id: I3695a40ec12d2b4e6dac41cf4559daca6dddd68e Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/local.go | 53 +++++++++++++++++++++++++++-------- net/dns/resolver/forwarder.go | 6 ++-- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 605b9f58c..35cc73123 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1974,12 +1974,32 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log return dcfg } + for _, dom := range nm.DNS.Domains { + fqdn, err := dnsname.ToFQDN(dom) + if err != nil { + logf("[unexpected] non-FQDN search domain %q", dom) + } + dcfg.SearchDomains = append(dcfg.SearchDomains, fqdn) + } + if nm.DNS.Proxied { // actually means "enable MagicDNS" + for _, dom := range magicDNSRootDomains(nm) { + dcfg.Routes[dom] = nil // resolve internally with dcfg.Hosts + } + } + addDefault := func(resolvers []dnstype.Resolver) { for _, r := range resolvers { dcfg.DefaultResolvers = append(dcfg.DefaultResolvers, normalizeResolver(r)) } } + // If we're using an exit node and that exit node is new enough (1.19.x+) + // to run a DoH DNS proxy, then send all our DNS traffic through it. + if dohURL, ok := exitNodeCanProxyDNS(nm, prefs.ExitNodeID); ok { + addDefault([]dnstype.Resolver{{Addr: dohURL}}) + return dcfg + } + addDefault(nm.DNS.Resolvers) for suffix, resolvers := range nm.DNS.Routes { fqdn, err := dnsname.ToFQDN(suffix) @@ -2001,18 +2021,6 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs *ipn.Prefs, logf logger.Log dcfg.Routes[fqdn] = append(dcfg.Routes[fqdn], normalizeResolver(r)) } } - for _, dom := range nm.DNS.Domains { - fqdn, err := dnsname.ToFQDN(dom) - if err != nil { - logf("[unexpected] non-FQDN search domain %q", dom) - } - dcfg.SearchDomains = append(dcfg.SearchDomains, fqdn) - } - if nm.DNS.Proxied { // actually means "enable MagicDNS" - for _, dom := range magicDNSRootDomains(nm) { - dcfg.Routes[dom] = nil // resolve internally with dcfg.Hosts - } - } // Set FallbackResolvers as the default resolvers in the // scenarios that can't handle a purely split-DNS config. See @@ -3098,3 +3106,24 @@ func (b *LocalBackend) allowExitNodeDNSProxyToServeName(name string) bool { } return true } + +// exitNodeCanProxyDNS reports the DoH base URL ("http://foo/dns-query") without query parameters +// to exitNodeID's DoH service, if available. +// +// If exitNodeID is the zero valid, it returns "", false. +func exitNodeCanProxyDNS(nm *netmap.NetworkMap, exitNodeID tailcfg.StableNodeID) (dohURL string, ok bool) { + if exitNodeID.IsZero() { + return "", false + } + for _, p := range nm.Peers { + if p.StableID != exitNodeID { + continue + } + for _, s := range p.Hostinfo.Services { + if s.Proto == tailcfg.PeerAPIDNS && s.Port >= 1 { + return peerAPIBase(nm, p) + "/dns-query", true + } + } + } + return "", false +} diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 0e8cbca9a..65bd1ce82 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -423,8 +423,10 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client, // send expects the reply to have the same txid as txidOut. func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) ([]byte, error) { if strings.HasPrefix(rr.name.Addr, "http://") { - metricDNSFwdErrorType.Add(1) - return nil, fmt.Errorf("http:// resolvers not supported yet") + // TODO(bradfitz): this only work for TUN mode right now; plumb a universal dialer + // that can handle the dozen special cases for modes/platforms/routes. + TODOHTTPClient := http.DefaultClient + return f.sendDoH(ctx, rr.name.Addr, TODOHTTPClient, fq.packet) } if strings.HasPrefix(rr.name.Addr, "https://") { metricDNSFwdErrorType.Add(1)