psi.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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 psi
  20. // This package is a shim between Java/Obj-C and the "psiphon" package. Due to limitations
  21. // on what Go types may be exposed (http://godoc.org/golang.org/x/mobile/cmd/gobind),
  22. // a psiphon.Controller cannot be directly used by Java. This shim exposes a trivial
  23. // Start/Stop interface on top of a single Controller instance.
  24. import (
  25. "context"
  26. "encoding/json"
  27. "fmt"
  28. "os"
  29. "path/filepath"
  30. "strings"
  31. "sync"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo"
  35. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tun"
  36. )
  37. type PsiphonProviderNoticeHandler interface {
  38. Notice(noticeJSON string)
  39. }
  40. type PsiphonProviderNetwork interface {
  41. HasNetworkConnectivity() int
  42. GetNetworkID() string
  43. IPv6Synthesize(IPv4Addr string) string
  44. }
  45. type PsiphonProvider interface {
  46. PsiphonProviderNoticeHandler
  47. PsiphonProviderNetwork
  48. BindToDevice(fileDescriptor int) (string, error)
  49. GetPrimaryDnsServer() string
  50. GetSecondaryDnsServer() string
  51. }
  52. type PsiphonProviderFeedbackHandler interface {
  53. SendFeedbackCompleted(err error)
  54. }
  55. func NoticeUserLog(message string) {
  56. psiphon.NoticeUserLog(message)
  57. }
  58. // HomepageFilePath returns the path where homepage files will be paved.
  59. //
  60. // rootDataDirectoryPath is the configured data root directory.
  61. //
  62. // Note: homepage files will only be paved if UseNoticeFiles is set in the
  63. // config passed to Start().
  64. func HomepageFilePath(rootDataDirectoryPath string) string {
  65. return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.HomepageFilename)
  66. }
  67. // NoticesFilePath returns the path where the notices file will be paved.
  68. //
  69. // rootDataDirectoryPath is the configured data root directory.
  70. //
  71. // Note: notices will only be paved if UseNoticeFiles is set in the config
  72. // passed to Start().
  73. func NoticesFilePath(rootDataDirectoryPath string) string {
  74. return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.NoticesFilename)
  75. }
  76. // OldNoticesFilePath returns the path where the notices file is moved to when
  77. // file rotation occurs.
  78. //
  79. // rootDataDirectoryPath is the configured data root directory.
  80. //
  81. // Note: notices will only be paved if UseNoticeFiles is set in the config
  82. // passed to Start().
  83. func OldNoticesFilePath(rootDataDirectoryPath string) string {
  84. return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.OldNoticesFilename)
  85. }
  86. // UpgradeDownloadFilePath returns the path where the downloaded upgrade file
  87. // will be paved.
  88. //
  89. // rootDataDirectoryPath is the configured data root directory.
  90. //
  91. // Note: upgrades will only be paved if UpgradeDownloadURLs is set in the config
  92. // passed to Start() and there are upgrades available.
  93. func UpgradeDownloadFilePath(rootDataDirectoryPath string) string {
  94. return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.UpgradeDownloadFilename)
  95. }
  96. var controllerMutex sync.Mutex
  97. var embeddedServerListWaitGroup *sync.WaitGroup
  98. var controller *psiphon.Controller
  99. var controllerCtx context.Context
  100. var stopController context.CancelFunc
  101. var controllerWaitGroup *sync.WaitGroup
  102. func Start(
  103. configJson,
  104. embeddedServerEntryList,
  105. embeddedServerEntryListFilename string,
  106. provider PsiphonProvider,
  107. useDeviceBinder,
  108. useIPv6Synthesizer bool) error {
  109. controllerMutex.Lock()
  110. defer controllerMutex.Unlock()
  111. if controller != nil {
  112. return fmt.Errorf("already started")
  113. }
  114. // Clients may toggle Stop/Start immediately to apply new config settings
  115. // such as EgressRegion or Authorizations. When this restart is within the
  116. // same process and in a memory contrained environment, it is useful to
  117. // force garbage collection here to reclaim memory used by the previous
  118. // Controller.
  119. psiphon.DoGarbageCollection()
  120. // Wrap the provider in a layer that locks a mutex before calling a provider function.
  121. // As the provider callbacks are Java/Obj-C via gomobile, they are cgo calls that
  122. // can cause OS threads to be spawned. The mutex prevents many calling goroutines from
  123. // causing unbounded numbers of OS threads to be spawned.
  124. // TODO: replace the mutex with a semaphore, to allow a larger but still bounded concurrent
  125. // number of calls to the provider?
  126. provider = newMutexPsiphonProvider(provider)
  127. config, err := psiphon.LoadConfig([]byte(configJson))
  128. if err != nil {
  129. return fmt.Errorf("error loading configuration file: %s", err)
  130. }
  131. config.NetworkConnectivityChecker = provider
  132. config.NetworkIDGetter = provider
  133. if useDeviceBinder {
  134. config.DeviceBinder = provider
  135. config.DnsServerGetter = provider
  136. }
  137. if useIPv6Synthesizer {
  138. config.IPv6Synthesizer = provider
  139. }
  140. // All config fields should be set before calling Commit.
  141. err = config.Commit(true)
  142. if err != nil {
  143. return fmt.Errorf("error committing configuration file: %s", err)
  144. }
  145. psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  146. func(notice []byte) {
  147. provider.Notice(string(notice))
  148. }))
  149. // BuildInfo is a diagnostic notice, so emit only after config.Commit
  150. // sets EmitDiagnosticNotices.
  151. psiphon.NoticeBuildInfo()
  152. err = psiphon.OpenDataStore(config)
  153. if err != nil {
  154. return fmt.Errorf("error initializing datastore: %s", err)
  155. }
  156. controllerCtx, stopController = context.WithCancel(context.Background())
  157. // If specified, the embedded server list is loaded and stored. When there
  158. // are no server candidates at all, we wait for this import to complete
  159. // before starting the Psiphon controller. Otherwise, we import while
  160. // concurrently starting the controller to minimize delay before attempting
  161. // to connect to existing candidate servers.
  162. //
  163. // If the import fails, an error notice is emitted, but the controller is
  164. // still started: either existing candidate servers may suffice, or the
  165. // remote server list fetch may obtain candidate servers.
  166. //
  167. // The import will be interrupted if it's still running when the controller
  168. // is stopped.
  169. embeddedServerListWaitGroup = new(sync.WaitGroup)
  170. embeddedServerListWaitGroup.Add(1)
  171. go func() {
  172. defer embeddedServerListWaitGroup.Done()
  173. err := psiphon.ImportEmbeddedServerEntries(
  174. controllerCtx,
  175. config,
  176. embeddedServerEntryListFilename,
  177. embeddedServerEntryList)
  178. if err != nil {
  179. psiphon.NoticeError("error importing embedded server entry list: %s", err)
  180. return
  181. }
  182. }()
  183. if !psiphon.HasServerEntries() {
  184. psiphon.NoticeInfo("awaiting embedded server entry list import")
  185. embeddedServerListWaitGroup.Wait()
  186. }
  187. controller, err = psiphon.NewController(config)
  188. if err != nil {
  189. stopController()
  190. embeddedServerListWaitGroup.Wait()
  191. psiphon.CloseDataStore()
  192. return fmt.Errorf("error initializing controller: %s", err)
  193. }
  194. controllerWaitGroup = new(sync.WaitGroup)
  195. controllerWaitGroup.Add(1)
  196. go func() {
  197. defer controllerWaitGroup.Done()
  198. controller.Run(controllerCtx)
  199. }()
  200. return nil
  201. }
  202. func Stop() {
  203. controllerMutex.Lock()
  204. defer controllerMutex.Unlock()
  205. if controller != nil {
  206. stopController()
  207. controllerWaitGroup.Wait()
  208. embeddedServerListWaitGroup.Wait()
  209. psiphon.CloseDataStore()
  210. controller = nil
  211. controllerCtx = nil
  212. stopController = nil
  213. controllerWaitGroup = nil
  214. }
  215. }
  216. // ReconnectTunnel initiates a reconnect of the current tunnel, if one is
  217. // running.
  218. func ReconnectTunnel() {
  219. controllerMutex.Lock()
  220. defer controllerMutex.Unlock()
  221. if controller != nil {
  222. controller.TerminateNextActiveTunnel()
  223. }
  224. }
  225. // SetDynamicConfig overrides the sponsor ID and authorizations fields set in
  226. // the config passed to Start. SetDynamicConfig has no effect if no Controller
  227. // is started.
  228. //
  229. // The input newAuthorizationsList is a space-delimited list of base64
  230. // authorizations. This is a workaround for gobind type limitations.
  231. func SetDynamicConfig(newSponsorID, newAuthorizationsList string) {
  232. controllerMutex.Lock()
  233. defer controllerMutex.Unlock()
  234. if controller != nil {
  235. var authorizations []string
  236. if len(newAuthorizationsList) > 0 {
  237. authorizations = strings.Split(newAuthorizationsList, " ")
  238. }
  239. controller.SetDynamicConfig(
  240. newSponsorID,
  241. authorizations)
  242. }
  243. }
  244. // ExportExchangePayload creates a payload for client-to-client server
  245. // connection info exchange.
  246. //
  247. // ExportExchangePayload will succeed only when Psiphon is running, between
  248. // Start and Stop.
  249. //
  250. // The return value is a payload that may be exchanged with another client;
  251. // when "", the export failed and a diagnostic has been logged.
  252. func ExportExchangePayload() string {
  253. controllerMutex.Lock()
  254. defer controllerMutex.Unlock()
  255. if controller == nil {
  256. return ""
  257. }
  258. return controller.ExportExchangePayload()
  259. }
  260. // ImportExchangePayload imports a payload generated by ExportExchangePayload.
  261. //
  262. // If an import occurs when Psiphon is working to establsh a tunnel, the newly
  263. // imported server entry is prioritized.
  264. //
  265. // The return value indicates a successful import. If the import failed, a a
  266. // diagnostic notice has been logged.
  267. func ImportExchangePayload(payload string) bool {
  268. controllerMutex.Lock()
  269. defer controllerMutex.Unlock()
  270. if controller == nil {
  271. return false
  272. }
  273. return controller.ImportExchangePayload(payload)
  274. }
  275. var sendFeedbackMutex sync.Mutex
  276. var sendFeedbackCtx context.Context
  277. var stopSendFeedback context.CancelFunc
  278. var sendFeedbackWaitGroup *sync.WaitGroup
  279. // StartSendFeedback encrypts the provided diagnostics and then attempts to
  280. // upload the encrypted diagnostics to one of the feedback upload locations
  281. // supplied by the provided config or tactics.
  282. //
  283. // Returns immediately after starting the operation in a goroutine. The
  284. // operation has completed when SendFeedbackCompleted(error) is called on the
  285. // provided PsiphonProviderFeedbackHandler; if error is non-nil, then the
  286. // operation failed.
  287. //
  288. // Only one active upload is supported at a time. An ongoing upload will be
  289. // cancelled if this function is called again before it completes.
  290. //
  291. // Warnings:
  292. // - Should not be used with Start concurrently in the same process
  293. // - An ongoing feedback upload started with StartSendFeedback should be
  294. // stopped with StopSendFeedback before the process exists. This ensures that
  295. // any underlying resources are cleaned up; failing to do so may result in
  296. // data store corruption or other undefined behavior.
  297. // - Start and StartSendFeedback both make an attempt to migrate persistent
  298. // files from legacy locations in a one-time operation. If these functions
  299. // are called in parallel, then there is a chance that the migration attempts
  300. // could execute at the same time and result in non-fatal errors in one, or
  301. // both, of the migration operations.
  302. // - Calling StartSendFeedback or StopSendFeedback on the same call stack
  303. // that the PsiphonProviderFeedbackHandler.SendFeedbackCompleted() callback
  304. // is delivered on can cause a deadlock. I.E. the callback code must return
  305. // so the wait group can complete and the lock acquired in StopSendFeedback
  306. // can be released.
  307. func StartSendFeedback(
  308. configJson,
  309. diagnosticsJson,
  310. uploadPath string,
  311. feedbackHandler PsiphonProviderFeedbackHandler,
  312. networkInfoProvider PsiphonProviderNetwork,
  313. noticeHandler PsiphonProviderNoticeHandler,
  314. useIPv6Synthesizer bool) error {
  315. // Cancel any ongoing uploads.
  316. StopSendFeedback()
  317. sendFeedbackMutex.Lock()
  318. defer sendFeedbackMutex.Unlock()
  319. sendFeedbackCtx, stopSendFeedback = context.WithCancel(context.Background())
  320. // Unlike in Start, the provider is not wrapped in a newMutexPsiphonProvider
  321. // or equivilent, as SendFeedback is not expected to be used in a memory
  322. // constrained environment.
  323. psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  324. func(notice []byte) {
  325. noticeHandler.Notice(string(notice))
  326. }))
  327. config, err := psiphon.LoadConfig([]byte(configJson))
  328. if err != nil {
  329. return fmt.Errorf("error loading configuration file: %s", err)
  330. }
  331. config.NetworkConnectivityChecker = networkInfoProvider
  332. config.NetworkIDGetter = networkInfoProvider
  333. if useIPv6Synthesizer {
  334. config.IPv6Synthesizer = networkInfoProvider
  335. }
  336. // All config fields should be set before calling Commit.
  337. err = config.Commit(true)
  338. if err != nil {
  339. return fmt.Errorf("error committing configuration file: %s", err)
  340. }
  341. sendFeedbackWaitGroup = new(sync.WaitGroup)
  342. sendFeedbackWaitGroup.Add(1)
  343. go func() {
  344. defer sendFeedbackWaitGroup.Done()
  345. err := psiphon.SendFeedback(sendFeedbackCtx, config,
  346. diagnosticsJson, uploadPath)
  347. feedbackHandler.SendFeedbackCompleted(err)
  348. }()
  349. return nil
  350. }
  351. // StopSendFeedback interrupts an in-progress feedback upload operation
  352. // started with `StartSendFeedback`.
  353. //
  354. // Warning: should not be used with Start concurrently in the same process.
  355. func StopSendFeedback() {
  356. sendFeedbackMutex.Lock()
  357. defer sendFeedbackMutex.Unlock()
  358. if stopSendFeedback != nil {
  359. stopSendFeedback()
  360. sendFeedbackWaitGroup.Wait()
  361. sendFeedbackCtx = nil
  362. stopSendFeedback = nil
  363. sendFeedbackWaitGroup = nil
  364. // Allow the notice receiver to be deallocated.
  365. psiphon.SetNoticeWriter(os.Stderr)
  366. }
  367. }
  368. // Get build info from tunnel-core
  369. func GetBuildInfo() string {
  370. buildInfo, err := json.Marshal(buildinfo.GetBuildInfo())
  371. if err != nil {
  372. return ""
  373. }
  374. return string(buildInfo)
  375. }
  376. func GetPacketTunnelMTU() int {
  377. return tun.DEFAULT_MTU
  378. }
  379. func GetPacketTunnelDNSResolverIPv4Address() string {
  380. return tun.GetTransparentDNSResolverIPv4Address().String()
  381. }
  382. func GetPacketTunnelDNSResolverIPv6Address() string {
  383. return tun.GetTransparentDNSResolverIPv6Address().String()
  384. }
  385. // WriteRuntimeProfiles writes Go runtime profile information to a set of
  386. // files in the specified output directory. See common.WriteRuntimeProfiles
  387. // for more details.
  388. //
  389. // If called before Start, log notices will emit to stderr.
  390. func WriteRuntimeProfiles(outputDirectory string, cpuSampleDurationSeconds, blockSampleDurationSeconds int) {
  391. common.WriteRuntimeProfiles(
  392. psiphon.NoticeCommonLogger(),
  393. outputDirectory,
  394. "",
  395. cpuSampleDurationSeconds,
  396. blockSampleDurationSeconds)
  397. }
  398. type mutexPsiphonProvider struct {
  399. sync.Mutex
  400. p PsiphonProvider
  401. }
  402. func newMutexPsiphonProvider(p PsiphonProvider) *mutexPsiphonProvider {
  403. return &mutexPsiphonProvider{p: p}
  404. }
  405. func (p *mutexPsiphonProvider) Notice(noticeJSON string) {
  406. p.Lock()
  407. defer p.Unlock()
  408. p.p.Notice(noticeJSON)
  409. }
  410. func (p *mutexPsiphonProvider) HasNetworkConnectivity() int {
  411. p.Lock()
  412. defer p.Unlock()
  413. return p.p.HasNetworkConnectivity()
  414. }
  415. func (p *mutexPsiphonProvider) BindToDevice(fileDescriptor int) (string, error) {
  416. p.Lock()
  417. defer p.Unlock()
  418. return p.p.BindToDevice(fileDescriptor)
  419. }
  420. func (p *mutexPsiphonProvider) IPv6Synthesize(IPv4Addr string) string {
  421. p.Lock()
  422. defer p.Unlock()
  423. return p.p.IPv6Synthesize(IPv4Addr)
  424. }
  425. func (p *mutexPsiphonProvider) GetPrimaryDnsServer() string {
  426. p.Lock()
  427. defer p.Unlock()
  428. return p.p.GetPrimaryDnsServer()
  429. }
  430. func (p *mutexPsiphonProvider) GetSecondaryDnsServer() string {
  431. p.Lock()
  432. defer p.Unlock()
  433. return p.p.GetSecondaryDnsServer()
  434. }
  435. func (p *mutexPsiphonProvider) GetNetworkID() string {
  436. p.Lock()
  437. defer p.Unlock()
  438. return p.p.GetNetworkID()
  439. }