@ -5,7 +5,12 @@
package main
import (
"encoding/json"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
)
@ -17,11 +22,12 @@ func BenchmarkHandleBootstrapDNS(b *testing.B) {
} ( )
refreshBootstrapDNS ( )
w := new ( bitbucketResponseWriter )
req , _ := http . NewRequest ( "GET" , "https://localhost/bootstrap-dns?q=" + url . QueryEscape ( "log.tailscale.io" ) , nil )
b . ReportAllocs ( )
b . ResetTimer ( )
b . RunParallel ( func ( b * testing . PB ) {
for b . Next ( ) {
handleBootstrapDNS ( w , nil )
handleBootstrapDNS ( w , req )
}
} )
}
@ -33,3 +39,116 @@ func (b *bitbucketResponseWriter) Header() http.Header { return make(http.Header
func ( b * bitbucketResponseWriter ) Write ( p [ ] byte ) ( int , error ) { return len ( p ) , nil }
func ( b * bitbucketResponseWriter ) WriteHeader ( statusCode int ) { }
func getBootstrapDNS ( t * testing . T , q string ) dnsEntryMap {
t . Helper ( )
req , _ := http . NewRequest ( "GET" , "https://localhost/bootstrap-dns?q=" + url . QueryEscape ( q ) , nil )
w := httptest . NewRecorder ( )
handleBootstrapDNS ( w , req )
res := w . Result ( )
if res . StatusCode != 200 {
t . Fatalf ( "got status=%d; want %d" , res . StatusCode , 200 )
}
var ips dnsEntryMap
if err := json . NewDecoder ( res . Body ) . Decode ( & ips ) ; err != nil {
t . Fatalf ( "error decoding response body: %v" , err )
}
return ips
}
func TestUnpublishedDNS ( t * testing . T ) {
const published = "login.tailscale.com"
const unpublished = "log.tailscale.io"
prev1 , prev2 := * bootstrapDNS , * unpublishedDNS
* bootstrapDNS = published
* unpublishedDNS = unpublished
t . Cleanup ( func ( ) {
* bootstrapDNS = prev1
* unpublishedDNS = prev2
} )
refreshBootstrapDNS ( )
refreshUnpublishedDNS ( )
hasResponse := func ( q string ) bool {
_ , found := getBootstrapDNS ( t , q ) [ q ]
return found
}
if ! hasResponse ( published ) {
t . Errorf ( "expected response for: %s" , published )
}
if ! hasResponse ( unpublished ) {
t . Errorf ( "expected response for: %s" , unpublished )
}
// Verify that querying for a random query or a real query does not
// leak our unpublished domain
m1 := getBootstrapDNS ( t , published )
if _ , found := m1 [ unpublished ] ; found {
t . Errorf ( "found unpublished domain %s: %+v" , unpublished , m1 )
}
m2 := getBootstrapDNS ( t , "random.example.com" )
if _ , found := m2 [ unpublished ] ; found {
t . Errorf ( "found unpublished domain %s: %+v" , unpublished , m2 )
}
}
func resetMetrics ( ) {
publishedDNSHits . Set ( 0 )
publishedDNSMisses . Set ( 0 )
unpublishedDNSHits . Set ( 0 )
unpublishedDNSMisses . Set ( 0 )
}
// Verify that we don't count an empty list in the unpublishedDNSCache as a
// cache hit in our metrics.
func TestUnpublishedDNSEmptyList ( t * testing . T ) {
pub := dnsEntryMap {
"tailscale.com" : { net . IPv4 ( 10 , 10 , 10 , 10 ) } ,
}
dnsCache . Store ( pub )
dnsCacheBytes . Store ( [ ] byte ( ` { "tailscale.com":["10.10.10.10"]} ` ) )
unpublishedDNSCache . Store ( dnsEntryMap {
"log.tailscale.io" : { } ,
"controlplane.tailscale.com" : { net . IPv4 ( 1 , 2 , 3 , 4 ) } ,
} )
t . Run ( "CacheMiss" , func ( t * testing . T ) {
// One domain in map but empty, one not in map at all
for _ , q := range [ ] string { "log.tailscale.io" , "login.tailscale.com" } {
resetMetrics ( )
ips := getBootstrapDNS ( t , q )
// Expected our public map to be returned on a cache miss
if ! reflect . DeepEqual ( ips , pub ) {
t . Errorf ( "got ips=%+v; want %+v" , ips , pub )
}
if v := unpublishedDNSHits . Value ( ) ; v != 0 {
t . Errorf ( "got hits=%d; want 0" , v )
}
if v := unpublishedDNSMisses . Value ( ) ; v != 1 {
t . Errorf ( "got misses=%d; want 1" , v )
}
}
} )
// Verify that we do get a valid response and metric.
t . Run ( "CacheHit" , func ( t * testing . T ) {
resetMetrics ( )
ips := getBootstrapDNS ( t , "controlplane.tailscale.com" )
want := dnsEntryMap { "controlplane.tailscale.com" : { net . IPv4 ( 1 , 2 , 3 , 4 ) } }
if ! reflect . DeepEqual ( ips , want ) {
t . Errorf ( "got ips=%+v; want %+v" , ips , want )
}
if v := unpublishedDNSHits . Value ( ) ; v != 1 {
t . Errorf ( "got hits=%d; want 1" , v )
}
if v := unpublishedDNSMisses . Value ( ) ; v != 0 {
t . Errorf ( "got misses=%d; want 0" , v )
}
} )
}