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

Add SelectRandomizedTLSProfileProbability and SelectTLSProfile tests

Rod Hynes 7 лет назад
Родитель
Сommit
76c17928ef
3 измененных файлов с 90 добавлено и 15 удалено
  1. 4 2
      psiphon/common/parameters/clientParameters.go
  2. 21 7
      psiphon/tlsDialer.go
  3. 65 6
      psiphon/tlsDialer_test.go

+ 4 - 2
psiphon/common/parameters/clientParameters.go

@@ -93,6 +93,7 @@ const (
 	LimitTunnelProtocols                             = "LimitTunnelProtocols"
 	LimitTLSProfilesProbability                      = "LimitTLSProfilesProbability"
 	LimitTLSProfiles                                 = "LimitTLSProfiles"
+	SelectRandomizedTLSProfileProbability            = "SelectRandomizedTLSProfileProbability"
 	LimitQUICVersionsProbability                     = "LimitQUICVersionsProbability"
 	LimitQUICVersions                                = "LimitQUICVersions"
 	FragmentorProbability                            = "FragmentorProbability"
@@ -265,8 +266,9 @@ var defaultClientParameters = map[string]struct {
 	LimitTunnelProtocolsProbability: {value: 1.0, minimum: 0.0},
 	LimitTunnelProtocols:            {value: protocol.TunnelProtocols{}},
 
-	LimitTLSProfilesProbability: {value: 1.0, minimum: 0.0},
-	LimitTLSProfiles:            {value: protocol.TLSProfiles{}},
+	LimitTLSProfilesProbability:           {value: 1.0, minimum: 0.0},
+	LimitTLSProfiles:                      {value: protocol.TLSProfiles{}},
+	SelectRandomizedTLSProfileProbability: {value: 0.25, minimum: 0.0},
 
 	LimitQUICVersionsProbability: {value: 1.0, minimum: 0.0},
 	LimitQUICVersions:            {value: protocol.QUICVersions{}},

+ 21 - 7
psiphon/tlsDialer.go

@@ -143,13 +143,18 @@ func (config *CustomTLSConfig) EnableClientSessionCache(
 	}
 }
 
-// SelectTLSProfile picks a random TLS profile from the available candidates.
+// SelectTLSProfile picks a TLS profile at random from the available candidates.
 func SelectTLSProfile(
 	p *parameters.ClientParametersSnapshot) string {
 
+	// Two TLS profile lists are constructed, subject to limit constraints: fixed
+	// parrots and randomized. If one list is empty, the non-empty list is used.
+	// Otherwise SelectRandomizedTLSProfileProbability determines which list is used.
+
 	limitTLSProfiles := p.TLSProfiles(parameters.LimitTLSProfiles)
 
-	tlsProfiles := make([]string, 0)
+	randomizedTLSProfiles := make([]string, 0)
+	parrotTLSProfiles := make([]string, 0)
 
 	for _, tlsProfile := range protocol.SupportedTLSProfiles {
 
@@ -158,16 +163,25 @@ func SelectTLSProfile(
 			continue
 		}
 
-		tlsProfiles = append(tlsProfiles, tlsProfile)
+		if protocol.TLSProfileIsRandomized(tlsProfile) {
+			randomizedTLSProfiles = append(randomizedTLSProfiles, tlsProfile)
+		} else {
+			parrotTLSProfiles = append(parrotTLSProfiles, tlsProfile)
+		}
 	}
 
-	if len(tlsProfiles) == 0 {
-		return ""
+	if len(randomizedTLSProfiles) > 0 &&
+		(len(parrotTLSProfiles) == 0 ||
+			p.WeightedCoinFlip(parameters.SelectRandomizedTLSProfileProbability)) {
+
+		return randomizedTLSProfiles[prng.Intn(len(randomizedTLSProfiles))]
 	}
 
-	choice := prng.Intn(len(tlsProfiles))
+	if len(parrotTLSProfiles) == 0 {
+		return ""
+	}
 
-	return tlsProfiles[choice]
+	return parrotTLSProfiles[prng.Intn(len(parrotTLSProfiles))]
 }
 
 func getUTLSClientHelloID(tlsProfile string) utls.ClientHelloID {

+ 65 - 6
psiphon/tlsCompatibility_test.go → psiphon/tlsDialer_test.go

@@ -35,20 +35,25 @@ import (
 	utls "github.com/refraction-networking/utls"
 )
 
-func TestTLSCompatibility(t *testing.T) {
+func TestTLSDialerCompatibility(t *testing.T) {
 
-	// Config should be newline delimited list of domain/IP:port TLS host
-	// addresses to connect to.
+	// This test checks that each TLS profile can successfully complete a TLS
+	// handshake with various servers. By default, only the "psiphon" case is
+	// run, which runs the same TLS listener used by a Psiphon server.
+	//
+	// An optional config file, when supplied, enables testing against remote
+	// servers. Config should be newline delimited list of domain/IP:port TLS
+	// host addresses to connect to.
 
 	var configAddresses []string
-	config, err := ioutil.ReadFile("tlsCompatibility_test.config")
+	config, err := ioutil.ReadFile("tlsDialerCompatibility_test.config")
 	if err == nil {
 		configAddresses = strings.Split(string(config), "\n")
 	}
 
 	runner := func(address string) func(t *testing.T) {
 		return func(t *testing.T) {
-			testTLSCompatibility(t, address)
+			testTLSDialerCompatibility(t, address)
 		}
 	}
 
@@ -61,7 +66,7 @@ func TestTLSCompatibility(t *testing.T) {
 	t.Run("psiphon", runner(""))
 }
 
-func testTLSCompatibility(t *testing.T, address string) {
+func testTLSDialerCompatibility(t *testing.T, address string) {
 
 	if address == "" {
 
@@ -162,6 +167,60 @@ func testTLSCompatibility(t *testing.T, address string) {
 	}
 }
 
+func TestSelectTLSProfile(t *testing.T) {
+
+	clientParameters, err := parameters.NewClientParameters(nil)
+	if err != nil {
+		t.Fatalf("%s\n", err)
+	}
+
+	selected := make(map[string]int)
+
+	numSelections := 10000
+
+	for i := 0; i < numSelections; i++ {
+		profile := SelectTLSProfile(clientParameters.Get())
+		selected[profile] += 1
+	}
+
+	// All TLS profiles should be selected at least once.
+
+	for _, profile := range protocol.SupportedTLSProfiles {
+		if selected[profile] < 1 {
+			t.Errorf("TLS profile %s not selected", profile)
+		}
+	}
+
+	// Randomized TLS profiles should be selected with expected probability.
+
+	numRandomized := 0
+	for profile, n := range selected {
+		if protocol.TLSProfileIsRandomized(profile) {
+			numRandomized += n
+		}
+	}
+
+	t.Logf("ratio of randomized selected: %d/%d",
+		numRandomized, numSelections)
+
+	randomizedProbability := clientParameters.Get().Float(
+		parameters.SelectRandomizedTLSProfileProbability)
+
+	if numRandomized < int(0.9*float64(numSelections)*randomizedProbability) ||
+		numRandomized > int(1.1*float64(numSelections)*randomizedProbability) {
+
+		t.Error("Unexpected ratio")
+	}
+
+	// getUTLSClientHelloID should map each TLS profile to a utls ClientHelloID.
+
+	for _, profile := range protocol.SupportedTLSProfiles {
+		if getUTLSClientHelloID(profile) == utls.HelloGolang {
+			t.Errorf("TLS profile %s has no utls ClientHelloID", profile)
+		}
+	}
+}
+
 func BenchmarkRandomizedGetClientHelloVersion(b *testing.B) {
 	for n := 0; n < b.N; n++ {
 		utlsClientHelloID := utls.HelloRandomized