captivedetection_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package captivedetection
  4. import (
  5. "context"
  6. "net/http"
  7. "net/http/httptest"
  8. "net/url"
  9. "runtime"
  10. "strconv"
  11. "sync"
  12. "sync/atomic"
  13. "testing"
  14. "time"
  15. "tailscale.com/derp/derpserver"
  16. "tailscale.com/net/netmon"
  17. "tailscale.com/syncs"
  18. "tailscale.com/tstest/nettest"
  19. "tailscale.com/util/must"
  20. )
  21. func TestAvailableEndpointsAlwaysAtLeastTwo(t *testing.T) {
  22. endpoints := availableEndpoints(nil, 0, t.Logf, runtime.GOOS)
  23. if len(endpoints) == 0 {
  24. t.Errorf("Expected non-empty AvailableEndpoints, got an empty slice instead")
  25. }
  26. if len(endpoints) == 1 {
  27. t.Errorf("Expected at least two AvailableEndpoints for redundancy, got only one instead")
  28. }
  29. for _, e := range endpoints {
  30. if e.URL.Scheme != "http" {
  31. t.Errorf("Expected HTTP URL in Endpoint, got HTTPS")
  32. }
  33. }
  34. }
  35. func TestDetectCaptivePortalReturnsFalse(t *testing.T) {
  36. d := NewDetector(t.Logf)
  37. found := d.Detect(context.Background(), netmon.NewStatic(), nil, 0)
  38. if found {
  39. t.Errorf("DetectCaptivePortal returned true, expected false.")
  40. }
  41. }
  42. func TestEndpointsAreUpAndReturnExpectedResponse(t *testing.T) {
  43. nettest.SkipIfNoNetwork(t)
  44. d := NewDetector(t.Logf)
  45. endpoints := availableEndpoints(nil, 0, t.Logf, runtime.GOOS)
  46. t.Logf("testing %d endpoints", len(endpoints))
  47. ctx, cancel := context.WithCancel(context.Background())
  48. defer cancel()
  49. var good atomic.Bool
  50. var wg sync.WaitGroup
  51. sem := syncs.NewSemaphore(5)
  52. for _, e := range endpoints {
  53. wg.Add(1)
  54. go func(endpoint Endpoint) {
  55. defer wg.Done()
  56. if !sem.AcquireContext(ctx) {
  57. return
  58. }
  59. defer sem.Release()
  60. found, err := d.verifyCaptivePortalEndpoint(ctx, endpoint, 0)
  61. if err != nil && ctx.Err() == nil {
  62. t.Logf("verifyCaptivePortalEndpoint failed with endpoint %v: %v", endpoint, err)
  63. }
  64. if found {
  65. t.Logf("verifyCaptivePortalEndpoint with endpoint %v says we're behind a captive portal, but we aren't", endpoint)
  66. return
  67. }
  68. good.Store(true)
  69. t.Logf("endpoint good: %v", endpoint)
  70. cancel()
  71. }(e)
  72. }
  73. wg.Wait()
  74. if !good.Load() {
  75. t.Errorf("no good endpoints found")
  76. }
  77. }
  78. func TestCaptivePortalRequest(t *testing.T) {
  79. d := NewDetector(t.Logf)
  80. now := time.Now()
  81. d.clock = func() time.Time { return now }
  82. ctx, cancel := context.WithCancel(context.Background())
  83. defer cancel()
  84. s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  85. if r.Method != "GET" {
  86. t.Errorf("expected GET, got %q", r.Method)
  87. }
  88. if r.URL.Path != "/generate_204" {
  89. t.Errorf("expected /generate_204, got %q", r.URL.Path)
  90. }
  91. q := r.URL.Query()
  92. if got, want := q.Get("t"), strconv.Itoa(int(now.Unix())); got != want {
  93. t.Errorf("timestamp param; got %v, want %v", got, want)
  94. }
  95. w.Header().Set("X-Tailscale-Response", "response "+r.Header.Get("X-Tailscale-Challenge"))
  96. w.WriteHeader(http.StatusNoContent)
  97. }))
  98. defer s.Close()
  99. e := Endpoint{
  100. URL: must.Get(url.Parse(s.URL + "/generate_204")),
  101. StatusCode: 204,
  102. ExpectedContent: "",
  103. SupportsTailscaleChallenge: true,
  104. }
  105. found, err := d.verifyCaptivePortalEndpoint(ctx, e, 0)
  106. if err != nil {
  107. t.Fatalf("verifyCaptivePortalEndpoint = %v, %v", found, err)
  108. }
  109. if found {
  110. t.Errorf("verifyCaptivePortalEndpoint = %v, want false", found)
  111. }
  112. }
  113. func TestAgainstDERPHandler(t *testing.T) {
  114. d := NewDetector(t.Logf)
  115. ctx, cancel := context.WithCancel(context.Background())
  116. defer cancel()
  117. s := httptest.NewServer(http.HandlerFunc(derpserver.ServeNoContent))
  118. defer s.Close()
  119. e := Endpoint{
  120. URL: must.Get(url.Parse(s.URL + "/generate_204")),
  121. StatusCode: 204,
  122. ExpectedContent: "",
  123. SupportsTailscaleChallenge: true,
  124. }
  125. found, err := d.verifyCaptivePortalEndpoint(ctx, e, 0)
  126. if err != nil {
  127. t.Fatalf("verifyCaptivePortalEndpoint = %v, %v", found, err)
  128. }
  129. if found {
  130. t.Errorf("verifyCaptivePortalEndpoint = %v, want false", found)
  131. }
  132. }