bootstrap_dns_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package main
  4. import (
  5. "encoding/json"
  6. "net"
  7. "net/http"
  8. "net/http/httptest"
  9. "net/url"
  10. "reflect"
  11. "testing"
  12. "tailscale.com/tstest"
  13. )
  14. func BenchmarkHandleBootstrapDNS(b *testing.B) {
  15. tstest.Replace(b, bootstrapDNS, "log.tailscale.io,login.tailscale.com,controlplane.tailscale.com,login.us.tailscale.com")
  16. refreshBootstrapDNS()
  17. w := new(bitbucketResponseWriter)
  18. req, _ := http.NewRequest("GET", "https://localhost/bootstrap-dns?q="+url.QueryEscape("log.tailscale.io"), nil)
  19. b.ReportAllocs()
  20. b.ResetTimer()
  21. b.RunParallel(func(b *testing.PB) {
  22. for b.Next() {
  23. handleBootstrapDNS(w, req)
  24. }
  25. })
  26. }
  27. type bitbucketResponseWriter struct{}
  28. func (b *bitbucketResponseWriter) Header() http.Header { return make(http.Header) }
  29. func (b *bitbucketResponseWriter) Write(p []byte) (int, error) { return len(p), nil }
  30. func (b *bitbucketResponseWriter) WriteHeader(statusCode int) {}
  31. func getBootstrapDNS(t *testing.T, q string) dnsEntryMap {
  32. t.Helper()
  33. req, _ := http.NewRequest("GET", "https://localhost/bootstrap-dns?q="+url.QueryEscape(q), nil)
  34. w := httptest.NewRecorder()
  35. handleBootstrapDNS(w, req)
  36. res := w.Result()
  37. if res.StatusCode != 200 {
  38. t.Fatalf("got status=%d; want %d", res.StatusCode, 200)
  39. }
  40. var ips dnsEntryMap
  41. if err := json.NewDecoder(res.Body).Decode(&ips); err != nil {
  42. t.Fatalf("error decoding response body: %v", err)
  43. }
  44. return ips
  45. }
  46. func TestUnpublishedDNS(t *testing.T) {
  47. const published = "login.tailscale.com"
  48. const unpublished = "log.tailscale.io"
  49. prev1, prev2 := *bootstrapDNS, *unpublishedDNS
  50. *bootstrapDNS = published
  51. *unpublishedDNS = unpublished
  52. t.Cleanup(func() {
  53. *bootstrapDNS = prev1
  54. *unpublishedDNS = prev2
  55. })
  56. refreshBootstrapDNS()
  57. refreshUnpublishedDNS()
  58. hasResponse := func(q string) bool {
  59. _, found := getBootstrapDNS(t, q)[q]
  60. return found
  61. }
  62. if !hasResponse(published) {
  63. t.Errorf("expected response for: %s", published)
  64. }
  65. if !hasResponse(unpublished) {
  66. t.Errorf("expected response for: %s", unpublished)
  67. }
  68. // Verify that querying for a random query or a real query does not
  69. // leak our unpublished domain
  70. m1 := getBootstrapDNS(t, published)
  71. if _, found := m1[unpublished]; found {
  72. t.Errorf("found unpublished domain %s: %+v", unpublished, m1)
  73. }
  74. m2 := getBootstrapDNS(t, "random.example.com")
  75. if _, found := m2[unpublished]; found {
  76. t.Errorf("found unpublished domain %s: %+v", unpublished, m2)
  77. }
  78. }
  79. func resetMetrics() {
  80. publishedDNSHits.Set(0)
  81. publishedDNSMisses.Set(0)
  82. unpublishedDNSHits.Set(0)
  83. unpublishedDNSMisses.Set(0)
  84. }
  85. // Verify that we don't count an empty list in the unpublishedDNSCache as a
  86. // cache hit in our metrics.
  87. func TestUnpublishedDNSEmptyList(t *testing.T) {
  88. pub := dnsEntryMap{
  89. "tailscale.com": {net.IPv4(10, 10, 10, 10)},
  90. }
  91. dnsCache.Store(pub)
  92. dnsCacheBytes.Store([]byte(`{"tailscale.com":["10.10.10.10"]}`))
  93. unpublishedDNSCache.Store(dnsEntryMap{
  94. "log.tailscale.io": {},
  95. "controlplane.tailscale.com": {net.IPv4(1, 2, 3, 4)},
  96. })
  97. t.Run("CacheMiss", func(t *testing.T) {
  98. // One domain in map but empty, one not in map at all
  99. for _, q := range []string{"log.tailscale.io", "login.tailscale.com"} {
  100. resetMetrics()
  101. ips := getBootstrapDNS(t, q)
  102. // Expected our public map to be returned on a cache miss
  103. if !reflect.DeepEqual(ips, pub) {
  104. t.Errorf("got ips=%+v; want %+v", ips, pub)
  105. }
  106. if v := unpublishedDNSHits.Value(); v != 0 {
  107. t.Errorf("got hits=%d; want 0", v)
  108. }
  109. if v := unpublishedDNSMisses.Value(); v != 1 {
  110. t.Errorf("got misses=%d; want 1", v)
  111. }
  112. }
  113. })
  114. // Verify that we do get a valid response and metric.
  115. t.Run("CacheHit", func(t *testing.T) {
  116. resetMetrics()
  117. ips := getBootstrapDNS(t, "controlplane.tailscale.com")
  118. want := dnsEntryMap{"controlplane.tailscale.com": {net.IPv4(1, 2, 3, 4)}}
  119. if !reflect.DeepEqual(ips, want) {
  120. t.Errorf("got ips=%+v; want %+v", ips, want)
  121. }
  122. if v := unpublishedDNSHits.Value(); v != 1 {
  123. t.Errorf("got hits=%d; want 1", v)
  124. }
  125. if v := unpublishedDNSMisses.Value(); v != 0 {
  126. t.Errorf("got misses=%d; want 0", v)
  127. }
  128. })
  129. }