quality_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * Copyright (c) 2025, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package inproxy
  20. import (
  21. "fmt"
  22. "testing"
  23. "time"
  24. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  25. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/internal/testutils"
  26. lrucache "github.com/cognusion/go-cache-lru"
  27. )
  28. func TestProxyQualityState(t *testing.T) {
  29. err := runTestProxyQualityState()
  30. if err != nil {
  31. t.Error(errors.Trace(err).Error())
  32. }
  33. }
  34. func TestProxyQualityReporter(t *testing.T) {
  35. err := runTestProxyQualityReporter()
  36. if err != nil {
  37. t.Error(errors.Trace(err).Error())
  38. }
  39. }
  40. func runTestProxyQualityState() error {
  41. qualityTTL := 100 * time.Millisecond
  42. pendingFailedMatchDeadline := 100 * time.Millisecond
  43. failedMatchThreshold := 3
  44. q := NewProxyQuality()
  45. // Substitute a cache with a much shorter janitor interval, to ensure
  46. // evictions happen within the artificially short test intervals.
  47. q.pendingFailedMatches = lrucache.NewWithLRU(0, 1*time.Millisecond, proxyQualityMaxPendingFailedMatches)
  48. q.pendingFailedMatches.OnEvicted(q.addFailedMatch)
  49. q.SetParameters(
  50. qualityTTL, pendingFailedMatchDeadline, failedMatchThreshold)
  51. testProxyASN := "65537"
  52. testClientASN1 := "65538"
  53. testClientASN2 := "65539"
  54. testClientASN3 := "65540"
  55. proxyID, err := MakeID()
  56. if err != nil {
  57. return errors.Trace(err)
  58. }
  59. proxyKey := MakeProxyQualityKey(proxyID, testProxyASN)
  60. q.AddQuality(proxyKey, ProxyQualityASNCounts{testClientASN1: 1, testClientASN2: 2})
  61. // Test: HasQuality for any client ASN
  62. if !q.HasQuality(proxyID, testProxyASN, "") {
  63. return errors.TraceNew("unexpected HasQuality")
  64. }
  65. // Test: HasQuality for specific client ASN
  66. if !q.HasQuality(proxyID, testProxyASN, testClientASN1) {
  67. return errors.TraceNew("unexpected HasQuality")
  68. }
  69. if q.HasQuality(proxyID, testProxyASN, testClientASN3) {
  70. return errors.TraceNew("unexpected HasQuality")
  71. }
  72. // Test: TTL expires
  73. time.Sleep(qualityTTL + 1*time.Millisecond)
  74. if q.HasQuality(proxyID, testProxyASN, "") {
  75. return errors.TraceNew("unexpected HasQuality")
  76. }
  77. // Test: flush
  78. qualityTTL = proxyQualityTTL
  79. q.SetParameters(
  80. qualityTTL, pendingFailedMatchDeadline, failedMatchThreshold)
  81. q.AddQuality(proxyKey, ProxyQualityASNCounts{testClientASN1: 1, testClientASN2: 2})
  82. q.Flush()
  83. if q.HasQuality(proxyID, testProxyASN, "") {
  84. return errors.TraceNew("unexpected HasQuality")
  85. }
  86. // Test: quality removed once failed match threshold reached
  87. q.AddQuality(proxyKey, ProxyQualityASNCounts{testClientASN1: 1, testClientASN2: 2})
  88. for i := 0; i < failedMatchThreshold; i++ {
  89. q.Matched(proxyID, testProxyASN)
  90. time.Sleep(pendingFailedMatchDeadline + 10*time.Millisecond)
  91. expectQuality := i < failedMatchThreshold-1
  92. if q.HasQuality(proxyID, testProxyASN, "") != expectQuality {
  93. return errors.TraceNew("unexpected HasQuality")
  94. }
  95. }
  96. return nil
  97. }
  98. func runTestProxyQualityReporter() error {
  99. // This unit test exercises the report queue state, but not the report
  100. // requests. ProxyQualityReporter.requestScheduler/sendToBrokers are
  101. // exercised in TestInproxy.
  102. r, err := NewProxyQualityReporter(
  103. testutils.NewTestLogger(),
  104. nil,
  105. SessionPrivateKey{},
  106. nil,
  107. nil,
  108. nil)
  109. if err != nil {
  110. return errors.Trace(err)
  111. }
  112. maxEntries := 10
  113. expectedRequestCount := 2
  114. r.SetRequestParameters(maxEntries, 0, 0, 0)
  115. var proxyKeys []ProxyQualityKey
  116. testProxyASN := "65537"
  117. for i := 0; i < 20; i++ {
  118. proxyID, err := MakeID()
  119. if err != nil {
  120. return errors.Trace(err)
  121. }
  122. proxyKey := MakeProxyQualityKey(proxyID, testProxyASN)
  123. for j := 0; j < 10; j++ {
  124. testClientASN := fmt.Sprintf("%d", 65538+j)
  125. for k := 0; k <= i; k++ {
  126. r.ReportQuality(
  127. proxyID, testProxyASN, testClientASN)
  128. }
  129. }
  130. proxyKeys = append(proxyKeys, proxyKey)
  131. }
  132. if r.reportQueue.Len() != len(proxyKeys) {
  133. return errors.TraceNew("unexpected queue size")
  134. }
  135. for count := 0; count < expectedRequestCount; count++ {
  136. requestCounts := r.prepareNextRequest()
  137. if len(requestCounts) == 0 {
  138. return errors.TraceNew("unexpected requestCounts")
  139. }
  140. for i := count * 10; i < count*10+10; i++ {
  141. counts, ok := requestCounts[proxyKeys[i]]
  142. if !ok {
  143. return errors.TraceNew("missing proxyID")
  144. }
  145. for j := 0; j < 10; j++ {
  146. testClientASN := fmt.Sprintf("%d", 65538+j)
  147. count, ok := counts[testClientASN]
  148. if !ok {
  149. return errors.TraceNew("missing client ASN")
  150. }
  151. if count != i+1 {
  152. return errors.Tracef("unexpected count")
  153. }
  154. }
  155. }
  156. }
  157. if len(r.prepareNextRequest()) != 0 {
  158. return errors.TraceNew("unexpected prepareNextRequest")
  159. }
  160. return nil
  161. }