mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-20 21:23:07 +08:00
net/dns/publicdns: don't upgrade Control D port-53-only addresses to DoH (#20463)
DoHEndpointFromIP mapped the entire 2606:1a40::/48 range to a dns.controld.com/<id> DoH URL, but the ID-encoded addresses in that range are legacy plaintext-DNS endpoints that refuse :443. They now fall through as ordinary port-53 resolvers; the free anycast freedns.controld.com/pN addresses still upgrade via exact match. Fixes #20433 Signed-off-by: Brendan Creane <bcreane@gmail.com>
This commit is contained in:
parent
689c6c2e6d
commit
d2af6a4d39
@ -9,7 +9,6 @@
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sort"
|
||||
@ -57,17 +56,15 @@ func DoHEndpointFromIP(ip netip.Addr) (dohBase string, dohOnly bool, ok bool) {
|
||||
return sb.String(), true, true
|
||||
}
|
||||
|
||||
// Control D DoH URLs are of the form "https://dns.controld.com/8yezwenugs"
|
||||
// where the path component is represented by 8 bytes (7-14) of the IPv6 address in base36
|
||||
// Control D's free anycast resolvers (freedns.controld.com/pN) are the only
|
||||
// Control D addresses that serve DoH; they're registered as exact entries in
|
||||
// dohOfIP (see populate) and are handled by the lookup above.
|
||||
//
|
||||
// TODO(#20433): the ID-encoded addresses in this /48 are legacy port-53-only
|
||||
// endpoints and refuse DoH on :443, so upgrading them to DoH is wrong. Only the
|
||||
// shared anycast addresses (e.g. freedns.controld.com/pN) actually serve DoH.
|
||||
// Distinguish the two rather than mapping the whole range.
|
||||
if controlDv6RangeA.Contains(ip) || controlDv6RangeB.Contains(ip) {
|
||||
path := big.NewInt(0).SetBytes(ip.AsSlice()[6:14]).Text(36)
|
||||
return controlDBase + path, true, true
|
||||
}
|
||||
// The ID-encoded addresses in the 2606:1a40::/48 ranges (customer resolver ID
|
||||
// base36-encoded into the address) are legacy plaintext-DNS (port 53) endpoints
|
||||
// that refuse DoH on :443, so we deliberately don't upgrade them to DoH here:
|
||||
// they fall through and are used as ordinary port-53 resolvers. Premium DoH is
|
||||
// only reachable via the dns.controld.com/<id> URL path (see DoHIPsOfBase).
|
||||
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
@ -28,6 +28,46 @@ func TestInit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoHEndpointFromIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
wantBase string
|
||||
wantOnly bool
|
||||
wantOK bool
|
||||
}{
|
||||
// Control D free anycast resolvers (freedns.controld.com/pN) serve DoH
|
||||
// and are dual-stack (also speak port 53), so dohOnly is false.
|
||||
{"controld_free_v6_p0", "2606:1a40::", "https://freedns.controld.com/p0", false, true},
|
||||
{"controld_free_v6_p1", "2606:1a40:1::1", "https://freedns.controld.com/p1", false, true},
|
||||
{"controld_free_v4_p1", "76.76.2.1", "https://freedns.controld.com/p1", false, true},
|
||||
{"controld_free_v4_family", "76.76.10.4", "https://freedns.controld.com/family", false, true},
|
||||
|
||||
// ID-encoded addresses in the Control D /48 ranges are legacy
|
||||
// plaintext-DNS (port 53) endpoints that refuse DoH, so they must not be
|
||||
// upgraded to DoH; DoHEndpointFromIP reports them as unknown.
|
||||
{"controld_id_encoded_a", "2606:1a40:0:0:0:1234:5678:9abc", "", false, false},
|
||||
{"controld_id_encoded_b", "2606:1a40:1:0:0:1234:5678:9abc", "", false, false},
|
||||
// The premium anycast address has no resolver ID in the bare IP, so it
|
||||
// can't be mapped to a DoH URL here either.
|
||||
{"controld_premium_anycast", "2606:1a40::22", "", false, false},
|
||||
|
||||
// A well-known dual-stack DoH server upgrades but isn't dohOnly.
|
||||
{"google_v4", "8.8.8.8", "https://dns.google/dns-query", false, true},
|
||||
// A NextDNS ID-encoded address is DoH-only.
|
||||
{"nextdns_v6", "2a07:a8c0::c3:a884", "https://dns.nextdns.io/c3a884", true, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
base, only, ok := DoHEndpointFromIP(netip.MustParseAddr(tt.ip))
|
||||
if base != tt.wantBase || only != tt.wantOnly || ok != tt.wantOK {
|
||||
t.Errorf("DoHEndpointFromIP(%s) = (%q, %v, %v); want (%q, %v, %v)",
|
||||
tt.ip, base, only, ok, tt.wantBase, tt.wantOnly, tt.wantOK)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoHV6(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
|
||||
@ -146,9 +146,19 @@ func TestResolversWithDelays(t *testing.T) {
|
||||
want: o("https://dns.nextdns.io/c3a884"),
|
||||
},
|
||||
{
|
||||
name: "controld-ipv6-expand",
|
||||
// ID-encoded Control D addresses are legacy port-53-only
|
||||
// endpoints (see #20433); they must not be upgraded to DoH and
|
||||
// instead pass through as ordinary port-53 resolvers.
|
||||
name: "controld-ipv6-id-encoded-not-doh",
|
||||
in: q("2606:1a40:0:6:7b5b:5949:35ad:0"),
|
||||
want: o("https://dns.controld.com/hyq3ipr2ct"),
|
||||
want: o("2606:1a40:0:6:7b5b:5949:35ad:0"),
|
||||
},
|
||||
{
|
||||
// The free anycast resolvers (freedns.controld.com/pN) do serve
|
||||
// DoH and are upgraded.
|
||||
name: "controld-free-anycast-doh",
|
||||
in: q("2606:1a40::1"),
|
||||
want: o("https://freedns.controld.com/p1", "2606:1a40::1+0.5s"),
|
||||
},
|
||||
{
|
||||
name: "controld-doh-input",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user