github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/ClientLibrary/PsiphonTunnel.go (about) 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 20 package main 21 22 /* 23 #include <stdlib.h> 24 #include <stdint.h> 25 26 // For descriptions of fields, see below. 27 // Additional information can also be found in the Parameters structure in clientlib.go. 28 struct Parameters { 29 size_t sizeofStruct; // Must be set to sizeof(Parameters); helps with ABI compatibiity 30 char *dataRootDirectory; 31 char *clientPlatform; 32 char *networkID; 33 int32_t *establishTunnelTimeoutSeconds; 34 }; 35 */ 36 import "C" 37 38 import ( 39 "context" 40 "encoding/json" 41 "fmt" 42 "time" 43 "unsafe" 44 45 "github.com/Psiphon-Labs/psiphon-tunnel-core/ClientLibrary/clientlib" 46 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 47 ) 48 49 /* 50 If/when new fields are added to the C Parameters struct, we can use this code to ensure 51 ABI compatibility. We'll take these steps: 52 1. Copy the old struct into a new `ParametersV1`. The new struct will be `Parameters`. 53 2. Uncomment the code below. It will not compile (link, specifically) if the size of 54 `Parameters` is the same as the size of `ParametersV1`. 55 - If the compile fails, padding may need to be added to `Parameters` to force it to be 56 a different size than `ParametersV1`. 57 3. In `Start`, we'll check the value of `sizeofStruct` to determine which version of 58 `Parameters` the caller is using, and behave according. 59 4. Do similar kinds of things for V2, V3, etc. 60 */ 61 /* 62 func nonexistentFunction() 63 func init() { 64 if C.sizeof_struct_Parameters == C.sizeof_struct_ParametersV1 { 65 // There is only an attempt to link this nonexistent function if the struct sizes 66 // are the same. So they must not be. 67 nonexistentFunction() 68 } 69 } 70 */ 71 72 type startResultCode int 73 74 const ( 75 startResultCodeSuccess startResultCode = 0 76 startResultCodeTimeout startResultCode = 1 77 startResultCodeOtherError startResultCode = 2 78 ) 79 80 type startResult struct { 81 Code startResultCode 82 ConnectTimeMS int64 `json:",omitempty"` 83 Error string `json:",omitempty"` 84 HTTPProxyPort int `json:",omitempty"` 85 SOCKSProxyPort int `json:",omitempty"` 86 } 87 88 var tunnel *clientlib.PsiphonTunnel 89 90 // Memory managed by PsiphonTunnel which is allocated in Start and freed in Stop 91 var managedStartResult *C.char 92 93 //export PsiphonTunnelStart 94 // 95 // ******************************* WARNING ******************************** 96 // The underlying memory referenced by the return value of Start is managed 97 // by PsiphonTunnel and attempting to free it explicitly will cause the 98 // program to crash. This memory is freed once Stop is called, or if Start 99 // is called again. 100 // ************************************************************************ 101 // 102 // Start starts the controller and returns once one of the following has occured: 103 // an active tunnel has been established, the timeout has elapsed before an active tunnel 104 // could be established, or an error has occured. 105 // 106 // Start returns a startResult object serialized as a JSON string in the form of a 107 // null-terminated buffer of C chars. 108 // Start will return, 109 // On success: 110 // { 111 // "Code": 0, 112 // "ConnectTimeMS": <milliseconds to establish tunnel>, 113 // "HTTPProxyPort": <http proxy port number>, 114 // "SOCKSProxyPort": <socks proxy port number> 115 // } 116 // 117 // On timeout: 118 // { 119 // "Code": 1, 120 // "Error": <error message> 121 // } 122 // 123 // On other error: 124 // { 125 // "Code": 2, 126 // "Error": <error message> 127 // } 128 // 129 // Parameters.clientPlatform should be of the form OS_OSVersion_BundleIdentifier where 130 // both the OSVersion and BundleIdentifier fields are optional. If clientPlatform is set 131 // to an empty string the "ClientPlatform" field in the provided JSON config will be 132 // used instead. 133 // 134 // Provided below are links to platform specific code which can be used to find some of the above fields: 135 // Android: 136 // - OSVersion: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L573 137 // - BundleIdentifier: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L575 138 // iOS: 139 // - OSVersion: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L612 140 // - BundleIdentifier: https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L622 141 // 142 // Some examples of valid client platform strings are: 143 // 144 // "Android_4.2.2_com.example.exampleApp" 145 // "iOS_11.4_com.example.exampleApp" 146 // "Windows" 147 // 148 // Parameters.networkID must be a non-empty string and follow the format specified by: 149 // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter. 150 // Provided below are links to platform specific code which can be used to generate 151 // valid network identifier strings: 152 // Android: 153 // - https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java#L371 154 // iOS: 155 // - https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/3d344194d21b250e0f18ededa4b4459a373b0690/MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m#L1105 156 // 157 // Parameters.establishTunnelTimeoutSeconds specifies a time limit after which to stop 158 // attempting to connect and return an error if an active tunnel has not been established. 159 // A timeout of 0 will result in no timeout condition and the controller will attempt to 160 // establish an active tunnel indefinitely (or until PsiphonTunnelStop is called). 161 // Timeout values >= 0 override the optional `EstablishTunnelTimeoutSeconds` config field; 162 // null causes the config value to be used. 163 func PsiphonTunnelStart(cConfigJSON, cEmbeddedServerEntryList *C.char, cParams *C.struct_Parameters) *C.char { 164 // Stop any active tunnels 165 PsiphonTunnelStop() 166 167 if cConfigJSON == nil { 168 err := errors.Tracef("configJSON is required") 169 managedStartResult = startErrorJSON(err) 170 return managedStartResult 171 } 172 173 if cParams == nil { 174 err := errors.Tracef("params is required") 175 managedStartResult = startErrorJSON(err) 176 return managedStartResult 177 } 178 179 if cParams.sizeofStruct != C.sizeof_struct_Parameters { 180 err := errors.Tracef("sizeofStruct does not match sizeof(Parameters)") 181 managedStartResult = startErrorJSON(err) 182 return managedStartResult 183 } 184 185 // NOTE: all arguments which may be referenced once Start returns must be copied onto 186 // the Go heap to ensure that they don't disappear later on and cause Go to crash. 187 configJSON := []byte(C.GoString(cConfigJSON)) 188 embeddedServerEntryList := C.GoString(cEmbeddedServerEntryList) 189 190 params := clientlib.Parameters{} 191 if cParams.dataRootDirectory != nil { 192 v := C.GoString(cParams.dataRootDirectory) 193 params.DataRootDirectory = &v 194 } 195 if cParams.clientPlatform != nil { 196 v := C.GoString(cParams.clientPlatform) 197 params.ClientPlatform = &v 198 } 199 if cParams.networkID != nil { 200 v := C.GoString(cParams.networkID) 201 params.NetworkID = &v 202 } 203 if cParams.establishTunnelTimeoutSeconds != nil { 204 v := int(*cParams.establishTunnelTimeoutSeconds) 205 params.EstablishTunnelTimeoutSeconds = &v 206 } 207 208 // As Client Library doesn't currently implement callbacks, diagnostic 209 // notices aren't relayed to the client application. Set 210 // EmitDiagnosticNoticesToFiles to ensure the rotating diagnostic log file 211 // facility is used when EmitDiagnosticNotices is specified in the config. 212 params.EmitDiagnosticNoticesToFiles = true 213 214 startTime := time.Now() 215 216 // Start the tunnel connection 217 var err error 218 tunnel, err = clientlib.StartTunnel( 219 context.Background(), configJSON, embeddedServerEntryList, params, nil, nil) 220 221 if err != nil { 222 if err == clientlib.ErrTimeout { 223 managedStartResult = marshalStartResult(startResult{ 224 Code: startResultCodeTimeout, 225 Error: fmt.Sprintf("Timeout occurred before Psiphon connected: %s", err.Error()), 226 }) 227 } else { 228 managedStartResult = marshalStartResult(startResult{ 229 Code: startResultCodeOtherError, 230 Error: err.Error(), 231 }) 232 } 233 return managedStartResult 234 } 235 236 // Success 237 managedStartResult = marshalStartResult(startResult{ 238 Code: startResultCodeSuccess, 239 ConnectTimeMS: int64(time.Since(startTime) / time.Millisecond), 240 HTTPProxyPort: tunnel.HTTPProxyPort, 241 SOCKSProxyPort: tunnel.SOCKSProxyPort, 242 }) 243 return managedStartResult 244 } 245 246 //export PsiphonTunnelStop 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 and memory is released. 252 // It is safe to call this function when the tunnel is not running. 253 func PsiphonTunnelStop() { 254 freeManagedStartResult() 255 if tunnel != nil { 256 tunnel.Stop() 257 } 258 } 259 260 // marshalStartResult serializes a startResult object as a JSON string in the form 261 // of a null-terminated buffer of C chars. 262 func marshalStartResult(result startResult) *C.char { 263 resultJSON, err := json.Marshal(result) 264 if err != nil { 265 err = errors.TraceMsg(err, "json.Marshal failed") 266 // Fail back to manually constructing the JSON 267 return C.CString(fmt.Sprintf("{\"Code\":%d, \"Error\": \"%s\"}", 268 startResultCodeOtherError, err.Error())) 269 } 270 271 return C.CString(string(resultJSON)) 272 } 273 274 // startErrorJSON returns a startResult object serialized as a JSON string in the form of 275 // 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 277 // provided error. 278 // 279 // The JSON will be in the form of: 280 // { 281 // "Code": 2, 282 // "Error": <error message> 283 // } 284 func startErrorJSON(err error) *C.char { 285 var result startResult 286 result.Code = startResultCodeOtherError 287 result.Error = err.Error() 288 289 return marshalStartResult(result) 290 } 291 292 // freeManagedStartResult frees the memory on the heap pointed to by managedStartResult. 293 func freeManagedStartResult() { 294 if managedStartResult != nil { 295 managedMemory := unsafe.Pointer(managedStartResult) 296 if managedMemory != nil { 297 C.free(managedMemory) 298 } 299 managedStartResult = nil 300 } 301 } 302 303 // main is a stub required by cgo. 304 func main() {}