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

Add ReplayIgnoreChangedConfigState

Rod Hynes 2 лет назад
Родитель
Сommit
9142dd87bb

+ 2 - 0
psiphon/common/parameters/parameters.go

@@ -222,6 +222,7 @@ const (
 	ReplayTargetTunnelDuration                       = "ReplayTargetTunnelDuration"
 	ReplayLaterRoundMoveToFrontProbability           = "ReplayLaterRoundMoveToFrontProbability"
 	ReplayRetainFailedProbability                    = "ReplayRetainFailedProbability"
+	ReplayIgnoreChangedConfigState                   = "ReplayIgnoreChangedConfigState"
 	ReplayBPF                                        = "ReplayBPF"
 	ReplaySSH                                        = "ReplaySSH"
 	ReplayObfuscatorPadding                          = "ReplayObfuscatorPadding"
@@ -573,6 +574,7 @@ var defaultParameters = map[string]struct {
 	ReplayTargetTunnelDuration:             {value: 1 * time.Second, minimum: time.Duration(0)},
 	ReplayLaterRoundMoveToFrontProbability: {value: 0.0, minimum: 0.0},
 	ReplayRetainFailedProbability:          {value: 0.5, minimum: 0.0},
+	ReplayIgnoreChangedConfigState:         {value: false},
 	ReplayBPF:                              {value: true},
 	ReplaySSH:                              {value: true},
 	ReplayObfuscatorPadding:                {value: true},

+ 5 - 0
psiphon/config.go

@@ -741,6 +741,7 @@ type Config struct {
 	ReplayTargetTunnelDurationSeconds      *int
 	ReplayLaterRoundMoveToFrontProbability *float64
 	ReplayRetainFailedProbability          *float64
+	ReplayIgnoreChangedConfigState         *bool
 
 	// NetworkLatencyMultiplierMin and other NetworkLatencyMultiplier fields are
 	// for testing purposes.
@@ -1733,6 +1734,10 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.ReplayRetainFailedProbability] = *config.ReplayRetainFailedProbability
 	}
 
+	if config.ReplayIgnoreChangedConfigState != nil {
+		applyParameters[parameters.ReplayIgnoreChangedConfigState] = *config.ReplayIgnoreChangedConfigState
+	}
+
 	if config.UseOnlyCustomTLSProfiles != nil {
 		applyParameters[parameters.UseOnlyCustomTLSProfiles] = *config.UseOnlyCustomTLSProfiles
 	}

+ 43 - 12
psiphon/dialParameters.go

@@ -70,6 +70,7 @@ type DialParameters struct {
 
 	LastUsedTimestamp       time.Time
 	LastUsedConfigStateHash []byte
+	LastUsedServerEntryHash []byte
 
 	NetworkLatencyMultiplier float64
 
@@ -184,6 +185,7 @@ func MakeDialParameters(
 	p := config.GetParameters().Get()
 
 	ttl := p.Duration(parameters.ReplayDialParametersTTL)
+	replayIgnoreChangedConfigState := p.Bool(parameters.ReplayIgnoreChangedConfigState)
 	replayBPF := p.Bool(parameters.ReplayBPF)
 	replaySSH := p.Bool(parameters.ReplaySSH)
 	replayObfuscatorPadding := p.Bool(parameters.ReplayObfuscatorPadding)
@@ -230,19 +232,34 @@ func MakeDialParameters(
 
 	var currentTimestamp time.Time
 	var configStateHash []byte
+	var serverEntryHash []byte
 
 	// When TTL is 0, replay is disabled; the timestamp remains 0 and the
 	// output DialParameters will not be stored by Success.
 
 	if ttl > 0 {
 		currentTimestamp = time.Now()
-		configStateHash = getConfigStateHash(config, p, serverEntry)
+		configStateHash, serverEntryHash = getDialStateHashes(config, p, serverEntry)
 	}
 
 	if dialParams != nil &&
 		(ttl <= 0 ||
 			dialParams.LastUsedTimestamp.Before(currentTimestamp.Add(-ttl)) ||
-			!bytes.Equal(dialParams.LastUsedConfigStateHash, configStateHash) ||
+
+			// Replay is disabled when the current config state hash -- config
+			// dial parameters and the current tactics tag -- have changed
+			// since the last dial. This prioritizes applying any potential
+			// tactics change over redialing with parameters that may have
+			// changed in tactics.
+			//
+			// Because of this, frequent tactics changes may degrade replay
+			// effectiveness. When ReplayIgnoreChangedConfigState is set,
+			// differences in the config state hash are ignored.
+			(!replayIgnoreChangedConfigState && !bytes.Equal(dialParams.LastUsedConfigStateHash, configStateHash)) ||
+
+			// Replay is disabled when the server entry has changed.
+			!bytes.Equal(dialParams.LastUsedServerEntryHash, serverEntryHash) ||
+
 			(dialParams.TLSProfile != "" &&
 				!common.Contains(protocol.SupportedTLSProfiles, dialParams.TLSProfile)) ||
 			(dialParams.QUICVersion != "" &&
@@ -330,6 +347,7 @@ func MakeDialParameters(
 
 	dialParams.LastUsedTimestamp = currentTimestamp
 	dialParams.LastUsedConfigStateHash = configStateHash
+	dialParams.LastUsedServerEntryHash = serverEntryHash
 
 	// Initialize dial parameters.
 	//
@@ -1240,29 +1258,36 @@ func (dialParams *ExchangedDialParameters) MakeDialParameters(
 	p parameters.ParametersAccessor,
 	serverEntry *protocol.ServerEntry) *DialParameters {
 
+	configStateHash, serverEntryHash := getDialStateHashes(config, p, serverEntry)
+
 	return &DialParameters{
 		IsExchanged:             true,
 		LastUsedTimestamp:       time.Now(),
-		LastUsedConfigStateHash: getConfigStateHash(config, p, serverEntry),
+		LastUsedConfigStateHash: configStateHash,
+		LastUsedServerEntryHash: serverEntryHash,
 		TunnelProtocol:          dialParams.TunnelProtocol,
 	}
 }
 
-func getConfigStateHash(
+// getDialStateHashes returns two hashes: the config state hash reflects the
+// config dial parameters and tactics tag used for a dial; and the server
+// entry hash relects the server entry used for a dial.
+//
+// These hashes change if the input values change in a way that invalidates
+// any stored dial parameters.
+func getDialStateHashes(
 	config *Config,
 	p parameters.ParametersAccessor,
-	serverEntry *protocol.ServerEntry) []byte {
-
-	// The config state hash should reflect config, tactics, and server entry
-	// settings that impact the dial parameters. The hash should change if any
-	// of these input values change in a way that invalidates any stored dial
-	// parameters.
+	serverEntry *protocol.ServerEntry) ([]byte, []byte) {
 
 	// MD5 hash is used solely as a data checksum and not for any security
 	// purpose.
 	hash := md5.New()
 
-	// Add a hash of relevant config fields.
+	// Add a hash of relevant dial parameter config fields. Config fields
+	// that change due to user preference changes, such as selected egress
+	// region, are not to be included in config.dialParametersHash.
+	//
 	// Limitation: the config hash may change even when tactics will override the
 	// changed config field.
 	hash.Write(config.dialParametersHash)
@@ -1270,6 +1295,10 @@ func getConfigStateHash(
 	// Add the active tactics tag.
 	hash.Write([]byte(p.Tag()))
 
+	clientStateHash := hash.Sum(nil)
+
+	hash = md5.New()
+
 	// Add the server entry version and local timestamp, both of which should
 	// change when the server entry contents change and/or a new local copy is
 	// imported.
@@ -1281,7 +1310,9 @@ func getConfigStateHash(
 	hash.Write(serverEntryConfigurationVersion[:])
 	hash.Write([]byte(serverEntry.LocalTimestamp))
 
-	return hash.Sum(nil)
+	serverEntryHash := hash.Sum(nil)
+
+	return clientStateHash, serverEntryHash
 }
 
 func selectFrontingParameters(

+ 19 - 0
psiphon/dialParameters_test.go

@@ -385,9 +385,28 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) {
 		t.Fatalf("mismatching ObfuscatedQUICNonceTransformerParameters fields")
 	}
 
+	// Test: replay after change tactics, with ReplayIgnoreChangedClientState = true
+
+	applyParameters[parameters.ReplayDialParametersTTL] = "1s"
+	applyParameters[parameters.ReplayIgnoreChangedConfigState] = true
+	err = clientConfig.SetParameters("tag2a", false, applyParameters)
+	if err != nil {
+		t.Fatalf("SetParameters failed: %s", err)
+	}
+
+	dialParams, err = MakeDialParameters(clientConfig, nil, canReplay, selectProtocol, serverEntries[0], false, 0, 0)
+	if err != nil {
+		t.Fatalf("MakeDialParameters failed: %s", err)
+	}
+
+	if !replayDialParams.IsReplay {
+		t.Fatalf("unexpected non-replay")
+	}
+
 	// Test: no replay after change tactics
 
 	applyParameters[parameters.ReplayDialParametersTTL] = "1s"
+	applyParameters[parameters.ReplayIgnoreChangedConfigState] = false
 	err = clientConfig.SetParameters("tag2", false, applyParameters)
 	if err != nil {
 		t.Fatalf("SetParameters failed: %s", err)