mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 11:35:35 +00:00
58abae1f83
NextDNS is unique in that users create accounts and then get user-specific DNS IPs & DoH URLs. For DoH, the customer ID is in the URL path. For IPv6, the IP address includes the customer ID in the lower bits. For IPv4, there's a fragile "IP linking" mechanism to associate your public IPv4 with an assigned NextDNS IPv4 and that tuple maps to your customer ID. We don't use the IP linking mechanism. Instead, NextDNS is DoH-only. Which means using NextDNS necessarily shunts all DNS traffic through 100.100.100.100 (programming the OS to use 100.100.100.100 as the global resolver) because operating systems can't usually do DoH themselves. Once it's in Tailscale's DoH client, we then connect out to the known NextDNS IPv4/IPv6 anycast addresses. If the control plane sends the client a NextDNS IPv6 address, we then map it to the corresponding NextDNS DoH with the same client ID, and we dial that DoH server using the combination of v4/v6 anycast IPs. Updates #2452 Change-Id: I3439d798d21d5fc9df5a2701839910f5bef85463 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
97 lines
2.2 KiB
Go
97 lines
2.2 KiB
Go
// 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 (
|
|
"net/netip"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestInit(t *testing.T) {
|
|
for _, baseKey := range KnownDoHPrefixes() {
|
|
baseSet := DoHIPsOfBase(baseKey)
|
|
for _, addr := range baseSet {
|
|
back, only, ok := DoHEndpointFromIP(addr)
|
|
if !ok {
|
|
t.Errorf("DoHEndpointFromIP(%v) not mapped back to %v", addr, baseKey)
|
|
continue
|
|
}
|
|
if only {
|
|
t.Errorf("unexpected DoH only bit set for %v", addr)
|
|
}
|
|
if back != baseKey {
|
|
t.Errorf("Expected %v to map to %s, got %s", addr, baseKey, back)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDoHV6(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
firstIP netip.Addr
|
|
want bool
|
|
}{
|
|
{"https://cloudflare-dns.com/dns-query", netip.MustParseAddr("2606:4700:4700::1111"), true},
|
|
{"https://dns.google/dns-query", netip.MustParseAddr("2001:4860:4860::8888"), true},
|
|
{"bogus", netip.Addr{}, 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDoHIPsOfBase(t *testing.T) {
|
|
ips := func(s ...string) (ret []netip.Addr) {
|
|
for _, ip := range s {
|
|
ret = append(ret, netip.MustParseAddr(ip))
|
|
}
|
|
return
|
|
}
|
|
tests := []struct {
|
|
base string
|
|
want []netip.Addr
|
|
}{
|
|
{
|
|
base: "https://cloudflare-dns.com/dns-query",
|
|
want: ips("1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001"),
|
|
},
|
|
{
|
|
base: "https://dns.nextdns.io/",
|
|
want: ips(),
|
|
},
|
|
{
|
|
base: "https://dns.nextdns.io/ff",
|
|
want: ips(
|
|
"45.90.28.0",
|
|
"45.90.30.0",
|
|
"2a07:a8c0::ff",
|
|
"2a07:a8c1::ff",
|
|
),
|
|
},
|
|
{
|
|
base: "https://dns.nextdns.io/c3a884",
|
|
want: ips(
|
|
"45.90.28.0",
|
|
"45.90.30.0",
|
|
"2a07:a8c0::c3:a884",
|
|
"2a07:a8c1::c3:a884",
|
|
),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
got := DoHIPsOfBase(tt.base)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("DoHIPsOfBase(%q) = %v; want %v", tt.base, got, tt.want)
|
|
}
|
|
}
|
|
}
|