tactics_test.go 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  1. /*
  2. * Copyright (c) 2018, 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 tactics
  20. import (
  21. "bytes"
  22. "context"
  23. "fmt"
  24. "io/ioutil"
  25. "net"
  26. "net/http"
  27. "os"
  28. "reflect"
  29. "strings"
  30. "testing"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  35. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/stacktrace"
  36. )
  37. func TestTactics(t *testing.T) {
  38. // Server tactics configuration
  39. // Long and short region lists test both map and slice lookups.
  40. //
  41. // Repeated median aggregation tests aggregation memoization.
  42. //
  43. // The test-packetman-spec tests a reference between a filter tactics
  44. // and default tactics.
  45. tacticsConfigTemplate := `
  46. {
  47. "RequestPublicKey" : "%s",
  48. "RequestPrivateKey" : "%s",
  49. "RequestObfuscatedKey" : "%s",
  50. "DefaultTactics" : {
  51. "TTL" : "1s",
  52. "Probability" : %0.1f,
  53. "Parameters" : {
  54. "NetworkLatencyMultiplier" : %0.1f,
  55. "ServerPacketManipulationSpecs" : [{"Name": "test-packetman-spec", "PacketSpecs": [["TCP-flags S"]]}]
  56. }
  57. },
  58. "FilteredTactics" : [
  59. {
  60. "Filter" : {
  61. "Regions": ["R1", "R2", "R3", "R4", "R5", "R6"],
  62. "APIParameters" : {"client_platform" : ["P1"]},
  63. "SpeedTestRTTMilliseconds" : {
  64. "Aggregation" : "Median",
  65. "AtLeast" : 1
  66. }
  67. },
  68. "Tactics" : {
  69. "Parameters" : {
  70. "ConnectionWorkerPoolSize" : %d
  71. }
  72. }
  73. },
  74. {
  75. "Filter" : {
  76. "Regions": ["R1"],
  77. "ASNs": ["1"],
  78. "APIParameters" : {"client_platform" : ["P1"], "client_version": ["V1"]},
  79. "SpeedTestRTTMilliseconds" : {
  80. "Aggregation" : "Median",
  81. "AtLeast" : 1
  82. }
  83. },
  84. "Tactics" : {
  85. "Parameters" : {
  86. %s
  87. }
  88. }
  89. },
  90. {
  91. "Filter" : {
  92. "Regions": ["R2"]
  93. },
  94. "Tactics" : {
  95. "Parameters" : {
  96. "ConnectionWorkerPoolSize" : %d
  97. }
  98. }
  99. },
  100. {
  101. "Filter" : {
  102. "Regions": ["R7"]
  103. },
  104. "Tactics" : {
  105. "Parameters" : {
  106. "ServerProtocolPacketManipulations": {"All" : ["test-packetman-spec"]}
  107. }
  108. }
  109. }
  110. ]
  111. }
  112. `
  113. if stringLookupThreshold != 5 {
  114. t.Fatalf("unexpected stringLookupThreshold")
  115. }
  116. encodedRequestPublicKey, encodedRequestPrivateKey, encodedObfuscatedKey, err := GenerateKeys()
  117. if err != nil {
  118. t.Fatalf("GenerateKeys failed: %s", err)
  119. }
  120. tacticsProbability := 0.5
  121. tacticsNetworkLatencyMultiplier := 2.0
  122. tacticsConnectionWorkerPoolSize := 5
  123. tacticsLimitTunnelProtocols := protocol.TunnelProtocols{"OSSH", "SSH"}
  124. jsonTacticsLimitTunnelProtocols := `"LimitTunnelProtocols" : ["OSSH", "SSH"]`
  125. expectedApplyCount := 3
  126. tacticsConfig := fmt.Sprintf(
  127. tacticsConfigTemplate,
  128. encodedRequestPublicKey,
  129. encodedRequestPrivateKey,
  130. encodedObfuscatedKey,
  131. tacticsProbability,
  132. tacticsNetworkLatencyMultiplier,
  133. tacticsConnectionWorkerPoolSize,
  134. jsonTacticsLimitTunnelProtocols,
  135. tacticsConnectionWorkerPoolSize+1)
  136. file, err := ioutil.TempFile("", "tactics.config")
  137. if err != nil {
  138. t.Fatalf("TempFile create failed: %s", err)
  139. }
  140. _, err = file.Write([]byte(tacticsConfig))
  141. if err != nil {
  142. t.Fatalf("TempFile write failed: %s", err)
  143. }
  144. file.Close()
  145. configFileName := file.Name()
  146. defer os.Remove(configFileName)
  147. // Configure and run server
  148. // Mock server uses an insecure HTTP transport that exposes endpoint names
  149. clientGeoIPData := common.GeoIPData{Country: "R1", ASN: "1"}
  150. logger := newTestLogger()
  151. validator := func(
  152. apiParams common.APIParameters) error {
  153. expectedParams := []string{"client_platform", "client_version"}
  154. for _, name := range expectedParams {
  155. value, ok := apiParams[name]
  156. if !ok {
  157. return fmt.Errorf("missing param: %s", name)
  158. }
  159. _, ok = value.(string)
  160. if !ok {
  161. return fmt.Errorf("invalid param type: %s", name)
  162. }
  163. }
  164. return nil
  165. }
  166. formatter := func(
  167. geoIPData common.GeoIPData,
  168. apiParams common.APIParameters) common.LogFields {
  169. return common.LogFields(apiParams)
  170. }
  171. server, err := NewServer(
  172. logger,
  173. formatter,
  174. validator,
  175. configFileName)
  176. if err != nil {
  177. t.Fatalf("NewServer failed: %s", err)
  178. }
  179. listener, err := net.Listen("tcp", "127.0.0.1:0")
  180. if err != nil {
  181. t.Fatalf("Listen failed: %s", err)
  182. }
  183. serverAddress := listener.Addr().String()
  184. go func() {
  185. serveMux := http.NewServeMux()
  186. serveMux.HandleFunc(
  187. "/",
  188. func(w http.ResponseWriter, r *http.Request) {
  189. // Ensure RTT takes at least 1 millisecond for speed test
  190. time.Sleep(1 * time.Millisecond)
  191. endPoint := strings.Trim(r.URL.Path, "/")
  192. if !server.HandleEndPoint(endPoint, clientGeoIPData, w, r) {
  193. http.NotFound(w, r)
  194. }
  195. })
  196. httpServer := &http.Server{
  197. Addr: serverAddress,
  198. Handler: serveMux,
  199. }
  200. httpServer.Serve(listener)
  201. }()
  202. // Configure client
  203. params, err := parameters.NewParameters(
  204. func(err error) {
  205. t.Fatalf("Parameters getValue failed: %s", err)
  206. })
  207. if err != nil {
  208. t.Fatalf("NewParameters failed: %s", err)
  209. }
  210. networkID := "NETWORK1"
  211. getNetworkID := func() string { return networkID }
  212. apiParams := common.APIParameters{
  213. "client_platform": "P1",
  214. "client_version": "V1"}
  215. storer := newTestStorer()
  216. endPointRegion := "R0"
  217. endPointProtocol := "OSSH"
  218. differentEndPointProtocol := "SSH"
  219. obfuscatedRoundTripper := func(
  220. ctx context.Context,
  221. endPoint string,
  222. requestBody []byte) ([]byte, error) {
  223. // This mock ObfuscatedRoundTripper does not actually obfuscate the endpoint
  224. // value.
  225. request, err := http.NewRequest(
  226. "POST",
  227. fmt.Sprintf("http://%s/%s", serverAddress, endPoint),
  228. bytes.NewReader(requestBody))
  229. if err != nil {
  230. return nil, err
  231. }
  232. request = request.WithContext(ctx)
  233. response, err := http.DefaultClient.Do(request)
  234. if err != nil {
  235. return nil, err
  236. }
  237. defer response.Body.Close()
  238. if response.StatusCode != http.StatusOK {
  239. return nil, fmt.Errorf("HTTP request failed: %d", response.StatusCode)
  240. }
  241. body, err := ioutil.ReadAll(response.Body)
  242. if err != nil {
  243. return nil, err
  244. }
  245. return body, nil
  246. }
  247. // There should be no local tactics
  248. tacticsRecord, err := UseStoredTactics(storer, networkID)
  249. if err != nil {
  250. t.Fatalf("UseStoredTactics failed: %s", err)
  251. }
  252. if tacticsRecord != nil {
  253. t.Fatalf("unexpected tactics record")
  254. }
  255. // Helper to check that expected tactics parameters are returned
  256. checkParameters := func(r *Record) {
  257. p, err := parameters.NewParameters(nil)
  258. if err != nil {
  259. t.Fatalf("NewParameters failed: %s", err)
  260. }
  261. if r.Tactics.Probability != tacticsProbability {
  262. t.Fatalf("Unexpected probability: %f", r.Tactics.Probability)
  263. }
  264. // skipOnError is true for Psiphon clients
  265. counts, err := p.Set(r.Tag, true, r.Tactics.Parameters)
  266. if err != nil {
  267. t.Fatalf("Apply failed: %s", err)
  268. }
  269. if counts[0] != expectedApplyCount {
  270. t.Fatalf("Unexpected apply count: %d", counts[0])
  271. }
  272. multipler := p.Get().Float(parameters.NetworkLatencyMultiplier)
  273. if multipler != tacticsNetworkLatencyMultiplier {
  274. t.Fatalf("Unexpected NetworkLatencyMultiplier: %v", multipler)
  275. }
  276. connectionWorkerPoolSize := p.Get().Int(parameters.ConnectionWorkerPoolSize)
  277. if connectionWorkerPoolSize != tacticsConnectionWorkerPoolSize {
  278. t.Fatalf("Unexpected ConnectionWorkerPoolSize: %v", connectionWorkerPoolSize)
  279. }
  280. limitTunnelProtocols := p.Get().TunnelProtocols(parameters.LimitTunnelProtocols)
  281. if !reflect.DeepEqual(limitTunnelProtocols, tacticsLimitTunnelProtocols) {
  282. t.Fatalf("Unexpected LimitTunnelProtocols: %v", limitTunnelProtocols)
  283. }
  284. }
  285. // Initial tactics request; will also run a speed test
  286. // Request should complete in < 1 second
  287. ctx, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second)
  288. initialFetchTacticsRecord, err := FetchTactics(
  289. ctx,
  290. params,
  291. storer,
  292. getNetworkID,
  293. apiParams,
  294. endPointProtocol,
  295. endPointRegion,
  296. encodedRequestPublicKey,
  297. encodedObfuscatedKey,
  298. obfuscatedRoundTripper)
  299. cancelFunc()
  300. if err != nil {
  301. t.Fatalf("FetchTactics failed: %s", err)
  302. }
  303. if initialFetchTacticsRecord == nil {
  304. t.Fatalf("expected tactics record")
  305. }
  306. checkParameters(initialFetchTacticsRecord)
  307. // There should now be cached local tactics
  308. storedTacticsRecord, err := UseStoredTactics(storer, networkID)
  309. if err != nil {
  310. t.Fatalf("UseStoredTactics failed: %s", err)
  311. }
  312. if storedTacticsRecord == nil {
  313. t.Fatalf("expected stored tactics record")
  314. }
  315. // Strip monotonic component so comparisons will work
  316. initialFetchTacticsRecord.Expiry = initialFetchTacticsRecord.Expiry.Round(0)
  317. if !reflect.DeepEqual(initialFetchTacticsRecord, storedTacticsRecord) {
  318. t.Fatalf("tactics records are not identical:\n\n%#v\n\n%#v\n\n",
  319. initialFetchTacticsRecord, storedTacticsRecord)
  320. }
  321. checkParameters(storedTacticsRecord)
  322. // There should now be a speed test sample
  323. speedTestSamples, err := getSpeedTestSamples(storer, networkID)
  324. if err != nil {
  325. t.Fatalf("getSpeedTestSamples failed: %s", err)
  326. }
  327. if len(speedTestSamples) != 1 {
  328. t.Fatalf("unexpected speed test samples count")
  329. }
  330. // Wait for tactics to expire
  331. time.Sleep(1 * time.Second)
  332. storedTacticsRecord, err = UseStoredTactics(storer, networkID)
  333. if err != nil {
  334. t.Fatalf("UseStoredTactics failed: %s", err)
  335. }
  336. if storedTacticsRecord != nil {
  337. t.Fatalf("unexpected stored tactics record")
  338. }
  339. // Next fetch should merge empty payload as tag matches
  340. // TODO: inspect tactics response payload
  341. fetchTacticsRecord, err := FetchTactics(
  342. context.Background(),
  343. params,
  344. storer,
  345. getNetworkID,
  346. apiParams,
  347. endPointProtocol,
  348. endPointRegion,
  349. encodedRequestPublicKey,
  350. encodedObfuscatedKey,
  351. obfuscatedRoundTripper)
  352. if err != nil {
  353. t.Fatalf("FetchTactics failed: %s", err)
  354. }
  355. if fetchTacticsRecord == nil {
  356. t.Fatalf("expected tactics record")
  357. }
  358. if initialFetchTacticsRecord.Tag != fetchTacticsRecord.Tag {
  359. t.Fatalf("tags are not identical")
  360. }
  361. if initialFetchTacticsRecord.Expiry.Equal(fetchTacticsRecord.Expiry) {
  362. t.Fatalf("expiries unexpectedly identical")
  363. }
  364. if !reflect.DeepEqual(initialFetchTacticsRecord.Tactics, fetchTacticsRecord.Tactics) {
  365. t.Fatalf("tactics are not identical:\n\n%#v\n\n%#v\n\n",
  366. initialFetchTacticsRecord.Tactics, fetchTacticsRecord.Tactics)
  367. }
  368. checkParameters(fetchTacticsRecord)
  369. // Modify tactics configuration to change payload
  370. tacticsConnectionWorkerPoolSize = 6
  371. tacticsLimitTunnelProtocols = protocol.TunnelProtocols{}
  372. jsonTacticsLimitTunnelProtocols = ``
  373. expectedApplyCount = 2
  374. // Omitting LimitTunnelProtocols entirely tests this bug fix: When a new
  375. // tactics payload is obtained, all previous parameters should be cleared.
  376. //
  377. // In the bug, any previous parameters not in the new tactics were
  378. // incorrectly retained. In this test case, LimitTunnelProtocols is
  379. // omitted in the new tactics; if FetchTactics fails to clear the old
  380. // LimitTunnelProtocols then the test will fail.
  381. tacticsConfig = fmt.Sprintf(
  382. tacticsConfigTemplate,
  383. encodedRequestPublicKey,
  384. encodedRequestPrivateKey,
  385. encodedObfuscatedKey,
  386. tacticsProbability,
  387. tacticsNetworkLatencyMultiplier,
  388. tacticsConnectionWorkerPoolSize,
  389. jsonTacticsLimitTunnelProtocols,
  390. tacticsConnectionWorkerPoolSize+1)
  391. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  392. if err != nil {
  393. t.Fatalf("WriteFile failed: %s", err)
  394. }
  395. reloaded, err := server.Reload()
  396. if err != nil {
  397. t.Fatalf("Reload failed: %s", err)
  398. }
  399. if !reloaded {
  400. t.Fatalf("Server config failed to reload")
  401. }
  402. // Next fetch should return a different payload
  403. fetchTacticsRecord, err = FetchTactics(
  404. context.Background(),
  405. params,
  406. storer,
  407. getNetworkID,
  408. apiParams,
  409. endPointProtocol,
  410. endPointRegion,
  411. encodedRequestPublicKey,
  412. encodedObfuscatedKey,
  413. obfuscatedRoundTripper)
  414. if err != nil {
  415. t.Fatalf("FetchTactics failed: %s", err)
  416. }
  417. if fetchTacticsRecord == nil {
  418. t.Fatalf("expected tactics record")
  419. }
  420. if initialFetchTacticsRecord.Tag == fetchTacticsRecord.Tag {
  421. t.Fatalf("tags unexpectedly identical")
  422. }
  423. if initialFetchTacticsRecord.Expiry.Equal(fetchTacticsRecord.Expiry) {
  424. t.Fatalf("expires unexpectedly identical")
  425. }
  426. if reflect.DeepEqual(initialFetchTacticsRecord.Tactics, fetchTacticsRecord.Tactics) {
  427. t.Fatalf("tactics unexpectedly identical")
  428. }
  429. checkParameters(fetchTacticsRecord)
  430. // Exercise handshake transport of tactics
  431. // Wait for tactics to expire; handshake should renew
  432. time.Sleep(1 * time.Second)
  433. handshakeParams := common.APIParameters{
  434. "client_platform": "P1",
  435. "client_version": "V1"}
  436. err = SetTacticsAPIParameters(storer, networkID, handshakeParams)
  437. if err != nil {
  438. t.Fatalf("SetTacticsAPIParameters failed: %s", err)
  439. }
  440. tacticsPayload, err := server.GetTacticsPayload(clientGeoIPData, handshakeParams)
  441. if err != nil {
  442. t.Fatalf("GetTacticsPayload failed: %s", err)
  443. }
  444. handshakeTacticsRecord, err := HandleTacticsPayload(storer, networkID, tacticsPayload)
  445. if err != nil {
  446. t.Fatalf("HandleTacticsPayload failed: %s", err)
  447. }
  448. if handshakeTacticsRecord == nil {
  449. t.Fatalf("expected tactics record")
  450. }
  451. if fetchTacticsRecord.Tag != handshakeTacticsRecord.Tag {
  452. t.Fatalf("tags are not identical")
  453. }
  454. if fetchTacticsRecord.Expiry.Equal(handshakeTacticsRecord.Expiry) {
  455. t.Fatalf("expiries unexpectedly identical")
  456. }
  457. if !reflect.DeepEqual(fetchTacticsRecord.Tactics, handshakeTacticsRecord.Tactics) {
  458. t.Fatalf("tactics are not identical:\n\n%#v\n\n%#v\n\n",
  459. fetchTacticsRecord.Tactics, handshakeTacticsRecord.Tactics)
  460. }
  461. checkParameters(handshakeTacticsRecord)
  462. // Now there should be stored tactics
  463. storedTacticsRecord, err = UseStoredTactics(storer, networkID)
  464. if err != nil {
  465. t.Fatalf("UseStoredTactics failed: %s", err)
  466. }
  467. if storedTacticsRecord == nil {
  468. t.Fatalf("expected stored tactics record")
  469. }
  470. handshakeTacticsRecord.Expiry = handshakeTacticsRecord.Expiry.Round(0)
  471. if !reflect.DeepEqual(handshakeTacticsRecord, storedTacticsRecord) {
  472. t.Fatalf("tactics records are not identical:\n\n%#v\n\n%#v\n\n",
  473. handshakeTacticsRecord, storedTacticsRecord)
  474. }
  475. checkParameters(storedTacticsRecord)
  476. // Change network ID, should be no stored tactics
  477. networkID = "NETWORK2"
  478. storedTacticsRecord, err = UseStoredTactics(storer, networkID)
  479. if err != nil {
  480. t.Fatalf("UseStoredTactics failed: %s", err)
  481. }
  482. if storedTacticsRecord != nil {
  483. t.Fatalf("unexpected stored tactics record")
  484. }
  485. // Exercise speed test sample truncation
  486. maxSamples := params.Get().Int(parameters.SpeedTestMaxSampleCount)
  487. for i := 0; i < maxSamples*2; i++ {
  488. response, err := MakeSpeedTestResponse(0, 0)
  489. if err != nil {
  490. t.Fatalf("MakeSpeedTestResponse failed: %s", err)
  491. }
  492. err = AddSpeedTestSample(
  493. params,
  494. storer,
  495. networkID,
  496. "",
  497. differentEndPointProtocol,
  498. 100*time.Millisecond,
  499. nil,
  500. response)
  501. if err != nil {
  502. t.Fatalf("AddSpeedTestSample failed: %s", err)
  503. }
  504. }
  505. speedTestSamples, err = getSpeedTestSamples(storer, networkID)
  506. if err != nil {
  507. t.Fatalf("getSpeedTestSamples failed: %s", err)
  508. }
  509. if len(speedTestSamples) != maxSamples {
  510. t.Fatalf("unexpected speed test samples count")
  511. }
  512. for _, sample := range speedTestSamples {
  513. if sample.EndPointProtocol == endPointProtocol {
  514. t.Fatalf("unexpected old speed test sample")
  515. }
  516. }
  517. // Fetch should fail when using incorrect keys
  518. encodedIncorrectRequestPublicKey, _, encodedIncorrectObfuscatedKey, err := GenerateKeys()
  519. if err != nil {
  520. t.Fatalf("GenerateKeys failed: %s", err)
  521. }
  522. _, err = FetchTactics(
  523. context.Background(),
  524. params,
  525. storer,
  526. getNetworkID,
  527. apiParams,
  528. endPointProtocol,
  529. endPointRegion,
  530. encodedIncorrectRequestPublicKey,
  531. encodedObfuscatedKey,
  532. obfuscatedRoundTripper)
  533. if err == nil {
  534. t.Fatalf("FetchTactics succeeded unexpectedly with incorrect request key")
  535. }
  536. _, err = FetchTactics(
  537. context.Background(),
  538. params,
  539. storer,
  540. getNetworkID,
  541. apiParams,
  542. endPointProtocol,
  543. endPointRegion,
  544. encodedRequestPublicKey,
  545. encodedIncorrectObfuscatedKey,
  546. obfuscatedRoundTripper)
  547. if err == nil {
  548. t.Fatalf("FetchTactics succeeded unexpectedly with incorrect obfuscated key")
  549. }
  550. // When no keys are supplied, untunneled tactics requests are not supported, but
  551. // handshake tactics (GetTacticsPayload) should still work.
  552. tacticsConfig = fmt.Sprintf(
  553. tacticsConfigTemplate,
  554. "",
  555. "",
  556. "",
  557. tacticsProbability,
  558. tacticsNetworkLatencyMultiplier,
  559. tacticsConnectionWorkerPoolSize,
  560. jsonTacticsLimitTunnelProtocols,
  561. tacticsConnectionWorkerPoolSize+1)
  562. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  563. if err != nil {
  564. t.Fatalf("WriteFile failed: %s", err)
  565. }
  566. reloaded, err = server.Reload()
  567. if err != nil {
  568. t.Fatalf("Reload failed: %s", err)
  569. }
  570. if !reloaded {
  571. t.Fatalf("Server config failed to reload")
  572. }
  573. _, err = server.GetTacticsPayload(clientGeoIPData, handshakeParams)
  574. if err != nil {
  575. t.Fatalf("GetTacticsPayload failed: %s", err)
  576. }
  577. handled := server.HandleEndPoint(TACTICS_END_POINT, clientGeoIPData, nil, nil)
  578. if handled {
  579. t.Fatalf("HandleEndPoint unexpectedly handled request")
  580. }
  581. handled = server.HandleEndPoint(SPEED_TEST_END_POINT, clientGeoIPData, nil, nil)
  582. if handled {
  583. t.Fatalf("HandleEndPoint unexpectedly handled request")
  584. }
  585. // TODO: test replay attack defence
  586. // TODO: test Server.Validate with invalid tactics configurations
  587. }
  588. func TestTacticsFilterGeoIPScope(t *testing.T) {
  589. encodedRequestPublicKey, encodedRequestPrivateKey, encodedObfuscatedKey, err := GenerateKeys()
  590. if err != nil {
  591. t.Fatalf("GenerateKeys failed: %s", err)
  592. }
  593. tacticsConfigTemplate := fmt.Sprintf(`
  594. {
  595. "RequestPublicKey" : "%s",
  596. "RequestPrivateKey" : "%s",
  597. "RequestObfuscatedKey" : "%s",
  598. "DefaultTactics" : {
  599. "TTL" : "60s",
  600. "Probability" : 1.0
  601. },
  602. %%s
  603. }
  604. `, encodedRequestPublicKey, encodedRequestPrivateKey, encodedObfuscatedKey)
  605. // Test: region-only scope
  606. filteredTactics := `
  607. "FilteredTactics" : [
  608. {
  609. "Filter" : {
  610. "Regions": ["R1", "R2", "R3"]
  611. }
  612. },
  613. {
  614. "Filter" : {
  615. "Regions": ["R4", "R5", "R6"]
  616. }
  617. }
  618. ]
  619. `
  620. tacticsConfig := fmt.Sprintf(tacticsConfigTemplate, filteredTactics)
  621. file, err := ioutil.TempFile("", "tactics.config")
  622. if err != nil {
  623. t.Fatalf("TempFile create failed: %s", err)
  624. }
  625. _, err = file.Write([]byte(tacticsConfig))
  626. if err != nil {
  627. t.Fatalf("TempFile write failed: %s", err)
  628. }
  629. file.Close()
  630. configFileName := file.Name()
  631. defer os.Remove(configFileName)
  632. server, err := NewServer(
  633. nil,
  634. nil,
  635. nil,
  636. configFileName)
  637. if err != nil {
  638. t.Fatalf("NewServer failed: %s", err)
  639. }
  640. reload := func() {
  641. tacticsConfig = fmt.Sprintf(tacticsConfigTemplate, filteredTactics)
  642. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  643. if err != nil {
  644. t.Fatalf("WriteFile failed: %s", err)
  645. }
  646. reloaded, err := server.Reload()
  647. if err != nil {
  648. t.Fatalf("Reload failed: %s", err)
  649. }
  650. if !reloaded {
  651. t.Fatalf("Server config failed to reload")
  652. }
  653. }
  654. geoIPData := common.GeoIPData{
  655. Country: "R0",
  656. ISP: "I0",
  657. ASN: "0",
  658. City: "C0",
  659. }
  660. scope := server.GetFilterGeoIPScope(geoIPData)
  661. if scope != GeoIPScopeRegion {
  662. t.Fatalf("unexpected scope: %b", scope)
  663. }
  664. // Test: ISP-only scope
  665. filteredTactics = `
  666. "FilteredTactics" : [
  667. {
  668. "Filter" : {
  669. "ISPs": ["I1", "I2", "I3"]
  670. }
  671. },
  672. {
  673. "Filter" : {
  674. "ISPs": ["I4", "I5", "I6"]
  675. }
  676. }
  677. ]
  678. `
  679. reload()
  680. scope = server.GetFilterGeoIPScope(geoIPData)
  681. if scope != GeoIPScopeISP {
  682. t.Fatalf("unexpected scope: %b", scope)
  683. }
  684. // Test: ASN-only scope
  685. filteredTactics = `
  686. "FilteredTactics" : [
  687. {
  688. "Filter" : {
  689. "ASNs": ["1", "2", "3"]
  690. }
  691. },
  692. {
  693. "Filter" : {
  694. "ASNs": ["4", "5", "6"]
  695. }
  696. }
  697. ]
  698. `
  699. reload()
  700. scope = server.GetFilterGeoIPScope(geoIPData)
  701. if scope != GeoIPScopeASN {
  702. t.Fatalf("unexpected scope: %b", scope)
  703. }
  704. // Test: City-only scope
  705. filteredTactics = `
  706. "FilteredTactics" : [
  707. {
  708. "Filter" : {
  709. "Cities": ["C1", "C2", "C3"]
  710. }
  711. },
  712. {
  713. "Filter" : {
  714. "Cities": ["C4", "C5", "C6"]
  715. }
  716. }
  717. ]
  718. `
  719. reload()
  720. scope = server.GetFilterGeoIPScope(geoIPData)
  721. if scope != GeoIPScopeCity {
  722. t.Fatalf("unexpected scope: %b", scope)
  723. }
  724. // Test: full scope
  725. filteredTactics = `
  726. "FilteredTactics" : [
  727. {
  728. "Filter" : {
  729. "Regions": ["R1", "R2", "R3"]
  730. }
  731. },
  732. {
  733. "Filter" : {
  734. "ISPs": ["I1", "I2", "I3"]
  735. }
  736. },
  737. {
  738. "Filter" : {
  739. "ASNs": ["1", "2", "3"]
  740. }
  741. },
  742. {
  743. "Filter" : {
  744. "Cities": ["C4", "C5", "C6"]
  745. }
  746. }
  747. ]
  748. `
  749. reload()
  750. scope = server.GetFilterGeoIPScope(geoIPData)
  751. if scope != GeoIPScopeRegion|GeoIPScopeISP|GeoIPScopeASN|GeoIPScopeCity {
  752. t.Fatalf("unexpected scope: %b", scope)
  753. }
  754. // Test: conditional scopes
  755. filteredTactics = `
  756. "FilteredTactics" : [
  757. {
  758. "Filter" : {
  759. "Regions": ["R1"]
  760. }
  761. },
  762. {
  763. "Filter" : {
  764. "Regions": ["R2"],
  765. "ISPs": ["I2a"]
  766. }
  767. },
  768. {
  769. "Filter" : {
  770. "Regions": ["R2"],
  771. "ISPs": ["I2b"]
  772. }
  773. },
  774. {
  775. "Filter" : {
  776. "Regions": ["R3"],
  777. "ISPs": ["I3a"],
  778. "Cities": ["C3a"]
  779. }
  780. },
  781. {
  782. "Filter" : {
  783. "Regions": ["R3"],
  784. "ISPs": ["I3b"],
  785. "Cities": ["C3b"]
  786. }
  787. },
  788. {
  789. "Filter" : {
  790. "Regions": ["R4"],
  791. "ASNs": ["4"]
  792. }
  793. },
  794. {
  795. "Filter" : {
  796. "Regions": ["R4"],
  797. "ASNs": ["4"]
  798. }
  799. },
  800. {
  801. "Filter" : {
  802. "Regions": ["R5"],
  803. "ASNs": ["5"],
  804. "Cities": ["C3a"]
  805. }
  806. },
  807. {
  808. "Filter" : {
  809. "Regions": ["R5"],
  810. "ASNs": ["5"],
  811. "Cities": ["C3b"]
  812. }
  813. }
  814. ]
  815. `
  816. reload()
  817. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R0"})
  818. if scope != GeoIPScopeRegion {
  819. t.Fatalf("unexpected scope: %b", scope)
  820. }
  821. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R1"})
  822. if scope != GeoIPScopeRegion {
  823. t.Fatalf("unexpected scope: %b", scope)
  824. }
  825. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R2"})
  826. if scope != GeoIPScopeRegion|GeoIPScopeISP {
  827. t.Fatalf("unexpected scope: %b", scope)
  828. }
  829. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R3"})
  830. if scope != GeoIPScopeRegion|GeoIPScopeISP|GeoIPScopeCity {
  831. t.Fatalf("unexpected scope: %b", scope)
  832. }
  833. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R4"})
  834. if scope != GeoIPScopeRegion|GeoIPScopeASN {
  835. t.Fatalf("unexpected scope: %b", scope)
  836. }
  837. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R5"})
  838. if scope != GeoIPScopeRegion|GeoIPScopeASN|GeoIPScopeCity {
  839. t.Fatalf("unexpected scope: %b", scope)
  840. }
  841. // Test: reset regional map optimization
  842. filteredTactics = `
  843. "FilteredTactics" : [
  844. {
  845. "Filter" : {
  846. "Regions": ["R1"],
  847. "ISPs": ["I1"]
  848. }
  849. },
  850. {
  851. "Filter" : {
  852. "Cities": ["C1"]
  853. }
  854. }
  855. ]
  856. `
  857. reload()
  858. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R0"})
  859. if scope != GeoIPScopeRegion|GeoIPScopeISP|GeoIPScopeCity {
  860. t.Fatalf("unexpected scope: %b", scope)
  861. }
  862. filteredTactics = `
  863. "FilteredTactics" : [
  864. {
  865. "Filter" : {
  866. "Regions": ["R1"],
  867. "Cities": ["C1"]
  868. }
  869. },
  870. {
  871. "Filter" : {
  872. "ISPs": ["I1"]
  873. }
  874. }
  875. ]
  876. `
  877. reload()
  878. scope = server.GetFilterGeoIPScope(common.GeoIPData{Country: "R0"})
  879. if scope != GeoIPScopeRegion|GeoIPScopeISP|GeoIPScopeCity {
  880. t.Fatalf("unexpected scope: %b", scope)
  881. }
  882. }
  883. type testStorer struct {
  884. tacticsRecords map[string][]byte
  885. speedTestSampleRecords map[string][]byte
  886. }
  887. func newTestStorer() *testStorer {
  888. return &testStorer{
  889. tacticsRecords: make(map[string][]byte),
  890. speedTestSampleRecords: make(map[string][]byte),
  891. }
  892. }
  893. func (s *testStorer) SetTacticsRecord(networkID string, record []byte) error {
  894. s.tacticsRecords[networkID] = record
  895. return nil
  896. }
  897. func (s *testStorer) GetTacticsRecord(networkID string) ([]byte, error) {
  898. return s.tacticsRecords[networkID], nil
  899. }
  900. func (s *testStorer) SetSpeedTestSamplesRecord(networkID string, record []byte) error {
  901. s.speedTestSampleRecords[networkID] = record
  902. return nil
  903. }
  904. func (s *testStorer) GetSpeedTestSamplesRecord(networkID string) ([]byte, error) {
  905. return s.speedTestSampleRecords[networkID], nil
  906. }
  907. type testLogger struct {
  908. }
  909. func newTestLogger() *testLogger {
  910. return &testLogger{}
  911. }
  912. func (l *testLogger) WithTrace() common.LogTrace {
  913. return &testLoggerTrace{trace: stacktrace.GetParentFunctionName()}
  914. }
  915. func (l *testLogger) WithTraceFields(fields common.LogFields) common.LogTrace {
  916. return &testLoggerTrace{
  917. trace: stacktrace.GetParentFunctionName(),
  918. fields: fields,
  919. }
  920. }
  921. func (l *testLogger) LogMetric(metric string, fields common.LogFields) {
  922. fmt.Printf("METRIC: %s: fields=%+v\n", metric, fields)
  923. }
  924. type testLoggerTrace struct {
  925. trace string
  926. fields common.LogFields
  927. }
  928. func (l *testLoggerTrace) log(priority, message string) {
  929. fmt.Printf("%s: %s: %s fields=%+v\n", priority, l.trace, message, l.fields)
  930. }
  931. func (l *testLoggerTrace) Debug(args ...interface{}) {
  932. l.log("DEBUG", fmt.Sprint(args...))
  933. }
  934. func (l *testLoggerTrace) Info(args ...interface{}) {
  935. l.log("INFO", fmt.Sprint(args...))
  936. }
  937. func (l *testLoggerTrace) Warning(args ...interface{}) {
  938. l.log("WARNING", fmt.Sprint(args...))
  939. }
  940. func (l *testLoggerTrace) Error(args ...interface{}) {
  941. l.log("ERROR", fmt.Sprint(args...))
  942. }