tactics_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  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. )
  37. func TestTactics(t *testing.T) {
  38. // Server tactics configuration
  39. // Long and short region lists test both map and slice lookups
  40. // Repeated median aggregation tests aggregation memoization
  41. tacticsConfigTemplate := `
  42. {
  43. "RequestPublicKey" : "%s",
  44. "RequestPrivateKey" : "%s",
  45. "RequestObfuscatedKey" : "%s",
  46. "DefaultTactics" : {
  47. "TTL" : "1s",
  48. "Probability" : %0.1f,
  49. "Parameters" : {
  50. "NetworkLatencyMultiplier" : %0.1f
  51. }
  52. },
  53. "FilteredTactics" : [
  54. {
  55. "Filter" : {
  56. "Regions": ["R1", "R2", "R3", "R4", "R5", "R6"],
  57. "APIParameters" : {"client_platform" : ["P1"]},
  58. "SpeedTestRTTMilliseconds" : {
  59. "Aggregation" : "Median",
  60. "AtLeast" : 1
  61. }
  62. },
  63. "Tactics" : {
  64. "Parameters" : {
  65. "ConnectionWorkerPoolSize" : %d
  66. }
  67. }
  68. },
  69. {
  70. "Filter" : {
  71. "Regions": ["R1"],
  72. "APIParameters" : {"client_platform" : ["P1"], "client_version": ["V1"]},
  73. "SpeedTestRTTMilliseconds" : {
  74. "Aggregation" : "Median",
  75. "AtLeast" : 1
  76. }
  77. },
  78. "Tactics" : {
  79. "Parameters" : {
  80. %s
  81. }
  82. }
  83. },
  84. {
  85. "Filter" : {
  86. "Regions": ["R2"]
  87. },
  88. "Tactics" : {
  89. "Parameters" : {
  90. "ConnectionWorkerPoolSize" : %d
  91. }
  92. }
  93. },
  94. {
  95. "Filter" : {
  96. "Regions": ["R7"]
  97. },
  98. "Tactics" : {
  99. "Parameters" : {
  100. "FragmentorDownstreamProbability" : 1.0,
  101. "FragmentorDownstreamMinTotalBytes" : 1,
  102. "FragmentorDownstreamMaxTotalBytes" : 1,
  103. "FragmentorDownstreamMinWriteBytes" : 1,
  104. "FragmentorDownstreamMaxWriteBytes" : 1,
  105. "FragmentorDownstreamLimitProtocols" : ["OSSH"]
  106. }
  107. }
  108. }
  109. ]
  110. }
  111. `
  112. if stringLookupThreshold != 5 {
  113. t.Fatalf("unexpected stringLookupThreshold")
  114. }
  115. encodedRequestPublicKey, encodedRequestPrivateKey, encodedObfuscatedKey, err := GenerateKeys()
  116. if err != nil {
  117. t.Fatalf("GenerateKeys failed: %s", err)
  118. }
  119. tacticsProbability := 0.5
  120. tacticsNetworkLatencyMultiplier := 2.0
  121. tacticsConnectionWorkerPoolSize := 5
  122. tacticsLimitTunnelProtocols := protocol.TunnelProtocols{"OSSH", "SSH"}
  123. jsonTacticsLimitTunnelProtocols := `"LimitTunnelProtocols" : ["OSSH", "SSH"]`
  124. expectedApplyCount := 3
  125. listenerProtocol := "OSSH"
  126. listenerFragmentedGeoIP := func(string) common.GeoIPData { return common.GeoIPData{Country: "R7"} }
  127. listenerUnfragmentedGeoIP := func(string) common.GeoIPData { return common.GeoIPData{Country: "R8"} }
  128. tacticsConfig := fmt.Sprintf(
  129. tacticsConfigTemplate,
  130. encodedRequestPublicKey,
  131. encodedRequestPrivateKey,
  132. encodedObfuscatedKey,
  133. tacticsProbability,
  134. tacticsNetworkLatencyMultiplier,
  135. tacticsConnectionWorkerPoolSize,
  136. jsonTacticsLimitTunnelProtocols,
  137. tacticsConnectionWorkerPoolSize+1)
  138. file, err := ioutil.TempFile("", "tactics.config")
  139. if err != nil {
  140. t.Fatalf("TempFile create failed: %s", err)
  141. }
  142. _, err = file.Write([]byte(tacticsConfig))
  143. if err != nil {
  144. t.Fatalf("TempFile write failed: %s", err)
  145. }
  146. file.Close()
  147. configFileName := file.Name()
  148. defer os.Remove(configFileName)
  149. // Configure and run server
  150. // Mock server uses an insecure HTTP transport that exposes endpoint names
  151. clientGeoIPData := common.GeoIPData{Country: "R1"}
  152. logger := newTestLogger()
  153. validator := func(
  154. params common.APIParameters) error {
  155. expectedParams := []string{"client_platform", "client_version"}
  156. for _, name := range expectedParams {
  157. value, ok := params[name]
  158. if !ok {
  159. return fmt.Errorf("missing param: %s", name)
  160. }
  161. _, ok = value.(string)
  162. if !ok {
  163. return fmt.Errorf("invalid param type: %s", name)
  164. }
  165. }
  166. return nil
  167. }
  168. formatter := func(
  169. geoIPData common.GeoIPData,
  170. params common.APIParameters) common.LogFields {
  171. return common.LogFields(params)
  172. }
  173. server, err := NewServer(
  174. logger,
  175. formatter,
  176. validator,
  177. configFileName)
  178. if err != nil {
  179. t.Fatalf("NewServer failed: %s", err)
  180. }
  181. listener, err := net.Listen("tcp", "127.0.0.1:0")
  182. if err != nil {
  183. t.Fatalf("Listen failed: %s", err)
  184. }
  185. serverAddress := listener.Addr().String()
  186. go func() {
  187. serveMux := http.NewServeMux()
  188. serveMux.HandleFunc(
  189. "/",
  190. func(w http.ResponseWriter, r *http.Request) {
  191. // Ensure RTT takes at least 1 millisecond for speed test
  192. time.Sleep(1 * time.Millisecond)
  193. endPoint := strings.Trim(r.URL.Path, "/")
  194. if !server.HandleEndPoint(endPoint, clientGeoIPData, w, r) {
  195. http.NotFound(w, r)
  196. }
  197. })
  198. httpServer := &http.Server{
  199. Addr: serverAddress,
  200. Handler: serveMux,
  201. }
  202. httpServer.Serve(listener)
  203. }()
  204. // Configure client
  205. clientParams, err := parameters.NewClientParameters(
  206. func(err error) {
  207. t.Fatalf("ClientParameters getValue failed: %s", err)
  208. })
  209. if err != nil {
  210. t.Fatalf("NewClientParameters failed: %s", err)
  211. }
  212. networkID := "NETWORK1"
  213. getNetworkID := func() string { return networkID }
  214. apiParams := common.APIParameters{
  215. "client_platform": "P1",
  216. "client_version": "V1"}
  217. storer := newTestStorer()
  218. endPointRegion := "R0"
  219. endPointProtocol := "OSSH"
  220. differentEndPointProtocol := "SSH"
  221. roundTripper := func(
  222. ctx context.Context,
  223. endPoint string,
  224. requestBody []byte) ([]byte, error) {
  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.NewClientParameters(nil)
  258. if err != nil {
  259. t.Fatalf("NewClientParameters 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. clientParams,
  291. storer,
  292. getNetworkID,
  293. apiParams,
  294. endPointProtocol,
  295. endPointRegion,
  296. encodedRequestPublicKey,
  297. encodedObfuscatedKey,
  298. roundTripper)
  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. clientParams,
  344. storer,
  345. getNetworkID,
  346. apiParams,
  347. endPointProtocol,
  348. endPointRegion,
  349. encodedRequestPublicKey,
  350. encodedObfuscatedKey,
  351. roundTripper)
  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. clientParams,
  406. storer,
  407. getNetworkID,
  408. apiParams,
  409. endPointProtocol,
  410. endPointRegion,
  411. encodedRequestPublicKey,
  412. encodedObfuscatedKey,
  413. roundTripper)
  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(clientParams, 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 := clientParams.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. clientParams,
  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. clientParams,
  525. storer,
  526. getNetworkID,
  527. apiParams,
  528. endPointProtocol,
  529. endPointRegion,
  530. encodedIncorrectRequestPublicKey,
  531. encodedObfuscatedKey,
  532. roundTripper)
  533. if err == nil {
  534. t.Fatalf("FetchTactics succeeded unexpectedly with incorrect request key")
  535. }
  536. _, err = FetchTactics(
  537. context.Background(),
  538. clientParams,
  539. storer,
  540. getNetworkID,
  541. apiParams,
  542. endPointProtocol,
  543. endPointRegion,
  544. encodedRequestPublicKey,
  545. encodedIncorrectObfuscatedKey,
  546. roundTripper)
  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. // Test Listener
  586. tacticsProbability = 1.0
  587. tacticsConfig = fmt.Sprintf(
  588. tacticsConfigTemplate,
  589. "",
  590. "",
  591. "",
  592. tacticsProbability,
  593. tacticsNetworkLatencyMultiplier,
  594. tacticsConnectionWorkerPoolSize,
  595. jsonTacticsLimitTunnelProtocols,
  596. tacticsConnectionWorkerPoolSize+1)
  597. err = ioutil.WriteFile(configFileName, []byte(tacticsConfig), 0600)
  598. if err != nil {
  599. t.Fatalf("WriteFile failed: %s", err)
  600. }
  601. reloaded, err = server.Reload()
  602. if err != nil {
  603. t.Fatalf("Reload failed: %s", err)
  604. }
  605. listenerTestCases := []struct {
  606. description string
  607. geoIPLookup func(string) common.GeoIPData
  608. expectFragmentor bool
  609. }{
  610. {
  611. "fragmented",
  612. listenerFragmentedGeoIP,
  613. true,
  614. },
  615. {
  616. "unfragmented",
  617. listenerUnfragmentedGeoIP,
  618. false,
  619. },
  620. }
  621. for _, testCase := range listenerTestCases {
  622. t.Run(testCase.description, func(t *testing.T) {
  623. tcpListener, err := net.Listen("tcp", ":0")
  624. if err != nil {
  625. t.Fatalf(" net.Listen failed: %s", err)
  626. }
  627. tacticsListener := NewListener(
  628. tcpListener,
  629. server,
  630. listenerProtocol,
  631. testCase.geoIPLookup)
  632. clientConn, err := net.Dial("tcp", tacticsListener.Addr().String())
  633. if err != nil {
  634. t.Fatalf(" net.Dial failed: %s", err)
  635. return
  636. }
  637. result := make(chan net.Conn, 1)
  638. go func() {
  639. serverConn, err := tacticsListener.Accept()
  640. if err == nil {
  641. result <- serverConn
  642. }
  643. }()
  644. timer := time.NewTimer(3 * time.Second)
  645. defer timer.Stop()
  646. select {
  647. case serverConn := <-result:
  648. _, isFragmentor := serverConn.(*fragmentor.Conn)
  649. if testCase.expectFragmentor && !isFragmentor {
  650. t.Fatalf("unexpected non-fragmentor: %T", serverConn)
  651. } else if !testCase.expectFragmentor && isFragmentor {
  652. t.Fatalf("unexpected fragmentor: %T", serverConn)
  653. }
  654. serverConn.Close()
  655. case <-timer.C:
  656. t.Fatalf("timeout before expected accepted connection")
  657. }
  658. clientConn.Close()
  659. tacticsListener.Close()
  660. })
  661. }
  662. // TODO: test replay attack defence
  663. // TODO: test Server.Validate with invalid tactics configurations
  664. }
  665. type testStorer struct {
  666. tacticsRecords map[string][]byte
  667. speedTestSampleRecords map[string][]byte
  668. }
  669. func newTestStorer() *testStorer {
  670. return &testStorer{
  671. tacticsRecords: make(map[string][]byte),
  672. speedTestSampleRecords: make(map[string][]byte),
  673. }
  674. }
  675. func (s *testStorer) SetTacticsRecord(networkID string, record []byte) error {
  676. s.tacticsRecords[networkID] = record
  677. return nil
  678. }
  679. func (s *testStorer) GetTacticsRecord(networkID string) ([]byte, error) {
  680. return s.tacticsRecords[networkID], nil
  681. }
  682. func (s *testStorer) SetSpeedTestSamplesRecord(networkID string, record []byte) error {
  683. s.speedTestSampleRecords[networkID] = record
  684. return nil
  685. }
  686. func (s *testStorer) GetSpeedTestSamplesRecord(networkID string) ([]byte, error) {
  687. return s.speedTestSampleRecords[networkID], nil
  688. }
  689. type testLogger struct {
  690. }
  691. func newTestLogger() *testLogger {
  692. return &testLogger{}
  693. }
  694. func (l *testLogger) WithContext() common.LogContext {
  695. return &testLoggerContext{context: common.GetParentContext()}
  696. }
  697. func (l *testLogger) WithContextFields(fields common.LogFields) common.LogContext {
  698. return &testLoggerContext{
  699. context: common.GetParentContext(),
  700. fields: fields,
  701. }
  702. }
  703. func (l *testLogger) LogMetric(metric string, fields common.LogFields) {
  704. fmt.Printf("METRIC: %s: fields=%+v\n", metric, fields)
  705. }
  706. type testLoggerContext struct {
  707. context string
  708. fields common.LogFields
  709. }
  710. func (l *testLoggerContext) log(priority, message string) {
  711. fmt.Printf("%s: %s: %s fields=%+v\n", priority, l.context, message, l.fields)
  712. }
  713. func (l *testLoggerContext) Debug(args ...interface{}) {
  714. l.log("DEBUG", fmt.Sprint(args...))
  715. }
  716. func (l *testLoggerContext) Info(args ...interface{}) {
  717. l.log("INFO", fmt.Sprint(args...))
  718. }
  719. func (l *testLoggerContext) Warning(args ...interface{}) {
  720. l.log("WARNING", fmt.Sprint(args...))
  721. }
  722. func (l *testLoggerContext) Error(args ...interface{}) {
  723. l.log("ERROR", fmt.Sprint(args...))
  724. }