diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index e4d4e8475..411861d37 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -204,6 +204,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/logtail/filch from tailscale.com/logpolicy 💣 tailscale.com/metrics from tailscale.com/derp+ tailscale.com/net/dns from tailscale.com/cmd/tailscaled+ + tailscale.com/net/dns/publicdns from tailscale.com/net/dns/resolver tailscale.com/net/dns/resolvconffile from tailscale.com/net/dns+ tailscale.com/net/dns/resolver from tailscale.com/ipn/ipnlocal+ tailscale.com/net/dnscache from tailscale.com/control/controlclient+ diff --git a/net/dns/publicdns/publicdns.go b/net/dns/publicdns/publicdns.go new file mode 100644 index 000000000..4164e1801 --- /dev/null +++ b/net/dns/publicdns/publicdns.go @@ -0,0 +1,98 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package publicdns contains mapping and helpers for working with +// public DNS providers. +package publicdns + +import ( + "sync" + + "inet.af/netaddr" +) + +var knownDoH = map[netaddr.IP]string{} // 8.8.8.8 => "https://..." +var dohIPsOfBase = map[string][]netaddr.IP{} +var populateOnce sync.Once + +// KnownDoH returns a map of well-known public DNS IPs to their DoH URL. +// The returned map should not be modified. +func KnownDoH() map[netaddr.IP]string { + populateOnce.Do(populate) + return knownDoH +} + +// DoHIPsOfBase returns a map of DNS server IP addresses keyed +// by their DoH URL. It is the inverse of KnownDoH. +func DoHIPsOfBase() map[string][]netaddr.IP { + populateOnce.Do(populate) + return dohIPsOfBase +} + +// DoHV6 returns the first IPv6 DNS address from a given public DNS provider +// if found, along with a boolean indicating success. +func DoHV6(base string) (ip netaddr.IP, ok bool) { + for _, ip := range dohIPsOfBase[base] { + if ip.Is6() { + return ip, true + } + } + return ip, false +} + +// addDoH parses a given well-formed ip string into a netaddr.IP type and +// adds it to both knownDoH and dohIPsOFBase maps. +func addDoH(ipStr, base string) { + ip := netaddr.MustParseIP(ipStr) + knownDoH[ip] = base + dohIPsOfBase[base] = append(dohIPsOfBase[base], ip) +} + +// populate is called once to initialize the knownDoH and dohIPsOfBase maps. +func populate() { + // Cloudflare + addDoH("1.1.1.1", "https://cloudflare-dns.com/dns-query") + addDoH("1.0.0.1", "https://cloudflare-dns.com/dns-query") + addDoH("2606:4700:4700::1111", "https://cloudflare-dns.com/dns-query") + addDoH("2606:4700:4700::1001", "https://cloudflare-dns.com/dns-query") + + // Cloudflare -Malware + addDoH("1.1.1.2", "https://security.cloudflare-dns.com/dns-query") + addDoH("1.0.0.2", "https://security.cloudflare-dns.com/dns-query") + addDoH("2606:4700:4700::1112", "https://security.cloudflare-dns.com/dns-query") + addDoH("2606:4700:4700::1002", "https://security.cloudflare-dns.com/dns-query") + + // Cloudflare -Malware -Adult + addDoH("1.1.1.3", "https://family.cloudflare-dns.com/dns-query") + addDoH("1.0.0.3", "https://family.cloudflare-dns.com/dns-query") + addDoH("2606:4700:4700::1113", "https://family.cloudflare-dns.com/dns-query") + addDoH("2606:4700:4700::1003", "https://family.cloudflare-dns.com/dns-query") + + // Google + addDoH("8.8.8.8", "https://dns.google/dns-query") + addDoH("8.8.4.4", "https://dns.google/dns-query") + addDoH("2001:4860:4860::8888", "https://dns.google/dns-query") + addDoH("2001:4860:4860::8844", "https://dns.google/dns-query") + + // OpenDNS + // TODO(bradfitz): OpenDNS is unique amongst this current set in that + // its DoH DNS names resolve to different IPs than its normal DNS + // IPs. Support that later. For now we assume that they're the same. + // addDoH("208.67.222.222", "https://doh.opendns.com/dns-query") + // addDoH("208.67.220.220", "https://doh.opendns.com/dns-query") + // addDoH("208.67.222.123", "https://doh.familyshield.opendns.com/dns-query") + // addDoH("208.67.220.123", "https://doh.familyshield.opendns.com/dns-query") + + // Quad9 + addDoH("9.9.9.9", "https://dns.quad9.net/dns-query") + addDoH("149.112.112.112", "https://dns.quad9.net/dns-query") + addDoH("2620:fe::fe", "https://dns.quad9.net/dns-query") + addDoH("2620:fe::fe:9", "https://dns.quad9.net/dns-query") + + // Quad9 -DNSSEC + addDoH("9.9.9.10", "https://dns10.quad9.net/dns-query") + addDoH("149.112.112.10", "https://dns10.quad9.net/dns-query") + addDoH("2620:fe::10", "https://dns10.quad9.net/dns-query") + addDoH("2620:fe::fe:10", "https://dns10.quad9.net/dns-query") +} diff --git a/net/dns/publicdns/publicdns_test.go b/net/dns/publicdns/publicdns_test.go new file mode 100644 index 000000000..af07da55c --- /dev/null +++ b/net/dns/publicdns/publicdns_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package publicdns + +import ( + "testing" + + "inet.af/netaddr" +) + +func TestInit(t *testing.T) { + for baseKey, baseSet := range DoHIPsOfBase() { + for _, addr := range baseSet { + if KnownDoH()[addr] != baseKey { + t.Errorf("Expected %v to map to %s, got %s", addr, baseKey, KnownDoH()[addr]) + } + } + } +} + +func TestDohV6(t *testing.T) { + tests := []struct { + in string + firstIP netaddr.IP + want bool + }{ + {"https://cloudflare-dns.com/dns-query", netaddr.MustParseIP("2606:4700:4700::1111"), true}, + {"https://dns.google/dns-query", netaddr.MustParseIP("2001:4860:4860::8888"), true}, + {"bogus", netaddr.IP{}, false}, + } + for _, test := range tests { + t.Run(test.in, func(t *testing.T) { + ip, ok := DoHV6(test.in) + if ok != test.want || ip != test.firstIP { + t.Errorf("DohV6 got (%v: IPv6 %v) for %v, want (%v: IPv6 %v)", ip, ok, test.in, test.firstIP, test.want) + } + }) + } +} diff --git a/net/dns/resolver/doh_test.go b/net/dns/resolver/doh_test.go index 492602b7c..aed624507 100644 --- a/net/dns/resolver/doh_test.go +++ b/net/dns/resolver/doh_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -11,6 +11,7 @@ import ( "testing" "golang.org/x/net/dns/dnsmessage" + "tailscale.com/net/dns/publicdns" ) var testDoH = flag.Bool("test-doh", false, "do real DoH tests against the network") @@ -40,7 +41,7 @@ func TestDoH(t *testing.T) { if !*testDoH { t.Skip("skipping manual test without --test-doh flag") } - if len(knownDoH) == 0 { + if len(publicdns.KnownDoH()) == 0 { t.Fatal("no known DoH") } @@ -48,7 +49,7 @@ func TestDoH(t *testing.T) { dohSem: make(chan struct{}, 10), } - for ip := range knownDoH { + for ip := range publicdns.KnownDoH() { t.Run(ip.String(), func(t *testing.T) { urlBase, c, ok := f.getKnownDoHClient(ip) if !ok { @@ -85,9 +86,9 @@ func TestDoH(t *testing.T) { } func TestDoHV6Fallback(t *testing.T) { - for ip, base := range knownDoH { + for ip, base := range publicdns.KnownDoH() { if ip.Is4() { - ip6, ok := dohV6(base) + ip6, ok := publicdns.DoHV6(base) if !ok { t.Errorf("no v6 DoH known for %v", ip) } else if !ip6.Is6() { diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index f874d3786..310293554 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -25,6 +25,7 @@ import ( dns "golang.org/x/net/dns/dnsmessage" "inet.af/netaddr" "tailscale.com/hostinfo" + "tailscale.com/net/dns/publicdns" "tailscale.com/net/neterror" "tailscale.com/net/netns" "tailscale.com/net/tsdial" @@ -242,7 +243,7 @@ func resolversWithDelays(resolvers []dnstype.Resolver) []resolverAndDelay { rr := make([]resolverAndDelay, len(resolvers)) for _, r := range resolvers { if ip, err := netaddr.ParseIP(r.Addr); err == nil { - if host, ok := knownDoH[ip]; ok { + if host, ok := publicdns.KnownDoH()[ip]; ok { total[hostAndFam{host, ip.BitLen()}]++ } } @@ -252,7 +253,7 @@ func resolversWithDelays(resolvers []dnstype.Resolver) []resolverAndDelay { for i, r := range resolvers { var startDelay time.Duration if ip, err := netaddr.ParseIP(r.Addr); err == nil { - if host, ok := knownDoH[ip]; ok { + if host, ok := publicdns.KnownDoH()[ip]; ok { key4 := hostAndFam{host, 32} key6 := hostAndFam{host, 128} switch { @@ -332,7 +333,7 @@ func (f *forwarder) packetListener(ip netaddr.IP) (packetListener, error) { } func (f *forwarder) getKnownDoHClient(ip netaddr.IP) (urlBase string, c *http.Client, ok bool) { - urlBase, ok = knownDoH[ip] + urlBase, ok = publicdns.KnownDoH()[ip] if !ok { return } @@ -356,7 +357,7 @@ func (f *forwarder) getKnownDoHClient(ip netaddr.IP) (urlBase string, c *http.Cl c, err := nsDialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), "443")) // If v4 failed, try an equivalent v6 also in the time remaining. if err != nil && ctx.Err() == nil { - if ip6, ok := dohV6(urlBase); ok && ip.Is4() { + if ip6, ok := publicdns.DoHV6(urlBase); ok && ip.Is4() { if c6, err := nsDialer.DialContext(ctx, "tcp", net.JoinHostPort(ip6.String(), "443")); err == nil { return c6, nil } @@ -776,69 +777,3 @@ func (p *closePool) Close() error { } return nil } - -var knownDoH = map[netaddr.IP]string{} // 8.8.8.8 => "https://..." - -var dohIPsOfBase = map[string][]netaddr.IP{} - -func addDoH(ipStr, base string) { - ip := netaddr.MustParseIP(ipStr) - knownDoH[ip] = base - dohIPsOfBase[base] = append(dohIPsOfBase[base], ip) -} - -func dohV6(base string) (ip netaddr.IP, ok bool) { - for _, ip := range dohIPsOfBase[base] { - if ip.Is6() { - return ip, true - } - } - return ip, false -} - -func init() { - // Cloudflare - addDoH("1.1.1.1", "https://cloudflare-dns.com/dns-query") - addDoH("1.0.0.1", "https://cloudflare-dns.com/dns-query") - addDoH("2606:4700:4700::1111", "https://cloudflare-dns.com/dns-query") - addDoH("2606:4700:4700::1001", "https://cloudflare-dns.com/dns-query") - - // Cloudflare -Malware - addDoH("1.1.1.2", "https://security.cloudflare-dns.com/dns-query") - addDoH("1.0.0.2", "https://security.cloudflare-dns.com/dns-query") - addDoH("2606:4700:4700::1112", "https://security.cloudflare-dns.com/dns-query") - addDoH("2606:4700:4700::1002", "https://security.cloudflare-dns.com/dns-query") - - // Cloudflare -Malware -Adult - addDoH("1.1.1.3", "https://family.cloudflare-dns.com/dns-query") - addDoH("1.0.0.3", "https://family.cloudflare-dns.com/dns-query") - addDoH("2606:4700:4700::1113", "https://family.cloudflare-dns.com/dns-query") - addDoH("2606:4700:4700::1003", "https://family.cloudflare-dns.com/dns-query") - - // Google - addDoH("8.8.8.8", "https://dns.google/dns-query") - addDoH("8.8.4.4", "https://dns.google/dns-query") - addDoH("2001:4860:4860::8888", "https://dns.google/dns-query") - addDoH("2001:4860:4860::8844", "https://dns.google/dns-query") - - // OpenDNS - // TODO(bradfitz): OpenDNS is unique amongst this current set in that - // its DoH DNS names resolve to different IPs than its normal DNS - // IPs. Support that later. For now we assume that they're the same. - // addDoH("208.67.222.222", "https://doh.opendns.com/dns-query") - // addDoH("208.67.220.220", "https://doh.opendns.com/dns-query") - // addDoH("208.67.222.123", "https://doh.familyshield.opendns.com/dns-query") - // addDoH("208.67.220.123", "https://doh.familyshield.opendns.com/dns-query") - - // Quad9 - addDoH("9.9.9.9", "https://dns.quad9.net/dns-query") - addDoH("149.112.112.112", "https://dns.quad9.net/dns-query") - addDoH("2620:fe::fe", "https://dns.quad9.net/dns-query") - addDoH("2620:fe::fe:9", "https://dns.quad9.net/dns-query") - - // Quad9 -DNSSEC - addDoH("9.9.9.10", "https://dns10.quad9.net/dns-query") - addDoH("149.112.112.10", "https://dns10.quad9.net/dns-query") - addDoH("2620:fe::10", "https://dns10.quad9.net/dns-query") - addDoH("2620:fe::fe:10", "https://dns10.quad9.net/dns-query") -}