remoteServerList.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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. SetUrlETag(canonicalURL, "")
  147. }
  148. // failed is set if any operation fails and should trigger a retry. When the OSL registry
  149. // fails to download, any cached registry is used instead; when any single OSL fails
  150. // to download, the overall operation proceeds. So this flag records whether to report
  151. // failure at the end when downloading has proceeded after a failure.
  152. // TODO: should disk-full conditions not trigger retries?
  153. var failed bool
  154. // updateCache is set when modifed registry content is downloaded. Both the cached
  155. // file and the persisted ETag will be updated in this case. The update is deferred
  156. // until after the registry has been authenticated.
  157. updateCache := false
  158. registryFilename := cachedFilename
  159. newETag, downloadStatRecorder, err := downloadRemoteServerListFile(
  160. ctx,
  161. config,
  162. tunnel,
  163. untunneledDialConfig,
  164. downloadTimeout,
  165. downloadURL,
  166. canonicalURL,
  167. rootURL.FrontingSpecs,
  168. rootURL.SkipVerify,
  169. config.DisableSystemRootCAs,
  170. "",
  171. downloadFilename)
  172. if err != nil {
  173. failed = true
  174. NoticeWarning("failed to download obfuscated server list registry: %s", errors.Trace(err))
  175. // Proceed with any existing cached OSL registry.
  176. }
  177. authenticatedDownload := false
  178. if downloadStatRecorder != nil {
  179. defer func() { downloadStatRecorder(authenticatedDownload) }()
  180. }
  181. if newETag != "" {
  182. updateCache = true
  183. registryFilename = downloadFilename
  184. }
  185. // Prevent excessive notice noise in cases such as a general database
  186. // failure, as GetSLOK may be called thousands of times per fetch.
  187. emittedGetSLOKAlert := int32(0)
  188. lookupSLOKs := func(slokID []byte) []byte {
  189. // Lookup SLOKs in local datastore
  190. key, err := GetSLOK(slokID)
  191. if err != nil && atomic.CompareAndSwapInt32(&emittedGetSLOKAlert, 0, 1) {
  192. NoticeWarning("GetSLOK failed: %s", err)
  193. }
  194. return key
  195. }
  196. registryFile, err := os.Open(registryFilename)
  197. if err != nil {
  198. return errors.Tracef("failed to read obfuscated server list registry: %s", errors.Trace(err))
  199. }
  200. defer registryFile.Close()
  201. registryStreamer, err := osl.NewRegistryStreamer(
  202. registryFile,
  203. publicKey,
  204. lookupSLOKs)
  205. if err != nil {
  206. // TODO: delete file? redownload if corrupt?
  207. return errors.Tracef("failed to read obfuscated server list registry: %s", errors.Trace(err))
  208. }
  209. authenticatedDownload = true
  210. // NewRegistryStreamer authenticates the downloaded registry, so now it would be
  211. // ok to update the cache. However, we defer that until after processing so we
  212. // can close the file first before copying it, avoiding related complications on
  213. // platforms such as Windows.
  214. // Note: we proceed to check individual OSLs even if the directory is unchanged,
  215. // as the set of local SLOKs may have changed.
  216. for {
  217. oslFileSpec, err := registryStreamer.Next()
  218. if err != nil {
  219. failed = true
  220. NoticeWarning("failed to stream obfuscated server list registry: %s", errors.Trace(err))
  221. break
  222. }
  223. if oslFileSpec == nil {
  224. break
  225. }
  226. if !downloadOSLFileSpec(
  227. ctx,
  228. config,
  229. tunnel,
  230. untunneledDialConfig,
  231. downloadTimeout,
  232. rootURL,
  233. canonicalRootURL,
  234. publicKey,
  235. lookupSLOKs,
  236. oslFileSpec) {
  237. // downloadOSLFileSpec emits notices with failure information. In the case
  238. // of a failure, set the retry flag but continue to process other OSL file
  239. // specs.
  240. failed = true
  241. }
  242. // Run a garbage collection to reclaim memory from the downloadOSLFileSpec
  243. // operation before processing the next file.
  244. DoGarbageCollection()
  245. }
  246. // Now that a new registry is downloaded, validated, and parsed, store
  247. // the response ETag so we won't re-download this same data again. First
  248. // close the file to avoid complications on platforms such as Windows.
  249. if updateCache {
  250. registryFile.Close()
  251. err := os.Rename(downloadFilename, cachedFilename)
  252. if err != nil {
  253. NoticeWarning("failed to set cached obfuscated server list registry: %s", errors.Trace(err))
  254. // This fetch is still reported as a success, even if we can't update the cache
  255. }
  256. err = SetUrlETag(canonicalURL, newETag)
  257. if err != nil {
  258. NoticeWarning("failed to set ETag for obfuscated server list registry: %s", errors.Trace(err))
  259. // This fetch is still reported as a success, even if we can't store the ETag
  260. }
  261. }
  262. if failed {
  263. return errors.TraceNew("one or more operations failed")
  264. }
  265. return nil
  266. }
  267. // downloadOSLFileSpec downloads, authenticates, and imports the OSL specified
  268. // by oslFileSpec. The return value indicates whether the operation succeeded.
  269. // Failure information is emitted in notices.
  270. func downloadOSLFileSpec(
  271. ctx context.Context,
  272. config *Config,
  273. tunnel *Tunnel,
  274. untunneledDialConfig *DialConfig,
  275. downloadTimeout time.Duration,
  276. rootURL *parameters.TransferURL,
  277. canonicalRootURL string,
  278. publicKey string,
  279. lookupSLOKs func(slokID []byte) []byte,
  280. oslFileSpec *osl.OSLFileSpec) bool {
  281. downloadFilename := osl.GetOSLFilename(
  282. config.GetObfuscatedServerListDownloadDirectory(), oslFileSpec.ID)
  283. downloadURL := osl.GetOSLFileURL(rootURL.URL, oslFileSpec.ID)
  284. canonicalURL := osl.GetOSLFileURL(canonicalRootURL, oslFileSpec.ID)
  285. hexID := hex.EncodeToString(oslFileSpec.ID)
  286. // Note: the MD5 checksum step assumes the remote server list host's ETag uses MD5
  287. // with a hex encoding. If this is not the case, the sourceETag should be left blank.
  288. sourceETag := fmt.Sprintf("\"%s\"", hex.EncodeToString(oslFileSpec.MD5Sum))
  289. newETag, downloadStatRecorder, err := downloadRemoteServerListFile(
  290. ctx,
  291. config,
  292. tunnel,
  293. untunneledDialConfig,
  294. downloadTimeout,
  295. downloadURL,
  296. canonicalURL,
  297. rootURL.FrontingSpecs,
  298. rootURL.SkipVerify,
  299. config.DisableSystemRootCAs,
  300. sourceETag,
  301. downloadFilename)
  302. if err != nil {
  303. NoticeWarning("failed to download obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  304. return false
  305. }
  306. authenticatedDownload := false
  307. if downloadStatRecorder != nil {
  308. defer func() { downloadStatRecorder(authenticatedDownload) }()
  309. }
  310. // When the resource is unchanged, skip.
  311. if newETag == "" {
  312. return true
  313. }
  314. file, err := os.Open(downloadFilename)
  315. if err != nil {
  316. NoticeWarning("failed to open obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  317. return false
  318. }
  319. defer file.Close()
  320. serverListPayloadReader, err := osl.NewOSLReader(
  321. file,
  322. oslFileSpec,
  323. lookupSLOKs,
  324. publicKey)
  325. if err != nil {
  326. NoticeWarning("failed to read obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  327. return false
  328. }
  329. // NewOSLReader authenticates the file before returning.
  330. authenticatedDownload = true
  331. err = StreamingStoreServerEntries(
  332. ctx,
  333. config,
  334. protocol.NewStreamingServerEntryDecoder(
  335. serverListPayloadReader,
  336. common.GetCurrentTimestamp(),
  337. protocol.SERVER_ENTRY_SOURCE_OBFUSCATED),
  338. true)
  339. if err != nil {
  340. NoticeWarning("failed to store obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  341. return false
  342. }
  343. // Now that the server entries are successfully imported, store the response
  344. // ETag so we won't re-download this same data again.
  345. err = SetUrlETag(canonicalURL, newETag)
  346. if err != nil {
  347. NoticeWarning("failed to set ETag for obfuscated server list file (%s): %s", hexID, errors.Trace(err))
  348. // This fetch is still reported as a success, even if we can't store the ETag
  349. return true
  350. }
  351. return true
  352. }
  353. // downloadRemoteServerListFile downloads the source URL to the destination
  354. // file, performing a resumable download. When the download completes and the
  355. // file content has changed, the new resource ETag is returned. Otherwise,
  356. // blank is returned. The caller is responsible for calling SetUrlETag once
  357. // the file content has been validated.
  358. //
  359. // The downloadStatReporter return value is a function that will invoke
  360. // RecordRemoteServerListStat to record a remote server list download event.
  361. // The caller must call this function if the return value is not nil,
  362. // providing a boolean argument indicating whether the download was
  363. // successfully authenticated.
  364. func downloadRemoteServerListFile(
  365. ctx context.Context,
  366. config *Config,
  367. tunnel *Tunnel,
  368. untunneledDialConfig *DialConfig,
  369. downloadTimeout time.Duration,
  370. sourceURL string,
  371. canonicalURL string,
  372. frontingSpecs parameters.FrontingSpecs,
  373. skipVerify bool,
  374. disableSystemRootCAs bool,
  375. sourceETag string,
  376. destinationFilename string) (string, func(bool), error) {
  377. // All download URLs with the same canonicalURL
  378. // must have the same entity and ETag.
  379. lastETag, err := GetUrlETag(canonicalURL)
  380. if err != nil {
  381. return "", nil, errors.Trace(err)
  382. }
  383. // sourceETag, when specified, is prior knowledge of the
  384. // remote ETag that can be used to skip the request entirely.
  385. // This will be set in the case of OSL files, from the MD5Sum
  386. // values stored in the registry.
  387. if lastETag != "" && sourceETag == lastETag {
  388. // TODO: notice?
  389. return "", nil, nil
  390. }
  391. var cancelFunc context.CancelFunc
  392. ctx, cancelFunc = context.WithTimeout(ctx, downloadTimeout)
  393. defer cancelFunc()
  394. // MakeDownloadHttpClient will select either a tunneled
  395. // or untunneled configuration.
  396. payloadSecure := true
  397. httpClient, tunneled, getParams, err := MakeDownloadHTTPClient(
  398. ctx,
  399. config,
  400. tunnel,
  401. untunneledDialConfig,
  402. skipVerify,
  403. disableSystemRootCAs,
  404. payloadSecure,
  405. frontingSpecs,
  406. func(frontingProviderID string) {
  407. NoticeInfo(
  408. "downloadRemoteServerListFile: selected fronting provider %s for %s",
  409. frontingProviderID, sourceURL)
  410. })
  411. if err != nil {
  412. return "", nil, errors.Trace(err)
  413. }
  414. startTime := time.Now()
  415. bytes, responseETag, err := ResumeDownload(
  416. ctx,
  417. httpClient,
  418. sourceURL,
  419. MakePsiphonUserAgent(config),
  420. destinationFilename,
  421. lastETag)
  422. duration := time.Since(startTime)
  423. NoticeRemoteServerListResourceDownloadedBytes(sourceURL, bytes, duration)
  424. if err != nil {
  425. return "", nil, errors.Trace(err)
  426. }
  427. if responseETag == lastETag {
  428. return "", nil, nil
  429. }
  430. NoticeRemoteServerListResourceDownloaded(sourceURL)
  431. // Parameters can be retrieved now because the request has completed.
  432. var additionalParameters common.APIParameters
  433. if getParams != nil {
  434. additionalParameters = getParams()
  435. }
  436. downloadStatRecorder := func(authenticated bool) {
  437. // Invoke DNS cache extension (if enabled in the resolver) now that
  438. // the download succeeded and the payload is authenticated. Only
  439. // extend when authenticated, as this demonstrates that any domain
  440. // name resolved to an endpoint that served a valid Psiphon remote
  441. // server list.
  442. //
  443. // TODO: when !skipVerify, invoke DNS cache extension earlier, in
  444. // ResumeDownload, after making the request but before downloading
  445. // the response body?
  446. resolver := config.GetResolver()
  447. url, err := url.Parse(sourceURL)
  448. if authenticated && resolver != nil && err == nil {
  449. resolver.VerifyCacheExtension(url.Hostname())
  450. }
  451. _ = RecordRemoteServerListStat(
  452. config, tunneled, sourceURL, responseETag, bytes, duration, authenticated, additionalParameters)
  453. }
  454. return responseETag, downloadStatRecorder, nil
  455. }