PsiphonTunnel.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package main
  2. // #include <stdlib.h>
  3. import "C"
  4. import (
  5. "context"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "sync"
  10. "time"
  11. "unsafe"
  12. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
  13. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  14. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  15. )
  16. type startResultCode int
  17. const (
  18. startResultCodeSuccess startResultCode = iota
  19. startResultCodeTimeout
  20. startResultCodeOtherError
  21. )
  22. type noticeEvent struct {
  23. Data map[string]interface{} `json:"data"`
  24. NoticeType string `json:"noticeType"`
  25. }
  26. type startResult struct {
  27. Code startResultCode `json:"result_code"`
  28. BootstrapTime float64 `json:"bootstrap_time,omitempty"`
  29. ErrorString string `json:"error,omitempty"`
  30. HttpProxyPort int `json:"http_proxy_port,omitempty"`
  31. SocksProxyPort int `json:"socks_proxy_port,omitempty"`
  32. }
  33. type psiphonTunnel struct {
  34. controllerWaitGroup sync.WaitGroup
  35. controllerCtx context.Context
  36. stopController context.CancelFunc
  37. httpProxyPort int
  38. socksProxyPort int
  39. }
  40. var tunnel psiphonTunnel
  41. // Memory managed by PsiphonTunnel which is allocated in Start and freed in Stop
  42. var managedStartResult *C.char
  43. //export psiphon_tunnel_start
  44. //
  45. // ******************************* WARNING ********************************
  46. // The underlying memory referenced by the return value of Start is managed
  47. // by PsiphonTunnel and attempting to free it explicitly will cause the
  48. // program to crash. This memory is freed once Stop is called.
  49. // ************************************************************************
  50. //
  51. // Start starts the controller and returns once either of the following has occured: an active tunnel has been
  52. // established, the timeout has elapsed before an active tunnel could be established or an error has occured.
  53. //
  54. // Start returns a startResult object serialized as a JSON string in the form of a null-terminated buffer of C chars.
  55. // Start will return,
  56. // On success:
  57. // {
  58. // "result_code": 0,
  59. // "bootstrap_time": <time_to_establish_tunnel>,
  60. // "http_proxy_port": <http_proxy_port_num>,
  61. // "socks_proxy_port": <socks_proxy_port_num>
  62. // }
  63. //
  64. // On timeout:
  65. // {
  66. // "result_code": 1,
  67. // "error": <error message>
  68. // }
  69. //
  70. // On other error:
  71. // {
  72. // "result_code": 2,
  73. // "error": <error message>
  74. // }
  75. //
  76. // clientPlatform should be of the form OS_OSVersion_BundleIdentifier where both the OSVersion and BundleIdentifier
  77. // fields are optional. If clientPlatform is set to an empty string the "ClientPlatform" field in the provided json
  78. // config will be used instead.
  79. //
  80. // Provided below are links to platform specific code which can be used to find some of the above fields:
  81. // Android:
  82. // - OSVersion: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L573
  83. // - BundleIdentifier: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L575
  84. // iOS:
  85. // - OSVersion: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L612
  86. // - BundleIdentifier: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L622
  87. //
  88. // Some examples of valid client platform strings are:
  89. //
  90. // "Android_4.2.2_com.example.exampleApp"
  91. // "iOS_11.4_com.example.exampleApp"
  92. // "Windows"
  93. //
  94. // networkID must be a non-empty string and follow the format specified by
  95. // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter.
  96. //
  97. // Provided below are links to platform specific code which can be used to generate valid network identifier strings:
  98. // Android:
  99. // - https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L371
  100. // iOS:
  101. // - https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L1105
  102. //
  103. // timeout specifies a time limit after which to stop attempting to connect and return an error if an active tunnel
  104. // has not been established. A timeout of 0 will result in no timeout condition and the controller will attempt to
  105. // establish an active tunnel indefinitely (or until psiphon_tunnel_stop is called). Timeout values >= 0 override
  106. // the optional `EstablishTunnelTimeoutSeconds` config field.
  107. func psiphon_tunnel_start(cConfigJSON, cEmbeddedServerEntryList, cClientPlatform, cNetworkID *C.char, timeout *int64) *C.char {
  108. // Stop any active tunnels
  109. psiphon_tunnel_stop()
  110. // Validate timeout value
  111. if timeout != nil && *timeout < 0 {
  112. managedStartResult = startErrorJson(errors.New("Timeout value must be non-negative"))
  113. return managedStartResult
  114. }
  115. // NOTE: all arguments which are still referenced once Start returns should be copied onto the Go heap
  116. // to ensure that they don't disappear later on and cause Go to crash.
  117. configJSON := C.GoString(cConfigJSON)
  118. embeddedServerEntryList := C.GoString(cEmbeddedServerEntryList)
  119. clientPlatform := C.GoString(cClientPlatform)
  120. networkID := C.GoString(cNetworkID)
  121. // Load provided config
  122. config, err := psiphon.LoadConfig([]byte(configJSON))
  123. if err != nil {
  124. managedStartResult = startErrorJson(err)
  125. return managedStartResult
  126. }
  127. // Set network ID
  128. config.NetworkID = networkID
  129. // Set client platform
  130. config.ClientPlatform = clientPlatform
  131. // Set timeout
  132. if timeout != nil {
  133. // timeout overrides optional timeout field in config
  134. config.EstablishTunnelTimeoutSeconds = nil
  135. }
  136. // All config fields should be set before calling commit
  137. err = config.Commit()
  138. if err != nil {
  139. managedStartResult = startErrorJson(err)
  140. return managedStartResult
  141. }
  142. // Setup signals
  143. connected := make(chan bool)
  144. testError := make(chan error)
  145. // Set up notice handling
  146. psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  147. func(notice []byte) {
  148. var event noticeEvent
  149. err := json.Unmarshal(notice, &event)
  150. if err != nil {
  151. err = errors.New(fmt.Sprintf("Failed to unmarshal json: %s", err.Error()))
  152. select {
  153. case testError <- err:
  154. default:
  155. }
  156. }
  157. if event.NoticeType == "ListeningHttpProxyPort" {
  158. port := event.Data["port"].(float64)
  159. tunnel.httpProxyPort = int(port)
  160. } else if event.NoticeType == "ListeningSocksProxyPort" {
  161. port := event.Data["port"].(float64)
  162. tunnel.socksProxyPort = int(port)
  163. } else if event.NoticeType == "Tunnels" {
  164. count := event.Data["count"].(float64)
  165. if count > 0 {
  166. select {
  167. case connected <- true:
  168. default:
  169. }
  170. }
  171. }
  172. }))
  173. // Initialize data store
  174. err = psiphon.OpenDataStore(config)
  175. if err != nil {
  176. managedStartResult = startErrorJson(err)
  177. return managedStartResult
  178. }
  179. // Store embedded server entries
  180. serverEntries, err := protocol.DecodeServerEntryList(
  181. embeddedServerEntryList,
  182. common.GetCurrentTimestamp(),
  183. protocol.SERVER_ENTRY_SOURCE_EMBEDDED)
  184. if err != nil {
  185. managedStartResult = startErrorJson(err)
  186. return managedStartResult
  187. }
  188. err = psiphon.StoreServerEntries(config, serverEntries, false)
  189. if err != nil {
  190. managedStartResult = startErrorJson(err)
  191. return managedStartResult
  192. }
  193. // Run Psiphon
  194. controller, err := psiphon.NewController(config)
  195. if err != nil {
  196. managedStartResult = startErrorJson(err)
  197. return managedStartResult
  198. }
  199. tunnel.controllerCtx, tunnel.stopController = context.WithCancel(context.Background())
  200. // Set start time
  201. startTime := time.Now()
  202. optionalTimeout := make(chan error)
  203. if timeout != nil && *timeout != 0 {
  204. // Setup timeout signal
  205. runtimeTimeout := time.Duration(*timeout) * time.Second
  206. timeoutSignal, cancelTimeout := context.WithTimeout(context.Background(), runtimeTimeout)
  207. defer cancelTimeout()
  208. go func() {
  209. <-timeoutSignal.Done()
  210. optionalTimeout <- timeoutSignal.Err()
  211. }()
  212. }
  213. // Run test
  214. var result startResult
  215. tunnel.controllerWaitGroup.Add(1)
  216. go func() {
  217. defer tunnel.controllerWaitGroup.Done()
  218. controller.Run(tunnel.controllerCtx)
  219. select {
  220. case testError <- errors.New("controller.Run exited unexpectedly"):
  221. default:
  222. }
  223. }()
  224. // Wait for an active tunnel, timeout or error
  225. select {
  226. case <-connected:
  227. result.Code = startResultCodeSuccess
  228. result.BootstrapTime = secondsBeforeNow(startTime)
  229. result.HttpProxyPort = tunnel.httpProxyPort
  230. result.SocksProxyPort = tunnel.socksProxyPort
  231. case err := <-optionalTimeout:
  232. result.Code = startResultCodeTimeout
  233. if err != nil {
  234. result.ErrorString = fmt.Sprintf("Timeout occured before Psiphon connected: %s", err.Error())
  235. }
  236. tunnel.stopController()
  237. case err := <-testError:
  238. result.Code = startResultCodeOtherError
  239. result.ErrorString = err.Error()
  240. tunnel.stopController()
  241. }
  242. // Return result
  243. managedStartResult = marshalStartResult(result)
  244. return managedStartResult
  245. }
  246. //export psiphon_tunnel_stop
  247. //
  248. // Stop stops the controller if it is running and waits for it to clean up and exit.
  249. //
  250. // Stop should always be called after a successful call to Start to ensure the
  251. // controller is not left running.
  252. func psiphon_tunnel_stop() {
  253. freeManagedStartResult()
  254. if tunnel.stopController != nil {
  255. tunnel.stopController()
  256. }
  257. tunnel.controllerWaitGroup.Wait()
  258. psiphon.CloseDataStore()
  259. }
  260. // secondsBeforeNow returns the delta seconds of the current time subtract startTime.
  261. func secondsBeforeNow(startTime time.Time) float64 {
  262. delta := time.Now().Sub(startTime)
  263. return delta.Seconds()
  264. }
  265. // marshalStartResult serializes a startResult object as a JSON string in the form
  266. // of a null-terminated buffer of C chars.
  267. func marshalStartResult(result startResult) *C.char {
  268. resultJSON, err := json.Marshal(result)
  269. if err != nil {
  270. return C.CString(fmt.Sprintf("{\"result_code\":%d, \"error\": \"%s\"}", startResultCodeOtherError, err.Error()))
  271. }
  272. return C.CString(string(resultJSON))
  273. }
  274. // startErrorJson returns a startResult object serialized as a JSON string in the form
  275. // of a null-terminated buffer of C chars. The object's return result code will be set to
  276. // startResultCodeOtherError (2) and its error string set to the error string of the provided error.
  277. //
  278. // The JSON will be in the form of:
  279. // {
  280. // "result_code": 2,
  281. // "error": <error message>
  282. // }
  283. func startErrorJson(err error) *C.char {
  284. var result startResult
  285. result.Code = startResultCodeOtherError
  286. result.ErrorString = err.Error()
  287. return marshalStartResult(result)
  288. }
  289. // freeManagedStartResult frees the memory on the heap pointed to by managedStartResult.
  290. func freeManagedStartResult() {
  291. if managedStartResult != nil {
  292. managedMemory := unsafe.Pointer(managedStartResult)
  293. if managedMemory != nil {
  294. C.free(managedMemory)
  295. }
  296. managedStartResult = nil
  297. }
  298. }
  299. // main is a stub required by cgo.
  300. func main() {}