tsdns: delegate requests asynchronously (#687)

Signed-Off-By: Dmytro Shynkevych <dmytro@tailscale.com>
This commit is contained in:
Dmytro Shynkevych
2020-08-19 15:39:25 -04:00
committed by GitHub
parent a583e498b0
commit 1af70e2468
4 changed files with 600 additions and 189 deletions
+170 -24
View File
@@ -7,11 +7,13 @@ package tsdns
import (
"bytes"
"errors"
"net"
"sync"
"testing"
dns "golang.org/x/net/dns/dnsmessage"
"inet.af/netaddr"
"tailscale.com/tstest"
)
var testipv4 = netaddr.IPv4(1, 2, 3, 4)
@@ -178,9 +180,13 @@ func TestRDNSNameToIPv6(t *testing.T) {
}
func TestResolve(t *testing.T) {
r := NewResolver(t.Logf, "ipn.dev")
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: false})
r.SetMap(dnsMap)
r.Start()
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
tests := []struct {
name string
@@ -212,9 +218,13 @@ func TestResolve(t *testing.T) {
}
func TestResolveReverse(t *testing.T) {
r := NewResolver(t.Logf, "ipn.dev")
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: false})
r.SetMap(dnsMap)
r.Start()
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
tests := []struct {
name string
@@ -244,6 +254,9 @@ func TestResolveReverse(t *testing.T) {
}
func TestDelegate(t *testing.T) {
rc := tstest.NewResourceCheck()
defer rc.Assert(t)
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6))
dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN)
@@ -271,12 +284,16 @@ func TestDelegate(t *testing.T) {
return
}
r := NewResolver(t.Logf, "ipn.dev")
r.SetNameservers([]string{
v4server.PacketConn.LocalAddr().String(),
v6server.PacketConn.LocalAddr().String(),
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: true})
r.SetUpstreams([]net.Addr{
v4server.PacketConn.LocalAddr(),
v6server.PacketConn.LocalAddr(),
})
r.Start()
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
tests := []struct {
name string
@@ -311,9 +328,92 @@ func TestDelegate(t *testing.T) {
}
}
func TestDelegateCollision(t *testing.T) {
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6))
server, errch := serveDNS("127.0.0.1:0")
defer func() {
if err := <-errch; err != nil {
t.Errorf("server error: %v", err)
}
}()
if server == nil {
return
}
defer server.Shutdown()
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: true})
r.SetUpstreams([]net.Addr{server.PacketConn.LocalAddr()})
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
packets := []struct {
qname string
qtype dns.Type
addr netaddr.IPPort
}{
{"test.site.", dns.TypeA, netaddr.IPPort{IP: netaddr.IPv4(1, 1, 1, 1), Port: 1001}},
{"test.site.", dns.TypeAAAA, netaddr.IPPort{IP: netaddr.IPv4(1, 1, 1, 1), Port: 1002}},
}
// packets will have the same dns txid.
for _, p := range packets {
payload := dnspacket(p.qname, p.qtype)
req := Packet{Payload: payload, Addr: p.addr}
err := r.EnqueueRequest(req)
if err != nil {
t.Error(err)
}
}
// Despite the txid collision, the answer(s) should still match the query.
resp, err := r.NextResponse()
if err != nil {
t.Error(err)
}
var p dns.Parser
_, err = p.Start(resp.Payload)
if err != nil {
t.Error(err)
}
err = p.SkipAllQuestions()
if err != nil {
t.Error(err)
}
ans, err := p.AllAnswers()
if err != nil {
t.Error(err)
}
var wantType dns.Type
switch ans[0].Body.(type) {
case *dns.AResource:
wantType = dns.TypeA
case *dns.AAAAResource:
wantType = dns.TypeAAAA
default:
t.Errorf("unexpected answer type: %T", ans[0].Body)
}
for _, p := range packets {
if p.qtype == wantType && p.addr != resp.Addr {
t.Errorf("addr = %v; want %v", resp.Addr, p.addr)
}
}
}
func TestConcurrentSetMap(t *testing.T) {
r := NewResolver(t.Logf, "ipn.dev.")
r.Start()
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: false})
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
// This is purely to ensure that Resolve does not race with SetMap.
var wg sync.WaitGroup
@@ -329,17 +429,36 @@ func TestConcurrentSetMap(t *testing.T) {
wg.Wait()
}
func TestConcurrentSetNameservers(t *testing.T) {
r := NewResolver(t.Logf, "ipn.dev.")
r.Start()
packet := dnspacket("google.com.", dns.TypeA)
func TestConcurrentSetUpstreams(t *testing.T) {
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6))
// This is purely to ensure that delegation does not race with SetNameservers.
server, errch := serveDNS("127.0.0.1:0")
defer func() {
if err := <-errch; err != nil {
t.Errorf("server error: %v", err)
}
}()
if server == nil {
return
}
defer server.Shutdown()
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: true})
r.SetMap(dnsMap)
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
packet := dnspacket("test.site.", dns.TypeA)
// This is purely to ensure that delegation does not race with SetUpstreams.
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
r.SetNameservers([]string{"9.9.9.9:53"})
r.SetUpstreams([]net.Addr{server.PacketConn.LocalAddr()})
}()
go func() {
defer wg.Done()
@@ -415,9 +534,13 @@ var nxdomainResponse = []byte{
}
func TestFull(t *testing.T) {
r := NewResolver(t.Logf, "ipn.dev.")
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: false})
r.SetMap(dnsMap)
r.Start()
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
// One full packet and one error packet
tests := []struct {
@@ -445,9 +568,13 @@ func TestFull(t *testing.T) {
}
func TestAllocs(t *testing.T) {
r := NewResolver(t.Logf, "ipn.dev.")
r := NewResolver(ResolverConfig{Logf: t.Logf, RootDomain: "ipn.dev.", Forward: false})
r.SetMap(dnsMap)
r.Start()
if err := r.Start(); err != nil {
t.Fatalf("start: %v", err)
}
defer r.Close()
// It is seemingly pointless to test allocs in the delegate path,
// as dialer.Dial -> Read -> Write alone comprise 12 allocs.
@@ -473,9 +600,28 @@ func TestAllocs(t *testing.T) {
}
func BenchmarkFull(b *testing.B) {
r := NewResolver(b.Logf, "ipn.dev.")
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6))
server, errch := serveDNS("127.0.0.1:0")
defer func() {
if err := <-errch; err != nil {
b.Errorf("server error: %v", err)
}
}()
if server == nil {
return
}
defer server.Shutdown()
r := NewResolver(ResolverConfig{Logf: b.Logf, RootDomain: "ipn.dev.", Forward: true})
r.SetMap(dnsMap)
r.Start()
r.SetUpstreams([]net.Addr{server.PacketConn.LocalAddr()})
if err := r.Start(); err != nil {
b.Fatalf("start: %v", err)
}
defer r.Close()
tests := []struct {
name string
@@ -483,7 +629,7 @@ func BenchmarkFull(b *testing.B) {
}{
{"forward", dnspacket("test1.ipn.dev.", dns.TypeA)},
{"reverse", dnspacket("4.3.2.1.in-addr.arpa.", dns.TypePTR)},
{"nxdomain", dnspacket("test3.ipn.dev.", dns.TypeA)},
{"delegated", dnspacket("test.site.", dns.TypeA)},
}
for _, tt := range tests {