github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/dialParameters_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  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"reflect"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    33  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
    34  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
    35  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
    36  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/values"
    37  )
    38  
    39  func TestDialParametersAndReplay(t *testing.T) {
    40  	for _, tunnelProtocol := range protocol.SupportedTunnelProtocols {
    41  		if !common.Contains(protocol.DefaultDisabledTunnelProtocols, tunnelProtocol) {
    42  			runDialParametersAndReplay(t, tunnelProtocol)
    43  		}
    44  	}
    45  }
    46  
    47  var testNetworkID = prng.HexString(8)
    48  
    49  type testNetworkGetter struct {
    50  }
    51  
    52  func (t *testNetworkGetter) GetNetworkID() string {
    53  	return testNetworkID
    54  }
    55  
    56  func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) {
    57  
    58  	t.Logf("Test %s...", tunnelProtocol)
    59  
    60  	testDataDirName, err := ioutil.TempDir("", "psiphon-dial-parameters-test")
    61  	if err != nil {
    62  		t.Fatalf("TempDir failed: %s", err)
    63  	}
    64  	defer os.RemoveAll(testDataDirName)
    65  
    66  	SetNoticeWriter(ioutil.Discard)
    67  
    68  	clientConfig := &Config{
    69  		PropagationChannelId: "0",
    70  		SponsorId:            "0",
    71  		DataRootDirectory:    testDataDirName,
    72  		NetworkIDGetter:      new(testNetworkGetter),
    73  	}
    74  
    75  	err = clientConfig.Commit(false)
    76  	if err != nil {
    77  		t.Fatalf("error committing configuration file: %s", err)
    78  	}
    79  
    80  	holdOffTunnelProtocols := protocol.TunnelProtocols{protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH}
    81  	frontingProviderID := prng.HexString(8)
    82  
    83  	applyParameters := make(map[string]interface{})
    84  	applyParameters[parameters.TransformHostNameProbability] = 1.0
    85  	applyParameters[parameters.PickUserAgentProbability] = 1.0
    86  	applyParameters[parameters.HoldOffTunnelMinDuration] = "1ms"
    87  	applyParameters[parameters.HoldOffTunnelMaxDuration] = "10ms"
    88  	applyParameters[parameters.HoldOffTunnelProtocols] = holdOffTunnelProtocols
    89  	applyParameters[parameters.HoldOffTunnelFrontingProviderIDs] = []string{frontingProviderID}
    90  	applyParameters[parameters.HoldOffTunnelProbability] = 1.0
    91  	applyParameters[parameters.DNSResolverAlternateServers] = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}
    92  	err = clientConfig.SetParameters("tag1", false, applyParameters)
    93  	if err != nil {
    94  		t.Fatalf("SetParameters failed: %s", err)
    95  	}
    96  
    97  	resolver := NewResolver(clientConfig, true)
    98  	defer resolver.Stop()
    99  	clientConfig.SetResolver(resolver)
   100  
   101  	err = OpenDataStore(clientConfig)
   102  	if err != nil {
   103  		t.Fatalf("error initializing client datastore: %s", err)
   104  	}
   105  	defer CloseDataStore()
   106  
   107  	serverEntries := makeMockServerEntries(tunnelProtocol, frontingProviderID, 100)
   108  
   109  	canReplay := func(serverEntry *protocol.ServerEntry, replayProtocol string) bool {
   110  		return replayProtocol == tunnelProtocol
   111  	}
   112  
   113  	selectProtocol := func(serverEntry *protocol.ServerEntry) (string, bool) {
   114  		return tunnelProtocol, true
   115  	}
   116  
   117  	values.SetSSHClientVersionsSpec(
   118  		values.NewPickOneSpec([]string{"SSH-2.0-A", "SSH-2.0-B", "SSH-2.0-C"}))
   119  
   120  	values.SetUserAgentsSpec(
   121  		values.NewPickOneSpec([]string{"ua1", "ua2", "ua3"}))
   122  
   123  	// Test: expected dial parameter fields set
   124  
   125  	upstreamProxyErrorCallback := func(_ error) {}
   126  
   127  	dialParams, err := MakeDialParameters(
   128  		clientConfig, upstreamProxyErrorCallback, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   129  	if err != nil {
   130  		t.Fatalf("MakeDialParameters failed: %s", err)
   131  	}
   132  
   133  	if dialParams.ServerEntry != serverEntries[0] {
   134  		t.Fatalf("unexpected server entry")
   135  	}
   136  
   137  	if dialParams.NetworkID != testNetworkID {
   138  		t.Fatalf("unexpected network ID")
   139  	}
   140  
   141  	if dialParams.IsReplay {
   142  		t.Fatalf("unexpected replay")
   143  	}
   144  
   145  	if dialParams.TunnelProtocol != tunnelProtocol {
   146  		t.Fatalf("unexpected tunnel protocol")
   147  	}
   148  
   149  	if !protocol.TunnelProtocolUsesMeek(tunnelProtocol) &&
   150  		dialParams.DirectDialAddress == "" {
   151  		t.Fatalf("missing direct dial fields")
   152  	}
   153  
   154  	if dialParams.DialPortNumber == "" {
   155  		t.Fatalf("missing port number fields")
   156  	}
   157  
   158  	if !dialParams.SelectedSSHClientVersion || dialParams.SSHClientVersion == "" || dialParams.SSHKEXSeed == nil {
   159  		t.Fatalf("missing SSH fields")
   160  	}
   161  
   162  	if protocol.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) &&
   163  		dialParams.ObfuscatorPaddingSeed == nil {
   164  		t.Fatalf("missing obfuscator fields")
   165  	}
   166  
   167  	if dialParams.FragmentorSeed == nil {
   168  		t.Fatalf("missing fragmentor field")
   169  	}
   170  
   171  	if protocol.TunnelProtocolUsesMeek(tunnelProtocol) &&
   172  		(dialParams.MeekDialAddress == "" ||
   173  			dialParams.MeekHostHeader == "" ||
   174  			dialParams.MeekObfuscatorPaddingSeed == nil) {
   175  		t.Fatalf("missing meek fields")
   176  	}
   177  
   178  	if protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) &&
   179  		(dialParams.MeekFrontingDialAddress == "" ||
   180  			dialParams.MeekFrontingHost == "" ||
   181  			dialParams.ResolveParameters == nil) {
   182  		t.Fatalf("missing meek fronting fields")
   183  	}
   184  
   185  	if protocol.TunnelProtocolUsesMeekHTTP(tunnelProtocol) &&
   186  		dialParams.UserAgent == "" {
   187  		t.Fatalf("missing meek HTTP fields")
   188  	}
   189  
   190  	if protocol.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) &&
   191  		(dialParams.MeekSNIServerName == "" ||
   192  			!dialParams.SelectedTLSProfile ||
   193  			dialParams.TLSProfile == "") {
   194  		t.Fatalf("missing meek HTTPS fields")
   195  	}
   196  
   197  	if protocol.TunnelProtocolUsesQUIC(tunnelProtocol) {
   198  		if dialParams.QUICVersion == "" {
   199  			t.Fatalf("missing QUIC version field")
   200  		}
   201  		if protocol.TunnelProtocolUsesFrontedMeekQUIC(tunnelProtocol) {
   202  			if dialParams.MeekFrontingDialAddress == "" ||
   203  				dialParams.MeekFrontingHost == "" ||
   204  				dialParams.MeekSNIServerName == "" {
   205  				t.Fatalf("missing fronted QUIC fields")
   206  			}
   207  		} else {
   208  			if dialParams.QUICDialSNIAddress == "" {
   209  				t.Fatalf("missing QUIC SNI field")
   210  			}
   211  		}
   212  	}
   213  
   214  	if dialParams.LivenessTestSeed == nil {
   215  		t.Fatalf("missing liveness test fields")
   216  	}
   217  
   218  	if dialParams.APIRequestPaddingSeed == nil {
   219  		t.Fatalf("missing API request fields")
   220  	}
   221  
   222  	if common.Contains(holdOffTunnelProtocols, tunnelProtocol) ||
   223  		protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) {
   224  		if dialParams.HoldOffTunnelDuration < 1*time.Millisecond ||
   225  			dialParams.HoldOffTunnelDuration > 10*time.Millisecond {
   226  			t.Fatalf("unexpected hold-off duration: %v", dialParams.HoldOffTunnelDuration)
   227  		}
   228  	} else {
   229  		if dialParams.HoldOffTunnelDuration != 0 {
   230  			t.Fatalf("unexpected hold-off duration: %v", dialParams.HoldOffTunnelDuration)
   231  		}
   232  	}
   233  
   234  	dialConfig := dialParams.GetDialConfig()
   235  	if dialConfig.UpstreamProxyErrorCallback == nil {
   236  		t.Fatalf("missing upstreamProxyErrorCallback")
   237  	}
   238  
   239  	// Test: no replay after dial reported to fail
   240  
   241  	dialParams.Failed(clientConfig)
   242  
   243  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   244  	if err != nil {
   245  		t.Fatalf("MakeDialParameters failed: %s", err)
   246  	}
   247  
   248  	if dialParams.IsReplay {
   249  		t.Fatalf("unexpected replay")
   250  	}
   251  
   252  	// Test: no replay after network ID changes
   253  
   254  	dialParams.Succeeded()
   255  
   256  	testNetworkID = prng.HexString(8)
   257  
   258  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   259  	if err != nil {
   260  		t.Fatalf("MakeDialParameters failed: %s", err)
   261  	}
   262  
   263  	if dialParams.NetworkID != testNetworkID {
   264  		t.Fatalf("unexpected network ID")
   265  	}
   266  
   267  	if dialParams.IsReplay {
   268  		t.Fatalf("unexpected replay")
   269  	}
   270  
   271  	// Test: replay after dial reported to succeed, and replay fields match previous dial parameters
   272  
   273  	dialParams.Succeeded()
   274  
   275  	replayDialParams, err := MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   276  	if err != nil {
   277  		t.Fatalf("MakeDialParameters failed: %s", err)
   278  	}
   279  
   280  	if !replayDialParams.IsReplay {
   281  		t.Fatalf("unexpected non-replay")
   282  	}
   283  
   284  	if !replayDialParams.LastUsedTimestamp.After(dialParams.LastUsedTimestamp) {
   285  		t.Fatalf("unexpected non-updated timestamp")
   286  	}
   287  
   288  	if replayDialParams.TunnelProtocol != dialParams.TunnelProtocol {
   289  		t.Fatalf("mismatching tunnel protocol")
   290  	}
   291  
   292  	if replayDialParams.DirectDialAddress != dialParams.DirectDialAddress ||
   293  		replayDialParams.DialPortNumber != dialParams.DialPortNumber {
   294  		t.Fatalf("mismatching dial fields")
   295  	}
   296  
   297  	identicalSeeds := func(seed1, seed2 *prng.Seed) bool {
   298  		if seed1 == nil {
   299  			return seed2 == nil
   300  		}
   301  		return bytes.Equal(seed1[:], seed2[:])
   302  	}
   303  
   304  	if replayDialParams.SelectedSSHClientVersion != dialParams.SelectedSSHClientVersion ||
   305  		replayDialParams.SSHClientVersion != dialParams.SSHClientVersion ||
   306  		!identicalSeeds(replayDialParams.SSHKEXSeed, dialParams.SSHKEXSeed) {
   307  		t.Fatalf("mismatching SSH fields")
   308  	}
   309  
   310  	if !identicalSeeds(replayDialParams.ObfuscatorPaddingSeed, dialParams.ObfuscatorPaddingSeed) {
   311  		t.Fatalf("mismatching obfuscator fields")
   312  	}
   313  
   314  	if !identicalSeeds(replayDialParams.FragmentorSeed, dialParams.FragmentorSeed) {
   315  		t.Fatalf("mismatching fragmentor fields")
   316  	}
   317  
   318  	if replayDialParams.MeekFrontingDialAddress != dialParams.MeekFrontingDialAddress ||
   319  		replayDialParams.MeekFrontingHost != dialParams.MeekFrontingHost ||
   320  		replayDialParams.MeekDialAddress != dialParams.MeekDialAddress ||
   321  		replayDialParams.MeekTransformedHostName != dialParams.MeekTransformedHostName ||
   322  		replayDialParams.MeekSNIServerName != dialParams.MeekSNIServerName ||
   323  		replayDialParams.MeekHostHeader != dialParams.MeekHostHeader ||
   324  		!identicalSeeds(replayDialParams.MeekObfuscatorPaddingSeed, dialParams.MeekObfuscatorPaddingSeed) {
   325  		t.Fatalf("mismatching meek fields")
   326  	}
   327  
   328  	if replayDialParams.SelectedUserAgent != dialParams.SelectedUserAgent ||
   329  		replayDialParams.UserAgent != dialParams.UserAgent {
   330  		t.Fatalf("mismatching user agent fields")
   331  	}
   332  
   333  	if replayDialParams.SelectedTLSProfile != dialParams.SelectedTLSProfile ||
   334  		replayDialParams.TLSProfile != dialParams.TLSProfile ||
   335  		!identicalSeeds(replayDialParams.RandomizedTLSProfileSeed, dialParams.RandomizedTLSProfileSeed) {
   336  		t.Fatalf("mismatching TLS fields")
   337  	}
   338  
   339  	if replayDialParams.QUICVersion != dialParams.QUICVersion ||
   340  		replayDialParams.QUICDialSNIAddress != dialParams.QUICDialSNIAddress ||
   341  		!identicalSeeds(replayDialParams.ObfuscatedQUICPaddingSeed, dialParams.ObfuscatedQUICPaddingSeed) {
   342  		t.Fatalf("mismatching QUIC fields")
   343  	}
   344  
   345  	if !identicalSeeds(replayDialParams.LivenessTestSeed, dialParams.LivenessTestSeed) {
   346  		t.Fatalf("mismatching liveness test fields")
   347  	}
   348  
   349  	if !identicalSeeds(replayDialParams.APIRequestPaddingSeed, dialParams.APIRequestPaddingSeed) {
   350  		t.Fatalf("mismatching API request fields")
   351  	}
   352  
   353  	if (replayDialParams.ResolveParameters == nil) != (dialParams.ResolveParameters == nil) ||
   354  		(replayDialParams.ResolveParameters != nil &&
   355  			!reflect.DeepEqual(replayDialParams.ResolveParameters, dialParams.ResolveParameters)) {
   356  		t.Fatalf("mismatching ResolveParameters fields")
   357  	}
   358  
   359  	// Test: no replay after change tactics
   360  
   361  	applyParameters[parameters.ReplayDialParametersTTL] = "1s"
   362  	err = clientConfig.SetParameters("tag2", false, applyParameters)
   363  	if err != nil {
   364  		t.Fatalf("SetParameters failed: %s", err)
   365  	}
   366  
   367  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   368  	if err != nil {
   369  		t.Fatalf("MakeDialParameters failed: %s", err)
   370  	}
   371  
   372  	if dialParams.IsReplay {
   373  		t.Fatalf("unexpected replay")
   374  	}
   375  
   376  	// Test: no replay after dial parameters expired
   377  
   378  	dialParams.Succeeded()
   379  
   380  	time.Sleep(1 * time.Second)
   381  
   382  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   383  	if err != nil {
   384  		t.Fatalf("MakeDialParameters failed: %s", err)
   385  	}
   386  
   387  	if dialParams.IsReplay {
   388  		t.Fatalf("unexpected replay")
   389  	}
   390  
   391  	// Test: no replay after server entry changes
   392  
   393  	dialParams.Succeeded()
   394  
   395  	serverEntries[0].ConfigurationVersion += 1
   396  
   397  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   398  	if err != nil {
   399  		t.Fatalf("MakeDialParameters failed: %s", err)
   400  	}
   401  
   402  	if dialParams.IsReplay {
   403  		t.Fatalf("unexpected replay")
   404  	}
   405  
   406  	// Test: disable replay elements (partial coverage)
   407  
   408  	applyParameters[parameters.ReplayDialParametersTTL] = "24h"
   409  	applyParameters[parameters.ReplaySSH] = false
   410  	applyParameters[parameters.ReplayObfuscatorPadding] = false
   411  	applyParameters[parameters.ReplayFragmentor] = false
   412  	applyParameters[parameters.ReplayRandomizedTLSProfile] = false
   413  	applyParameters[parameters.ReplayObfuscatedQUIC] = false
   414  	applyParameters[parameters.ReplayLivenessTest] = false
   415  	applyParameters[parameters.ReplayAPIRequestPadding] = false
   416  	err = clientConfig.SetParameters("tag3", false, applyParameters)
   417  	if err != nil {
   418  		t.Fatalf("SetParameters failed: %s", err)
   419  	}
   420  
   421  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   422  	if err != nil {
   423  		t.Fatalf("MakeDialParameters failed: %s", err)
   424  	}
   425  
   426  	dialParams.Succeeded()
   427  
   428  	replayDialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   429  	if err != nil {
   430  		t.Fatalf("MakeDialParameters failed: %s", err)
   431  	}
   432  
   433  	if !replayDialParams.IsReplay {
   434  		t.Fatalf("unexpected non-replay")
   435  	}
   436  
   437  	if identicalSeeds(replayDialParams.SSHKEXSeed, dialParams.SSHKEXSeed) ||
   438  		(protocol.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) &&
   439  			identicalSeeds(replayDialParams.ObfuscatorPaddingSeed, dialParams.ObfuscatorPaddingSeed)) ||
   440  		identicalSeeds(replayDialParams.FragmentorSeed, dialParams.FragmentorSeed) ||
   441  		(protocol.TunnelProtocolUsesMeek(tunnelProtocol) &&
   442  			identicalSeeds(replayDialParams.MeekObfuscatorPaddingSeed, dialParams.MeekObfuscatorPaddingSeed)) ||
   443  		(protocol.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) &&
   444  			identicalSeeds(replayDialParams.RandomizedTLSProfileSeed, dialParams.RandomizedTLSProfileSeed) &&
   445  			replayDialParams.RandomizedTLSProfileSeed != nil) ||
   446  		(protocol.TunnelProtocolUsesQUIC(tunnelProtocol) &&
   447  			identicalSeeds(replayDialParams.ObfuscatedQUICPaddingSeed, dialParams.ObfuscatedQUICPaddingSeed) &&
   448  			replayDialParams.ObfuscatedQUICPaddingSeed != nil) ||
   449  		identicalSeeds(replayDialParams.LivenessTestSeed, dialParams.LivenessTestSeed) ||
   450  		identicalSeeds(replayDialParams.APIRequestPaddingSeed, dialParams.APIRequestPaddingSeed) {
   451  		t.Fatalf("unexpected replayed fields")
   452  	}
   453  
   454  	// Test: client-side restrict fronting provider ID
   455  
   456  	applyParameters[parameters.RestrictFrontingProviderIDs] = []string{frontingProviderID}
   457  	applyParameters[parameters.RestrictFrontingProviderIDsClientProbability] = 1.0
   458  	err = clientConfig.SetParameters("tag4", false, applyParameters)
   459  	if err != nil {
   460  		t.Fatalf("SetParameters failed: %s", err)
   461  	}
   462  
   463  	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
   464  
   465  	if protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) {
   466  		if err == nil {
   467  			if dialParams != nil {
   468  				t.Fatalf("unexpected MakeDialParameters success")
   469  			}
   470  		}
   471  	} else {
   472  		if err != nil {
   473  			t.Fatalf("MakeDialParameters failed: %s", err)
   474  		}
   475  	}
   476  
   477  	applyParameters[parameters.RestrictFrontingProviderIDsClientProbability] = 0.0
   478  	err = clientConfig.SetParameters("tag5", false, applyParameters)
   479  	if err != nil {
   480  		t.Fatalf("SetParameters failed: %s", err)
   481  	}
   482  
   483  	// Test: iterator shuffles
   484  
   485  	for i, serverEntry := range serverEntries {
   486  
   487  		data, err := json.Marshal(serverEntry)
   488  		if err != nil {
   489  			t.Fatalf("json.Marshal failed: %s", err)
   490  		}
   491  
   492  		var serverEntryFields protocol.ServerEntryFields
   493  		err = json.Unmarshal(data, &serverEntryFields)
   494  		if err != nil {
   495  			t.Fatalf("json.Unmarshal failed: %s", err)
   496  		}
   497  
   498  		err = StoreServerEntry(serverEntryFields, false)
   499  		if err != nil {
   500  			t.Fatalf("StoreServerEntry failed: %s", err)
   501  		}
   502  
   503  		if i%10 == 0 {
   504  
   505  			dialParams, err := MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntry, false, 0, 0)
   506  			if err != nil {
   507  				t.Fatalf("MakeDialParameters failed: %s", err)
   508  			}
   509  
   510  			dialParams.Succeeded()
   511  		}
   512  	}
   513  
   514  	for i := 0; i < 5; i++ {
   515  
   516  		hasAffinity, iterator, err := NewServerEntryIterator(clientConfig)
   517  		if err != nil {
   518  			t.Fatalf("NewServerEntryIterator failed: %s", err)
   519  		}
   520  
   521  		if hasAffinity {
   522  			t.Fatalf("unexpected affinity server")
   523  		}
   524  
   525  		// Test: the first shuffle should move the replay candidates to the front
   526  
   527  		for j := 0; j < 10; j++ {
   528  
   529  			serverEntry, err := iterator.Next()
   530  			if err != nil {
   531  				t.Fatalf("ServerEntryIterator.Next failed: %s", err)
   532  			}
   533  
   534  			dialParams, err := MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntry, false, 0, 0)
   535  			if err != nil {
   536  				t.Fatalf("MakeDialParameters failed: %s", err)
   537  			}
   538  
   539  			if !dialParams.IsReplay {
   540  				t.Fatalf("unexpected non-replay")
   541  			}
   542  		}
   543  
   544  		iterator.Reset()
   545  
   546  		// Test: subsequent shuffles should not move the replay candidates
   547  
   548  		allReplay := true
   549  		for j := 0; j < 10; j++ {
   550  
   551  			serverEntry, err := iterator.Next()
   552  			if err != nil {
   553  				t.Fatalf("ServerEntryIterator.Next failed: %s", err)
   554  			}
   555  
   556  			dialParams, err := MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntry, false, 0, 0)
   557  			if err != nil {
   558  				t.Fatalf("MakeDialParameters failed: %s", err)
   559  			}
   560  
   561  			if !dialParams.IsReplay {
   562  				allReplay = false
   563  			}
   564  		}
   565  
   566  		if allReplay {
   567  			t.Fatalf("unexpected all replay")
   568  		}
   569  
   570  		iterator.Close()
   571  	}
   572  }
   573  
   574  func TestLimitTunnelDialPortNumbers(t *testing.T) {
   575  
   576  	testDataDirName, err := ioutil.TempDir("", "psiphon-limit-tunnel-dial-port-numbers-test")
   577  	if err != nil {
   578  		t.Fatalf("TempDir failed: %s", err)
   579  	}
   580  	defer os.RemoveAll(testDataDirName)
   581  
   582  	SetNoticeWriter(ioutil.Discard)
   583  
   584  	clientConfig := &Config{
   585  		PropagationChannelId: "0",
   586  		SponsorId:            "0",
   587  		DataRootDirectory:    testDataDirName,
   588  		NetworkIDGetter:      new(testNetworkGetter),
   589  	}
   590  
   591  	err = clientConfig.Commit(false)
   592  	if err != nil {
   593  		t.Fatalf("error committing configuration file: %s", err)
   594  	}
   595  
   596  	jsonLimitDialPortNumbers := `
   597      {
   598          "SSH" : [[10,11]],
   599          "OSSH" : [[20,21]],
   600          "QUIC-OSSH" : [[30,31]],
   601          "TAPDANCE-OSSH" : [[40,41]],
   602          "CONJURE-OSSH" : [[50,51]],
   603          "All" : [[60,61],80,443]
   604      }
   605      `
   606  
   607  	var limitTunnelDialPortNumbers parameters.TunnelProtocolPortLists
   608  	err = json.Unmarshal([]byte(jsonLimitDialPortNumbers), &limitTunnelDialPortNumbers)
   609  	if err != nil {
   610  		t.Fatalf("Unmarshal failed: %s", err)
   611  	}
   612  
   613  	applyParameters := make(map[string]interface{})
   614  	applyParameters[parameters.LimitTunnelDialPortNumbers] = limitTunnelDialPortNumbers
   615  	applyParameters[parameters.LimitTunnelDialPortNumbersProbability] = 1.0
   616  	err = clientConfig.SetParameters("tag1", false, applyParameters)
   617  	if err != nil {
   618  		t.Fatalf("SetParameters failed: %s", err)
   619  	}
   620  
   621  	constraints := &protocolSelectionConstraints{
   622  		limitTunnelDialPortNumbers: protocol.TunnelProtocolPortLists(
   623  			clientConfig.GetParameters().Get().TunnelProtocolPortLists(parameters.LimitTunnelDialPortNumbers)),
   624  	}
   625  
   626  	selectProtocol := func(serverEntry *protocol.ServerEntry) (string, bool) {
   627  		return constraints.selectProtocol(0, false, serverEntry)
   628  	}
   629  
   630  	for _, tunnelProtocol := range protocol.SupportedTunnelProtocols {
   631  
   632  		if common.Contains(protocol.DefaultDisabledTunnelProtocols, tunnelProtocol) {
   633  			continue
   634  		}
   635  
   636  		serverEntries := makeMockServerEntries(tunnelProtocol, "", 100)
   637  
   638  		selected := false
   639  		skipped := false
   640  
   641  		for _, serverEntry := range serverEntries {
   642  
   643  			selectedProtocol, ok := selectProtocol(serverEntry)
   644  
   645  			if ok {
   646  
   647  				if selectedProtocol != tunnelProtocol {
   648  					t.Fatalf("unexpected selected protocol: %s", selectedProtocol)
   649  				}
   650  
   651  				port, err := serverEntry.GetDialPortNumber(selectedProtocol)
   652  				if err != nil {
   653  					t.Fatalf("GetDialPortNumber failed: %s", err)
   654  				}
   655  
   656  				if port%10 != 0 && port%10 != 1 && !protocol.TunnelProtocolUsesFrontedMeek(selectedProtocol) {
   657  					t.Fatalf("unexpected dial port number: %d", port)
   658  				}
   659  
   660  				selected = true
   661  
   662  			} else {
   663  
   664  				skipped = true
   665  			}
   666  		}
   667  
   668  		if !selected {
   669  			t.Fatalf("expected at least one selected server entry: %s", tunnelProtocol)
   670  		}
   671  
   672  		if !skipped && !protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) {
   673  			t.Fatalf("expected at least one skipped server entry: %s", tunnelProtocol)
   674  		}
   675  	}
   676  }
   677  
   678  func makeMockServerEntries(
   679  	tunnelProtocol string,
   680  	frontingProviderID string,
   681  	count int) []*protocol.ServerEntry {
   682  
   683  	serverEntries := make([]*protocol.ServerEntry, count)
   684  
   685  	for i := 0; i < count; i++ {
   686  		serverEntries[i] = &protocol.ServerEntry{
   687  			IpAddress:                  fmt.Sprintf("192.168.0.%d", i),
   688  			SshPort:                    prng.Range(10, 19),
   689  			SshObfuscatedPort:          prng.Range(20, 29),
   690  			SshObfuscatedQUICPort:      prng.Range(30, 39),
   691  			SshObfuscatedTapDancePort:  prng.Range(40, 49),
   692  			SshObfuscatedConjurePort:   prng.Range(50, 59),
   693  			MeekServerPort:             prng.Range(60, 69),
   694  			MeekFrontingHosts:          []string{"www1.example.org", "www2.example.org", "www3.example.org"},
   695  			MeekFrontingAddressesRegex: "[a-z0-9]{1,64}.example.org",
   696  			FrontingProviderID:         frontingProviderID,
   697  			LocalSource:                protocol.SERVER_ENTRY_SOURCE_EMBEDDED,
   698  			LocalTimestamp:             common.TruncateTimestampToHour(common.GetCurrentTimestamp()),
   699  			Capabilities:               []string{protocol.GetCapability(tunnelProtocol)},
   700  		}
   701  	}
   702  
   703  	return serverEntries
   704  }