Просмотр исходного кода

add tunnel dial to clientlib

This enables making requests directly through the tunnel without using local proxies.

Also added parameters to disable the local proxies.
Adam Pritchard 1 год назад
Родитель
Сommit
e260cfb894
2 измененных файлов с 173 добавлено и 3 удалено
  1. 23 0
      ClientLibrary/clientlib/clientlib.go
  2. 150 3
      ClientLibrary/clientlib/clientlib_test.go

+ 23 - 0
ClientLibrary/clientlib/clientlib.go

@@ -24,6 +24,7 @@ import (
 	"encoding/json"
 	std_errors "errors"
 	"fmt"
+	"net"
 	"path/filepath"
 	"sync"
 
@@ -65,6 +66,12 @@ type Parameters struct {
 	// notices to noticeReceiver. Has no effect unless the tunnel
 	// config.EmitDiagnosticNotices flag is set.
 	EmitDiagnosticNoticesToFiles bool
+
+	// DisableLocalSocksProxy disables running the local SOCKS proxy.
+	DisableLocalSocksProxy *bool
+
+	// DisableLocalHTTPProxy disables running the local HTTP proxy.
+	DisableLocalHTTPProxy *bool
 }
 
 // PsiphonTunnel is the tunnel object. It can be used for stopping the tunnel and
@@ -73,6 +80,7 @@ type PsiphonTunnel struct {
 	embeddedServerListWaitGroup sync.WaitGroup
 	controllerWaitGroup         sync.WaitGroup
 	stopController              context.CancelFunc
+	controllerDial              func(string, net.Conn) (net.Conn, error)
 
 	// The port on which the HTTP proxy is running
 	HTTPProxyPort int
@@ -156,6 +164,14 @@ func StartTunnel(
 		}
 	} // else use the value in the config
 
+	if params.DisableLocalSocksProxy != nil {
+		config.DisableLocalSocksProxy = *params.DisableLocalSocksProxy
+	} // else use the value in the config
+
+	if params.DisableLocalHTTPProxy != nil {
+		config.DisableLocalHTTPProxy = *params.DisableLocalHTTPProxy
+	} // else use the value in the config
+
 	// config.Commit must be called before calling config.SetParameters
 	// or attempting to connect.
 	err = config.Commit(true)
@@ -280,6 +296,8 @@ func StartTunnel(
 		return nil, errors.TraceMsg(err, "psiphon.NewController failed")
 	}
 
+	tunnel.controllerDial = controller.Dial
+
 	// Begin tunnel connection
 	tunnel.controllerWaitGroup.Add(1)
 	go func() {
@@ -332,3 +350,8 @@ func (tunnel *PsiphonTunnel) Stop() {
 	tunnel.embeddedServerListWaitGroup.Wait()
 	psiphon.CloseDataStore()
 }
+
+// Dial connects to the specified address through the Psiphon tunnel.
+func (tunnel *PsiphonTunnel) Dial(remoteAddr string) (conn net.Conn, err error) {
+	return tunnel.controllerDial(remoteAddr, nil)
+}

+ 150 - 3
ClientLibrary/clientlib/clientlib_test.go

@@ -22,7 +22,6 @@ package clientlib
 import (
 	"context"
 	"encoding/json"
-	"io/ioutil"
 	"os"
 	"testing"
 	"time"
@@ -35,8 +34,9 @@ func TestStartTunnel(t *testing.T) {
 	networkID := "UNKNOWN"
 	timeout := 60
 	quickTimeout := 1
+	trueVal := true
 
-	configJSON, err := ioutil.ReadFile("../../psiphon/controller_test.config")
+	configJSON, err := os.ReadFile("../../psiphon/controller_test.config")
 	if err != nil {
 		// Skip, don't fail, if config file is not present
 		t.Skipf("error loading configuration file: %s", err)
@@ -45,7 +45,7 @@ func TestStartTunnel(t *testing.T) {
 	// Initialize a fresh datastore and create a modified config which cannot
 	// connect without known servers, to be used in timeout cases.
 
-	testDataDirName, err := ioutil.TempDir("", "psiphon-clientlib-test")
+	testDataDirName, err := os.MkdirTemp("", "psiphon-clientlib-test")
 	if err != nil {
 		t.Fatalf("ioutil.TempDir failed: %v", err)
 	}
@@ -130,6 +130,64 @@ func TestStartTunnel(t *testing.T) {
 			wantTunnel:  true,
 			expectedErr: nil,
 		},
+		{
+			name: "Success: disable SOCKS proxy",
+			args: args{
+				ctxTimeout:              0,
+				configJSON:              configJSON,
+				embeddedServerEntryList: "",
+				params: Parameters{
+					DataRootDirectory:             &testDataDirName,
+					ClientPlatform:                &clientPlatform,
+					NetworkID:                     &networkID,
+					EstablishTunnelTimeoutSeconds: &timeout,
+					DisableLocalSocksProxy:        &trueVal,
+				},
+				paramsDelta:    nil,
+				noticeReceiver: nil,
+			},
+			wantTunnel:  true,
+			expectedErr: nil,
+		},
+		{
+			name: "Success: disable HTTP proxy",
+			args: args{
+				ctxTimeout:              0,
+				configJSON:              configJSON,
+				embeddedServerEntryList: "",
+				params: Parameters{
+					DataRootDirectory:             &testDataDirName,
+					ClientPlatform:                &clientPlatform,
+					NetworkID:                     &networkID,
+					EstablishTunnelTimeoutSeconds: &timeout,
+					DisableLocalHTTPProxy:         &trueVal,
+				},
+				paramsDelta:    nil,
+				noticeReceiver: nil,
+			},
+			wantTunnel:  true,
+			expectedErr: nil,
+		},
+		{
+			name: "Success: disable SOCKS and HTTP proxies",
+			args: args{
+				ctxTimeout:              0,
+				configJSON:              configJSON,
+				embeddedServerEntryList: "",
+				params: Parameters{
+					DataRootDirectory:             &testDataDirName,
+					ClientPlatform:                &clientPlatform,
+					NetworkID:                     &networkID,
+					EstablishTunnelTimeoutSeconds: &timeout,
+					DisableLocalSocksProxy:        &trueVal,
+					DisableLocalHTTPProxy:         &trueVal,
+				},
+				paramsDelta:    nil,
+				noticeReceiver: nil,
+			},
+			wantTunnel:  true,
+			expectedErr: nil,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -167,6 +225,95 @@ func TestStartTunnel(t *testing.T) {
 				return
 			}
 
+			if tunnel == nil {
+				return
+			}
+
+			if tt.args.params.DisableLocalSocksProxy != nil && *tt.args.params.DisableLocalSocksProxy {
+				if tunnel.SOCKSProxyPort != 0 {
+					t.Fatalf("should not have started SOCKS proxy")
+				}
+			} else {
+				if tunnel.SOCKSProxyPort == 0 {
+					t.Fatalf("failed to start SOCKS proxy")
+				}
+			}
+
+			if tt.args.params.DisableLocalHTTPProxy != nil && *tt.args.params.DisableLocalHTTPProxy {
+				if tunnel.HTTPProxyPort != 0 {
+					t.Fatalf("should not have started HTTP proxy")
+				}
+			} else {
+				if tunnel.HTTPProxyPort == 0 {
+					t.Fatalf("failed to start HTTP proxy")
+				}
+			}
+		})
+	}
+}
+
+func TestPsiphonTunnel_Dial(t *testing.T) {
+	trueVal := true
+	configJSON, err := os.ReadFile("../../psiphon/controller_test.config")
+	if err != nil {
+		// Skip, don't fail, if config file is not present
+		t.Skipf("error loading configuration file: %s", err)
+	}
+
+	testDataDirName, err := os.MkdirTemp("", "psiphon-clientlib-test")
+	if err != nil {
+		t.Fatalf("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(testDataDirName)
+
+	type args struct {
+		remoteAddr string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name:    "Success: example.com",
+			args:    args{remoteAddr: "example.com:443"},
+			wantErr: false,
+		},
+		{
+			name:    "Failure: invalid address",
+			args:    args{remoteAddr: "example.com:99999"},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tunnel, err := StartTunnel(
+				context.Background(),
+				configJSON,
+				"",
+				Parameters{
+					DataRootDirectory: &testDataDirName,
+					// Don't need local proxies for dial tests
+					// (and this is likely the configuration that will be used by consumers of the library who utilitize Dial).
+					DisableLocalSocksProxy: &trueVal,
+					DisableLocalHTTPProxy:  &trueVal,
+				},
+				nil,
+				nil)
+			if err != nil {
+				t.Fatalf("StartTunnel() error = %v", err)
+			}
+			defer tunnel.Stop()
+
+			conn, err := tunnel.Dial(tt.args.remoteAddr)
+			if (err != nil) != tt.wantErr {
+				t.Fatalf("PsiphonTunnel.Dial() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			if tt.wantErr != (conn == nil) {
+				t.Fatalf("PsiphonTunnel.Dial() conn = %v, wantConn %v", conn, !tt.wantErr)
+			}
 		})
 	}
 }