github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/limitProtocols_test.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 psiphon
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    32  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
    33  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
    34  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server"
    35  )
    36  
    37  func TestLimitTunnelProtocols(t *testing.T) {
    38  
    39  	testDataDirName, err := ioutil.TempDir("", "psiphon-limit-tunnel-protocols-test")
    40  	if err != nil {
    41  		t.Fatalf("TempDir failed: %s", err)
    42  	}
    43  	defer os.RemoveAll(testDataDirName)
    44  
    45  	initialLimitTunnelProtocols := protocol.TunnelProtocols{"OSSH", "UNFRONTED-MEEK-HTTPS-OSSH"}
    46  	initialLimitTunnelProtocolsCandidateCount := 100
    47  	limitTunnelProtocols := protocol.TunnelProtocols{"SSH", "UNFRONTED-MEEK-OSSH"}
    48  
    49  	initialConnectingCount := 0
    50  	connectingCount := 0
    51  
    52  	SetNoticeWriter(NewNoticeReceiver(
    53  		func(notice []byte) {
    54  			noticeType, payload, err := GetNotice(notice)
    55  			if err != nil {
    56  				return
    57  			}
    58  
    59  			if noticeType == "ConnectingServer" {
    60  
    61  				connectingCount += 1
    62  
    63  				protocolField := payload["protocol"]
    64  				protocol := protocolField.(string)
    65  
    66  				if common.Contains(initialLimitTunnelProtocols, protocol) {
    67  					initialConnectingCount += 1
    68  				}
    69  
    70  				if common.Contains(limitTunnelProtocols, protocol) {
    71  					connectingCount += 1
    72  				}
    73  
    74  				// At the end of the InitialLimit phase, the order of
    75  				// ConnectingServer notices isn't strictly synchronized and
    76  				// it's possible for a Limit candidate ConnectingServer notice
    77  				// to arrive before the last InitialLimit notice. So strict
    78  				// checking of notice order is performed only up to 90% of
    79  				// InitialLimitTunnelProtocolsCandidateCount.
    80  
    81  				if initialConnectingCount <= (initialLimitTunnelProtocolsCandidateCount*9)/10 {
    82  
    83  					var expectedProtocols []string
    84  					if connectingCount <= initialLimitTunnelProtocolsCandidateCount {
    85  						expectedProtocols = initialLimitTunnelProtocols
    86  					} else {
    87  						expectedProtocols = limitTunnelProtocols
    88  					}
    89  
    90  					if !common.Contains(expectedProtocols, protocol) {
    91  						t.Fatalf("unexpected protocol: %s (%d %+v)", protocol, connectingCount, expectedProtocols)
    92  					}
    93  				}
    94  			}
    95  		}))
    96  
    97  	clientConfigJSON := `
    98      {
    99          "ClientPlatform" : "Windows",
   100          "ClientVersion" : "0",
   101          "SponsorId" : "0",
   102          "PropagationChannelId" : "0",
   103          "DisableRemoteServerListFetcher" : true
   104      }`
   105  	clientConfig, err := LoadConfig([]byte(clientConfigJSON))
   106  	if err != nil {
   107  		t.Fatalf("error processing configuration file: %s", err)
   108  	}
   109  
   110  	clientConfig.DataRootDirectory = testDataDirName
   111  
   112  	err = clientConfig.Commit(false)
   113  	if err != nil {
   114  		t.Fatalf("error committing configuration file: %s", err)
   115  	}
   116  
   117  	applyParameters := make(map[string]interface{})
   118  
   119  	applyParameters[parameters.ConnectionWorkerPoolSize] = initialLimitTunnelProtocolsCandidateCount / 2
   120  	applyParameters[parameters.LimitIntensiveConnectionWorkers] = initialLimitTunnelProtocolsCandidateCount / 4
   121  	applyParameters[parameters.TunnelConnectTimeout] = "1s"
   122  	applyParameters[parameters.EstablishTunnelPausePeriod] = "1s"
   123  	applyParameters[parameters.InitialLimitTunnelProtocols] = initialLimitTunnelProtocols
   124  	applyParameters[parameters.InitialLimitTunnelProtocolsCandidateCount] = initialLimitTunnelProtocolsCandidateCount
   125  	applyParameters[parameters.LimitTunnelProtocols] = limitTunnelProtocols
   126  
   127  	err = clientConfig.SetParameters("", true, applyParameters)
   128  	if err != nil {
   129  		t.Fatalf("error setting client parameters: %s", err)
   130  	}
   131  
   132  	err = OpenDataStore(clientConfig)
   133  	if err != nil {
   134  		t.Fatalf("error initializing client datastore: %s", err)
   135  	}
   136  	defer CloseDataStore()
   137  
   138  	if CountServerEntries() > 0 {
   139  		t.Fatalf("unexpected server entries")
   140  	}
   141  
   142  	serverEntries := make([]map[string]interface{}, len(protocol.SupportedTunnelProtocols))
   143  
   144  	for i, tunnelProtocol := range protocol.SupportedTunnelProtocols {
   145  
   146  		_, _, _, _, encodedServerEntry, err := server.GenerateConfig(
   147  			&server.GenerateConfigParams{
   148  				ServerIPAddress:      fmt.Sprintf("0.1.0.0"),
   149  				EnableSSHAPIRequests: true,
   150  				WebServerPort:        8000,
   151  				TunnelProtocolPorts:  map[string]int{tunnelProtocol: 4000},
   152  			})
   153  		if err != nil {
   154  			t.Fatalf("error generating server config: %s", err)
   155  		}
   156  
   157  		serverEntryFields, err := protocol.DecodeServerEntryFields(
   158  			string(encodedServerEntry),
   159  			common.GetCurrentTimestamp(),
   160  			protocol.SERVER_ENTRY_SOURCE_REMOTE)
   161  		if err != nil {
   162  			t.Fatalf("error decoding server entry: %s", err)
   163  		}
   164  
   165  		serverEntries[i] = serverEntryFields
   166  	}
   167  
   168  	for i := 0; i < 1000; i++ {
   169  
   170  		serverEntryFields := serverEntries[i%len(protocol.SupportedTunnelProtocols)]
   171  
   172  		serverEntryFields["ipAddress"] = fmt.Sprintf("0.1.%d.%d", (i>>8)&0xFF, i&0xFF)
   173  
   174  		err = StoreServerEntry(serverEntryFields, true)
   175  		if err != nil {
   176  			t.Fatalf("error storing server entry: %s", err)
   177  		}
   178  	}
   179  
   180  	controller, err := NewController(clientConfig)
   181  	if err != nil {
   182  		t.Fatalf("error creating client controller: %s", err)
   183  	}
   184  
   185  	ctx, cancelFunc := context.WithCancel(context.Background())
   186  
   187  	controllerWaitGroup := new(sync.WaitGroup)
   188  
   189  	controllerWaitGroup.Add(1)
   190  	go func() {
   191  		defer controllerWaitGroup.Done()
   192  		controller.Run(ctx)
   193  	}()
   194  
   195  	time.Sleep(10 * time.Second)
   196  
   197  	cancelFunc()
   198  
   199  	controllerWaitGroup.Wait()
   200  
   201  	t.Logf("initial-connecting and connecting count: %d/%d", initialConnectingCount, connectingCount)
   202  
   203  	if initialConnectingCount != initialLimitTunnelProtocolsCandidateCount {
   204  		t.Fatalf("unexpected initial-connecting count")
   205  	}
   206  
   207  	if connectingCount < 3*initialLimitTunnelProtocolsCandidateCount {
   208  		t.Fatalf("unexpected connecting count")
   209  	}
   210  }