clientlib.go 12 KB

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