remoteServerList.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  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. "errors"
  24. "fmt"
  25. "os"
  26. "time"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/osl"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  31. )
  32. type RemoteServerListFetcher func(
  33. ctx context.Context, config *Config, attempt int, tunnel *Tunnel, untunneledDialConfig *DialConfig) error
  34. // FetchCommonRemoteServerList downloads the common remote server list from
  35. // config.RemoteServerListURLs. It validates its digital signature using the
  36. // public key config.RemoteServerListSignaturePublicKey and parses the
  37. // data field into ServerEntry records.
  38. // config.RemoteServerListDownloadFilename is the location to store the
  39. // download. As the download is resumed after failure, this filename must
  40. // be unique and persistent.
  41. func FetchCommonRemoteServerList(
  42. ctx context.Context,
  43. config *Config,
  44. attempt int,
  45. tunnel *Tunnel,
  46. untunneledDialConfig *DialConfig) error {
  47. NoticeInfo("fetching common remote server list")
  48. p := config.clientParameters.Get()
  49. publicKey := p.String(parameters.RemoteServerListSignaturePublicKey)
  50. urls := p.DownloadURLs(parameters.RemoteServerListURLs)
  51. downloadTimeout := p.Duration(parameters.FetchRemoteServerListTimeout)
  52. p = nil
  53. downloadURL, canonicalURL, skipVerify := urls.Select(attempt)
  54. newETag, err := downloadRemoteServerListFile(
  55. ctx,
  56. config,
  57. tunnel,
  58. untunneledDialConfig,
  59. downloadTimeout,
  60. downloadURL,
  61. canonicalURL,
  62. skipVerify,
  63. "",
  64. config.RemoteServerListDownloadFilename)
  65. if err != nil {
  66. return fmt.Errorf("failed to download common remote server list: %s", common.ContextError(err))
  67. }
  68. // When the resource is unchanged, skip.
  69. if newETag == "" {
  70. return nil
  71. }
  72. file, err := os.Open(config.RemoteServerListDownloadFilename)
  73. if err != nil {
  74. return fmt.Errorf("failed to open common remote server list: %s", common.ContextError(err))
  75. }
  76. defer file.Close()
  77. serverListPayloadReader, err := common.NewAuthenticatedDataPackageReader(
  78. file, publicKey)
  79. if err != nil {
  80. return fmt.Errorf("failed to read remote server list: %s", common.ContextError(err))
  81. }
  82. err = StreamingStoreServerEntries(
  83. config,
  84. protocol.NewStreamingServerEntryDecoder(
  85. serverListPayloadReader,
  86. common.GetCurrentTimestamp(),
  87. protocol.SERVER_ENTRY_SOURCE_REMOTE),
  88. true)
  89. if err != nil {
  90. return fmt.Errorf("failed to store common remote server list: %s", common.ContextError(err))
  91. }
  92. // Now that the server entries are successfully imported, store the response
  93. // ETag so we won't re-download this same data again.
  94. err = SetUrlETag(canonicalURL, newETag)
  95. if err != nil {
  96. NoticeAlert("failed to set ETag for common remote server list: %s", common.ContextError(err))
  97. // This fetch is still reported as a success, even if we can't store the etag
  98. }
  99. return nil
  100. }
  101. // FetchObfuscatedServerLists downloads the obfuscated remote server lists
  102. // from config.ObfuscatedServerListRootURLs.
  103. // It first downloads the OSL registry, and then downloads each seeded OSL
  104. // advertised in the registry. All downloads are resumable, ETags are used
  105. // to skip both an unchanged registry or unchanged OSL files, and when an
  106. // individual download fails, the fetch proceeds if it can.
  107. // Authenticated package digital signatures are validated using the
  108. // public key config.RemoteServerListSignaturePublicKey.
  109. // config.ObfuscatedServerListDownloadDirectory is the location to store the
  110. // downloaded files. As downloads are resumed after failure, this directory
  111. // must be unique and persistent.
  112. func FetchObfuscatedServerLists(
  113. ctx context.Context,
  114. config *Config,
  115. attempt int,
  116. tunnel *Tunnel,
  117. untunneledDialConfig *DialConfig) error {
  118. NoticeInfo("fetching obfuscated remote server lists")
  119. p := config.clientParameters.Get()
  120. publicKey := p.String(parameters.RemoteServerListSignaturePublicKey)
  121. urls := p.DownloadURLs(parameters.ObfuscatedServerListRootURLs)
  122. downloadTimeout := p.Duration(parameters.FetchRemoteServerListTimeout)
  123. p = nil
  124. rootURL, canonicalRootURL, skipVerify := urls.Select(attempt)
  125. downloadURL := osl.GetOSLRegistryURL(rootURL)
  126. canonicalURL := osl.GetOSLRegistryURL(canonicalRootURL)
  127. downloadFilename := osl.GetOSLRegistryFilename(config.ObfuscatedServerListDownloadDirectory)
  128. cachedFilename := downloadFilename + ".cached"
  129. // If the cached registry is not present, we need to download or resume downloading
  130. // the registry, so clear the ETag to ensure that always happens.
  131. _, err := os.Stat(cachedFilename)
  132. if os.IsNotExist(err) {
  133. SetUrlETag(canonicalURL, "")
  134. }
  135. // failed is set if any operation fails and should trigger a retry. When the OSL registry
  136. // fails to download, any cached registry is used instead; when any single OSL fails
  137. // to download, the overall operation proceeds. So this flag records whether to report
  138. // failure at the end when downloading has proceeded after a failure.
  139. // TODO: should disk-full conditions not trigger retries?
  140. var failed bool
  141. // updateCache is set when modifed registry content is downloaded. Both the cached
  142. // file and the persisted ETag will be updated in this case. The update is deferred
  143. // until after the registry has been authenticated.
  144. updateCache := false
  145. registryFilename := cachedFilename
  146. newETag, err := downloadRemoteServerListFile(
  147. ctx,
  148. config,
  149. tunnel,
  150. untunneledDialConfig,
  151. downloadTimeout,
  152. downloadURL,
  153. canonicalURL,
  154. skipVerify,
  155. "",
  156. downloadFilename)
  157. if err != nil {
  158. failed = true
  159. NoticeAlert("failed to download obfuscated server list registry: %s", common.ContextError(err))
  160. // Proceed with any existing cached OSL registry.
  161. } else if newETag != "" {
  162. updateCache = true
  163. registryFilename = downloadFilename
  164. }
  165. lookupSLOKs := func(slokID []byte) []byte {
  166. // Lookup SLOKs in local datastore
  167. key, err := GetSLOK(slokID)
  168. if err != nil {
  169. NoticeAlert("GetSLOK failed: %s", err)
  170. }
  171. return key
  172. }
  173. registryFile, err := os.Open(registryFilename)
  174. if err != nil {
  175. return fmt.Errorf("failed to read obfuscated server list registry: %s", common.ContextError(err))
  176. }
  177. defer registryFile.Close()
  178. registryStreamer, err := osl.NewRegistryStreamer(
  179. registryFile,
  180. publicKey,
  181. lookupSLOKs)
  182. if err != nil {
  183. // TODO: delete file? redownload if corrupt?
  184. return fmt.Errorf("failed to read obfuscated server list registry: %s", common.ContextError(err))
  185. }
  186. // NewRegistryStreamer authenticates the downloaded registry, so now it would be
  187. // ok to update the cache. However, we defer that until after processing so we
  188. // can close the file first before copying it, avoiding related complications on
  189. // platforms such as Windows.
  190. // Note: we proceed to check individual OSLs even if the directory is unchanged,
  191. // as the set of local SLOKs may have changed.
  192. for {
  193. oslFileSpec, err := registryStreamer.Next()
  194. if err != nil {
  195. failed = true
  196. NoticeAlert("failed to stream obfuscated server list registry: %s", common.ContextError(err))
  197. break
  198. }
  199. if oslFileSpec == nil {
  200. break
  201. }
  202. downloadFilename := osl.GetOSLFilename(
  203. config.ObfuscatedServerListDownloadDirectory, oslFileSpec.ID)
  204. downloadURL := osl.GetOSLFileURL(rootURL, oslFileSpec.ID)
  205. canonicalURL := osl.GetOSLFileURL(canonicalRootURL, oslFileSpec.ID)
  206. hexID := hex.EncodeToString(oslFileSpec.ID)
  207. // Note: the MD5 checksum step assumes the remote server list host's ETag uses MD5
  208. // with a hex encoding. If this is not the case, the sourceETag should be left blank.
  209. sourceETag := fmt.Sprintf("\"%s\"", hex.EncodeToString(oslFileSpec.MD5Sum))
  210. newETag, err := downloadRemoteServerListFile(
  211. ctx,
  212. config,
  213. tunnel,
  214. untunneledDialConfig,
  215. downloadTimeout,
  216. downloadURL,
  217. canonicalURL,
  218. skipVerify,
  219. sourceETag,
  220. downloadFilename)
  221. if err != nil {
  222. failed = true
  223. NoticeAlert("failed to download obfuscated server list file (%s): %s", hexID, common.ContextError(err))
  224. continue
  225. }
  226. // When the resource is unchanged, skip.
  227. if newETag == "" {
  228. continue
  229. }
  230. file, err := os.Open(downloadFilename)
  231. if err != nil {
  232. failed = true
  233. NoticeAlert("failed to open obfuscated server list file (%s): %s", hexID, common.ContextError(err))
  234. continue
  235. }
  236. // Note: don't defer file.Close() since we're in a loop
  237. serverListPayloadReader, err := osl.NewOSLReader(
  238. file,
  239. oslFileSpec,
  240. lookupSLOKs,
  241. publicKey)
  242. if err != nil {
  243. file.Close()
  244. failed = true
  245. NoticeAlert("failed to read obfuscated server list file (%s): %s", hexID, common.ContextError(err))
  246. continue
  247. }
  248. err = StreamingStoreServerEntries(
  249. config,
  250. protocol.NewStreamingServerEntryDecoder(
  251. serverListPayloadReader,
  252. common.GetCurrentTimestamp(),
  253. protocol.SERVER_ENTRY_SOURCE_OBFUSCATED),
  254. true)
  255. if err != nil {
  256. file.Close()
  257. failed = true
  258. NoticeAlert("failed to store obfuscated server list file (%s): %s", hexID, common.ContextError(err))
  259. continue
  260. }
  261. // Now that the server entries are successfully imported, store the response
  262. // ETag so we won't re-download this same data again.
  263. err = SetUrlETag(canonicalURL, newETag)
  264. if err != nil {
  265. file.Close()
  266. NoticeAlert("failed to set ETag for obfuscated server list file (%s): %s", hexID, common.ContextError(err))
  267. continue
  268. // This fetch is still reported as a success, even if we can't store the ETag
  269. }
  270. file.Close()
  271. // Clear the reference to this OSL file streamer and immediately run
  272. // a garbage collection to reclaim its memory before processing the
  273. // next file.
  274. serverListPayloadReader = nil
  275. DoGarbageCollection()
  276. }
  277. // Now that a new registry is downloaded, validated, and parsed, store
  278. // the response ETag so we won't re-download this same data again. First
  279. // close the file to avoid complications on platforms such as Windows.
  280. if updateCache {
  281. registryFile.Close()
  282. err := os.Rename(downloadFilename, cachedFilename)
  283. if err != nil {
  284. NoticeAlert("failed to set cached obfuscated server list registry: %s", common.ContextError(err))
  285. // This fetch is still reported as a success, even if we can't update the cache
  286. }
  287. err = SetUrlETag(canonicalURL, newETag)
  288. if err != nil {
  289. NoticeAlert("failed to set ETag for obfuscated server list registry: %s", common.ContextError(err))
  290. // This fetch is still reported as a success, even if we can't store the ETag
  291. }
  292. }
  293. if failed {
  294. return errors.New("one or more operations failed")
  295. }
  296. return nil
  297. }
  298. // downloadRemoteServerListFile downloads the source URL to
  299. // the destination file, performing a resumable download. When
  300. // the download completes and the file content has changed, the
  301. // new resource ETag is returned. Otherwise, blank is returned.
  302. // The caller is responsible for calling SetUrlETag once the file
  303. // content has been validated.
  304. func downloadRemoteServerListFile(
  305. ctx context.Context,
  306. config *Config,
  307. tunnel *Tunnel,
  308. untunneledDialConfig *DialConfig,
  309. downloadTimeout time.Duration,
  310. sourceURL string,
  311. canonicalURL string,
  312. skipVerify bool,
  313. sourceETag string,
  314. destinationFilename string) (string, error) {
  315. // All download URLs with the same canonicalURL
  316. // must have the same entity and ETag.
  317. lastETag, err := GetUrlETag(canonicalURL)
  318. if err != nil {
  319. return "", common.ContextError(err)
  320. }
  321. // sourceETag, when specified, is prior knowledge of the
  322. // remote ETag that can be used to skip the request entirely.
  323. // This will be set in the case of OSL files, from the MD5Sum
  324. // values stored in the registry.
  325. if lastETag != "" && sourceETag == lastETag {
  326. // TODO: notice?
  327. return "", nil
  328. }
  329. var cancelFunc context.CancelFunc
  330. ctx, cancelFunc = context.WithTimeout(ctx, downloadTimeout)
  331. defer cancelFunc()
  332. // MakeDownloadHttpClient will select either a tunneled
  333. // or untunneled configuration.
  334. httpClient, err := MakeDownloadHTTPClient(
  335. ctx,
  336. config,
  337. tunnel,
  338. untunneledDialConfig,
  339. skipVerify)
  340. if err != nil {
  341. return "", common.ContextError(err)
  342. }
  343. n, responseETag, err := ResumeDownload(
  344. ctx,
  345. httpClient,
  346. sourceURL,
  347. MakePsiphonUserAgent(config),
  348. destinationFilename,
  349. lastETag)
  350. NoticeRemoteServerListResourceDownloadedBytes(sourceURL, n)
  351. if err != nil {
  352. return "", common.ContextError(err)
  353. }
  354. if responseETag == lastETag {
  355. return "", nil
  356. }
  357. NoticeRemoteServerListResourceDownloaded(sourceURL)
  358. RecordRemoteServerListStat(sourceURL, responseETag)
  359. return responseETag, nil
  360. }