dsl_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. /*
  2. * Copyright (c) 2025, 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 dsl
  20. import (
  21. "bytes"
  22. "context"
  23. "encoding/base64"
  24. "encoding/hex"
  25. "io/ioutil"
  26. "os"
  27. "runtime/debug"
  28. "sync"
  29. "sync/atomic"
  30. "testing"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/osl"
  35. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  36. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/internal/testutils"
  37. )
  38. type testConfig struct {
  39. name string
  40. alreadyDiscovered bool
  41. requireOSLKeys bool
  42. interruptDownloads bool
  43. enableRetries bool
  44. repeatBeforeTTL bool
  45. isTunneled bool
  46. expectFailure bool
  47. cacheServerEntries bool
  48. cacheOSLFileSpecs bool
  49. }
  50. func TestDSLs(t *testing.T) {
  51. tests := []*testConfig{
  52. {
  53. name: "undiscovered server entries",
  54. },
  55. {
  56. name: "require OSL keys",
  57. requireOSLKeys: true,
  58. },
  59. {
  60. name: "interruptions without retry",
  61. interruptDownloads: true,
  62. expectFailure: true,
  63. },
  64. {
  65. name: "interruptions with retry",
  66. interruptDownloads: true,
  67. enableRetries: true,
  68. },
  69. {
  70. name: "require OSL keys with interruptions",
  71. requireOSLKeys: true,
  72. interruptDownloads: true,
  73. enableRetries: true,
  74. },
  75. {
  76. name: "repeat before TTL",
  77. repeatBeforeTTL: true,
  78. },
  79. {
  80. name: "previously discovered server entries",
  81. alreadyDiscovered: true,
  82. },
  83. {
  84. name: "first request is-tunneled",
  85. isTunneled: true,
  86. },
  87. {
  88. name: "cache server entries",
  89. interruptDownloads: true,
  90. enableRetries: true,
  91. cacheServerEntries: true,
  92. },
  93. {
  94. name: "cache OSL file specs",
  95. requireOSLKeys: true,
  96. interruptDownloads: true,
  97. enableRetries: true,
  98. cacheOSLFileSpecs: true,
  99. },
  100. {
  101. name: "cache both",
  102. requireOSLKeys: true,
  103. interruptDownloads: true,
  104. enableRetries: true,
  105. cacheServerEntries: true,
  106. cacheOSLFileSpecs: true,
  107. },
  108. }
  109. for _, testConfig := range tests {
  110. t.Run(testConfig.name, func(t *testing.T) {
  111. err := testDSLs(testConfig)
  112. if err != nil && !testConfig.expectFailure {
  113. t.Fatal(err.Error())
  114. }
  115. })
  116. }
  117. }
  118. var (
  119. testClientIP = "192.168.0.1"
  120. testClientGeoIPData = common.GeoIPData{
  121. Country: "Country",
  122. City: "City",
  123. ISP: "ISP",
  124. ASN: "ASN",
  125. ASO: "ASO",
  126. }
  127. testHostID = "host_id"
  128. )
  129. func testDSLs(testConfig *testConfig) error {
  130. testDataDirName, err := ioutil.TempDir("", "psiphon-dsl-test")
  131. if err != nil {
  132. return errors.Trace(err)
  133. }
  134. defer os.RemoveAll(testDataDirName)
  135. // Initialize OSLs
  136. var backendOSLPaveData1 []*osl.PaveData
  137. var backendOSLPaveData2 []*osl.PaveData
  138. var clientSLOKs []*osl.SLOK
  139. if testConfig.requireOSLKeys {
  140. var err error
  141. backendOSLPaveData1, backendOSLPaveData2, clientSLOKs, err =
  142. testutils.InitializeTestOSLPaveData()
  143. if err != nil {
  144. return errors.Trace(err)
  145. }
  146. }
  147. // Initialize backend
  148. tlsConfig, err := testutils.NewTestDSLTLSConfig()
  149. if err != nil {
  150. return errors.Trace(err)
  151. }
  152. backend, err := testutils.NewTestDSLBackend(
  153. NewBackendTestShim(),
  154. tlsConfig,
  155. testClientIP, &testClientGeoIPData, testHostID,
  156. backendOSLPaveData1)
  157. if err != nil {
  158. return errors.Trace(err)
  159. }
  160. err = backend.Start()
  161. if err != nil {
  162. return errors.Trace(err)
  163. }
  164. defer backend.Stop()
  165. // Initialize relay
  166. expectValidMetric := false
  167. metricsValidator := func(metric string, fields common.LogFields) bool { return false }
  168. if testConfig.cacheServerEntries || testConfig.cacheOSLFileSpecs {
  169. expectValidMetric = true
  170. metricsValidator = func(metric string, fields common.LogFields) bool {
  171. // TODO: in "both" test case, check that both events are logged
  172. return (testConfig.cacheServerEntries && metric == "dsl_relay_get_server_entries") ||
  173. (testConfig.cacheOSLFileSpecs && metric == "dsl_relay_get_osl_file_specs")
  174. }
  175. }
  176. relayLogger := testutils.NewTestLoggerWithMetricValidator("relay", metricsValidator)
  177. relayCACertificatesFilename,
  178. relayHostCertificateFilename,
  179. relayHostKeyFilename,
  180. err := tlsConfig.WriteRelayFiles(testDataDirName)
  181. if err != nil {
  182. return errors.Trace(err)
  183. }
  184. relayGetServiceAddress := func(_ common.GeoIPData) (string, error) {
  185. return backend.GetAddress(), nil
  186. }
  187. relayConfig := &RelayConfig{
  188. Logger: relayLogger,
  189. CACertificatesFilename: relayCACertificatesFilename,
  190. HostCertificateFilename: relayHostCertificateFilename,
  191. HostKeyFilename: relayHostKeyFilename,
  192. GetServiceAddress: relayGetServiceAddress,
  193. HostID: testHostID,
  194. APIParameterValidator: func(params common.APIParameters) error { return nil },
  195. APIParameterLogFieldFormatter: func(
  196. _ string, _ common.GeoIPData, params common.APIParameters) common.LogFields {
  197. logFields := common.LogFields{}
  198. logFields.Add(common.LogFields(params))
  199. return logFields
  200. },
  201. }
  202. relay, err := NewRelay(relayConfig)
  203. if err != nil {
  204. return errors.Trace(err)
  205. }
  206. serverEntryCacheTTL := defaultServerEntryCacheTTL
  207. serverEntryCacheMaxSize := defaultServerEntryCacheMaxSize
  208. oslFileSpecCacheTTL := defaultOSLFileSpecCacheTTL
  209. oslFileSpecCacheMaxSize := defaultOSLFileSpecCacheMaxSize
  210. if !testConfig.cacheServerEntries {
  211. serverEntryCacheTTL = 0
  212. serverEntryCacheMaxSize = 0
  213. }
  214. if !testConfig.cacheOSLFileSpecs {
  215. oslFileSpecCacheTTL = 0
  216. oslFileSpecCacheMaxSize = 0
  217. }
  218. relay.SetCacheParameters(
  219. serverEntryCacheTTL,
  220. serverEntryCacheMaxSize,
  221. oslFileSpecCacheTTL,
  222. oslFileSpecCacheMaxSize)
  223. // Initialize client fetcher
  224. // Set transfer targets that will exercise various scenarios, including
  225. // requiring request size backoff (e.g. see Fetcher.doGetServerEntriesRequest)
  226. // to succeed.
  227. discoverCount := 128
  228. getCount := 64
  229. oslCount := 1
  230. interruptLimit := 0
  231. if testConfig.interruptDownloads {
  232. interruptLimit = 8192
  233. }
  234. retryCount := 0
  235. if testConfig.enableRetries {
  236. retryCount = 20
  237. }
  238. isTunneled := testConfig.isTunneled
  239. if isTunneled {
  240. discoverCount = 1
  241. }
  242. if backend.GetServerEntryCount(isTunneled) != 128 {
  243. return errors.TraceNew("unexpected server entry count")
  244. }
  245. dslClient := newDSLClient(clientSLOKs)
  246. clientRelayRoundTripper := func(
  247. ctx context.Context,
  248. requestPayload []byte) ([]byte, error) {
  249. // Normally, the Fetcher.RoundTripper would add a circumvention,
  250. // blocking resistant first hop. For this test, it's just a stub that
  251. // directly invokes the relay.
  252. responsePayload, err := relay.HandleRequest(
  253. ctx,
  254. nil,
  255. testClientIP,
  256. testClientGeoIPData,
  257. isTunneled,
  258. requestPayload)
  259. if err != nil {
  260. return GetRelayGenericErrorResponse(), errors.Trace(err)
  261. }
  262. // Simulate interruption of large response.
  263. if interruptLimit > 0 && len(responsePayload) > interruptLimit {
  264. return nil, errors.TraceNew("interrupted")
  265. }
  266. return responsePayload, nil
  267. }
  268. // TODO: exercise BaseAPIParameters?
  269. var unexpectedServerEntrySource atomic.Int32
  270. var unexpectedServerEntryPrioritizeDial atomic.Int32
  271. datastoreHasServerEntryWithCheck := func(
  272. tag ServerEntryTag,
  273. version int,
  274. prioritizeDial bool) bool {
  275. _, expectedPrioritizeDial, err := backend.GetServerEntryProperties(tag.String())
  276. if err != nil || prioritizeDial != expectedPrioritizeDial {
  277. unexpectedServerEntryPrioritizeDial.Store(1)
  278. }
  279. return dslClient.DatastoreHasServerEntry(tag, version)
  280. }
  281. datastoreStoreServerEntryWithCheck := func(
  282. packedServerEntryFields protocol.PackedServerEntryFields,
  283. source string,
  284. prioritizeDial bool) error {
  285. serverEntryFields, _ := protocol.DecodePackedServerEntryFields(packedServerEntryFields)
  286. tag := serverEntryFields.GetTag()
  287. expectedSource, expectedPrioritizeDial, err := backend.GetServerEntryProperties(tag)
  288. if err != nil || prioritizeDial != expectedPrioritizeDial {
  289. unexpectedServerEntryPrioritizeDial.Store(1)
  290. }
  291. if err != nil || source != expectedSource {
  292. unexpectedServerEntrySource.Store(1)
  293. }
  294. return errors.Trace(
  295. dslClient.DatastoreStoreServerEntry(packedServerEntryFields, source))
  296. }
  297. fetcherConfig := &FetcherConfig{
  298. Logger: testutils.NewTestLoggerWithComponent("fetcher"),
  299. RoundTripper: clientRelayRoundTripper,
  300. DatastoreGetLastFetchTime: dslClient.DatastoreGetLastFetchTime,
  301. DatastoreSetLastFetchTime: dslClient.DatastoreSetLastFetchTime,
  302. DatastoreGetLastActiveOSLsTime: dslClient.DatastoreGetLastActiveOSLsTime,
  303. DatastoreSetLastActiveOSLsTime: dslClient.DatastoreSetLastActiveOSLsTime,
  304. DatastoreHasServerEntry: datastoreHasServerEntryWithCheck,
  305. DatastoreStoreServerEntry: datastoreStoreServerEntryWithCheck,
  306. DatastoreKnownOSLIDs: dslClient.DatastoreKnownOSLIDs,
  307. DatastoreGetOSLState: dslClient.DatastoreGetOSLState,
  308. DatastoreStoreOSLState: dslClient.DatastoreStoreOSLState,
  309. DatastoreDeleteOSLState: dslClient.DatastoreDeleteOSLState,
  310. DatastoreSLOKLookup: dslClient.DatastoreSLOKLookup,
  311. RequestTimeout: 1 * time.Second,
  312. RequestRetryCount: retryCount,
  313. RequestRetryDelay: 1 * time.Millisecond,
  314. RequestRetryDelayJitter: 0.1,
  315. FetchTTL: 1 * time.Hour,
  316. DiscoverServerEntriesMinCount: discoverCount,
  317. DiscoverServerEntriesMaxCount: discoverCount,
  318. GetServerEntriesMinCount: getCount,
  319. GetServerEntriesMaxCount: getCount,
  320. GetLastActiveOSLsTTL: 1 * time.Hour,
  321. GetOSLFileSpecsMinCount: oslCount,
  322. GetOSLFileSpecsMaxCount: oslCount,
  323. DoGarbageCollection: debug.FreeOSMemory,
  324. }
  325. fetcher, err := NewFetcher(fetcherConfig)
  326. if err != nil {
  327. return errors.Trace(err)
  328. }
  329. // Fetch server entries
  330. ctx, cancelFunc := context.WithTimeout(context.Background(), 60*time.Second)
  331. defer cancelFunc()
  332. err = fetcher.Run(ctx)
  333. if testConfig.expectFailure && err == nil {
  334. err = errors.TraceNew("unexpected success")
  335. }
  336. if err != nil {
  337. return errors.Trace(err)
  338. }
  339. if testConfig.repeatBeforeTTL {
  340. // Invoke fetch again with before the last discover time TTL expires.
  341. // The always-failing round tripper will be hit if an unexpected
  342. // request is sent.
  343. fetcherConfig.RoundTripper = func(
  344. context.Context,
  345. []byte) ([]byte, error) {
  346. return nil, errors.TraceNew("round trip not permitted")
  347. }
  348. err = fetcher.Run(ctx)
  349. if err != nil {
  350. return errors.Trace(err)
  351. }
  352. }
  353. if testConfig.alreadyDiscovered && testConfig.isTunneled {
  354. return errors.TraceNew("invalid test configuration")
  355. }
  356. if testConfig.alreadyDiscovered {
  357. // Fetch again after resetting the last discover time TTL. A
  358. // DiscoverServerEntries request will be sent, but all tags should be
  359. // known, and no GetServerEntries requests should be sent or any
  360. // server entries stores, as will be checked via
  361. // dslClient.serverEntryStoreCount.
  362. dslClient.lastFetchTime = time.Time{}
  363. dslClient.lastActiveOSLsTime = time.Time{}
  364. err = fetcher.Run(ctx)
  365. if err != nil {
  366. return errors.Trace(err)
  367. }
  368. }
  369. if testConfig.isTunneled {
  370. if dslClient.serverEntryStoreCount != 1 {
  371. return errors.Tracef(
  372. "unexpected server entry store count: %d", dslClient.serverEntryStoreCount)
  373. }
  374. // If the first request was isTunneled, only one server entry will
  375. // have been fetched. Do another full fetch, and the following
  376. // dslClient.serverEntryStoreCount check will demonstrate that all
  377. // remaining server entries were downloaded and stored.
  378. dslClient.lastFetchTime = time.Time{}
  379. discoverCount = 128
  380. fetcherConfig.DiscoverServerEntriesMinCount = discoverCount
  381. fetcherConfig.DiscoverServerEntriesMaxCount = discoverCount
  382. err = fetcher.Run(ctx)
  383. if err != nil {
  384. return errors.Trace(err)
  385. }
  386. }
  387. // TODO: check "updated" and "known" counters in "DSL: fetched server
  388. // entries" logs.
  389. if dslClient.serverEntryStoreCount != backend.GetServerEntryCount(isTunneled) {
  390. return errors.Tracef(
  391. "unexpected server entry store count: %d", dslClient.serverEntryStoreCount)
  392. }
  393. if testConfig.cacheOSLFileSpecs {
  394. if !testConfig.requireOSLKeys {
  395. return errors.TraceNew("invalid test config")
  396. }
  397. // Refetch OSL file specs.
  398. dslClient.lastFetchTime = time.Time{}
  399. dslClient.lastActiveOSLsTime = time.Time{}
  400. dslClient.oslStates = make(map[string][]byte)
  401. err = fetcher.Run(ctx)
  402. if err != nil {
  403. return errors.Trace(err)
  404. }
  405. }
  406. if testConfig.requireOSLKeys {
  407. // Rotate to the next OSL period and clear all server entries. The
  408. // fetcher will download the new, unknown OSL and reassemble the key,
  409. // or else no server entries will be downloaded. Check that the
  410. // fetcher cleans up the old, no longer active OSL state via
  411. // dslClient.deleteOSLStateCount.
  412. dslClient.lastFetchTime = time.Time{}
  413. dslClient.lastActiveOSLsTime = time.Time{}
  414. dslClient.serverEntries = make(map[string]protocol.ServerEntryFields)
  415. backend.SetOSLPaveData(backendOSLPaveData2)
  416. err = fetcher.Run(ctx)
  417. if err != nil {
  418. return errors.Trace(err)
  419. }
  420. if dslClient.serverEntryStoreCount != backend.GetServerEntryCount(isTunneled) {
  421. return errors.Tracef(
  422. "unexpected server entry store count: %d", dslClient.serverEntryStoreCount)
  423. }
  424. if dslClient.deleteOSLStateCount < 1 {
  425. return errors.Tracef(
  426. "unexpected delete OSL state count: %d", dslClient.deleteOSLStateCount)
  427. }
  428. }
  429. err = relayLogger.CheckMetrics(expectValidMetric)
  430. if err != nil {
  431. return errors.Trace(err)
  432. }
  433. if unexpectedServerEntrySource.Load() != 0 {
  434. return errors.TraceNew("unexpected server entry source")
  435. }
  436. if unexpectedServerEntryPrioritizeDial.Load() != 0 {
  437. return errors.TraceNew("unexpected server entry prioritize dial")
  438. }
  439. return nil
  440. }
  441. type dslClient struct {
  442. mutex sync.Mutex
  443. lastFetchTime time.Time
  444. lastActiveOSLsTime time.Time
  445. serverEntries map[string]protocol.ServerEntryFields
  446. serverEntryStoreCount int
  447. oslStates map[string][]byte
  448. deleteOSLStateCount int
  449. SLOKs []*osl.SLOK
  450. }
  451. func newDSLClient(SLOKs []*osl.SLOK) *dslClient {
  452. return &dslClient{
  453. serverEntries: make(map[string]protocol.ServerEntryFields),
  454. oslStates: make(map[string][]byte),
  455. SLOKs: SLOKs,
  456. }
  457. }
  458. func (c *dslClient) DatastoreGetLastFetchTime() (time.Time, error) {
  459. c.mutex.Lock()
  460. defer c.mutex.Unlock()
  461. return c.lastFetchTime, nil
  462. }
  463. func (c *dslClient) DatastoreSetLastFetchTime(time time.Time) error {
  464. c.mutex.Lock()
  465. defer c.mutex.Unlock()
  466. c.lastFetchTime = time
  467. return nil
  468. }
  469. func (c *dslClient) DatastoreGetLastActiveOSLsTime() (time.Time, error) {
  470. c.mutex.Lock()
  471. defer c.mutex.Unlock()
  472. return c.lastActiveOSLsTime, nil
  473. }
  474. func (c *dslClient) DatastoreSetLastActiveOSLsTime(time time.Time) error {
  475. c.mutex.Lock()
  476. defer c.mutex.Unlock()
  477. c.lastActiveOSLsTime = time
  478. return nil
  479. }
  480. func (c *dslClient) DatastoreHasServerEntry(tag ServerEntryTag, version int) bool {
  481. c.mutex.Lock()
  482. defer c.mutex.Unlock()
  483. _, ok := c.serverEntries[base64.StdEncoding.EncodeToString(tag)]
  484. return ok
  485. }
  486. func (c *dslClient) DatastoreStoreServerEntry(
  487. packedServerEntryFields protocol.PackedServerEntryFields, source string) error {
  488. c.mutex.Lock()
  489. defer c.mutex.Unlock()
  490. c.serverEntryStoreCount += 1
  491. serverEntryFields, err := protocol.DecodePackedServerEntryFields(packedServerEntryFields)
  492. if err != nil {
  493. return errors.Trace(err)
  494. }
  495. serverEntryFields.SetLocalSource(source)
  496. serverEntryFields.SetLocalTimestamp(
  497. common.TruncateTimestampToHour(common.GetCurrentTimestamp()))
  498. c.serverEntries[serverEntryFields.GetTag()] = serverEntryFields
  499. return nil
  500. }
  501. func (c *dslClient) DatastoreKnownOSLIDs() ([]OSLID, error) {
  502. c.mutex.Lock()
  503. defer c.mutex.Unlock()
  504. var IDs []OSLID
  505. for IDStr := range c.oslStates {
  506. ID, _ := hex.DecodeString(IDStr)
  507. IDs = append(IDs, ID)
  508. }
  509. return IDs, nil
  510. }
  511. func (c *dslClient) DatastoreGetOSLState(ID OSLID) ([]byte, error) {
  512. c.mutex.Lock()
  513. defer c.mutex.Unlock()
  514. state, ok := c.oslStates[hex.EncodeToString(ID)]
  515. if !ok {
  516. return nil, nil
  517. }
  518. return state, nil
  519. }
  520. func (c *dslClient) DatastoreStoreOSLState(ID OSLID, state []byte) error {
  521. c.mutex.Lock()
  522. defer c.mutex.Unlock()
  523. c.oslStates[hex.EncodeToString(ID)] = state
  524. return nil
  525. }
  526. func (c *dslClient) DatastoreDeleteOSLState(ID OSLID) error {
  527. c.mutex.Lock()
  528. defer c.mutex.Unlock()
  529. c.deleteOSLStateCount += 1
  530. delete(c.oslStates, hex.EncodeToString(ID))
  531. return nil
  532. }
  533. func (c *dslClient) DatastoreSLOKLookup(SLOKID []byte) []byte {
  534. c.mutex.Lock()
  535. defer c.mutex.Unlock()
  536. for _, slok := range c.SLOKs {
  537. if bytes.Equal(slok.ID, SLOKID) {
  538. return slok.Key
  539. }
  540. }
  541. return nil
  542. }
  543. func (c *dslClient) DatastoreFatalError(err error) {
  544. panic(err.Error())
  545. }