remoteServerList.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /*
  2. * Copyright (c) 2015, 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 psiphon
  20. import (
  21. "context"
  22. "encoding/hex"
  23. "fmt"
  24. "net/url"
  25. "os"
  26. "sync/atomic"
  27. "time"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/osl"
  31. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  33. )
  34. type RemoteServerListFetcher func(
  35. ctx context.Context, config *Config, attempt int, tunnel *Tunnel, untunneledDialConfig *DialConfig) error
  36. // FetchCommonRemoteServerList downloads the common remote server list from
  37. // config.RemoteServerListURLs. It validates its digital signature using the
  38. // public key config.RemoteServerListSignaturePublicKey and parses the
  39. // data field into ServerEntry records.
  40. // config.GetRemoteServerListDownloadFilename() is the location to store the
  41. // download. As the download is resumed after failure, this filename must
  42. // be unique and persistent.
  43. func FetchCommonRemoteServerList(
  44. ctx context.Context,
  45. config *Config,
  46. attempt int,
  47. tunnel *Tunnel,
  48. untunneledDialConfig *DialConfig) error {
  49. NoticeInfo("fetching common remote server list")
  50. p := config.GetParameters().Get()
  51. publicKey := p.String(parameters.RemoteServerListSignaturePublicKey)
  52. urls := p.TransferURLs(parameters.RemoteServerListURLs)
  53. downloadTimeout := p.Duration(parameters.FetchRemoteServerListTimeout)
  54. p.Close()
  55. downloadURL := urls.Select(attempt)
  56. canonicalURL := urls.CanonicalURL()
  57. newETag, downloadStatRecorder, err := downloadRemoteServerListFile(
  58. ctx,
  59. config,
  60. tunnel,
  61. untunneledDialConfig,
  62. downloadTimeout,
  63. downloadURL.URL,
  64. canonicalURL,
  65. downloadURL.FrontingSpecs,
  66. downloadURL.SkipVerify,
  67. config.DisableSystemRootCAs,
  68. "",
  69. config.GetRemoteServerListDownloadFilename())
  70. if err != nil {
  71. return errors.Tracef("failed to download common remote server list: %s", errors.Trace(err))
  72. }
  73. authenticatedDownload := false
  74. if downloadStatRecorder != nil {
  75. defer func() { downloadStatRecorder(authenticatedDownload) }()
  76. }
  77. // When the resource is unchanged, skip.
  78. if newETag == "" {
  79. return nil
  80. }
  81. file, err := os.Open(config.GetRemoteServerListDownloadFilename())
  82. if err != nil {
  83. return errors.Tracef("failed to open common remote server list: %s", errors.Trace(err))
  84. }
  85. defer file.Close()
  86. serverListPayloadReader, err := common.NewAuthenticatedDataPackageReader(
  87. file, publicKey)
  88. if err != nil {
  89. return errors.Tracef("failed to read remote server list: %s", errors.Trace(err))
  90. }
  91. // NewAuthenticatedDataPackageReader authenticates the file before returning.
  92. authenticatedDownload = true
  93. err = StreamingStoreServerEntries(
  94. ctx,
  95. config,
  96. protocol.NewStreamingServerEntryDecoder(
  97. serverListPayloadReader,
  98. common.GetCurrentTimestamp(),
  99. protocol.SERVER_ENTRY_SOURCE_REMOTE),
  100. true)
  101. if err != nil {
  102. return errors.Tracef("failed to store common remote server list: %s", errors.Trace(err))
  103. }
  104. // Now that the server entries are successfully imported, store the response
  105. // ETag so we won't re-download this same data again.
  106. err = SetUrlETag(canonicalURL, newETag)
  107. if err != nil {
  108. NoticeWarning("failed to set ETag for common remote server list: %s", errors.Trace(err))
  109. // This fetch is still reported as a success, even if we can't store the etag
  110. }
  111. return nil
  112. }
  113. // FetchObfuscatedServerLists downloads the obfuscated remote server lists
  114. // from config.ObfuscatedServerListRootURLs.
  115. // It first downloads the OSL registry, and then downloads each seeded OSL
  116. // advertised in the registry. All downloads are resumable, ETags are used
  117. // to skip both an unchanged registry or unchanged OSL files, and when an
  118. // individual download fails, the fetch proceeds if it can.
  119. // Authenticated package digital signatures are validated using the
  120. // public key config.RemoteServerListSignaturePublicKey.
  121. // config.GetObfuscatedServerListDownloadDirectory() is the location to store
  122. // the downloaded files. As downloads are resumed after failure, this directory
  123. // must be unique and persistent.
  124. func FetchObfuscatedServerLists(
  125. ctx context.Context,
  126. config *Config,
  127. attempt int,
  128. tunnel *Tunnel,
  129. untunneledDialConfig *DialConfig) error {
  130. NoticeInfo("fetching obfuscated remote server lists")
  131. p := config.GetParameters().Get()
  132. publicKey := p.String(parameters.RemoteServerListSignaturePublicKey)
  133. urls := p.TransferURLs(parameters.ObfuscatedServerListRootURLs)
  134. downloadTimeout := p.Duration(parameters.FetchRemoteServerListTimeout)
  135. p.Close()
  136. rootURL := urls.Select(attempt)
  137. canonicalRootURL := urls.CanonicalURL()
  138. downloadURL := osl.GetOSLRegistryURL(rootURL.URL)
  139. canonicalURL := osl.GetOSLRegistryURL(canonicalRootURL)
  140. downloadFilename := osl.GetOSLRegistryFilename(config.GetObfuscatedServerListDownloadDirectory())
  141. cachedFilename := downloadFilename + ".cached"
  142. // If the cached registry is not present, we need to download or resume downloading
  143. // the registry, so clear the ETag to ensure that always happens.
  144. _, err := os.Stat(cachedFilename)
  145. if os.IsNotExist(err) {
  146. err := SetUrlETag(canonicalURL, "")
  147. if err != nil {
  148. NoticeWarning("SetUrlETag failed: %v", errors.Trace(err))
  149. // Continue
  150. }
  151. }
  152. // failed is set if any operation fails and should trigger a retry. When the OSL registry
  153. // fails to download, any cached registry is used instead; when any single OSL fails
  154. // to download, the overall operation proceeds. So this flag records whether to report
  155. // failure at the end when downloading has proceeded after a failure.
  156. // TODO: should disk-full conditions not trigger retries?
  157. var failed bool
  158. // updateCache is set when modifed registry content is downloaded. Both the cached
  159. // file and the persisted ETag will be updated in this case. The update is deferred
  160. // until after the registry has been authenticated.
  161. updateCache := false
  162. registryFilename := cachedFilename
  163. newETag, downloadStatRecorder, err := downloadRemoteServerListFile(
  164. ctx,
  165. config,
  166. tunnel,
  167. untunneledDialConfig,
  168. downloadTimeout,
  169. downloadURL,
  170. canonicalURL,
  171. rootURL.FrontingSpecs,
  172. rootURL.SkipVerify,
  173. config.DisableSystemRootCAs,
  174. "",
  175. downloadFilename)
  176. if err != nil {
  177. failed = true
  178. NoticeWarning("failed to download obfuscated server list registry: %s", errors.Trace(err))
  179. // Proceed with any existing cached OSL registry.
  180. }
  181. authenticatedDownload := false
  182. if downloadStatRecorder != nil {
  183. defer func() { downloadStatRecorder(authenticatedDownload) }()
  184. }
  185. if newETag != "" {
  186. updateCache = true
  187. registryFilename = downloadFilename
  188. }
  189. // Prevent excessive notice noise in cases such as a general database
  190. // failure, as GetSLOK may be called thousands of times per fetch.
  191. emittedGetSLOKAlert := int32(0)
  192. lookupSLOKs := func(slokID []byte) []byte {
  193. // Lookup SLOKs in local datastore
  194. key, err := GetSLOK(slokID)
  195. if err != nil && atomic.CompareAndSwapInt32(&emittedGetSLOKAlert, 0, 1) {
  196. NoticeWarning("GetSLOK failed: %s", err)
  197. }
  198. return key
  199. }
  200. registryFile, err := os.Open(registryFilename)
  201. if err != nil {
  202. return errors.Tracef("failed to read obfuscated server list registry: %s", errors.Trace(err))
  203. }
  204. defer registryFile.Close()
  205. registryStreamer, err := osl.NewRegistryStreamer(
  206. registryFile,
  207. publicKey,
  208. lookupSLOKs)
  209. if err != nil {
  210. // TODO: delete file? redownload if corrupt?
  211. return errors.Tracef("failed to read obfuscated server list registry: %s", errors.Trace(err))
  212. }
  213. authenticatedDownload = true
  214. // NewRegistryStreamer authenticates the downloaded registry, so now it would be
  215. // ok to update the cache. However, we defer that until after processing so we
  216. // can close the file first before copying it, avoiding related complications on
  217. // platforms such as Windows.
  218. // Note: we proceed to check individual OSLs even if the directory is unchanged,
  219. // as the set of local SLOKs may have changed.
  220. for {
  221. oslFileSpec, err := registryStreamer.Next()
  222. if err != nil {
  223. failed = true
  224. NoticeWarning("failed to stream obfuscated server list registry: %s", errors.Trace(err))
  225. break
  226. }
  227. if oslFileSpec == nil {
  228. break
  229. }
  230. if !downloadOSLFileSpec(
  231. ctx,
  232. config,
  233. tunnel,
  234. untunneledDialConfig,
  235. downloadTimeout,
  236. rootURL,
  237. canonicalRootURL,
  238. publicKey,
  239. lookupSLOKs,
  240. oslFileSpec) {
  241. // downloadOSLFileSpec emits notices with failure information. In the case
  242. // of a failure, set the retry flag but continue to process other OSL file
  243. // specs.
  244. failed = true
  245. }
  246. // Run a garbage collection to reclaim memory from the downloadOSLFileSpec
  247. // operation before processing the next file.
  248. DoGarbageCollection()
  249. }
  250. // Now that a new registry is downloaded, validated, and parsed, store
  251. // the response ETag so we won't re-download this same data again. First
  252. // close the file to avoid complications on platforms such as Windows.
  253. if updateCache {
  254. registryFile.Close()
  255. err := os.Rename(downloadFilename, cachedFilename)
  256. if err != nil {
  257. NoticeWarning("failed to set cached obfuscated server list registry: %s", errors.Trace(err))
  258. // This fetch is still reported as a success, even if we can't update the cache
  259. }
  260. err = SetUrlETag(canonicalURL, newETag)
  261. if err != nil {
  262. NoticeWarning("failed to set ETag for obfuscated server list registry: %s", errors.Trace(err))
  263. // This fetch is still reported as a success, even if we can't store the ETag
  264. }
  265. }
  266. if failed {
  267. return errors.TraceNew("one or more operations failed")
  268. }
  269. return nil
  270. }
  271. // downloadOSLFileSpec downloads, authenticates, and imports the OSL specified
  272. // by oslFileSpec. The return value indicates whether the operation succeeded.
  273. // Failure information is emitted in notices.
  274. func downloadOSLFileSpec(
  275. ctx context.Context,
  276. config *Config,
  277. tunnel *Tunnel,
  278. untunneledDialConfig *DialConfig,
  279. downloadTimeout time.Duration,
  280. rootURL *parameters.TransferURL,
  281. canonicalRootURL string,
  282. publicKey string,
  283. lookupSLOKs func(slokID []byte) []byte,
  284. oslFileSpec *osl.OSLFileSpec) bool {
  285. downloadFilename := osl.GetOSLFilename(
  286. config.GetObfuscatedServerListDownloadDirectory(), oslFileSpec.ID)
  287. downloadURL := osl.GetOSLFileURL(rootURL.URL, oslFileSpec.ID)
  288. canonicalURL := osl.GetOSLFileURL(canonicalRootURL, oslFileSpec.ID)
  289. hexID := hex.EncodeToString(oslFileSpec.ID)
  290. // Note: the MD5 checksum step assumes the remote server list host's ETag uses MD5
  291. // with a hex encoding. If this is not the case, the sourceETag should be left blank.
  292. sourceETag := fmt.Sprintf("\"%s\"", hex.EncodeToString(oslFileSpec.MD5Sum))
  293. newETag, downloadStatRecorder, err := downloadRemoteServerListFile(
  294. ctx,
  295. config,
  296. tunnel,
  297. untunneledDialConfig,
  298. downloadTimeout,
  299. downloadURL,
  300. canonicalURL,
  301. rootURL.FrontingSpecs,
  302. rootURL.SkipVerify,
  303. config.DisableSystemRootCAs,
  304. sourceETag,
  305. downloadFilename)
  306. if err != nil {
  307. NoticeWarning("failed to download obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  308. return false
  309. }
  310. authenticatedDownload := false
  311. if downloadStatRecorder != nil {
  312. defer func() { downloadStatRecorder(authenticatedDownload) }()
  313. }
  314. // When the resource is unchanged, skip.
  315. if newETag == "" {
  316. return true
  317. }
  318. file, err := os.Open(downloadFilename)
  319. if err != nil {
  320. NoticeWarning("failed to open obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  321. return false
  322. }
  323. defer file.Close()
  324. serverListPayloadReader, err := osl.NewOSLReader(
  325. file,
  326. oslFileSpec,
  327. lookupSLOKs,
  328. publicKey)
  329. if err != nil {
  330. NoticeWarning("failed to read obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  331. return false
  332. }
  333. // NewOSLReader authenticates the file before returning.
  334. authenticatedDownload = true
  335. err = StreamingStoreServerEntries(
  336. ctx,
  337. config,
  338. protocol.NewStreamingServerEntryDecoder(
  339. serverListPayloadReader,
  340. common.GetCurrentTimestamp(),
  341. protocol.SERVER_ENTRY_SOURCE_OBFUSCATED),
  342. true)
  343. if err != nil {
  344. NoticeWarning("failed to store obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  345. return false
  346. }
  347. // Now that the server entries are successfully imported, store the response
  348. // ETag so we won't re-download this same data again.
  349. err = SetUrlETag(canonicalURL, newETag)
  350. if err != nil {
  351. NoticeWarning("failed to set ETag for obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  352. // This fetch is still reported as a success, even if we can't store the ETag
  353. return true
  354. }
  355. return true
  356. }
  357. // downloadRemoteServerListFile downloads the source URL to the destination
  358. // file, performing a resumable download. When the download completes and the
  359. // file content has changed, the new resource ETag is returned. Otherwise,
  360. // blank is returned. The caller is responsible for calling SetUrlETag once
  361. // the file content has been validated.
  362. //
  363. // The downloadStatReporter return value is a function that will invoke
  364. // RecordRemoteServerListStat to record a remote server list download event.
  365. // The caller must call this function if the return value is not nil,
  366. // providing a boolean argument indicating whether the download was
  367. // successfully authenticated.
  368. func downloadRemoteServerListFile(
  369. ctx context.Context,
  370. config *Config,
  371. tunnel *Tunnel,
  372. untunneledDialConfig *DialConfig,
  373. downloadTimeout time.Duration,
  374. sourceURL string,
  375. canonicalURL string,
  376. frontingSpecs parameters.FrontingSpecs,
  377. skipVerify bool,
  378. disableSystemRootCAs bool,
  379. sourceETag string,
  380. destinationFilename string) (string, func(bool), error) {
  381. // All download URLs with the same canonicalURL
  382. // must have the same entity and ETag.
  383. lastETag, err := GetUrlETag(canonicalURL)
  384. if err != nil {
  385. return "", nil, errors.Trace(err)
  386. }
  387. // sourceETag, when specified, is prior knowledge of the
  388. // remote ETag that can be used to skip the request entirely.
  389. // This will be set in the case of OSL files, from the MD5Sum
  390. // values stored in the registry.
  391. if lastETag != "" && sourceETag == lastETag {
  392. // TODO: notice?
  393. return "", nil, nil
  394. }
  395. var cancelFunc context.CancelFunc
  396. ctx, cancelFunc = context.WithTimeout(ctx, downloadTimeout)
  397. defer cancelFunc()
  398. // MakeDownloadHttpClient will select either a tunneled
  399. // or untunneled configuration.
  400. payloadSecure := true
  401. frontingUseDeviceBinder := true
  402. httpClient, tunneled, getParams, err := MakeDownloadHTTPClient(
  403. ctx,
  404. config,
  405. tunnel,
  406. untunneledDialConfig,
  407. skipVerify,
  408. disableSystemRootCAs,
  409. payloadSecure,
  410. frontingSpecs,
  411. frontingUseDeviceBinder,
  412. func(frontingProviderID string) {
  413. NoticeInfo(
  414. "downloadRemoteServerListFile: selected fronting provider %s for %s",
  415. frontingProviderID, sourceURL)
  416. })
  417. if err != nil {
  418. return "", nil, errors.Trace(err)
  419. }
  420. startTime := time.Now()
  421. bytes, responseETag, err := ResumeDownload(
  422. ctx,
  423. httpClient,
  424. sourceURL,
  425. MakePsiphonUserAgent(config),
  426. destinationFilename,
  427. lastETag)
  428. duration := time.Since(startTime)
  429. NoticeRemoteServerListResourceDownloadedBytes(sourceURL, bytes, duration)
  430. if err != nil {
  431. return "", nil, errors.Trace(err)
  432. }
  433. if responseETag == lastETag {
  434. return "", nil, nil
  435. }
  436. NoticeRemoteServerListResourceDownloaded(sourceURL)
  437. // Parameters can be retrieved now because the request has completed.
  438. var additionalParameters common.APIParameters
  439. if getParams != nil {
  440. additionalParameters = getParams()
  441. }
  442. downloadStatRecorder := func(authenticated bool) {
  443. // Invoke DNS cache extension (if enabled in the resolver) now that
  444. // the download succeeded and the payload is authenticated. Only
  445. // extend when authenticated, as this demonstrates that any domain
  446. // name resolved to an endpoint that served a valid Psiphon remote
  447. // server list.
  448. //
  449. // TODO: when !skipVerify, invoke DNS cache extension earlier, in
  450. // ResumeDownload, after making the request but before downloading
  451. // the response body?
  452. resolver := config.GetResolver()
  453. url, err := url.Parse(sourceURL)
  454. if authenticated && resolver != nil && err == nil {
  455. resolver.VerifyCacheExtension(url.Hostname())
  456. }
  457. _ = RecordRemoteServerListStat(
  458. config, tunneled, sourceURL, responseETag, bytes, duration, authenticated, additionalParameters)
  459. }
  460. return responseETag, downloadStatRecorder, nil
  461. }