tactics_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  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/fragmentor"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  35. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  36. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/stacktrace"
  37. )
  38. func TestTactics(t *testing.T) {
  39. // Server tactics configuration
  40. // Long and short region lists test both map and slice lookups
  41. // Repeated median aggregation tests aggregation memoization
  42. tacticsConfigTemplate := `
  43. {
  44. "RequestPublicKey" : "%s",
  45. "RequestPrivateKey" : "%s",
  46. "RequestObfuscatedKey" : "%s",
  47. "DefaultTactics" : {
  48. "TTL" : "1s",
  49. "Probability" : %0.1f,
  50. "Parameters" : {
  51. "NetworkLatencyMultiplier" : %0.1f
  52. }
  53. },
  54. "FilteredTactics" : [
  55. {
  56. "Filter" : {
  57. "Regions": ["R1", "R2", "R3", "R4", "R5", "R6"],
  58. "APIParameters" : {"client_platform" : ["P1"]},
  59. "SpeedTestRTTMilliseconds" : {
  60. "Aggregation" : "Median",
  61. "AtLeast" : 1
  62. }
  63. },
  64. "Tactics" : {
  65. "Parameters" : {
  66. "ConnectionWorkerPoolSize" : %d
  67. }
  68. }
  69. },
  70. {
  71. "Filter" : {
  72. "Regions": ["R1"],
  73. "APIParameters" : {"client_platform" : ["P1"], "client_version": ["V1"]},
  74. "SpeedTestRTTMilliseconds" : {
  75. "Aggregation" : "Median",
  76. "AtLeast" : 1
  77. }
  78. },
  79. "Tactics" : {
  80. "Parameters" : {
  81. %s
  82. }
  83. }
  84. },
  85. {
  86. "Filter" : {
  87. "Regions": ["R2"]
  88. },
  89. "Tactics" : {
  90. "Parameters" : {
  91. "ConnectionWorkerPoolSize" : %d
  92. }
  93. }
  94. },
  95. {
  96. "Filter" : {
  97. "Regions": ["R7"],
  98. "ISPs": ["I1"],
  99. "Cities": ["C1"]
  100. },
  101. "Tactics" : {
  102. "Parameters" : {
  103. "FragmentorDownstreamProbability" : 1.0,
  104. "FragmentorDownstreamMinTotalBytes" : 1,
  105. "FragmentorDownstreamMaxTotalBytes" : 1,
  106. "FragmentorDownstreamMinWriteBytes" : 1,
  107. "FragmentorDownstreamMaxWriteBytes" : 1,
  108. "FragmentorDownstreamLimitProtocols" : ["OSSH"]
  109. }
  110. }
  111. }
  112. ]
  113. }
  114. `
  115. if stringLookupThreshold != 5 {
  116. t.Fatalf("unexpected stringLookupThreshold")
  117. }
  118. encodedRequestPublicKey, encodedRequestPrivateKey, encodedObfuscatedKey, err := GenerateKeys()
  119. if err != nil {
  120. t.Fatalf("GenerateKeys failed: %s", err)
  121. }
  122. tacticsProbability := 0.5
  123. tacticsNetworkLatencyMultiplier := 2.0
  124. tacticsConnectionWorkerPoolSize := 5
  125. tacticsLimitTunnelProtocols := protocol.TunnelProtocols{"OSSH", "SSH"}
  126. jsonTacticsLimitTunnelProtocols := `"LimitTunnelProtocols" : ["OSSH", "SSH"]`
  127. expectedApplyCount := 3
  128. listenerProtocol := "OSSH"
  129. listenerFragmentedGeoIP := func(string) common.GeoIPData {
  130. return common.GeoIPData{Country: "R7", ISP: "I1", City: "C1"}
  131. }
  132. listenerUnfragmentedGeoIPWrongRegion := func(string) common.GeoIPData {
  133. return common.GeoIPData{Country: "R8", ISP: "I1", City: "C1"}
  134. }
  135. listenerUnfragmentedGeoIPWrongISP := func(string) common.GeoIPData {
  136. return common.GeoIPData{Country: "R7", ISP: "I2", City: "C1"}
  137. }
  138. listenerUnfragmentedGeoIPWrongCity := func(string) common.GeoIPData {
  139. return common.GeoIPData{Country: "R7", ISP: "I1", City: "C2"}
  140. }
  141. tacticsConfig := fmt.Sprintf(
  142. tacticsConfigTemplate,
  143. encodedRequestPublicKey,
  144. encodedRequestPrivateKey,
  145. encodedObfuscatedKey,
  146. tacticsProbability,
  147. tacticsNetworkLatencyMultiplier,
  148. tacticsConnectionWorkerPoolSize,
  149. jsonTacticsLimitTunnelProtocols,
  150. tacticsConnectionWorkerPoolSize+1)
  151. file, err := ioutil.TempFile("", "tactics.config")
  152. if err != nil {
  153. t.Fatalf("TempFile create failed: %s", err)
  154. }
  155. _, err = file.Write([]byte(tacticsConfig))
  156. if err != nil {
  157. t.Fatalf("TempFile write failed: %s", err)
  158. }
  159. file.Close()
  160. configFileName := file.Name()
  161. defer os.Remove(configFileName)
  162. // Configure and run server
  163. // Mock server uses an insecure HTTP transport that exposes endpoint names
  164. clientGeoIPData := common.GeoIPData{Country: "R1"}
  165. logger := newTestLogger()
  166. validator := func(
  167. params common.APIParameters) error {
  168. expectedParams := []string{"client_platform", "client_version"}
  169. for _, name := range expectedParams {
  170. value, ok := params[name]
  171. if !ok {
  172. return fmt.Errorf("missing param: %s", name)
  173. }
  174. _, ok = value.(string)
  175. if !ok {
  176. return fmt.Errorf("invalid param type: %s", name)
  177. }
  178. }
  179. return nil
  180. }
  181. formatter := func(
  182. geoIPData common.GeoIPData,
  183. params common.APIParameters) common.LogFields {
  184. return common.LogFields(params)
  185. }
  186. server, err := NewServer(
  187. logger,
  188. formatter,
  189. validator,
  190. configFileName)
  191. if err != nil {
  192. t.Fatalf("NewServer failed: %s", err)
  193. }
  194. listener, err := net.Listen("tcp", "127.0.0.1:0")
  195. if err != nil {
  196. t.Fatalf("Listen failed: %s", err)
  197. }
  198. serverAddress := listener.Addr().String()
  199. go func() {
  200. serveMux := http.NewServeMux()
  201. serveMux.HandleFunc(
  202. "/",
  203. func(w http.ResponseWriter, r *http.Request) {
  204. // Ensure RTT takes at least 1 millisecond for speed test
  205. time.Sleep(1 * time.Millisecond)
  206. endPoint := strings.Trim(r.URL.Path, "/")
  207. if !server.HandleEndPoint(endPoint, clientGeoIPData, w, r) {
  208. http.NotFound(w, r)
  209. }
  210. })
  211. httpServer := &http.Server{
  212. Addr: serverAddress,
  213. Handler: serveMux,
  214. }
  215. httpServer.Serve(listener)
  216. }()
  217. // Configure client
  218. clientParams, err := parameters.NewClientParameters(
  219. func(err error) {
  220. t.Fatalf("ClientParameters getValue failed: %s", err)
  221. })
  222. if err != nil {
  223. t.Fatalf("NewClientParameters failed: %s", err)
  224. }
  225. networkID := "NETWORK1"
  226. getNetworkID := func() string { return networkID }
  227. apiParams := common.APIParameters{
  228. "client_platform": "P1",
  229. "client_version": "V1"}
  230. storer := newTestStorer()
  231. endPointRegion := "R0"
  232. endPointProtocol := "OSSH"
  233. differentEndPointProtocol := "SSH"
  234. roundTripper := func(
  235. ctx context.Context,
  236. endPoint string,
  237. requestBody []byte) ([]byte, error) {
  238. request, err := http.NewRequest(
  239. "POST",
  240. fmt.Sprintf("http://%s/%s", serverAddress, endPoint),
  241. bytes.NewReader(requestBody))
  242. if err != nil {
  243. return nil, err
  244. }
  245. request = request.WithContext(ctx)
  246. response, err := http.DefaultClient.Do(request)
  247. if err != nil {
  248. return nil, err
  249. }
  250. defer response.Body.Close()
  251. if response.StatusCode != http.StatusOK {
  252. return nil, fmt.Errorf("HTTP request failed: %d", response.StatusCode)
  253. }
  254. body, err := ioutil.ReadAll(response.Body)
  255. if err != nil {
  256. return nil, err
  257. }
  258. return body, nil
  259. }
  260. // There should be no local tactics
  261. tacticsRecord, err := UseStoredTactics(storer, networkID)
  262. if err != nil {
  263. t.Fatalf("UseStoredTactics failed: %s", err)
  264. }
  265. if tacticsRecord != nil {
  266. t.Fatalf("unexpected tactics record")
  267. }
  268. // Helper to check that expected tactics parameters are returned
  269. checkParameters := func(r *Record) {
  270. p, err := parameters.NewClientParameters(nil)
  271. if err != nil {
  272. t.Fatalf("NewClientParameters failed: %s", err)
  273. }
  274. if r.Tactics.Probability != tacticsProbability {
  275. t.Fatalf("Unexpected probability: %f", r.Tactics.Probability)
  276. }
  277. // skipOnError is true for Psiphon clients
  278. counts, err := p.Set(r.Tag, true, r.Tactics.Parameters)
  279. if err != nil {
  280. t.Fatalf("Apply failed: %s", err)
  281. }
  282. if counts[0] != expectedApplyCount {
  283. t.Fatalf("Unexpected apply count: %d", counts[0])
  284. }
  285. multipler := p.Get().Float(parameters.NetworkLatencyMultiplier)
  286. if multipler != tacticsNetworkLatencyMultiplier {
  287. t.Fatalf("Unexpected NetworkLatencyMultiplier: %v", multipler)
  288. }
  289. connectionWorkerPoolSize := p.Get().Int(parameters.ConnectionWorkerPoolSize)
  290. if connectionWorkerPoolSize != tacticsConnectionWorkerPoolSize {
  291. t.Fatalf("Unexpected ConnectionWorkerPoolSize: %v", connectionWorkerPoolSize)
  292. }
  293. limitTunnelProtocols := p.Get().TunnelProtocols(parameters.LimitTunnelProtocols)
  294. if !reflect.DeepEqual(limitTunnelProtocols, tacticsLimitTunnelProtocols) {
  295. t.Fatalf("Unexpected LimitTunnelProtocols: %v", limitTunnelProtocols)
  296. }
  297. }
  298. // Initial tactics request; will also run a speed test
  299. // Request should complete in < 1 second
  300. ctx, cancelFunc := context.WithTimeout(context.Background(), 1*time.Second)
  301. initialFetchTacticsRecord, err := FetchTactics(
  302. ctx,
  303. clientParams,
  304. storer,
  305. getNetworkID,
  306. apiParams,
  307. endPointProtocol,
  308. endPointRegion,
  309. encodedRequestPublicKey,
  310. encodedObfuscatedKey,
  311. roundTripper)
  312. cancelFunc()
  313. if err != nil {
  314. t.Fatalf("FetchTactics failed: %s", err)
  315. }
  316. if initialFetchTacticsRecord == nil {
  317. t.Fatalf("expected tactics record")
  318. }
  319. checkParameters(initialFetchTacticsRecord)
  320. // There should now be cached local tactics
  321. storedTacticsRecord, err := UseStoredTactics(storer, networkID)
  322. if err != nil {
  323. t.Fatalf("UseStoredTactics failed: %s", err)
  324. }
  325. if storedTacticsRecord == nil {
  326. t.Fatalf("expected stored tactics record")
  327. }
  328. // Strip monotonic component so comparisons will work
  329. initialFetchTacticsRecord.Expiry = initialFetchTacticsRecord.Expiry.Round(0)
  330. if !reflect.DeepEqual(initialFetchTacticsRecord, storedTacticsRecord) {
  331. t.Fatalf("tactics records are not identical:\n\n%#v\n\n%#v\n\n",
  332. initialFetchTacticsRecord, storedTacticsRecord)
  333. }
  334. checkParameters(storedTacticsRecord)
  335. // There should now be a speed test sample
  336. speedTestSamples, err := getSpeedTestSamples(storer, networkID)
  337. if err != nil {
  338. t.Fatalf("getSpeedTestSamples failed: %s", err)
  339. }
  340. if len(speedTestSamples) != 1 {
  341. t.Fatalf("unexpected speed test samples count")
  342. }
  343. // Wait for tactics to expire
  344. time.Sleep(1 * time.Second)
  345. storedTacticsRecord, err = UseStoredTactics(storer, networkID)
  346. if err != nil {
  347. t.Fatalf("UseStoredTactics failed: %s", err)
  348. }
  349. if storedTacticsRecord != nil {
  350. t.Fatalf("unexpected stored tactics record")
  351. }
  352. // Next fetch should merge empty payload as tag matches
  353. // TODO: inspect tactics response payload
  354. fetchTacticsRecord, err := FetchTactics(
  355. context.Background(),
  356. clientParams,
  357. storer,
  358. getNetworkID,
  359. apiParams,
  360. endPointProtocol,
  361. endPointRegion,
  362. encodedRequestPublicKey,
  363. encodedObfuscatedKey,
  364. roundTripper)
  365. if err != nil {
  366. t.Fatalf("FetchTactics failed: %s", err)
  367. }
  368. if fetchTacticsRecord == nil {
  369. t.Fatalf("expected tactics record")
  370. }
  371. if initialFetchTacticsRecord.Tag != fetchTacticsRecord.Tag {
  372. t.Fatalf("tags are not identical")
  373. }
  374. if initialFetchTacticsRecord.Expiry.Equal(fetchTacticsRecord.Expiry) {
  375. t.Fatalf("expiries unexpectedly identical")
  376. }
  377. if !reflect.DeepEqual(initialFetchTacticsRecord.Tactics, fetchTacticsRecord.Tactics) {
  378. t.Fatalf("tactics are not identical:\n\n%#v\n\n%#v\n\n",
  379. initialFetchTacticsRecord.Tactics, fetchTacticsRecord.Tactics)
  380. }
  381. checkParameters(fetchTacticsRecord)
  382. // Modify tactics configuration to change payload
  383. tacticsConnectionWorkerPoolSize = 6
  384. tacticsLimitTunnelProtocols = protocol.TunnelProtocols{}
  385. jsonTacticsLimitTunnelProtocols = ``
  386. expectedApplyCount = 2
  387. // Omitting LimitTunnelProtocols entirely tests this bug fix: When a new
  388. // tactics payload is obtained, all previous parameters should be cleared.
  389. //
  390. // In the bug, any previous parameters not in the new tactics were
  391. // incorrectly retained. In this test case, LimitTunnelProtocols is
  392. // omitted in the new tactics; if FetchTactics fails to clear the old
  393. // LimitTunnelProtocols then the test will fail.
  394. tacticsConfig = fmt.Sprintf(
  395. tacticsConfigTemplate,
  396. encodedRequestPublicKey,
  397. encodedRequestPrivateKey,
  398. encodedObfuscatedKey,
  399. tacticsProbability,
  400. tacticsNetworkLatencyMultiplier,
  401. tacticsConnectionWorkerPoolSize,
  402. jsonTacticsLimitTunnelProtocols,
  403. tacticsConnectionWorkerPoolSize+1)
  404. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  405. if err != nil {
  406. t.Fatalf("WriteFile failed: %s", err)
  407. }
  408. reloaded, err := server.Reload()
  409. if err != nil {
  410. t.Fatalf("Reload failed: %s", err)
  411. }
  412. if !reloaded {
  413. t.Fatalf("Server config failed to reload")
  414. }
  415. // Next fetch should return a different payload
  416. fetchTacticsRecord, err = FetchTactics(
  417. context.Background(),
  418. clientParams,
  419. storer,
  420. getNetworkID,
  421. apiParams,
  422. endPointProtocol,
  423. endPointRegion,
  424. encodedRequestPublicKey,
  425. encodedObfuscatedKey,
  426. roundTripper)
  427. if err != nil {
  428. t.Fatalf("FetchTactics failed: %s", err)
  429. }
  430. if fetchTacticsRecord == nil {
  431. t.Fatalf("expected tactics record")
  432. }
  433. if initialFetchTacticsRecord.Tag == fetchTacticsRecord.Tag {
  434. t.Fatalf("tags unexpectedly identical")
  435. }
  436. if initialFetchTacticsRecord.Expiry.Equal(fetchTacticsRecord.Expiry) {
  437. t.Fatalf("expires unexpectedly identical")
  438. }
  439. if reflect.DeepEqual(initialFetchTacticsRecord.Tactics, fetchTacticsRecord.Tactics) {
  440. t.Fatalf("tactics unexpectedly identical")
  441. }
  442. checkParameters(fetchTacticsRecord)
  443. // Exercise handshake transport of tactics
  444. // Wait for tactics to expire; handshake should renew
  445. time.Sleep(1 * time.Second)
  446. handshakeParams := common.APIParameters{
  447. "client_platform": "P1",
  448. "client_version": "V1"}
  449. err = SetTacticsAPIParameters(clientParams, storer, networkID, handshakeParams)
  450. if err != nil {
  451. t.Fatalf("SetTacticsAPIParameters failed: %s", err)
  452. }
  453. tacticsPayload, err := server.GetTacticsPayload(clientGeoIPData, handshakeParams)
  454. if err != nil {
  455. t.Fatalf("GetTacticsPayload failed: %s", err)
  456. }
  457. handshakeTacticsRecord, err := HandleTacticsPayload(storer, networkID, tacticsPayload)
  458. if err != nil {
  459. t.Fatalf("HandleTacticsPayload failed: %s", err)
  460. }
  461. if handshakeTacticsRecord == nil {
  462. t.Fatalf("expected tactics record")
  463. }
  464. if fetchTacticsRecord.Tag != handshakeTacticsRecord.Tag {
  465. t.Fatalf("tags are not identical")
  466. }
  467. if fetchTacticsRecord.Expiry.Equal(handshakeTacticsRecord.Expiry) {
  468. t.Fatalf("expiries unexpectedly identical")
  469. }
  470. if !reflect.DeepEqual(fetchTacticsRecord.Tactics, handshakeTacticsRecord.Tactics) {
  471. t.Fatalf("tactics are not identical:\n\n%#v\n\n%#v\n\n",
  472. fetchTacticsRecord.Tactics, handshakeTacticsRecord.Tactics)
  473. }
  474. checkParameters(handshakeTacticsRecord)
  475. // Now there should be stored tactics
  476. storedTacticsRecord, err = UseStoredTactics(storer, networkID)
  477. if err != nil {
  478. t.Fatalf("UseStoredTactics failed: %s", err)
  479. }
  480. if storedTacticsRecord == nil {
  481. t.Fatalf("expected stored tactics record")
  482. }
  483. handshakeTacticsRecord.Expiry = handshakeTacticsRecord.Expiry.Round(0)
  484. if !reflect.DeepEqual(handshakeTacticsRecord, storedTacticsRecord) {
  485. t.Fatalf("tactics records are not identical:\n\n%#v\n\n%#v\n\n",
  486. handshakeTacticsRecord, storedTacticsRecord)
  487. }
  488. checkParameters(storedTacticsRecord)
  489. // Change network ID, should be no stored tactics
  490. networkID = "NETWORK2"
  491. storedTacticsRecord, err = UseStoredTactics(storer, networkID)
  492. if err != nil {
  493. t.Fatalf("UseStoredTactics failed: %s", err)
  494. }
  495. if storedTacticsRecord != nil {
  496. t.Fatalf("unexpected stored tactics record")
  497. }
  498. // Exercise speed test sample truncation
  499. maxSamples := clientParams.Get().Int(parameters.SpeedTestMaxSampleCount)
  500. for i := 0; i < maxSamples*2; i++ {
  501. response, err := MakeSpeedTestResponse(0, 0)
  502. if err != nil {
  503. t.Fatalf("MakeSpeedTestResponse failed: %s", err)
  504. }
  505. err = AddSpeedTestSample(
  506. clientParams,
  507. storer,
  508. networkID,
  509. "",
  510. differentEndPointProtocol,
  511. 100*time.Millisecond,
  512. nil,
  513. response)
  514. if err != nil {
  515. t.Fatalf("AddSpeedTestSample failed: %s", err)
  516. }
  517. }
  518. speedTestSamples, err = getSpeedTestSamples(storer, networkID)
  519. if err != nil {
  520. t.Fatalf("getSpeedTestSamples failed: %s", err)
  521. }
  522. if len(speedTestSamples) != maxSamples {
  523. t.Fatalf("unexpected speed test samples count")
  524. }
  525. for _, sample := range speedTestSamples {
  526. if sample.EndPointProtocol == endPointProtocol {
  527. t.Fatalf("unexpected old speed test sample")
  528. }
  529. }
  530. // Fetch should fail when using incorrect keys
  531. encodedIncorrectRequestPublicKey, _, encodedIncorrectObfuscatedKey, err := GenerateKeys()
  532. if err != nil {
  533. t.Fatalf("GenerateKeys failed: %s", err)
  534. }
  535. _, err = FetchTactics(
  536. context.Background(),
  537. clientParams,
  538. storer,
  539. getNetworkID,
  540. apiParams,
  541. endPointProtocol,
  542. endPointRegion,
  543. encodedIncorrectRequestPublicKey,
  544. encodedObfuscatedKey,
  545. roundTripper)
  546. if err == nil {
  547. t.Fatalf("FetchTactics succeeded unexpectedly with incorrect request key")
  548. }
  549. _, err = FetchTactics(
  550. context.Background(),
  551. clientParams,
  552. storer,
  553. getNetworkID,
  554. apiParams,
  555. endPointProtocol,
  556. endPointRegion,
  557. encodedRequestPublicKey,
  558. encodedIncorrectObfuscatedKey,
  559. roundTripper)
  560. if err == nil {
  561. t.Fatalf("FetchTactics succeeded unexpectedly with incorrect obfuscated key")
  562. }
  563. // When no keys are supplied, untunneled tactics requests are not supported, but
  564. // handshake tactics (GetTacticsPayload) should still work.
  565. tacticsConfig = fmt.Sprintf(
  566. tacticsConfigTemplate,
  567. "",
  568. "",
  569. "",
  570. tacticsProbability,
  571. tacticsNetworkLatencyMultiplier,
  572. tacticsConnectionWorkerPoolSize,
  573. jsonTacticsLimitTunnelProtocols,
  574. tacticsConnectionWorkerPoolSize+1)
  575. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  576. if err != nil {
  577. t.Fatalf("WriteFile failed: %s", err)
  578. }
  579. reloaded, err = server.Reload()
  580. if err != nil {
  581. t.Fatalf("Reload failed: %s", err)
  582. }
  583. if !reloaded {
  584. t.Fatalf("Server config failed to reload")
  585. }
  586. _, err = server.GetTacticsPayload(clientGeoIPData, handshakeParams)
  587. if err != nil {
  588. t.Fatalf("GetTacticsPayload failed: %s", err)
  589. }
  590. handled := server.HandleEndPoint(TACTICS_END_POINT, clientGeoIPData, nil, nil)
  591. if handled {
  592. t.Fatalf("HandleEndPoint unexpectedly handled request")
  593. }
  594. handled = server.HandleEndPoint(SPEED_TEST_END_POINT, clientGeoIPData, nil, nil)
  595. if handled {
  596. t.Fatalf("HandleEndPoint unexpectedly handled request")
  597. }
  598. // Test Listener
  599. tacticsProbability = 1.0
  600. tacticsConfig = fmt.Sprintf(
  601. tacticsConfigTemplate,
  602. "",
  603. "",
  604. "",
  605. tacticsProbability,
  606. tacticsNetworkLatencyMultiplier,
  607. tacticsConnectionWorkerPoolSize,
  608. jsonTacticsLimitTunnelProtocols,
  609. tacticsConnectionWorkerPoolSize+1)
  610. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  611. if err != nil {
  612. t.Fatalf("WriteFile failed: %s", err)
  613. }
  614. reloaded, err = server.Reload()
  615. if err != nil {
  616. t.Fatalf("Reload failed: %s", err)
  617. }
  618. if !reloaded {
  619. t.Fatalf("Server config failed to reload")
  620. }
  621. listenerTestCases := []struct {
  622. description string
  623. geoIPLookup func(string) common.GeoIPData
  624. expectFragmentor bool
  625. }{
  626. {
  627. "fragmented",
  628. listenerFragmentedGeoIP,
  629. true,
  630. },
  631. {
  632. "unfragmented-region",
  633. listenerUnfragmentedGeoIPWrongRegion,
  634. false,
  635. },
  636. {
  637. "unfragmented-ISP",
  638. listenerUnfragmentedGeoIPWrongISP,
  639. false,
  640. },
  641. {
  642. "unfragmented-city",
  643. listenerUnfragmentedGeoIPWrongCity,
  644. false,
  645. },
  646. }
  647. for _, testCase := range listenerTestCases {
  648. t.Run(testCase.description, func(t *testing.T) {
  649. tcpListener, err := net.Listen("tcp", ":0")
  650. if err != nil {
  651. t.Fatalf(" net.Listen failed: %s", err)
  652. }
  653. tacticsListener := NewListener(
  654. tcpListener,
  655. server,
  656. listenerProtocol,
  657. testCase.geoIPLookup)
  658. clientConn, err := net.Dial("tcp", tacticsListener.Addr().String())
  659. if err != nil {
  660. t.Fatalf(" net.Dial failed: %s", err)
  661. return
  662. }
  663. result := make(chan net.Conn, 1)
  664. go func() {
  665. serverConn, err := tacticsListener.Accept()
  666. if err == nil {
  667. result <- serverConn
  668. }
  669. }()
  670. timer := time.NewTimer(3 * time.Second)
  671. defer timer.Stop()
  672. select {
  673. case serverConn := <-result:
  674. _, isFragmentor := serverConn.(*fragmentor.Conn)
  675. if testCase.expectFragmentor && !isFragmentor {
  676. t.Fatalf("unexpected non-fragmentor: %T", serverConn)
  677. } else if !testCase.expectFragmentor && isFragmentor {
  678. t.Fatalf("unexpected fragmentor: %T", serverConn)
  679. }
  680. serverConn.Close()
  681. case <-timer.C:
  682. t.Fatalf("timeout before expected accepted connection")
  683. }
  684. clientConn.Close()
  685. tacticsListener.Close()
  686. })
  687. }
  688. // TODO: test replay attack defence
  689. // TODO: test Server.Validate with invalid tactics configurations
  690. }
  691. type testStorer struct {
  692. tacticsRecords map[string][]byte
  693. speedTestSampleRecords map[string][]byte
  694. }
  695. func newTestStorer() *testStorer {
  696. return &testStorer{
  697. tacticsRecords: make(map[string][]byte),
  698. speedTestSampleRecords: make(map[string][]byte),
  699. }
  700. }
  701. func (s *testStorer) SetTacticsRecord(networkID string, record []byte) error {
  702. s.tacticsRecords[networkID] = record
  703. return nil
  704. }
  705. func (s *testStorer) GetTacticsRecord(networkID string) ([]byte, error) {
  706. return s.tacticsRecords[networkID], nil
  707. }
  708. func (s *testStorer) SetSpeedTestSamplesRecord(networkID string, record []byte) error {
  709. s.speedTestSampleRecords[networkID] = record
  710. return nil
  711. }
  712. func (s *testStorer) GetSpeedTestSamplesRecord(networkID string) ([]byte, error) {
  713. return s.speedTestSampleRecords[networkID], nil
  714. }
  715. type testLogger struct {
  716. }
  717. func newTestLogger() *testLogger {
  718. return &testLogger{}
  719. }
  720. func (l *testLogger) WithTrace() common.LogTrace {
  721. return &testLoggerTrace{trace: stacktrace.GetParentFunctionName()}
  722. }
  723. func (l *testLogger) WithTraceFields(fields common.LogFields) common.LogTrace {
  724. return &testLoggerTrace{
  725. trace: stacktrace.GetParentFunctionName(),
  726. fields: fields,
  727. }
  728. }
  729. func (l *testLogger) LogMetric(metric string, fields common.LogFields) {
  730. fmt.Printf("METRIC: %s: fields=%+v\n", metric, fields)
  731. }
  732. type testLoggerTrace struct {
  733. trace string
  734. fields common.LogFields
  735. }
  736. func (l *testLoggerTrace) log(priority, message string) {
  737. fmt.Printf("%s: %s: %s fields=%+v\n", priority, l.trace, message, l.fields)
  738. }
  739. func (l *testLoggerTrace) Debug(args ...interface{}) {
  740. l.log("DEBUG", fmt.Sprint(args...))
  741. }
  742. func (l *testLoggerTrace) Info(args ...interface{}) {
  743. l.log("INFO", fmt.Sprint(args...))
  744. }
  745. func (l *testLoggerTrace) Warning(args ...interface{}) {
  746. l.log("WARNING", fmt.Sprint(args...))
  747. }
  748. func (l *testLoggerTrace) Error(args ...interface{}) {
  749. l.log("ERROR", fmt.Sprint(args...))
  750. }