tactics_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /*
  2. * Copyright (c) 2020, 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 server
  20. import (
  21. "fmt"
  22. "io/ioutil"
  23. "path/filepath"
  24. "testing"
  25. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  26. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tactics"
  27. )
  28. func TestServerTacticsParametersCache(t *testing.T) {
  29. tacticsConfigJSONFormat := `
  30. {
  31. "RequestPublicKey" : "%s",
  32. "RequestPrivateKey" : "%s",
  33. "RequestObfuscatedKey" : "%s",
  34. "DefaultTactics" : {
  35. "TTL" : "60s",
  36. "Probability" : 1.0,
  37. "Parameters" : {
  38. "ConnectionWorkerPoolSize" : 1
  39. }
  40. },
  41. "FilteredTactics" : [
  42. {
  43. "Filter" : {
  44. "Regions": ["R1"]
  45. },
  46. "Tactics" : {
  47. "Parameters" : {
  48. "ConnectionWorkerPoolSize" : 2
  49. }
  50. }
  51. },
  52. {
  53. "Filter" : {
  54. "Regions": ["R2"],
  55. "ISPs": ["I2a"]
  56. },
  57. "Tactics" : {
  58. "Parameters" : {
  59. "ConnectionWorkerPoolSize" : 3
  60. }
  61. }
  62. },
  63. {
  64. "Filter" : {
  65. "Regions": ["R2"],
  66. "ISPs": ["I2b"]
  67. },
  68. "Tactics" : {
  69. "Parameters" : {
  70. "ConnectionWorkerPoolSize" : 4
  71. }
  72. }
  73. },
  74. {
  75. "Filter" : {
  76. "Regions": ["R2"],
  77. "ISPs": ["I2c"]
  78. },
  79. "Tactics" : {
  80. "Parameters" : {
  81. "ConnectionWorkerPoolSize" : 4
  82. }
  83. }
  84. }
  85. ]
  86. }
  87. `
  88. tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey, err :=
  89. tactics.GenerateKeys()
  90. if err != nil {
  91. t.Fatalf("error generating tactics keys: %s", err)
  92. }
  93. tacticsConfigJSON := fmt.Sprintf(
  94. tacticsConfigJSONFormat,
  95. tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey)
  96. tacticsConfigFilename := filepath.Join(testDataDirName, "tactics_config.json")
  97. err = ioutil.WriteFile(tacticsConfigFilename, []byte(tacticsConfigJSON), 0600)
  98. if err != nil {
  99. t.Fatalf("error paving tactics config file: %s", err)
  100. }
  101. tacticsServer, err := tactics.NewServer(
  102. nil,
  103. nil,
  104. nil,
  105. tacticsConfigFilename)
  106. if err != nil {
  107. t.Fatalf("NewServer failed: %s", err)
  108. }
  109. support := &SupportServices{
  110. TacticsServer: tacticsServer,
  111. }
  112. support.ReplayCache = NewReplayCache(support)
  113. support.ServerTacticsParametersCache =
  114. NewServerTacticsParametersCache(support)
  115. keySplitTestCases := []struct {
  116. description string
  117. geoIPData GeoIPData
  118. expectedConnectionWorkerPoolSize int
  119. expectedCacheSizeBefore int
  120. expectedCacheSizeAfter int
  121. expectedParameterReferencesSizeAfter int
  122. }{
  123. {
  124. "add new cache entry, default parameter",
  125. GeoIPData{Country: "R0", ISP: "I0", City: "C0"},
  126. 1,
  127. 0, 1, 1,
  128. },
  129. {
  130. "region already cached, region-only key",
  131. GeoIPData{Country: "R0", ISP: "I1", City: "C1"},
  132. 1,
  133. 1, 1, 1,
  134. },
  135. {
  136. "add new cache entry, filtered parameter",
  137. GeoIPData{Country: "R1", ISP: "I1a", City: "C1a"},
  138. 2,
  139. 1, 2, 2,
  140. },
  141. {
  142. "region already cached, region-only key",
  143. GeoIPData{Country: "R1", ISP: "I1a", City: "C1a"},
  144. 2,
  145. 2, 2, 2,
  146. },
  147. {
  148. "region already cached, region-only key",
  149. GeoIPData{Country: "R1", ISP: "I1b", City: "C1b"},
  150. 2,
  151. 2, 2, 2,
  152. },
  153. {
  154. "region already cached, region-only key",
  155. GeoIPData{Country: "R1", ISP: "I1b", City: "C1c"},
  156. 2,
  157. 2, 2, 2,
  158. },
  159. {
  160. "add new cache entry, filtered parameter, region/ISP key",
  161. GeoIPData{Country: "R2", ISP: "I2a", City: "C2a"},
  162. 3,
  163. 2, 3, 3,
  164. },
  165. {
  166. "region/ISP already cached",
  167. GeoIPData{Country: "R2", ISP: "I2a", City: "C2a"},
  168. 3,
  169. 3, 3, 3,
  170. },
  171. {
  172. "region/ISP already cached, city is ignored",
  173. GeoIPData{Country: "R2", ISP: "I2a", City: "C2b"},
  174. 3,
  175. 3, 3, 3,
  176. },
  177. {
  178. "add new cache entry, filtered parameter, region/ISP key",
  179. GeoIPData{Country: "R2", ISP: "I2b", City: "C2a"},
  180. 4,
  181. 3, 4, 4,
  182. },
  183. {
  184. "region/ISP already cached, city is ignored",
  185. GeoIPData{Country: "R2", ISP: "I2b", City: "C2b"},
  186. 4,
  187. 4, 4, 4,
  188. },
  189. {
  190. "add new cache entry, filtered parameter, region/ISP key, duplicate parameters",
  191. GeoIPData{Country: "R2", ISP: "I2c", City: "C2a"},
  192. 4,
  193. 4, 5, 4,
  194. },
  195. }
  196. for _, testCase := range keySplitTestCases {
  197. t.Run(testCase.description, func(t *testing.T) {
  198. support.ServerTacticsParametersCache.mutex.Lock()
  199. cacheSize := support.ServerTacticsParametersCache.tacticsCache.Len()
  200. support.ServerTacticsParametersCache.mutex.Unlock()
  201. if cacheSize != testCase.expectedCacheSizeBefore {
  202. t.Fatalf("unexpected tacticsCache size before lookup: %d", cacheSize)
  203. }
  204. p, err := support.ServerTacticsParametersCache.Get(testCase.geoIPData)
  205. if err != nil {
  206. t.Fatalf("ServerTacticsParametersCache.Get failed: %d", err)
  207. }
  208. connectionWorkerPoolSize := p.Int(parameters.ConnectionWorkerPoolSize)
  209. if connectionWorkerPoolSize != testCase.expectedConnectionWorkerPoolSize {
  210. t.Fatalf("unexpected ConnectionWorkerPoolSize value: %d", connectionWorkerPoolSize)
  211. }
  212. support.ServerTacticsParametersCache.mutex.Lock()
  213. cacheSize = support.ServerTacticsParametersCache.tacticsCache.Len()
  214. support.ServerTacticsParametersCache.mutex.Unlock()
  215. if cacheSize != testCase.expectedCacheSizeAfter {
  216. t.Fatalf("unexpected cache size after lookup: %d", cacheSize)
  217. }
  218. support.ServerTacticsParametersCache.mutex.Lock()
  219. paramRefsSize := len(support.ServerTacticsParametersCache.parameterReferences)
  220. support.ServerTacticsParametersCache.mutex.Unlock()
  221. if paramRefsSize != testCase.expectedParameterReferencesSizeAfter {
  222. t.Fatalf("unexpected parameterReferences size after lookup: %d", paramRefsSize)
  223. }
  224. })
  225. }
  226. metrics := support.ServerTacticsParametersCache.GetMetrics()
  227. if metrics["server_tactics_max_cache_entries"].(int64) != 5 ||
  228. metrics["server_tactics_max_parameter_references"].(int64) != 4 ||
  229. metrics["server_tactics_cache_hit_count"].(int64) != 7 ||
  230. metrics["server_tactics_cache_miss_count"].(int64) != 5 {
  231. t.Fatalf("unexpected metrics: %v", metrics)
  232. }
  233. // Test: force eviction and check parameterReferences cleanup.
  234. for i := 0; i < TACTICS_CACHE_MAX_ENTRIES*2; i++ {
  235. _, err := support.ServerTacticsParametersCache.Get(
  236. GeoIPData{Country: "R2", ISP: fmt.Sprintf("I-%d", i), City: "C2a"})
  237. if err != nil {
  238. t.Fatalf("ServerTacticsParametersCache.Get failed: %d", err)
  239. }
  240. }
  241. support.ServerTacticsParametersCache.mutex.Lock()
  242. cacheSize := support.ServerTacticsParametersCache.tacticsCache.Len()
  243. paramRefsSize := len(support.ServerTacticsParametersCache.parameterReferences)
  244. support.ServerTacticsParametersCache.mutex.Unlock()
  245. if cacheSize != TACTICS_CACHE_MAX_ENTRIES {
  246. t.Fatalf("unexpected tacticsCache size before lookup: %d", cacheSize)
  247. }
  248. if paramRefsSize != 1 {
  249. t.Fatalf("unexpected parameterReferences size after lookup: %d", paramRefsSize)
  250. }
  251. }