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() {}