github.com/bepass-org/wireguard-go@v1.0.4-rc2.0.20240304192354-ebce6572bc24/psiphon/p.go (about)

     1  package psiphon
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"path/filepath"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
    15  	"github.com/refraction-networking/conjure/pkg/station/log"
    16  )
    17  
    18  // Parameters provide an easier way to modify the tunnel config at runtime.
    19  type Parameters struct {
    20  	// Used as the directory for the datastore, remote server list, and obfuscasted
    21  	// server list.
    22  	// Empty string means the default will be used (current working directory).
    23  	// nil means the values in the config file will be used.
    24  	// Optional, but strongly recommended.
    25  	DataRootDirectory *string
    26  
    27  	// Overrides config.ClientPlatform. See config.go for details.
    28  	// nil means the value in the config file will be used.
    29  	// Optional, but strongly recommended.
    30  	ClientPlatform *string
    31  
    32  	// Overrides config.NetworkID. For details see:
    33  	// https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter
    34  	// nil means the value in the config file will be used. (If not set in the config,
    35  	// an error will result.)
    36  	// Empty string will produce an error.
    37  	// Optional, but strongly recommended.
    38  	NetworkID *string
    39  
    40  	// Overrides config.EstablishTunnelTimeoutSeconds. See config.go for details.
    41  	// nil means the EstablishTunnelTimeoutSeconds value in the config file will be used.
    42  	// If there's no such value in the config file, the default will be used.
    43  	// Zero means there will be no timeout.
    44  	// Optional.
    45  	EstablishTunnelTimeoutSeconds *int
    46  
    47  	// EmitDiagnosticNoticesToFile indicates whether to use the rotating log file
    48  	// facility to record diagnostic notices instead of sending diagnostic
    49  	// notices to noticeReceiver. Has no effect unless the tunnel
    50  	// config.EmitDiagnosticNotices flag is set.
    51  	EmitDiagnosticNoticesToFiles bool
    52  }
    53  
    54  // Tunnel is the tunnel object. It can be used for stopping the tunnel and
    55  // retrieving proxy ports.
    56  type Tunnel struct {
    57  	embeddedServerListWaitGroup sync.WaitGroup
    58  	controllerWaitGroup         sync.WaitGroup
    59  	stopController              context.CancelFunc
    60  
    61  	// The port on which the HTTP proxy is running
    62  	HTTPProxyPort int
    63  	// The port on which the SOCKS proxy is running
    64  	SOCKSProxyPort int
    65  }
    66  
    67  // ParametersDelta allows for fine-grained modification of parameters.Parameters.
    68  // NOTE: Ordinary users of this library should never need this.
    69  type ParametersDelta map[string]interface{}
    70  
    71  // NoticeEvent represents the notices emitted by tunnel core. It will be passed to
    72  // noticeReceiver, if supplied.
    73  // NOTE: Ordinary users of this library should never need this.
    74  type NoticeEvent struct {
    75  	Data      map[string]interface{} `json:"data"`
    76  	Type      string                 `json:"noticeType"`
    77  	Timestamp string                 `json:"timestamp"`
    78  }
    79  
    80  // ErrTimeout is returned when the tunnel establishment attempt fails due to timeout
    81  var ErrTimeout = errors.New("clientlib: tunnel establishment timeout")
    82  
    83  // StartTunnel establishes a Psiphon tunnel. It returns an error if the establishment
    84  // was not successful. If the returned error is nil, the returned tunnel can be used
    85  // to find out the proxy ports and subsequently stop the tunnel.
    86  //
    87  // ctx may be cancelable, if the caller wants to be able to interrupt the establishment
    88  // attempt, or context.Background().
    89  //
    90  // configJSON will be passed to psiphon.LoadConfig to configure the tunnel. Required.
    91  //
    92  // embeddedServerEntryList is the encoded embedded server entry list. It is optional.
    93  //
    94  // params are config values that typically need to be overridden at runtime.
    95  //
    96  // paramsDelta contains changes that will be applied to the Parameters.
    97  // NOTE: Ordinary users of this library should never need this and should pass nil.
    98  //
    99  // noticeReceiver, if non-nil, will be called for each notice emitted by tunnel core.
   100  // NOTE: Ordinary users of this library should never need this and should pass nil.
   101  func StartTunnel(
   102  	ctx context.Context,
   103  	configJSON []byte,
   104  	embeddedServerEntryList string,
   105  	params Parameters,
   106  	paramsDelta ParametersDelta,
   107  	noticeReceiver func(NoticeEvent)) (retTunnel *Tunnel, retErr error) {
   108  
   109  	config, err := psiphon.LoadConfig(configJSON)
   110  	if err != nil {
   111  		return nil, errors.New("failed to load config file")
   112  	}
   113  
   114  	// Use params.DataRootDirectory to set related config values.
   115  	if params.DataRootDirectory != nil {
   116  		config.DataRootDirectory = *params.DataRootDirectory
   117  
   118  		// Migrate old fields
   119  		config.MigrateDataStoreDirectory = *params.DataRootDirectory
   120  		config.MigrateObfuscatedServerListDownloadDirectory = *params.DataRootDirectory
   121  		config.MigrateRemoteServerListDownloadFilename = filepath.Join(*params.DataRootDirectory, "server_list_compressed")
   122  	}
   123  
   124  	if params.NetworkID != nil {
   125  		config.NetworkID = *params.NetworkID
   126  	}
   127  
   128  	if params.ClientPlatform != nil {
   129  		config.ClientPlatform = *params.ClientPlatform
   130  	} // else use the value in config
   131  
   132  	if params.EstablishTunnelTimeoutSeconds != nil {
   133  		config.EstablishTunnelTimeoutSeconds = params.EstablishTunnelTimeoutSeconds
   134  	} // else use the value in config
   135  
   136  	if config.UseNoticeFiles == nil && config.EmitDiagnosticNotices && params.EmitDiagnosticNoticesToFiles {
   137  		config.UseNoticeFiles = &psiphon.UseNoticeFiles{
   138  			RotatingFileSize:      0,
   139  			RotatingSyncFrequency: 0,
   140  		}
   141  	} // else use the value in the config
   142  
   143  	// config.Commit must be called before calling config.SetParameters
   144  	// or attempting to connect.
   145  	err = config.Commit(true)
   146  	if err != nil {
   147  		return nil, errors.New("config.Commit failed")
   148  	}
   149  
   150  	// If supplied, apply the parameters delta
   151  	if len(paramsDelta) > 0 {
   152  		err = config.SetParameters("", false, paramsDelta)
   153  		if err != nil {
   154  			return nil, fmt.Errorf("set parameters failed for delta %v : %w", paramsDelta, err)
   155  		}
   156  	}
   157  
   158  	// Will receive a value when the tunnel has successfully connected.
   159  	connected := make(chan struct{}, 1)
   160  	// Will receive a value if an error occurs during the connection sequence.
   161  	errored := make(chan error, 1)
   162  
   163  	// Create the tunnel object
   164  	tunnel := new(Tunnel)
   165  
   166  	// Set up notice handling
   167  	psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
   168  		func(notice []byte) {
   169  			var event NoticeEvent
   170  			err := json.Unmarshal(notice, &event)
   171  			if err != nil {
   172  				// This is unexpected and probably indicates something fatal has occurred.
   173  				// We'll interpret it as a connection error and abort.
   174  				err = errors.New("failed to unmarshal notice JSON")
   175  				select {
   176  				case errored <- err:
   177  				default:
   178  				}
   179  				return
   180  			}
   181  
   182  			if event.Type == "ListeningHttpProxyPort" {
   183  				port := event.Data["port"].(float64)
   184  				tunnel.HTTPProxyPort = int(port)
   185  			} else if event.Type == "ListeningSocksProxyPort" {
   186  				port := event.Data["port"].(float64)
   187  				tunnel.SOCKSProxyPort = int(port)
   188  			} else if event.Type == "EstablishTunnelTimeout" {
   189  				select {
   190  				case errored <- ErrTimeout:
   191  				default:
   192  				}
   193  			} else if event.Type == "Tunnels" {
   194  				count := event.Data["count"].(float64)
   195  				if count > 0 {
   196  					select {
   197  					case connected <- struct{}{}:
   198  					default:
   199  					}
   200  				}
   201  			}
   202  
   203  			// Some users of this package may need to add special processing of notices.
   204  			// If the caller has requested it, we'll pass on the notices.
   205  			if noticeReceiver != nil {
   206  				noticeReceiver(event)
   207  			}
   208  		}))
   209  
   210  	err = psiphon.OpenDataStore(config)
   211  	if err != nil {
   212  		return nil, errors.New("failed to open data store")
   213  	}
   214  	// Make sure we close the datastore in case of error
   215  	defer func() {
   216  		if retErr != nil {
   217  			tunnel.controllerWaitGroup.Wait()
   218  			tunnel.embeddedServerListWaitGroup.Wait()
   219  			psiphon.CloseDataStore()
   220  		}
   221  	}()
   222  
   223  	// Create a cancelable context that will be used for stopping the tunnel
   224  	var controllerCtx context.Context
   225  	controllerCtx, tunnel.stopController = context.WithCancel(ctx)
   226  
   227  	// If specified, the embedded server list is loaded and stored. When there
   228  	// are no server candidates at all, we wait for this import to complete
   229  	// before starting the Psiphon controller. Otherwise, we import while
   230  	// concurrently starting the controller to minimize delay before attempting
   231  	// to connect to existing candidate servers.
   232  	//
   233  	// If the import fails, an error notice is emitted, but the controller is
   234  	// still started: either existing candidate servers may suffice, or the
   235  	// remote server list fetch may obtain candidate servers.
   236  	//
   237  	// The import will be interrupted if it's still running when the controller
   238  	// is stopped.
   239  	tunnel.embeddedServerListWaitGroup.Add(1)
   240  	go func() {
   241  		defer tunnel.embeddedServerListWaitGroup.Done()
   242  
   243  		err := psiphon.ImportEmbeddedServerEntries(
   244  			controllerCtx,
   245  			config,
   246  			"",
   247  			embeddedServerEntryList)
   248  		if err != nil {
   249  			psiphon.NoticeError("error importing embedded server entry list: %s", err)
   250  			return
   251  		}
   252  	}()
   253  	if !psiphon.HasServerEntries() {
   254  		psiphon.NoticeInfo("awaiting embedded server entry list import")
   255  		tunnel.embeddedServerListWaitGroup.Wait()
   256  	}
   257  
   258  	// Create the Psiphon controller
   259  	controller, err := psiphon.NewController(config)
   260  	if err != nil {
   261  		tunnel.stopController()
   262  		tunnel.embeddedServerListWaitGroup.Wait()
   263  		return nil, errors.New("psiphon.NewController failed")
   264  	}
   265  
   266  	// Begin tunnel connection
   267  	tunnel.controllerWaitGroup.Add(1)
   268  	go func() {
   269  		defer tunnel.controllerWaitGroup.Done()
   270  
   271  		// Start the tunnel. Only returns on error (or internal timeout).
   272  		controller.Run(controllerCtx)
   273  
   274  		// controller.Run does not exit until the goroutine that posts
   275  		// EstablishTunnelTimeout has terminated; so, if there was a
   276  		// EstablishTunnelTimeout event, ErrTimeout is guaranteed to be sent to
   277  		// errord before this next error and will be the StartTunnel return value.
   278  
   279  		var err error
   280  		switch ctx.Err() {
   281  		case context.DeadlineExceeded:
   282  			err = ErrTimeout
   283  		case context.Canceled:
   284  			err = errors.New("StartTunnel canceled")
   285  		default:
   286  			err = errors.New("controller.Run exited unexpectedly")
   287  		}
   288  		select {
   289  		case errored <- err:
   290  		default:
   291  		}
   292  	}()
   293  
   294  	// Wait for an active tunnel or error
   295  	select {
   296  	case <-connected:
   297  		return tunnel, nil
   298  	case err := <-errored:
   299  		tunnel.Stop()
   300  		if err != ErrTimeout {
   301  			err = errors.New("tunnel start produced error")
   302  		}
   303  		return nil, err
   304  	}
   305  }
   306  
   307  // Stop stops/disconnects/shuts down the tunnel. It is safe to call when not connected.
   308  // Not safe to call concurrently with Start.
   309  func (tunnel *Tunnel) Stop() {
   310  	if tunnel.stopController == nil {
   311  		return
   312  	}
   313  	tunnel.stopController()
   314  	tunnel.controllerWaitGroup.Wait()
   315  	tunnel.embeddedServerListWaitGroup.Wait()
   316  	psiphon.CloseDataStore()
   317  }
   318  
   319  func RunPsiphon(wgBind, localSocksPort, country string, ctx context.Context) error {
   320  	// Embedded configuration
   321  	host, port, err := net.SplitHostPort(localSocksPort)
   322  	if err != nil {
   323  		return err
   324  	}
   325  	if strings.HasPrefix(host, "127.0.0") {
   326  		host = ""
   327  	} else {
   328  		host = "any"
   329  	}
   330  	configJSON := `{
   331  		"EgressRegion": "` + country + `",
   332  		"ListenInterface": "` + host + `",
   333  		"LocalSocksProxyPort": ` + port + `,
   334  		"UpstreamProxyURL": "socks5://` + wgBind + `",
   335  		"DisableLocalHTTPProxy": true,
   336  		"PropagationChannelId":"FFFFFFFFFFFFFFFF",
   337  		"RemoteServerListDownloadFilename":"remote_server_list",
   338  		"RemoteServerListSignaturePublicKey":"MIICIDANBgkqhkiG9w0BAQEFAAOCAg0AMIICCAKCAgEAt7Ls+/39r+T6zNW7GiVpJfzq/xvL9SBH5rIFnk0RXYEYavax3WS6HOD35eTAqn8AniOwiH+DOkvgSKF2caqk/y1dfq47Pdymtwzp9ikpB1C5OfAysXzBiwVJlCdajBKvBZDerV1cMvRzCKvKwRmvDmHgphQQ7WfXIGbRbmmk6opMBh3roE42KcotLFtqp0RRwLtcBRNtCdsrVsjiI1Lqz/lH+T61sGjSjQ3CHMuZYSQJZo/KrvzgQXpkaCTdbObxHqb6/+i1qaVOfEsvjoiyzTxJADvSytVtcTjijhPEV6XskJVHE1Zgl+7rATr/pDQkw6DPCNBS1+Y6fy7GstZALQXwEDN/qhQI9kWkHijT8ns+i1vGg00Mk/6J75arLhqcodWsdeG/M/moWgqQAnlZAGVtJI1OgeF5fsPpXu4kctOfuZlGjVZXQNW34aOzm8r8S0eVZitPlbhcPiR4gT/aSMz/wd8lZlzZYsje/Jr8u/YtlwjjreZrGRmG8KMOzukV3lLmMppXFMvl4bxv6YFEmIuTsOhbLTwFgh7KYNjodLj/LsqRVfwz31PgWQFTEPICV7GCvgVlPRxnofqKSjgTWI4mxDhBpVcATvaoBl1L/6WLbFvBsoAUBItWwctO2xalKxF5szhGm8lccoc5MZr8kfE0uxMgsxz4er68iCID+rsCAQM=",
   339  		"RemoteServerListUrl":"https://s3.amazonaws.com//psiphon/web/mjr4-p23r-puwl/server_list_compressed",
   340  		"SponsorId":"FFFFFFFFFFFFFFFF",
   341  		"UseIndistinguishableTLS":true,
   342  		"AllowDefaultDNSResolverWithBindToDevice":true
   343  	}`
   344  
   345  	dir := "."
   346  	ClientPlatform := "Android_4.0.4_com.example.exampleClientLibraryApp"
   347  	network := "test"
   348  	timeout := 60
   349  
   350  	p := Parameters{
   351  		DataRootDirectory:             &dir,
   352  		ClientPlatform:                &ClientPlatform,
   353  		NetworkID:                     &network,
   354  		EstablishTunnelTimeoutSeconds: &timeout,
   355  		EmitDiagnosticNoticesToFiles:  false,
   356  	}
   357  
   358  	log.Println("Handshaking, Please Wait...")
   359  
   360  	var tunnel *Tunnel
   361  	startTime := time.Now()
   362  
   363  	internalCtx := context.Background()
   364  
   365  	timeoutTimer := time.NewTimer(2 * time.Minute)
   366  	defer timeoutTimer.Stop()
   367  
   368  	for {
   369  		select {
   370  		case <-ctx.Done():
   371  			internalCtx.Done()
   372  			return fmt.Errorf("psiphon handshake operation canceled by user")
   373  		case <-timeoutTimer.C:
   374  			// Handle the internal timeout
   375  			internalCtx.Done()
   376  			return fmt.Errorf("psiphon handshake maximum time exceeded")
   377  		default:
   378  			tunnel, err = StartTunnel(internalCtx, []byte(configJSON), "", p, nil, nil)
   379  			if err == nil {
   380  				log.Println("Psiphon started successfully on port", tunnel.SOCKSProxyPort, "handshake operation took", int64(time.Since(startTime)/time.Millisecond), "milliseconds")
   381  				return nil
   382  			}
   383  			log.Error("Unable to start psiphon", err, "reconnecting...")
   384  			time.Sleep(1 * time.Second)
   385  		}
   386  	}
   387  }