clientlib.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /*
  2. * Copyright (c) 2018, 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 clientlib
  20. import (
  21. "context"
  22. "encoding/json"
  23. std_errors "errors"
  24. "fmt"
  25. "net"
  26. "path/filepath"
  27. "sync"
  28. "sync/atomic"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  30. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  31. )
  32. // Parameters provide an easier way to modify the tunnel config at runtime.
  33. type Parameters struct {
  34. // Used as the directory for the datastore, remote server list, and obfuscasted
  35. // server list.
  36. // Empty string means the default will be used (current working directory).
  37. // nil means the values in the config file will be used.
  38. // Optional, but strongly recommended.
  39. DataRootDirectory *string
  40. // Overrides config.ClientPlatform. See config.go for details.
  41. // nil means the value in the config file will be used.
  42. // Optional, but strongly recommended.
  43. ClientPlatform *string
  44. // Overrides config.NetworkID. For details see:
  45. // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter
  46. // nil means the value in the config file will be used. (If not set in the config,
  47. // an error will result.)
  48. // Empty string will produce an error.
  49. // Optional, but strongly recommended.
  50. NetworkID *string
  51. // Overrides config.EstablishTunnelTimeoutSeconds. See config.go for details.
  52. // nil means the EstablishTunnelTimeoutSeconds value in the config file will be used.
  53. // If there's no such value in the config file, the default will be used.
  54. // Zero means there will be no timeout.
  55. // Optional.
  56. EstablishTunnelTimeoutSeconds *int
  57. // EmitDiagnosticNoticesToFile indicates whether to use the rotating log file
  58. // facility to record diagnostic notices instead of sending diagnostic
  59. // notices to noticeReceiver. Has no effect unless the tunnel
  60. // config.EmitDiagnosticNotices flag is set.
  61. EmitDiagnosticNoticesToFiles bool
  62. // DisableLocalSocksProxy disables running the local SOCKS proxy.
  63. DisableLocalSocksProxy *bool
  64. // DisableLocalHTTPProxy disables running the local HTTP proxy.
  65. DisableLocalHTTPProxy *bool
  66. }
  67. // PsiphonTunnel is the tunnel object. It can be used for stopping the tunnel and
  68. // retrieving proxy ports.
  69. type PsiphonTunnel struct {
  70. mu sync.Mutex
  71. stop func()
  72. embeddedServerListWaitGroup sync.WaitGroup
  73. controllerWaitGroup sync.WaitGroup
  74. controllerDial func(string, net.Conn) (net.Conn, error)
  75. // The port on which the HTTP proxy is running
  76. HTTPProxyPort int
  77. // The port on which the SOCKS proxy is running
  78. SOCKSProxyPort int
  79. }
  80. // ParametersDelta allows for fine-grained modification of parameters.Parameters.
  81. // NOTE: Ordinary users of this library should never need this.
  82. type ParametersDelta map[string]interface{}
  83. // NoticeEvent represents the notices emitted by tunnel core. It will be passed to
  84. // noticeReceiver, if supplied.
  85. // NOTE: Ordinary users of this library should never need this.
  86. type NoticeEvent struct {
  87. Data map[string]interface{} `json:"data"`
  88. Type string `json:"noticeType"`
  89. Timestamp string `json:"timestamp"`
  90. }
  91. // ErrTimeout is returned when the tunnel establishment attempt fails due to timeout
  92. var ErrTimeout = std_errors.New("clientlib: tunnel establishment timeout")
  93. var errMultipleStart = std_errors.New("clientlib: StartTunnel called multiple times")
  94. // started is used to ensure that only one tunnel is started at a time
  95. var started atomic.Bool
  96. // StartTunnel establishes a Psiphon tunnel. It returns an error if the establishment
  97. // was not successful. If the returned error is nil, the returned tunnel can be used
  98. // to find out the proxy ports and subsequently stop the tunnel.
  99. //
  100. // ctx may be cancelable, if the caller wants to be able to interrupt the establishment
  101. // attempt, or context.Background().
  102. //
  103. // configJSON will be passed to psiphon.LoadConfig to configure the tunnel. Required.
  104. //
  105. // embeddedServerEntryList is the encoded embedded server entry list. It is optional.
  106. //
  107. // params are config values that typically need to be overridden at runtime.
  108. //
  109. // paramsDelta contains changes that will be applied to the Parameters.
  110. // NOTE: Ordinary users of this library should never need this and should pass nil.
  111. //
  112. // noticeReceiver, if non-nil, will be called for each notice emitted by tunnel core.
  113. // NOTE: Ordinary users of this library should never need this and should pass nil.
  114. func StartTunnel(
  115. ctx context.Context,
  116. configJSON []byte,
  117. embeddedServerEntryList string,
  118. params Parameters,
  119. paramsDelta ParametersDelta,
  120. noticeReceiver func(NoticeEvent)) (retTunnel *PsiphonTunnel, retErr error) {
  121. if !started.CompareAndSwap(false, true) {
  122. return nil, errMultipleStart
  123. }
  124. // There _must_ not be an early return between here and where tunnel.stop is deferred,
  125. // otherwise `started` will not get set back to false and we will be unable to call
  126. // StartTunnel again.
  127. // Will be closed when the tunnel has successfully connected
  128. connectedSignal := make(chan struct{})
  129. // Will receive a value if an error occurs during the connection sequence
  130. erroredCh := make(chan error, 1)
  131. // Create the tunnel object
  132. tunnel := new(PsiphonTunnel)
  133. // Set up notice handling. It is important to do this before config operations, as
  134. // otherwise they will write notices to stderr.
  135. err := psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  136. func(notice []byte) {
  137. var event NoticeEvent
  138. err := json.Unmarshal(notice, &event)
  139. if err != nil {
  140. // This is unexpected and probably indicates something fatal has occurred.
  141. // We'll interpret it as a connection error and abort.
  142. err = errors.TraceMsg(err, "failed to unmarshal notice JSON")
  143. select {
  144. case erroredCh <- err:
  145. default:
  146. }
  147. return
  148. }
  149. if event.Type == "ListeningHttpProxyPort" {
  150. port := event.Data["port"].(float64)
  151. tunnel.HTTPProxyPort = int(port)
  152. } else if event.Type == "ListeningSocksProxyPort" {
  153. port := event.Data["port"].(float64)
  154. tunnel.SOCKSProxyPort = int(port)
  155. } else if event.Type == "EstablishTunnelTimeout" {
  156. select {
  157. case erroredCh <- ErrTimeout:
  158. default:
  159. }
  160. } else if event.Type == "Tunnels" {
  161. count := event.Data["count"].(float64)
  162. if count > 0 {
  163. close(connectedSignal)
  164. }
  165. }
  166. // Some users of this package may need to add special processing of notices.
  167. // If the caller has requested it, we'll pass on the notices.
  168. if noticeReceiver != nil {
  169. noticeReceiver(event)
  170. }
  171. }))
  172. if err != nil {
  173. return nil, errors.Trace(err)
  174. }
  175. // Create a cancelable context that will be used for stopping the tunnel
  176. tunnelCtx, cancelTunnelCtx := context.WithCancel(ctx)
  177. // Because the tunnel object is only returned on success, there are at least two
  178. // problems that we don't need to worry about:
  179. // 1. This stop function is called both by the error-defer here and by a call to the
  180. // tunnel's Stop method.
  181. // 2. This stop function is called via the tunnel's Stop method before the WaitGroups
  182. // are incremented (causing a race condition).
  183. tunnel.stop = func() {
  184. cancelTunnelCtx()
  185. tunnel.embeddedServerListWaitGroup.Wait()
  186. tunnel.controllerWaitGroup.Wait()
  187. // This is safe to call even if the data store hasn't been opened
  188. psiphon.CloseDataStore()
  189. started.Store(false)
  190. // Clear our notice receiver, as it is no longer needed and we should let it be
  191. // garbage-collected.
  192. psiphon.ResetNoticeWriter()
  193. }
  194. defer func() {
  195. if retErr != nil {
  196. tunnel.stop()
  197. }
  198. }()
  199. // We have now set up our on-error cleanup and it is safe to have early error returns.
  200. config, err := psiphon.LoadConfig(configJSON)
  201. if err != nil {
  202. return nil, errors.TraceMsg(err, "failed to load config file")
  203. }
  204. // Use params.DataRootDirectory to set related config values.
  205. if params.DataRootDirectory != nil {
  206. config.DataRootDirectory = *params.DataRootDirectory
  207. // Migrate old fields
  208. config.MigrateDataStoreDirectory = *params.DataRootDirectory
  209. config.MigrateObfuscatedServerListDownloadDirectory = *params.DataRootDirectory
  210. config.MigrateRemoteServerListDownloadFilename = filepath.Join(*params.DataRootDirectory, "server_list_compressed")
  211. }
  212. if params.NetworkID != nil {
  213. config.NetworkID = *params.NetworkID
  214. }
  215. if params.ClientPlatform != nil {
  216. config.ClientPlatform = *params.ClientPlatform
  217. } // else use the value in config
  218. if params.EstablishTunnelTimeoutSeconds != nil {
  219. config.EstablishTunnelTimeoutSeconds = params.EstablishTunnelTimeoutSeconds
  220. } // else use the value in config
  221. if config.UseNoticeFiles == nil && config.EmitDiagnosticNotices && params.EmitDiagnosticNoticesToFiles {
  222. config.UseNoticeFiles = &psiphon.UseNoticeFiles{
  223. RotatingFileSize: 0,
  224. RotatingSyncFrequency: 0,
  225. }
  226. } // else use the value in the config
  227. if params.DisableLocalSocksProxy != nil {
  228. config.DisableLocalSocksProxy = *params.DisableLocalSocksProxy
  229. } // else use the value in the config
  230. if params.DisableLocalHTTPProxy != nil {
  231. config.DisableLocalHTTPProxy = *params.DisableLocalHTTPProxy
  232. } // else use the value in the config
  233. // config.Commit must be called before calling config.SetParameters
  234. // or attempting to connect.
  235. err = config.Commit(true)
  236. if err != nil {
  237. return nil, errors.TraceMsg(err, "config.Commit failed")
  238. }
  239. // If supplied, apply the parameters delta
  240. if len(paramsDelta) > 0 {
  241. err = config.SetParameters("", false, paramsDelta)
  242. if err != nil {
  243. return nil, errors.TraceMsg(err, fmt.Sprintf("SetParameters failed for delta: %v", paramsDelta))
  244. }
  245. }
  246. err = psiphon.OpenDataStore(config)
  247. if err != nil {
  248. return nil, errors.TraceMsg(err, "failed to open data store")
  249. }
  250. // If specified, the embedded server list is loaded and stored. When there
  251. // are no server candidates at all, we wait for this import to complete
  252. // before starting the Psiphon controller. Otherwise, we import while
  253. // concurrently starting the controller to minimize delay before attempting
  254. // to connect to existing candidate servers.
  255. //
  256. // If the import fails, an error notice is emitted, but the controller is
  257. // still started: either existing candidate servers may suffice, or the
  258. // remote server list fetch may obtain candidate servers.
  259. //
  260. // The import will be interrupted if it's still running when the controller
  261. // is stopped.
  262. tunnel.embeddedServerListWaitGroup.Add(1)
  263. go func() {
  264. defer tunnel.embeddedServerListWaitGroup.Done()
  265. err := psiphon.ImportEmbeddedServerEntries(
  266. tunnelCtx,
  267. config,
  268. "",
  269. embeddedServerEntryList)
  270. if err != nil {
  271. psiphon.NoticeError("error importing embedded server entry list: %s", err)
  272. return
  273. }
  274. }()
  275. if !psiphon.HasServerEntries() {
  276. psiphon.NoticeInfo("awaiting embedded server entry list import")
  277. tunnel.embeddedServerListWaitGroup.Wait()
  278. }
  279. // Create the Psiphon controller
  280. controller, err := psiphon.NewController(config)
  281. if err != nil {
  282. return nil, errors.TraceMsg(err, "psiphon.NewController failed")
  283. }
  284. tunnel.controllerDial = controller.Dial
  285. // Begin tunnel connection
  286. tunnel.controllerWaitGroup.Add(1)
  287. go func() {
  288. defer tunnel.controllerWaitGroup.Done()
  289. // Start the tunnel. Only returns on error (or internal timeout).
  290. controller.Run(tunnelCtx)
  291. // controller.Run does not exit until the goroutine that posts
  292. // EstablishTunnelTimeout has terminated; so, if there was a
  293. // EstablishTunnelTimeout event, ErrTimeout is guaranteed to be sent to
  294. // errored before this next error and will be the StartTunnel return value.
  295. err := ctx.Err()
  296. switch err {
  297. case context.DeadlineExceeded:
  298. err = ErrTimeout
  299. case context.Canceled:
  300. err = errors.TraceMsg(err, "StartTunnel canceled")
  301. default:
  302. err = errors.TraceMsg(err, "controller.Run exited unexpectedly")
  303. }
  304. select {
  305. case erroredCh <- err:
  306. default:
  307. }
  308. }()
  309. // Wait for an active tunnel or error
  310. select {
  311. case <-connectedSignal:
  312. return tunnel, nil
  313. case err := <-erroredCh:
  314. if err != ErrTimeout {
  315. err = errors.TraceMsg(err, "tunnel start produced error")
  316. }
  317. return nil, err
  318. }
  319. }
  320. // Stop stops/disconnects/shuts down the tunnel.
  321. // It is safe to call Stop multiple times.
  322. // It is safe to call concurrently with Dial and with itself.
  323. func (tunnel *PsiphonTunnel) Stop() {
  324. // Holding a lock while calling the stop function ensures that any concurrent call
  325. // to Stop will wait for the first call to finish before returning, rather than
  326. // returning immediately (because tunnel.stop is nil) and thereby indicating
  327. // (erroneously) that the tunnel has been stopped.
  328. // Stopping a tunnel happens quickly enough that this processing block shouldn't be
  329. // a problem.
  330. tunnel.mu.Lock()
  331. defer tunnel.mu.Unlock()
  332. if tunnel.stop == nil {
  333. return
  334. }
  335. tunnel.stop()
  336. tunnel.stop = nil
  337. tunnel.controllerDial = nil
  338. }
  339. // Dial connects to the specified address through the Psiphon tunnel.
  340. // It is safe to call Dial after the tunnel has been stopped.
  341. // It is safe to call Dial concurrently with Stop.
  342. func (tunnel *PsiphonTunnel) Dial(remoteAddr string) (conn net.Conn, err error) {
  343. // Ensure the dial is accessed in a thread-safe manner, without holding the lock
  344. // while calling the dial function.
  345. // Note that it is safe for controller.Dial to be called even after or during a tunnel
  346. // shutdown (i.e., if the context has been canceled).
  347. tunnel.mu.Lock()
  348. dial := tunnel.controllerDial
  349. tunnel.mu.Unlock()
  350. if dial == nil {
  351. return nil, errors.TraceNew("tunnel not started")
  352. }
  353. return dial(remoteAddr, nil)
  354. }