mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 03:25: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>
103 lines
2.3 KiB
Go
103 lines
2.3 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 resolver
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"net/http"
|
|
"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")
|
|
|
|
const someDNSID = 123 // something non-zero as a test; in violation of spec's SHOULD of 0
|
|
|
|
func someDNSQuestion(t testing.TB) []byte {
|
|
b := dnsmessage.NewBuilder(nil, dnsmessage.Header{
|
|
OpCode: 0, // query
|
|
RecursionDesired: true,
|
|
ID: someDNSID,
|
|
})
|
|
b.StartQuestions() // err
|
|
b.Question(dnsmessage.Question{
|
|
Name: dnsmessage.MustNewName("tailscale.com."),
|
|
Type: dnsmessage.TypeA,
|
|
Class: dnsmessage.ClassINET,
|
|
})
|
|
msg, err := b.Finish()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func TestDoH(t *testing.T) {
|
|
if !*testDoH {
|
|
t.Skip("skipping manual test without --test-doh flag")
|
|
}
|
|
prefixes := publicdns.KnownDoHPrefixes()
|
|
if len(prefixes) == 0 {
|
|
t.Fatal("no known DoH")
|
|
}
|
|
|
|
f := &forwarder{
|
|
dohSem: make(chan struct{}, 10),
|
|
}
|
|
|
|
for _, urlBase := range prefixes {
|
|
t.Run(urlBase, func(t *testing.T) {
|
|
c, ok := f.getKnownDoHClientForProvider(urlBase)
|
|
if !ok {
|
|
t.Fatal("expected DoH")
|
|
}
|
|
res, err := f.sendDoH(context.Background(), urlBase, c, someDNSQuestion(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.Transport.(*http.Transport).CloseIdleConnections()
|
|
|
|
var p dnsmessage.Parser
|
|
h, err := p.Start(res)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.ID != someDNSID {
|
|
t.Errorf("response DNS ID = %v; want %v", h.ID, someDNSID)
|
|
}
|
|
|
|
p.SkipAllQuestions()
|
|
aa, err := p.AllAnswers()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(aa) == 0 {
|
|
t.Fatal("no answers")
|
|
}
|
|
for _, r := range aa {
|
|
t.Logf("got: %v", r.GoString())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDoHV6Fallback(t *testing.T) {
|
|
for _, base := range publicdns.KnownDoHPrefixes() {
|
|
for _, ip := range publicdns.DoHIPsOfBase(base) {
|
|
if ip.Is4() {
|
|
ip6, ok := publicdns.DoHV6(base)
|
|
if !ok {
|
|
t.Errorf("no v6 DoH known for %v", ip)
|
|
} else if !ip6.Is6() {
|
|
t.Errorf("dohV6(%q) returned non-v6 address %v", base, ip6)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|