quality_test.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. lrucache "github.com/cognusion/go-cache-lru"
  26. )
  27. func TestProxyQualityState(t *testing.T) {
  28. err := runTestProxyQualityState()
  29. if err != nil {
  30. t.Error(errors.Trace(err).Error())
  31. }
  32. }
  33. func TestProxyQualityReporter(t *testing.T) {
  34. err := runTestProxyQualityReporter()
  35. if err != nil {
  36. t.Error(errors.Trace(err).Error())
  37. }
  38. }
  39. func runTestProxyQualityState() error {
  40. qualityTTL := 100 * time.Millisecond
  41. pendingFailedMatchDeadline := 100 * time.Millisecond
  42. failedMatchThreshold := 3
  43. q := NewProxyQuality()
  44. // Substitute a cache with a much shorter janitor interval, to ensure
  45. // evictions happen within the artificially short test intervals.
  46. q.pendingFailedMatches = lrucache.NewWithLRU(0, 1*time.Millisecond, proxyQualityMaxPendingFailedMatches)
  47. q.pendingFailedMatches.OnEvicted(q.addFailedMatch)
  48. q.SetParameters(
  49. qualityTTL, pendingFailedMatchDeadline, failedMatchThreshold)
  50. testProxyASN := "65537"
  51. testClientASN1 := "65538"
  52. testClientASN2 := "65539"
  53. testClientASN3 := "65540"
  54. proxyID, err := MakeID()
  55. if err != nil {
  56. return errors.Trace(err)
  57. }
  58. proxyKey := MakeProxyQualityKey(proxyID, testProxyASN)
  59. q.AddQuality(proxyKey, ProxyQualityASNCounts{testClientASN1: 1, testClientASN2: 2})
  60. // Test: HasQuality for any client ASN
  61. if !q.HasQuality(proxyID, testProxyASN, "") {
  62. return errors.TraceNew("unexpected HasQuality")
  63. }
  64. // Test: HasQuality for specific client ASN
  65. if !q.HasQuality(proxyID, testProxyASN, testClientASN1) {
  66. return errors.TraceNew("unexpected HasQuality")
  67. }
  68. if q.HasQuality(proxyID, testProxyASN, testClientASN3) {
  69. return errors.TraceNew("unexpected HasQuality")
  70. }
  71. // Test: TTL expires
  72. time.Sleep(qualityTTL + 1*time.Millisecond)
  73. if q.HasQuality(proxyID, testProxyASN, "") {
  74. return errors.TraceNew("unexpected HasQuality")
  75. }
  76. // Test: flush
  77. qualityTTL = proxyQualityTTL
  78. q.SetParameters(
  79. qualityTTL, pendingFailedMatchDeadline, failedMatchThreshold)
  80. q.AddQuality(proxyKey, ProxyQualityASNCounts{testClientASN1: 1, testClientASN2: 2})
  81. q.Flush()
  82. if q.HasQuality(proxyID, testProxyASN, "") {
  83. return errors.TraceNew("unexpected HasQuality")
  84. }
  85. // Test: quality removed once failed match threshold reached
  86. q.AddQuality(proxyKey, ProxyQualityASNCounts{testClientASN1: 1, testClientASN2: 2})
  87. for i := 0; i < failedMatchThreshold; i++ {
  88. q.Matched(proxyID, testProxyASN)
  89. time.Sleep(pendingFailedMatchDeadline + 10*time.Millisecond)
  90. expectQuality := i < failedMatchThreshold-1
  91. if q.HasQuality(proxyID, testProxyASN, "") != expectQuality {
  92. return errors.TraceNew("unexpected HasQuality")
  93. }
  94. }
  95. return nil
  96. }
  97. func runTestProxyQualityReporter() error {
  98. // This unit test exercises the report queue state, but not the report
  99. // requests. ProxyQualityReporter.requestScheduler/sendToBrokers are
  100. // exercised in TestInproxy.
  101. r, err := NewProxyQualityReporter(
  102. newTestLogger(),
  103. nil,
  104. SessionPrivateKey{},
  105. nil,
  106. nil,
  107. nil)
  108. if err != nil {
  109. return errors.Trace(err)
  110. }
  111. maxEntries := 10
  112. expectedRequestCount := 2
  113. r.SetRequestParameters(maxEntries, 0, 0, 0)
  114. var proxyKeys []ProxyQualityKey
  115. testProxyASN := "65537"
  116. for i := 0; i < 20; i++ {
  117. proxyID, err := MakeID()
  118. if err != nil {
  119. return errors.Trace(err)
  120. }
  121. proxyKey := MakeProxyQualityKey(proxyID, testProxyASN)
  122. for j := 0; j < 10; j++ {
  123. testClientASN := fmt.Sprintf("%d", 65538+j)
  124. for k := 0; k <= i; k++ {
  125. r.ReportQuality(
  126. proxyID, testProxyASN, testClientASN)
  127. }
  128. }
  129. proxyKeys = append(proxyKeys, proxyKey)
  130. }
  131. if r.reportQueue.Len() != len(proxyKeys) {
  132. return errors.TraceNew("unexpected queue size")
  133. }
  134. for count := 0; count < expectedRequestCount; count++ {
  135. requestCounts := r.prepareNextRequest()
  136. if len(requestCounts) == 0 {
  137. return errors.TraceNew("unexpected requestCounts")
  138. }
  139. for i := count * 10; i < count*10+10; i++ {
  140. counts, ok := requestCounts[proxyKeys[i]]
  141. if !ok {
  142. return errors.TraceNew("missing proxyID")
  143. }
  144. for j := 0; j < 10; j++ {
  145. testClientASN := fmt.Sprintf("%d", 65538+j)
  146. count, ok := counts[testClientASN]
  147. if !ok {
  148. return errors.TraceNew("missing client ASN")
  149. }
  150. if count != i+1 {
  151. return errors.Tracef("unexpected count")
  152. }
  153. }
  154. }
  155. }
  156. if len(r.prepareNextRequest()) != 0 {
  157. return errors.TraceNew("unexpected prepareNextRequest")
  158. }
  159. return nil
  160. }