clientlib.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. "path/filepath"
  26. "sync"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  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/protocol"
  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. }
  63. // PsiphonTunnel is the tunnel object. It can be used for stopping the tunnel and
  64. // retrieving proxy ports.
  65. type PsiphonTunnel struct {
  66. controllerWaitGroup sync.WaitGroup
  67. stopController context.CancelFunc
  68. // The port on which the HTTP proxy is running
  69. HTTPProxyPort int
  70. // The port on which the SOCKS proxy is running
  71. SOCKSProxyPort int
  72. }
  73. // ClientParametersDelta allows for fine-grained modification of parameters.ClientParameters.
  74. // NOTE: Ordinary users of this library should never need this.
  75. type ClientParametersDelta map[string]interface{}
  76. // NoticeEvent represents the notices emitted by tunnel core. It will be passed to
  77. // noticeReceiver, if supplied.
  78. // NOTE: Ordinary users of this library should never need this.
  79. type NoticeEvent struct {
  80. Data map[string]interface{} `json:"data"`
  81. Type string `json:"noticeType"`
  82. Timestamp string `json:"timestamp"`
  83. }
  84. // ErrTimeout is returned when the tunnel connection attempt fails due to timeout
  85. var ErrTimeout = std_errors.New("clientlib: tunnel connection timeout")
  86. // StartTunnel makes a Psiphon tunnel connection. It returns an error if the connection
  87. // was not successful. If the returned error is nil, the returned tunnel can be used
  88. // to find out the proxy ports and subsequently stop the tunnel.
  89. //
  90. // ctx may be cancelable, if the caller wants to be able to interrupt the connection
  91. // attempt, or context.Background().
  92. //
  93. // configJSON will be passed to psiphon.LoadConfig to configure the tunnel. Required.
  94. //
  95. // embeddedServerEntryList is the encoded embedded server entry list. It is optional.
  96. //
  97. // params are config values that typically need to be overridden at runtime.
  98. //
  99. // paramsDelta contains changes that will be applied to the ClientParameters.
  100. // NOTE: Ordinary users of this library should never need this and should pass nil.
  101. //
  102. // noticeReceiver, if non-nil, will be called for each notice emitted by tunnel core.
  103. // NOTE: Ordinary users of this library should never need this and should pass nil.
  104. func StartTunnel(ctx context.Context,
  105. configJSON []byte, embeddedServerEntryList string,
  106. params Parameters, paramsDelta ClientParametersDelta,
  107. noticeReceiver func(NoticeEvent)) (tunnel *PsiphonTunnel, err error) {
  108. config, err := psiphon.LoadConfig(configJSON)
  109. if err != nil {
  110. return nil, errors.TraceMsg(err, "failed to load config file")
  111. }
  112. // Use params.DataRootDirectory to set related config values.
  113. if params.DataRootDirectory != nil {
  114. config.DataRootDirectory = *params.DataRootDirectory
  115. // Migrate old fields
  116. config.MigrateDataStoreDirectory = *params.DataRootDirectory
  117. config.MigrateObfuscatedServerListDownloadDirectory = *params.DataRootDirectory
  118. config.MigrateRemoteServerListDownloadFilename = filepath.Join(*params.DataRootDirectory, "server_list_compressed")
  119. }
  120. if params.NetworkID != nil {
  121. config.NetworkID = *params.NetworkID
  122. }
  123. if params.ClientPlatform != nil {
  124. config.ClientPlatform = *params.ClientPlatform
  125. } // else use the value in config
  126. if params.EstablishTunnelTimeoutSeconds != nil {
  127. config.EstablishTunnelTimeoutSeconds = params.EstablishTunnelTimeoutSeconds
  128. } // else use the value in config
  129. if config.UseNoticeFiles == nil && config.EmitDiagnosticNotices && params.EmitDiagnosticNoticesToFiles {
  130. config.UseNoticeFiles = &psiphon.UseNoticeFiles{
  131. RotatingFileSize: 0,
  132. RotatingSyncFrequency: 0,
  133. }
  134. } // else use the value in the config
  135. // config.Commit must be called before calling config.SetClientParameters
  136. // or attempting to connect.
  137. err = config.Commit()
  138. if err != nil {
  139. return nil, errors.TraceMsg(err, "config.Commit failed")
  140. }
  141. // If supplied, apply the client parameters delta
  142. if len(paramsDelta) > 0 {
  143. err = config.SetClientParameters("", false, paramsDelta)
  144. if err != nil {
  145. return nil, errors.TraceMsg(
  146. err, fmt.Sprintf("SetClientParameters failed for delta: %v", paramsDelta))
  147. }
  148. }
  149. err = psiphon.OpenDataStore(config)
  150. if err != nil {
  151. return nil, errors.TraceMsg(err, "failed to open data store")
  152. }
  153. // Make sure we close the datastore in case of error
  154. defer func() {
  155. if err != nil {
  156. psiphon.CloseDataStore()
  157. }
  158. }()
  159. // Store embedded server entries
  160. serverEntries, err := protocol.DecodeServerEntryList(
  161. embeddedServerEntryList,
  162. common.TruncateTimestampToHour(common.GetCurrentTimestamp()),
  163. protocol.SERVER_ENTRY_SOURCE_EMBEDDED)
  164. if err != nil {
  165. return nil, errors.TraceMsg(err, "failed to decode server entry list")
  166. }
  167. err = psiphon.StoreServerEntries(config, serverEntries, false)
  168. if err != nil {
  169. return nil, errors.TraceMsg(err, "failed to store server entries")
  170. }
  171. // Will receive a value when the tunnel has successfully connected.
  172. connected := make(chan struct{})
  173. // Will receive a value if the tunnel times out trying to connect.
  174. timedOut := make(chan struct{})
  175. // Will receive a value if an error occurs during the connection sequence.
  176. errored := make(chan error)
  177. // Create the tunnel object
  178. tunnel = new(PsiphonTunnel)
  179. // Set up notice handling
  180. psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  181. func(notice []byte) {
  182. var event NoticeEvent
  183. err := json.Unmarshal(notice, &event)
  184. if err != nil {
  185. // This is unexpected and probably indicates something fatal has occurred.
  186. // We'll interpret it as a connection error and abort.
  187. err = errors.TraceMsg(err, "failed to unmarshal notice JSON")
  188. select {
  189. case errored <- err:
  190. default:
  191. }
  192. return
  193. }
  194. if event.Type == "ListeningHttpProxyPort" {
  195. port := event.Data["port"].(float64)
  196. tunnel.HTTPProxyPort = int(port)
  197. } else if event.Type == "ListeningSocksProxyPort" {
  198. port := event.Data["port"].(float64)
  199. tunnel.SOCKSProxyPort = int(port)
  200. } else if event.Type == "EstablishTunnelTimeout" {
  201. select {
  202. case timedOut <- struct{}{}:
  203. default:
  204. }
  205. } else if event.Type == "Tunnels" {
  206. count := event.Data["count"].(float64)
  207. if count > 0 {
  208. select {
  209. case connected <- struct{}{}:
  210. default:
  211. }
  212. }
  213. }
  214. // Some users of this package may need to add special processing of notices.
  215. // If the caller has requested it, we'll pass on the notices.
  216. if noticeReceiver != nil {
  217. noticeReceiver(event)
  218. }
  219. }))
  220. // Create the Psiphon controller
  221. controller, err := psiphon.NewController(config)
  222. if err != nil {
  223. return nil, errors.TraceMsg(err, "psiphon.NewController failed")
  224. }
  225. // Create a cancelable context that will be used for stopping the tunnel
  226. var controllerCtx context.Context
  227. controllerCtx, tunnel.stopController = context.WithCancel(ctx)
  228. // Begin tunnel connection
  229. tunnel.controllerWaitGroup.Add(1)
  230. go func() {
  231. defer tunnel.controllerWaitGroup.Done()
  232. // Start the tunnel. Only returns on error (or internal timeout).
  233. controller.Run(controllerCtx)
  234. select {
  235. case errored <- errors.TraceNew("controller.Run exited unexpectedly"):
  236. default:
  237. }
  238. }()
  239. // Wait for an active tunnel, timeout, or error
  240. select {
  241. case <-connected:
  242. return tunnel, nil
  243. case <-timedOut:
  244. tunnel.Stop()
  245. return nil, ErrTimeout
  246. case err := <-errored:
  247. tunnel.Stop()
  248. return nil, errors.TraceMsg(err, "tunnel start produced error")
  249. }
  250. }
  251. // Stop stops/disconnects/shuts down the tunnel. It is safe to call when not connected.
  252. func (tunnel *PsiphonTunnel) Stop() {
  253. if tunnel.stopController != nil {
  254. tunnel.stopController()
  255. }
  256. tunnel.controllerWaitGroup.Wait()
  257. psiphon.CloseDataStore()
  258. }