tactics_test.go 27 KB

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