PsiphonTunnel.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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 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. // networkID should be not be blank and should follow the format specified by
  77. // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter.
  78. func Start(configJSON, embeddedServerEntryList, networkID string, timeout int64) *C.char {
  79. // Load provided config
  80. config, err := psiphon.LoadConfig([]byte(configJSON))
  81. if err != nil {
  82. return startErrorJson(err)
  83. }
  84. // Set network ID
  85. if networkID != "" {
  86. config.NetworkID = networkID
  87. }
  88. // All config fields should be set before calling commit
  89. err = config.Commit()
  90. if err != nil {
  91. return startErrorJson(err)
  92. }
  93. // Setup signals
  94. connected := make(chan bool)
  95. testError := make(chan error)
  96. // Set up notice handling
  97. psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  98. func(notice []byte) {
  99. var event noticeEvent
  100. err := json.Unmarshal(notice, &event)
  101. if err != nil {
  102. err = errors.New(fmt.Sprintf("Failed to unmarshal json: %s", err.Error()))
  103. select {
  104. case testError <- err:
  105. default:
  106. }
  107. }
  108. if event.NoticeType == "ListeningHttpProxyPort" {
  109. port := event.Data["port"].(float64)
  110. tunnel.httpProxyPort = int(port)
  111. } else if event.NoticeType == "ListeningSocksProxyPort" {
  112. port := event.Data["port"].(float64)
  113. tunnel.socksProxyPort = int(port)
  114. } else if event.NoticeType == "Tunnels" {
  115. count := event.Data["count"].(float64)
  116. if count > 0 {
  117. select {
  118. case connected <- true:
  119. default:
  120. }
  121. }
  122. }
  123. }))
  124. // Initialize data store
  125. err = psiphon.InitDataStore(config)
  126. if err != nil {
  127. return startErrorJson(err)
  128. }
  129. // Store embedded server entries
  130. serverEntries, err := protocol.DecodeServerEntryList(
  131. embeddedServerEntryList,
  132. common.GetCurrentTimestamp(),
  133. protocol.SERVER_ENTRY_SOURCE_EMBEDDED)
  134. if err != nil {
  135. return startErrorJson(err)
  136. }
  137. err = psiphon.StoreServerEntries(config, serverEntries, false)
  138. if err != nil {
  139. return startErrorJson(err)
  140. }
  141. // Run Psiphon
  142. controller, err := psiphon.NewController(config)
  143. if err != nil {
  144. return startErrorJson(err)
  145. }
  146. tunnel.controllerCtx, tunnel.stopController = context.WithCancel(context.Background())
  147. // Set start time
  148. startTime := time.Now()
  149. // Setup timeout signal
  150. runtimeTimeout := time.Duration(timeout) * time.Second
  151. timeoutSignal, cancelTimeout := context.WithTimeout(context.Background(), runtimeTimeout)
  152. defer cancelTimeout()
  153. // Run test
  154. var result startResult
  155. tunnel.controllerWaitGroup.Add(1)
  156. go func() {
  157. defer tunnel.controllerWaitGroup.Done()
  158. controller.Run(tunnel.controllerCtx)
  159. select {
  160. case testError <- errors.New("controller.Run exited unexpectedly"):
  161. default:
  162. }
  163. }()
  164. // Wait for an active tunnel, timeout or error
  165. select {
  166. case <-connected:
  167. result.Code = startResultCodeSuccess
  168. result.BootstrapTime = secondsBeforeNow(startTime)
  169. result.HttpProxyPort = tunnel.httpProxyPort
  170. result.SocksProxyPort = tunnel.socksProxyPort
  171. case <-timeoutSignal.Done():
  172. result.Code = startResultCodeTimeout
  173. err = timeoutSignal.Err()
  174. if err != nil {
  175. result.ErrorString = fmt.Sprintf("Timeout occured before Psiphon connected: %s", err.Error())
  176. }
  177. tunnel.stopController()
  178. case err := <-testError:
  179. result.Code = startResultCodeOtherError
  180. result.ErrorString = err.Error()
  181. tunnel.stopController()
  182. }
  183. // Free previous result
  184. freeManagedStartResult()
  185. // Return result
  186. managedStartResult = marshalStartResult(result)
  187. return managedStartResult
  188. }
  189. //export Stop
  190. // Stop stops the controller if it is running and waits for it to clean up and exit.
  191. //
  192. // Stop should always be called after a successful call to Start to ensure the
  193. // controller is not left running.
  194. func Stop() {
  195. freeManagedStartResult()
  196. if tunnel.stopController != nil {
  197. tunnel.stopController()
  198. }
  199. tunnel.controllerWaitGroup.Wait()
  200. }
  201. // secondsBeforeNow returns the delta seconds of the current time subtract startTime.
  202. func secondsBeforeNow(startTime time.Time) float64 {
  203. delta := time.Now().Sub(startTime)
  204. return delta.Seconds()
  205. }
  206. // marshalStartResult serializes a startResult object as a JSON string in the form
  207. // of a null-terminated buffer of C chars.
  208. func marshalStartResult(result startResult) *C.char {
  209. resultJSON, err := json.Marshal(result)
  210. if err != nil {
  211. return C.CString(fmt.Sprintf("{\"result_code\":%d, \"error\": \"%s\"}", startResultCodeOtherError, err.Error()))
  212. }
  213. return C.CString(string(resultJSON))
  214. }
  215. // startErrorJson returns a startResult object serialized as a JSON string in the form
  216. // of a null-terminated buffer of C chars. The object's return result code will be set to
  217. // startResultCodeOtherError (2) and its error string set to the error string of the provided error.
  218. //
  219. // The JSON will be in the form of:
  220. // {
  221. // "result_code": 2,
  222. // "error": <error message>
  223. // }
  224. func startErrorJson(err error) *C.char {
  225. var result startResult
  226. result.Code = startResultCodeOtherError
  227. result.ErrorString = err.Error()
  228. return marshalStartResult(result)
  229. }
  230. // freeManagedStartResult frees the memory on the heap pointed to by managedStartResult.
  231. func freeManagedStartResult() {
  232. if managedStartResult != nil {
  233. managedMemory := unsafe.Pointer(managedStartResult)
  234. if managedMemory != nil {
  235. C.free(managedMemory)
  236. }
  237. }
  238. }
  239. // main is a stub required by cgo.
  240. func main() {}