From e627219bfb4313c0189b4fa71534f7f9b84c8c25 Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 19 Nov 2024 13:02:50 -0500 Subject: [PATCH 1/6] Add HoldOffTunnelFronting --- psiphon/common/parameters/parameters.go | 17 ++++++--- psiphon/common/utils.go | 16 ++++++++ psiphon/config.go | 49 +++++++++++++++++++++---- psiphon/dialParameters.go | 30 +++++++++------ psiphon/dialParameters_test.go | 5 ++- 5 files changed, 92 insertions(+), 25 deletions(-) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index 66f93a2ac..e891880eb 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -313,8 +313,11 @@ const ( HoldOffTunnelMinDuration = "HoldOffTunnelMinDuration" HoldOffTunnelMaxDuration = "HoldOffTunnelMaxDuration" HoldOffTunnelProtocols = "HoldOffTunnelProtocols" - HoldOffTunnelFrontingProviderIDs = "HoldOffTunnelFrontingProviderIDs" HoldOffTunnelProbability = "HoldOffTunnelProbability" + HoldOffTunnelFrontingMinDuration = "HoldOffTunnelFrontingMinDuration" + HoldOffTunnelFrontingMaxDuration = "HoldOffTunnelFrontingMaxDuration" + HoldOffTunnelFrontingProviderIDs = "HoldOffTunnelFrontingProviderIDs" + HoldOffTunnelFrontingProbability = "HoldOffTunnelFrontingProbability" RestrictFrontingProviderIDs = "RestrictFrontingProviderIDs" RestrictFrontingProviderIDsServerProbability = "RestrictFrontingProviderIDsServerProbability" RestrictFrontingProviderIDsClientProbability = "RestrictFrontingProviderIDsClientProbability" @@ -806,11 +809,15 @@ var defaultParameters = map[string]struct { CustomHostNameProbability: {value: 0.0, minimum: 0.0}, CustomHostNameLimitProtocols: {value: protocol.TunnelProtocols{}}, - HoldOffTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffTunnelProtocols: {value: protocol.TunnelProtocols{}}, + HoldOffTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelProtocols: {value: protocol.TunnelProtocols{}}, + HoldOffTunnelProbability: {value: 0.0, minimum: 0.0}, + + HoldOffTunnelFrontingMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelFrontingMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, HoldOffTunnelFrontingProviderIDs: {value: []string{}}, - HoldOffTunnelProbability: {value: 0.0, minimum: 0.0}, + HoldOffTunnelFrontingProbability: {value: 0.0, minimum: 0.0}, RestrictFrontingProviderIDs: {value: []string{}}, RestrictFrontingProviderIDsServerProbability: {value: 0.0, minimum: 0.0, flags: serverSideOnly}, diff --git a/psiphon/common/utils.go b/psiphon/common/utils.go index 71c3ff576..ff2d06491 100644 --- a/psiphon/common/utils.go +++ b/psiphon/common/utils.go @@ -276,3 +276,19 @@ func MergeContextCancel(ctx, cancelCtx context.Context) (context.Context, contex cancel(context.Canceled) } } + +// MaxDuration returns the maximum duration in durations or 0 if durations is +// empty. +func MaxDuration(durations ...time.Duration) time.Duration { + if len(durations) == 0 { + return 0 + } + + max := durations[0] + for _, d := range durations[1:] { + if d > max { + max = d + } + } + return max +} diff --git a/psiphon/config.go b/psiphon/config.go index b972c2557..ec59d95b5 100755 --- a/psiphon/config.go +++ b/psiphon/config.go @@ -877,9 +877,15 @@ type Config struct { HoldOffTunnelMinDurationMilliseconds *int HoldOffTunnelMaxDurationMilliseconds *int HoldOffTunnelProtocols []string - HoldOffTunnelFrontingProviderIDs []string HoldOffTunnelProbability *float64 + // HoldOffTunnelFrontingMinDurationMilliseconds and other + // HoldOffTunnelFronting fields are for testing purposes. + HoldOffTunnelFrontingMinDurationMilliseconds *int + HoldOffTunnelFrontingMaxDurationMilliseconds *int + HoldOffTunnelFrontingProviderIDs []string + HoldOffTunnelFrontingProbability *float64 + // RestrictFrontingProviderIDs and other RestrictFrontingProviderIDs fields // are for testing purposes. RestrictFrontingProviderIDs []string @@ -2214,12 +2220,24 @@ func (config *Config) makeConfigParameters() map[string]interface{} { applyParameters[parameters.HoldOffTunnelProtocols] = protocol.TunnelProtocols(config.HoldOffTunnelProtocols) } + if config.HoldOffTunnelProbability != nil { + applyParameters[parameters.HoldOffTunnelProbability] = *config.HoldOffTunnelProbability + } + + if config.HoldOffTunnelFrontingMinDurationMilliseconds != nil { + applyParameters[parameters.HoldOffTunnelFrontingMinDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelFrontingMinDurationMilliseconds) + } + + if config.HoldOffTunnelFrontingMaxDurationMilliseconds != nil { + applyParameters[parameters.HoldOffTunnelFrontingMaxDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelFrontingMaxDurationMilliseconds) + } + if len(config.HoldOffTunnelFrontingProviderIDs) > 0 { applyParameters[parameters.HoldOffTunnelFrontingProviderIDs] = config.HoldOffTunnelFrontingProviderIDs } - if config.HoldOffTunnelProbability != nil { - applyParameters[parameters.HoldOffTunnelProbability] = *config.HoldOffTunnelProbability + if config.HoldOffTunnelFrontingProbability != nil { + applyParameters[parameters.HoldOffTunnelFrontingProbability] = *config.HoldOffTunnelFrontingProbability } if config.HoldOffDirectTunnelMinDurationMilliseconds != nil { @@ -3022,6 +3040,21 @@ func (config *Config) setDialParametersHash() { } } + if config.HoldOffTunnelProbability != nil { + hash.Write([]byte("HoldOffTunnelProbability")) + binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelProbability) + } + + if config.HoldOffTunnelFrontingMinDurationMilliseconds != nil { + hash.Write([]byte("HoldOffTunnelFrontingMinDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelFrontingMinDurationMilliseconds)) + } + + if config.HoldOffTunnelFrontingMaxDurationMilliseconds != nil { + hash.Write([]byte("HoldOffTunnelFrontingMaxDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelFrontingMaxDurationMilliseconds)) + } + if len(config.HoldOffTunnelFrontingProviderIDs) > 0 { hash.Write([]byte("HoldOffTunnelFrontingProviderIDs")) for _, providerID := range config.HoldOffTunnelFrontingProviderIDs { @@ -3029,6 +3062,11 @@ func (config *Config) setDialParametersHash() { } } + if config.HoldOffTunnelFrontingProbability != nil { + hash.Write([]byte("HoldOffTunnelFrontingProbability")) + binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelFrontingProbability) + } + if config.HoldOffDirectTunnelProbability != nil { hash.Write([]byte("HoldOffDirectTunnelProbability")) binary.Write(hash, binary.LittleEndian, *config.HoldOffDirectTunnelProbability) @@ -3054,11 +3092,6 @@ func (config *Config) setDialParametersHash() { } } - if config.HoldOffTunnelProbability != nil { - hash.Write([]byte("HoldOffTunnelProbability")) - binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelProbability) - } - if len(config.RestrictDirectProviderRegions) > 0 { hash.Write([]byte("RestrictDirectProviderRegions")) for providerID, regions := range config.RestrictDirectProviderRegions { diff --git a/psiphon/dialParameters.go b/psiphon/dialParameters.go index 135ca8a5b..1bfaaee8c 100644 --- a/psiphon/dialParameters.go +++ b/psiphon/dialParameters.go @@ -981,15 +981,11 @@ func MakeDialParameters( if !isReplay || !replayHoldOffTunnel { var holdOffTunnelDuration time.Duration + var holdOffTunnelFrontingDuration time.Duration var holdOffDirectTunnelDuration time.Duration if common.Contains( - p.TunnelProtocols(parameters.HoldOffTunnelProtocols), dialParams.TunnelProtocol) || - - (protocol.TunnelProtocolUsesFrontedMeek(dialParams.TunnelProtocol) && - common.Contains( - p.Strings(parameters.HoldOffTunnelFrontingProviderIDs), - dialParams.FrontingProviderID)) { + p.TunnelProtocols(parameters.HoldOffTunnelProtocols), dialParams.TunnelProtocol) { if p.WeightedCoinFlip(parameters.HoldOffTunnelProbability) { @@ -999,6 +995,19 @@ func MakeDialParameters( } } + if protocol.TunnelProtocolUsesFrontedMeek(dialParams.TunnelProtocol) && + common.Contains( + p.Strings(parameters.HoldOffTunnelFrontingProviderIDs), + dialParams.FrontingProviderID) { + + if p.WeightedCoinFlip(parameters.HoldOffTunnelFrontingProbability) { + + holdOffTunnelFrontingDuration = prng.Period( + p.Duration(parameters.HoldOffTunnelFrontingMinDuration), + p.Duration(parameters.HoldOffTunnelFrontingMaxDuration)) + } + } + if protocol.TunnelProtocolIsDirect(dialParams.TunnelProtocol) && common.ContainsAny( p.KeyStrings(parameters.HoldOffDirectTunnelProviderRegions, dialParams.ServerEntry.ProviderID), []string{"", serverEntry.Region}) { @@ -1012,11 +1021,10 @@ func MakeDialParameters( } // Use the longest hold off duration - if holdOffTunnelDuration >= holdOffDirectTunnelDuration { - dialParams.HoldOffTunnelDuration = holdOffTunnelDuration - } else { - dialParams.HoldOffTunnelDuration = holdOffDirectTunnelDuration - } + dialParams.HoldOffTunnelDuration = common.MaxDuration( + holdOffTunnelDuration, + holdOffTunnelFrontingDuration, + holdOffDirectTunnelDuration) } // OSSH prefix and seed transform are applied only to the OSSH tunnel protocol, diff --git a/psiphon/dialParameters_test.go b/psiphon/dialParameters_test.go index 4fcbea1e7..62efed4fe 100644 --- a/psiphon/dialParameters_test.go +++ b/psiphon/dialParameters_test.go @@ -96,8 +96,11 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { applyParameters[parameters.HoldOffTunnelMinDuration] = "1ms" applyParameters[parameters.HoldOffTunnelMaxDuration] = "10ms" applyParameters[parameters.HoldOffTunnelProtocols] = holdOffTunnelProtocols - applyParameters[parameters.HoldOffTunnelFrontingProviderIDs] = []string{frontingProviderID} applyParameters[parameters.HoldOffTunnelProbability] = 1.0 + applyParameters[parameters.HoldOffTunnelFrontingMinDuration] = "1ms" + applyParameters[parameters.HoldOffTunnelFrontingMaxDuration] = "10ms" + applyParameters[parameters.HoldOffTunnelFrontingProviderIDs] = []string{frontingProviderID} + applyParameters[parameters.HoldOffTunnelFrontingProbability] = 1.0 applyParameters[parameters.HoldOffDirectTunnelMinDuration] = "1ms" applyParameters[parameters.HoldOffDirectTunnelMaxDuration] = "10ms" applyParameters[parameters.HoldOffDirectTunnelProviderRegions] = holdOffDirectTunnelProviderRegions From 5178fc334d74a8198a5a493597bfb8a1f5728b70 Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 19 Nov 2024 14:41:57 -0500 Subject: [PATCH 2/6] Add HoldOffInproxy and RestrictInproxy --- psiphon/common/parameters/parameters.go | 16 ++++++ psiphon/config.go | 68 +++++++++++++++++++++++++ psiphon/dialParameters.go | 37 +++++++++++++- psiphon/dialParameters_test.go | 50 +++++++++++++++++- psiphon/server/api.go | 8 ++- psiphon/server/listener.go | 4 +- psiphon/server/server_test.go | 57 +++++++++++++++++---- psiphon/server/tunnelServer.go | 40 +++++++++++++++ psiphon/serverApi.go | 3 +- 9 files changed, 268 insertions(+), 15 deletions(-) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index e891880eb..4c61746d5 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -328,6 +328,13 @@ const ( RestrictDirectProviderRegions = "RestrictDirectProviderRegions" RestrictDirectProviderIDsServerProbability = "RestrictDirectProviderIDsServerProbability" RestrictDirectProviderIDsClientProbability = "RestrictDirectProviderIDsClientProbability" + HoldOffInproxyTunnelMinDuration = "HoldOffInproxyTunnelMinDuration" + HoldOffInproxyTunnelMaxDuration = "HoldOffInproxyTunnelMaxDuration" + HoldOffInproxyTunnelProviderRegions = "HoldOffInproxyTunnelProviderRegions" + HoldOffInproxyTunnelProbability = "HoldOffInproxyTunnelProbability" + RestrictInproxyProviderRegions = "RestrictInproxyProviderRegions" + RestrictInproxyProviderIDsServerProbability = "RestrictInproxyProviderIDsServerProbability" + RestrictInproxyProviderIDsClientProbability = "RestrictInproxyProviderIDsClientProbability" UpstreamProxyAllowAllServerEntrySources = "UpstreamProxyAllowAllServerEntrySources" DestinationBytesMetricsASN = "DestinationBytesMetricsASN" DestinationBytesMetricsASNs = "DestinationBytesMetricsASNs" @@ -832,6 +839,15 @@ var defaultParameters = map[string]struct { RestrictDirectProviderIDsServerProbability: {value: 0.0, minimum: 0.0, flags: serverSideOnly}, RestrictDirectProviderIDsClientProbability: {value: 0.0, minimum: 0.0}, + HoldOffInproxyTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffInproxyTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffInproxyTunnelProviderRegions: {value: []string{}}, + HoldOffInproxyTunnelProbability: {value: 0.0, minimum: 0.0}, + + RestrictInproxyProviderRegions: {value: KeyStrings{}}, + RestrictInproxyProviderIDsServerProbability: {value: 0.0, minimum: 0.0, flags: serverSideOnly}, + RestrictInproxyProviderIDsClientProbability: {value: 0.0, minimum: 0.0}, + UpstreamProxyAllowAllServerEntrySources: {value: false}, DestinationBytesMetricsASN: {value: "", flags: serverSideOnly}, diff --git a/psiphon/config.go b/psiphon/config.go index ec59d95b5..b2807f767 100755 --- a/psiphon/config.go +++ b/psiphon/config.go @@ -903,6 +903,18 @@ type Config struct { RestrictDirectProviderRegions map[string][]string RestrictDirectProviderIDsClientProbability *float64 + // HoldOffInproxyTunnelMinDurationMilliseconds and other HoldOffInproxy + // fields are for testing purposes. + HoldOffInproxyTunnelMinDurationMilliseconds *int + HoldOffInproxyTunnelMaxDurationMilliseconds *int + HoldOffInproxyTunnelProviderRegions map[string][]string + HoldOffInproxyTunnelProbability *float64 + + // RestrictInproxyProviderRegions and other RestrictInproxy fields are for + // testing purposes. + RestrictInproxyProviderRegions map[string][]string + RestrictInproxyProviderIDsClientProbability *float64 + // UpstreamProxyAllowAllServerEntrySources is for testing purposes. UpstreamProxyAllowAllServerEntrySources *bool @@ -2272,6 +2284,22 @@ func (config *Config) makeConfigParameters() map[string]interface{} { applyParameters[parameters.RestrictFrontingProviderIDsClientProbability] = *config.RestrictFrontingProviderIDsClientProbability } + if config.HoldOffInproxyTunnelMinDurationMilliseconds != nil { + applyParameters[parameters.HoldOffInproxyTunnelMinDuration] = fmt.Sprintf("%dms", *config.HoldOffInproxyTunnelMinDurationMilliseconds) + } + + if config.HoldOffInproxyTunnelMaxDurationMilliseconds != nil { + applyParameters[parameters.HoldOffInproxyTunnelMaxDuration] = fmt.Sprintf("%dms", *config.HoldOffInproxyTunnelMaxDurationMilliseconds) + } + + if len(config.HoldOffInproxyTunnelProviderRegions) > 0 { + applyParameters[parameters.HoldOffInproxyTunnelProviderRegions] = parameters.KeyStrings(config.HoldOffInproxyTunnelProviderRegions) + } + + if config.HoldOffInproxyTunnelProbability != nil { + applyParameters[parameters.HoldOffInproxyTunnelProbability] = *config.HoldOffInproxyTunnelProbability + } + if config.UpstreamProxyAllowAllServerEntrySources != nil { applyParameters[parameters.UpstreamProxyAllowAllServerEntrySources] = *config.UpstreamProxyAllowAllServerEntrySources } @@ -3119,6 +3147,46 @@ func (config *Config) setDialParametersHash() { binary.Write(hash, binary.LittleEndian, *config.RestrictFrontingProviderIDsClientProbability) } + if config.HoldOffInproxyTunnelProbability != nil { + hash.Write([]byte("HoldOffInproxyTunnelProbability")) + binary.Write(hash, binary.LittleEndian, *config.HoldOffInproxyTunnelProbability) + } + + if config.HoldOffInproxyTunnelMinDurationMilliseconds != nil { + hash.Write([]byte("HoldOffInproxyTunnelMinDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffInproxyTunnelMinDurationMilliseconds)) + } + + if config.HoldOffInproxyTunnelMaxDurationMilliseconds != nil { + hash.Write([]byte("HoldOffInproxyTunnelMaxDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffInproxyTunnelMaxDurationMilliseconds)) + } + + if len(config.HoldOffInproxyTunnelProviderRegions) > 0 { + hash.Write([]byte("HoldOffInproxyTunnelProviderRegions")) + for providerID, regions := range config.HoldOffInproxyTunnelProviderRegions { + hash.Write([]byte(providerID)) + for _, region := range regions { + hash.Write([]byte(region)) + } + } + } + + if len(config.RestrictInproxyProviderRegions) > 0 { + hash.Write([]byte("RestrictInproxyProviderRegions")) + for providerID, regions := range config.RestrictInproxyProviderRegions { + hash.Write([]byte(providerID)) + for _, region := range regions { + hash.Write([]byte(region)) + } + } + } + + if config.RestrictInproxyProviderIDsClientProbability != nil { + hash.Write([]byte("RestrictInproxyProviderIDsClientProbability")) + binary.Write(hash, binary.LittleEndian, *config.RestrictInproxyProviderIDsClientProbability) + } + if config.UpstreamProxyAllowAllServerEntrySources != nil { hash.Write([]byte("UpstreamProxyAllowAllServerEntrySources")) binary.Write(hash, binary.LittleEndian, *config.UpstreamProxyAllowAllServerEntrySources) diff --git a/psiphon/dialParameters.go b/psiphon/dialParameters.go index 1bfaaee8c..76b37caa0 100644 --- a/psiphon/dialParameters.go +++ b/psiphon/dialParameters.go @@ -502,6 +502,27 @@ func MakeDialParameters( } } + // Skip this candidate when the clients tactics restrict usage of the + // provider ID. See the corresponding server-side enforcement comments in + // server.sshClient.setHandshakeState. + if protocol.TunnelProtocolUsesInproxy(dialParams.TunnelProtocol) && + common.ContainsAny( + p.KeyStrings(parameters.RestrictInproxyProviderRegions, dialParams.ServerEntry.ProviderID), []string{"", serverEntry.Region}) { + if p.WeightedCoinFlip( + parameters.RestrictInproxyProviderIDsClientProbability) { + + // When skipping, return nil/nil as no error should be logged. + // NoticeSkipServerEntry emits each skip reason, regardless + // of server entry, at most once per session. + + NoticeSkipServerEntry( + "restricted provider ID: %s", + dialParams.ServerEntry.ProviderID) + + return nil, nil + } + } + // Skip this candidate when the clients tactics restrict usage of the // fronting provider ID. See the corresponding server-side enforcement // comments in server.MeekServer.getSessionOrEndpoint. @@ -983,6 +1004,7 @@ func MakeDialParameters( var holdOffTunnelDuration time.Duration var holdOffTunnelFrontingDuration time.Duration var holdOffDirectTunnelDuration time.Duration + var holdOffInproxyTunnelDuration time.Duration if common.Contains( p.TunnelProtocols(parameters.HoldOffTunnelProtocols), dialParams.TunnelProtocol) { @@ -1020,11 +1042,24 @@ func MakeDialParameters( } } + if protocol.TunnelProtocolUsesInproxy(dialParams.TunnelProtocol) && + common.ContainsAny( + p.KeyStrings(parameters.HoldOffInproxyTunnelProviderRegions, dialParams.ServerEntry.ProviderID), []string{"", serverEntry.Region}) { + + if p.WeightedCoinFlip(parameters.HoldOffInproxyTunnelProbability) { + + holdOffInproxyTunnelDuration = prng.Period( + p.Duration(parameters.HoldOffInproxyTunnelMinDuration), + p.Duration(parameters.HoldOffInproxyTunnelMaxDuration)) + } + } + // Use the longest hold off duration dialParams.HoldOffTunnelDuration = common.MaxDuration( holdOffTunnelDuration, holdOffTunnelFrontingDuration, - holdOffDirectTunnelDuration) + holdOffDirectTunnelDuration, + holdOffInproxyTunnelDuration) } // OSSH prefix and seed transform are applied only to the OSSH tunnel protocol, diff --git a/psiphon/dialParameters_test.go b/psiphon/dialParameters_test.go index 62efed4fe..7bcd7367d 100644 --- a/psiphon/dialParameters_test.go +++ b/psiphon/dialParameters_test.go @@ -90,6 +90,12 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { holdOffDirectTunnelProviderRegions = map[string][]string{providerID: {""}} } + var holdOffInproxyTunnelProviderRegions parameters.KeyStrings + if protocol.TunnelProtocolUsesInproxy(tunnelProtocol) && + protocol.TunnelProtocolMinusInproxy(tunnelProtocol) == protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH { + holdOffInproxyTunnelProviderRegions = map[string][]string{providerID: {""}} + } + applyParameters := make(map[string]interface{}) applyParameters[parameters.TransformHostNameProbability] = 1.0 applyParameters[parameters.PickUserAgentProbability] = 1.0 @@ -104,6 +110,10 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { applyParameters[parameters.HoldOffDirectTunnelMinDuration] = "1ms" applyParameters[parameters.HoldOffDirectTunnelMaxDuration] = "10ms" applyParameters[parameters.HoldOffDirectTunnelProviderRegions] = holdOffDirectTunnelProviderRegions + applyParameters[parameters.HoldOffInproxyTunnelProbability] = 1.0 + applyParameters[parameters.HoldOffInproxyTunnelMinDuration] = "1ms" + applyParameters[parameters.HoldOffInproxyTunnelMaxDuration] = "10ms" + applyParameters[parameters.HoldOffInproxyTunnelProviderRegions] = holdOffInproxyTunnelProviderRegions applyParameters[parameters.HoldOffDirectTunnelProbability] = 1.0 applyParameters[parameters.DNSResolverAlternateServers] = []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"} applyParameters[parameters.DirectHTTPProtocolTransformProbability] = 1.0 @@ -254,10 +264,15 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { common.ContainsAny( holdOffDirectTunnelProviderRegions[dialParams.ServerEntry.ProviderID], []string{"", dialParams.ServerEntry.Region}) + expectHoldOffInproxyTunnelProviderRegion := protocol.TunnelProtocolUsesInproxy(tunnelProtocol) && + common.ContainsAny( + holdOffInproxyTunnelProviderRegions[dialParams.ServerEntry.ProviderID], + []string{"", dialParams.ServerEntry.Region}) if expectHoldOffTunnelProtocols || expectHoldOffTunnelFrontingProviderIDs || - expectHoldOffDirectTunnelProviderRegion { + expectHoldOffDirectTunnelProviderRegion || + expectHoldOffInproxyTunnelProviderRegion { if dialParams.HoldOffTunnelDuration < 1*time.Millisecond || dialParams.HoldOffTunnelDuration > 10*time.Millisecond { t.Fatalf("unexpected hold-off duration: %v", dialParams.HoldOffTunnelDuration) @@ -560,7 +575,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { t.Fatalf("SetParameters failed: %s", err) } - // Test: client-side restrict provider ID by region + // Test: client-side restrict provider ID by region for direct protocols applyParameters[parameters.RestrictDirectProviderRegions] = map[string][]string{providerID: {"CA"}} applyParameters[parameters.RestrictDirectProviderIDsClientProbability] = 1.0 @@ -591,6 +606,37 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { t.Fatalf("SetParameters failed: %s", err) } + // Test: client-side restrict provider ID by region for inproxy protocols + + applyParameters[parameters.RestrictInproxyProviderRegions] = map[string][]string{providerID: {"CA"}} + applyParameters[parameters.RestrictInproxyProviderIDsClientProbability] = 1.0 + err = clientConfig.SetParameters("tag8", false, applyParameters) + if err != nil { + t.Fatalf("SetParameters failed: %s", err) + } + + dialParams, err = MakeDialParameters( + clientConfig, steeringIPCache, nil, nil, nil, canReplay, selectProtocol, serverEntries[0], nil, nil, false, 0, 0) + + if protocol.TunnelProtocolUsesInproxy(tunnelProtocol) { + if err == nil { + if dialParams != nil { + t.Fatalf("unexpected MakeDialParameters success") + } + } + } else { + if err != nil { + t.Fatalf("MakeDialParameters failed: %s", err) + } + } + + applyParameters[parameters.RestrictInproxyProviderRegions] = map[string][]string{} + applyParameters[parameters.RestrictInproxyProviderIDsClientProbability] = 0.0 + err = clientConfig.SetParameters("tag9", false, applyParameters) + if err != nil { + t.Fatalf("SetParameters failed: %s", err) + } + if protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) { steeringIPCache.Flush() diff --git a/psiphon/server/api.go b/psiphon/server/api.go index 9c9b5b2f5..e147663c2 100644 --- a/psiphon/server/api.go +++ b/psiphon/server/api.go @@ -119,8 +119,14 @@ func sshAPIRequestHandler( switch name { case protocol.PSIPHON_API_HANDSHAKE_REQUEST_NAME: - return handshakeAPIRequestHandler( + responsePayload, err := handshakeAPIRequestHandler( support, protocol.PSIPHON_API_PROTOCOL_SSH, sshClient, params) + if err != nil { + // Handshake failed, disconnect the client. + sshClient.stop() + return nil, errors.Trace(err) + } + return responsePayload, nil case protocol.PSIPHON_API_CONNECTED_REQUEST_NAME: return connectedAPIRequestHandler( diff --git a/psiphon/server/listener.go b/psiphon/server/listener.go index 8d4e8249e..9b08d00b6 100644 --- a/psiphon/server/listener.go +++ b/psiphon/server/listener.go @@ -117,7 +117,9 @@ func (listener *TacticsListener) accept() (net.Conn, error) { // peer IP is not the original client IP. Indirect protocols must determine // the original client IP before applying GeoIP specific tactics; see the // server-side enforcement of RestrictFrontingProviderIDs for fronted meek - // in server.MeekServer.getSessionOrEndpoint. + // in server.MeekServer.getSessionOrEndpoint or of + // RestrictInproxyProviderRegions for inproxy in + // server.sshClient.setHandshakeState. // // At this stage, GeoIP tactics filters are active, but handshake API // parameters are not. diff --git a/psiphon/server/server_test.go b/psiphon/server/server_test.go index b15cd8cfa..3c93d53a6 100644 --- a/psiphon/server/server_test.go +++ b/psiphon/server/server_test.go @@ -369,6 +369,17 @@ func TestInproxyOSSH(t *testing.T) { }) } +func TestRestrictInproxy(t *testing.T) { + if !inproxy.Enabled() { + t.Skip("inproxy is not enabled") + } + runServer(t, + &runServerConfig{ + tunnelProtocol: "INPROXY-WEBRTC-OSSH", + doRestrictInproxy: true, + }) +} + func TestInproxyQUICOSSH(t *testing.T) { if !quic.Enabled() { t.Skip("QUIC is not enabled") @@ -690,6 +701,7 @@ type runServerConfig struct { doTargetBrokerSpecs bool useLegacyAPIEncoding bool doPersonalPairing bool + doRestrictInproxy bool } var ( @@ -933,7 +945,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) { runConfig.applyPrefix, runConfig.forceFragmenting, "classic", - inproxyTacticsParametersJSON) + inproxyTacticsParametersJSON, + runConfig.doRestrictInproxy) } blocklistFilename := filepath.Join(testDataDirName, "blocklist.csv") @@ -1203,7 +1216,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) { runConfig.applyPrefix, runConfig.forceFragmenting, "consistent", - inproxyTacticsParametersJSON) + inproxyTacticsParametersJSON, + runConfig.doRestrictInproxy) } p, _ := os.FindProcess(os.Getpid()) @@ -1510,6 +1524,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) { pruneServerEntriesNoticesEmitted := make(chan struct{}, 1) serverAlertDisallowedNoticesEmitted := make(chan struct{}, 1) untunneledPortForward := make(chan struct{}, 1) + discardTunnel := make(chan struct{}, 1) psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver( func(notice []byte) { @@ -1581,6 +1596,11 @@ func runServer(t *testing.T, runConfig *runServerConfig) { if connectedClients == 1 && bytesUp > 0 && bytesDown > 0 { sendNotificationReceived(inproxyActivity) } + + case "Info": + if strings.Contains(payload["message"].(string), "discard tunnel") { + sendNotificationReceived(discardTunnel) + } } if printNotice { @@ -1633,12 +1653,19 @@ func runServer(t *testing.T, runConfig *runServerConfig) { close(timeoutSignal) }() - waitOnNotification(t, connectedServer, timeoutSignal, "connected server timeout exceeded") - if doInproxy { - waitOnNotification(t, inproxyActivity, timeoutSignal, "inproxy activity timeout exceeded") + expectDiscardTunnel := runConfig.doRestrictInproxy + + if expectDiscardTunnel { + waitOnNotification(t, discardTunnel, timeoutSignal, "discard tunnel timeout exceeded") + return + } else { + waitOnNotification(t, connectedServer, timeoutSignal, "connected server timeout exceeded") + if doInproxy { + waitOnNotification(t, inproxyActivity, timeoutSignal, "inproxy activity timeout exceeded") + } + waitOnNotification(t, tunnelsEstablished, timeoutSignal, "tunnel established timeout exceeded") + waitOnNotification(t, homepageReceived, timeoutSignal, "homepage received timeout exceeded") } - waitOnNotification(t, tunnelsEstablished, timeoutSignal, "tunnel established timeout exceeded") - waitOnNotification(t, homepageReceived, timeoutSignal, "homepage received timeout exceeded") if runConfig.doChangeBytesConfig { @@ -1672,7 +1699,8 @@ func runServer(t *testing.T, runConfig *runServerConfig) { runConfig.applyPrefix, runConfig.forceFragmenting, "consistent", - inproxyTacticsParametersJSON) + inproxyTacticsParametersJSON, + runConfig.doRestrictInproxy) p, _ := os.FindProcess(os.Getpid()) p.Signal(syscall.SIGUSR1) @@ -3451,7 +3479,8 @@ func paveTacticsConfigFile( applyOsshPrefix bool, enableOsshPrefixFragmenting bool, discoveryStategy string, - inproxyParametersJSON string) { + inproxyParametersJSON string, + doRestrictAllInproxyProviderRegions bool) { // Setting LimitTunnelProtocols passively exercises the // server-side LimitTunnelProtocols enforcement. @@ -3470,6 +3499,7 @@ func paveTacticsConfigFile( %s %s %s + %s "LimitTunnelProtocols" : ["%s"], "FragmentorLimitProtocols" : ["%s"], "FragmentorProbability" : 1.0, @@ -3577,6 +3607,14 @@ func paveTacticsConfigFile( `, strconv.FormatBool(enableOsshPrefixFragmenting)) } + restrictInproxyParameters := "" + if doRestrictAllInproxyProviderRegions { + restrictInproxyParameters = ` + "RestrictInproxyProviderRegions": {"" : [""]}, + "RestrictInproxyProviderIDsServerProbability": 1.0, + ` + } + tacticsConfigJSON := fmt.Sprintf( tacticsConfigJSONFormat, tacticsRequestPublicKey, @@ -3587,6 +3625,7 @@ func paveTacticsConfigFile( legacyDestinationBytesParameters, osshPrefix, inproxyParametersJSON, + restrictInproxyParameters, tunnelProtocol, tunnelProtocol, tunnelProtocol, diff --git a/psiphon/server/tunnelServer.go b/psiphon/server/tunnelServer.go index ee2c7bbf0..cf943b9f7 100644 --- a/psiphon/server/tunnelServer.go +++ b/psiphon/server/tunnelServer.go @@ -3784,6 +3784,46 @@ func (sshClient *sshClient) setHandshakeState( return nil, errors.TraceNew("handshake already completed") } + if sshClient.isInproxyTunnelProtocol { + + p, err := sshClient.sshServer.support.ServerTacticsParametersCache.Get(sshClient.clientGeoIPData) + if err != nil { + return nil, errors.Trace(err) + } + + // Skip check if no tactics are configured. + // + // Disconnect immediately if the tactics for the client restricts usage + // of the provider ID with inproxy protocols. The probability may be + // used to influence usage of a given provider with inproxy protocols; + // but when only that provider works for a given client, and the + // probability is less than 1.0, the client can retry until it gets a + // successful coin flip. + // + // Clients will also skip inproxy protocol candidates with restricted + // provider IDs. + // The client-side probability, + // RestrictInproxyProviderIDsClientProbability, is applied + // independently of the server-side coin flip here. + // + // At this stage, GeoIP tactics filters are active, but handshake API + // parameters are not. + // + // See the comment in server.LoadConfig regarding provider ID + // limitations. + if !p.IsNil() && + common.ContainsAny( + p.KeyStrings(parameters.RestrictInproxyProviderRegions, + sshClient.sshServer.support.Config.GetProviderID()), + []string{"", sshClient.sshServer.support.Config.GetRegion()}) { + + if p.WeightedCoinFlip( + parameters.RestrictInproxyProviderIDsServerProbability) { + return nil, errRestrictedProvider + } + } + } + // Verify the authorizations submitted by the client. Verified, active // (non-expired) access types will be available for traffic rules // filtering. diff --git a/psiphon/serverApi.go b/psiphon/serverApi.go index 9e1e39294..265be1a1d 100644 --- a/psiphon/serverApi.go +++ b/psiphon/serverApi.go @@ -142,7 +142,8 @@ func (serverContext *ServerContext) doHandshakeRequest(ignoreStatsRegexps bool) // The purpose of this mechanism is to rapidly add provider IDs to the // server entries in client local storage, and to ensure that the client has // a provider ID for its currently connected server as required for the - // RestrictDirectProviderRegions, and HoldOffDirectTunnelProviderRegions + // RestrictDirectProviderRegions, HoldOffDirectTunnelProviderRegions, + // RestrictInproxyProviderRegions, and HoldOffInproxyTunnelProviderRegions // tactics. // // The server entry will be included in handshakeResponse.EncodedServerList, From ffb4a37468f7919896c2ecab245ac3c9c5190c30 Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 26 Nov 2024 12:12:39 -0500 Subject: [PATCH 3/6] Rename as HoldOffFrontingTunnel Leaves HoldOffTunnelFrontingProviderIDs as legacy-only and distinct from new HoldOffFrontingTunnel parameters. --- psiphon/common/parameters/parameters.go | 16 ++++---- psiphon/config.go | 52 ++++++++++++------------- psiphon/dialParameters.go | 14 +++---- psiphon/dialParameters_test.go | 12 +++--- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index 4c61746d5..8763511f8 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -314,10 +314,10 @@ const ( HoldOffTunnelMaxDuration = "HoldOffTunnelMaxDuration" HoldOffTunnelProtocols = "HoldOffTunnelProtocols" HoldOffTunnelProbability = "HoldOffTunnelProbability" - HoldOffTunnelFrontingMinDuration = "HoldOffTunnelFrontingMinDuration" - HoldOffTunnelFrontingMaxDuration = "HoldOffTunnelFrontingMaxDuration" - HoldOffTunnelFrontingProviderIDs = "HoldOffTunnelFrontingProviderIDs" - HoldOffTunnelFrontingProbability = "HoldOffTunnelFrontingProbability" + HoldOffFrontingTunnelMinDuration = "HoldOffFrontingTunnelMinDuration" + HoldOffFrontingTunnelMaxDuration = "HoldOffFrontingTunnelMaxDuration" + HoldOffFrontingTunnelProviderIDs = "HoldOffFrontingTunnelProviderIDs" + HoldOffFrontingTunnelProbability = "HoldOffFrontingTunnelProbability" RestrictFrontingProviderIDs = "RestrictFrontingProviderIDs" RestrictFrontingProviderIDsServerProbability = "RestrictFrontingProviderIDsServerProbability" RestrictFrontingProviderIDsClientProbability = "RestrictFrontingProviderIDsClientProbability" @@ -821,10 +821,10 @@ var defaultParameters = map[string]struct { HoldOffTunnelProtocols: {value: protocol.TunnelProtocols{}}, HoldOffTunnelProbability: {value: 0.0, minimum: 0.0}, - HoldOffTunnelFrontingMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffTunnelFrontingMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffTunnelFrontingProviderIDs: {value: []string{}}, - HoldOffTunnelFrontingProbability: {value: 0.0, minimum: 0.0}, + HoldOffFrontingTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffFrontingTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffFrontingTunnelProviderIDs: {value: []string{}}, + HoldOffFrontingTunnelProbability: {value: 0.0, minimum: 0.0}, RestrictFrontingProviderIDs: {value: []string{}}, RestrictFrontingProviderIDsServerProbability: {value: 0.0, minimum: 0.0, flags: serverSideOnly}, diff --git a/psiphon/config.go b/psiphon/config.go index b2807f767..b94956dec 100755 --- a/psiphon/config.go +++ b/psiphon/config.go @@ -879,12 +879,12 @@ type Config struct { HoldOffTunnelProtocols []string HoldOffTunnelProbability *float64 - // HoldOffTunnelFrontingMinDurationMilliseconds and other - // HoldOffTunnelFronting fields are for testing purposes. - HoldOffTunnelFrontingMinDurationMilliseconds *int - HoldOffTunnelFrontingMaxDurationMilliseconds *int - HoldOffTunnelFrontingProviderIDs []string - HoldOffTunnelFrontingProbability *float64 + // HoldOffFrontingTunnelMinDurationMilliseconds and other + // HoldOffFrontingTunnel fields are for testing purposes. + HoldOffFrontingTunnelMinDurationMilliseconds *int + HoldOffFrontingTunnelMaxDurationMilliseconds *int + HoldOffFrontingTunnelProviderIDs []string + HoldOffFrontingTunnelProbability *float64 // RestrictFrontingProviderIDs and other RestrictFrontingProviderIDs fields // are for testing purposes. @@ -2236,20 +2236,20 @@ func (config *Config) makeConfigParameters() map[string]interface{} { applyParameters[parameters.HoldOffTunnelProbability] = *config.HoldOffTunnelProbability } - if config.HoldOffTunnelFrontingMinDurationMilliseconds != nil { - applyParameters[parameters.HoldOffTunnelFrontingMinDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelFrontingMinDurationMilliseconds) + if config.HoldOffFrontingTunnelMinDurationMilliseconds != nil { + applyParameters[parameters.HoldOffFrontingTunnelMinDuration] = fmt.Sprintf("%dms", *config.HoldOffFrontingTunnelMinDurationMilliseconds) } - if config.HoldOffTunnelFrontingMaxDurationMilliseconds != nil { - applyParameters[parameters.HoldOffTunnelFrontingMaxDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelFrontingMaxDurationMilliseconds) + if config.HoldOffFrontingTunnelMaxDurationMilliseconds != nil { + applyParameters[parameters.HoldOffFrontingTunnelMaxDuration] = fmt.Sprintf("%dms", *config.HoldOffFrontingTunnelMaxDurationMilliseconds) } - if len(config.HoldOffTunnelFrontingProviderIDs) > 0 { - applyParameters[parameters.HoldOffTunnelFrontingProviderIDs] = config.HoldOffTunnelFrontingProviderIDs + if len(config.HoldOffFrontingTunnelProviderIDs) > 0 { + applyParameters[parameters.HoldOffFrontingTunnelProviderIDs] = config.HoldOffFrontingTunnelProviderIDs } - if config.HoldOffTunnelFrontingProbability != nil { - applyParameters[parameters.HoldOffTunnelFrontingProbability] = *config.HoldOffTunnelFrontingProbability + if config.HoldOffFrontingTunnelProbability != nil { + applyParameters[parameters.HoldOffFrontingTunnelProbability] = *config.HoldOffFrontingTunnelProbability } if config.HoldOffDirectTunnelMinDurationMilliseconds != nil { @@ -3073,26 +3073,26 @@ func (config *Config) setDialParametersHash() { binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelProbability) } - if config.HoldOffTunnelFrontingMinDurationMilliseconds != nil { - hash.Write([]byte("HoldOffTunnelFrontingMinDurationMilliseconds")) - binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelFrontingMinDurationMilliseconds)) + if config.HoldOffFrontingTunnelMinDurationMilliseconds != nil { + hash.Write([]byte("HoldOffFrontingTunnelMinDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffFrontingTunnelMinDurationMilliseconds)) } - if config.HoldOffTunnelFrontingMaxDurationMilliseconds != nil { - hash.Write([]byte("HoldOffTunnelFrontingMaxDurationMilliseconds")) - binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelFrontingMaxDurationMilliseconds)) + if config.HoldOffFrontingTunnelMaxDurationMilliseconds != nil { + hash.Write([]byte("HoldOffFrontingTunnelMaxDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffFrontingTunnelMaxDurationMilliseconds)) } - if len(config.HoldOffTunnelFrontingProviderIDs) > 0 { - hash.Write([]byte("HoldOffTunnelFrontingProviderIDs")) - for _, providerID := range config.HoldOffTunnelFrontingProviderIDs { + if len(config.HoldOffFrontingTunnelProviderIDs) > 0 { + hash.Write([]byte("HoldOffFrontingTunnelProviderIDs")) + for _, providerID := range config.HoldOffFrontingTunnelProviderIDs { hash.Write([]byte(providerID)) } } - if config.HoldOffTunnelFrontingProbability != nil { - hash.Write([]byte("HoldOffTunnelFrontingProbability")) - binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelFrontingProbability) + if config.HoldOffFrontingTunnelProbability != nil { + hash.Write([]byte("HoldOffFrontingTunnelProbability")) + binary.Write(hash, binary.LittleEndian, *config.HoldOffFrontingTunnelProbability) } if config.HoldOffDirectTunnelProbability != nil { diff --git a/psiphon/dialParameters.go b/psiphon/dialParameters.go index 76b37caa0..6d58d45f2 100644 --- a/psiphon/dialParameters.go +++ b/psiphon/dialParameters.go @@ -1002,7 +1002,7 @@ func MakeDialParameters( if !isReplay || !replayHoldOffTunnel { var holdOffTunnelDuration time.Duration - var holdOffTunnelFrontingDuration time.Duration + var HoldOffFrontingTunnelDuration time.Duration var holdOffDirectTunnelDuration time.Duration var holdOffInproxyTunnelDuration time.Duration @@ -1019,14 +1019,14 @@ func MakeDialParameters( if protocol.TunnelProtocolUsesFrontedMeek(dialParams.TunnelProtocol) && common.Contains( - p.Strings(parameters.HoldOffTunnelFrontingProviderIDs), + p.Strings(parameters.HoldOffFrontingTunnelProviderIDs), dialParams.FrontingProviderID) { - if p.WeightedCoinFlip(parameters.HoldOffTunnelFrontingProbability) { + if p.WeightedCoinFlip(parameters.HoldOffFrontingTunnelProbability) { - holdOffTunnelFrontingDuration = prng.Period( - p.Duration(parameters.HoldOffTunnelFrontingMinDuration), - p.Duration(parameters.HoldOffTunnelFrontingMaxDuration)) + HoldOffFrontingTunnelDuration = prng.Period( + p.Duration(parameters.HoldOffFrontingTunnelMinDuration), + p.Duration(parameters.HoldOffFrontingTunnelMaxDuration)) } } @@ -1057,7 +1057,7 @@ func MakeDialParameters( // Use the longest hold off duration dialParams.HoldOffTunnelDuration = common.MaxDuration( holdOffTunnelDuration, - holdOffTunnelFrontingDuration, + HoldOffFrontingTunnelDuration, holdOffDirectTunnelDuration, holdOffInproxyTunnelDuration) } diff --git a/psiphon/dialParameters_test.go b/psiphon/dialParameters_test.go index 7bcd7367d..89acd9765 100644 --- a/psiphon/dialParameters_test.go +++ b/psiphon/dialParameters_test.go @@ -103,10 +103,10 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { applyParameters[parameters.HoldOffTunnelMaxDuration] = "10ms" applyParameters[parameters.HoldOffTunnelProtocols] = holdOffTunnelProtocols applyParameters[parameters.HoldOffTunnelProbability] = 1.0 - applyParameters[parameters.HoldOffTunnelFrontingMinDuration] = "1ms" - applyParameters[parameters.HoldOffTunnelFrontingMaxDuration] = "10ms" - applyParameters[parameters.HoldOffTunnelFrontingProviderIDs] = []string{frontingProviderID} - applyParameters[parameters.HoldOffTunnelFrontingProbability] = 1.0 + applyParameters[parameters.HoldOffFrontingTunnelMinDuration] = "1ms" + applyParameters[parameters.HoldOffFrontingTunnelMaxDuration] = "10ms" + applyParameters[parameters.HoldOffFrontingTunnelProviderIDs] = []string{frontingProviderID} + applyParameters[parameters.HoldOffFrontingTunnelProbability] = 1.0 applyParameters[parameters.HoldOffDirectTunnelMinDuration] = "1ms" applyParameters[parameters.HoldOffDirectTunnelMaxDuration] = "10ms" applyParameters[parameters.HoldOffDirectTunnelProviderRegions] = holdOffDirectTunnelProviderRegions @@ -259,7 +259,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { } expectHoldOffTunnelProtocols := common.Contains(holdOffTunnelProtocols, tunnelProtocol) - expectHoldOffTunnelFrontingProviderIDs := protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) + expectHoldOffFrontingTunnelProviderIDs := protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) expectHoldOffDirectTunnelProviderRegion := protocol.TunnelProtocolIsDirect(tunnelProtocol) && common.ContainsAny( holdOffDirectTunnelProviderRegions[dialParams.ServerEntry.ProviderID], @@ -270,7 +270,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { []string{"", dialParams.ServerEntry.Region}) if expectHoldOffTunnelProtocols || - expectHoldOffTunnelFrontingProviderIDs || + expectHoldOffFrontingTunnelProviderIDs || expectHoldOffDirectTunnelProviderRegion || expectHoldOffInproxyTunnelProviderRegion { if dialParams.HoldOffTunnelDuration < 1*time.Millisecond || From 1171c5c92654262d37d94b284e5db65523a446ef Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 26 Nov 2024 12:26:48 -0500 Subject: [PATCH 4/6] Rename as HoldOffTunnelProtocol Leaves HoldOffTunnel as legacy-only and distinct from new HoldOffTunnelProtocol parameters. --- psiphon/common/parameters/parameters.go | 16 ++++---- psiphon/config.go | 52 ++++++++++++------------- psiphon/dialParameters.go | 14 +++---- psiphon/dialParameters_test.go | 14 +++---- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index 8763511f8..c4638990a 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -310,10 +310,10 @@ const ( CustomHostNameRegexes = "CustomHostNameRegexes" CustomHostNameProbability = "CustomHostNameProbability" CustomHostNameLimitProtocols = "CustomHostNameLimitProtocols" - HoldOffTunnelMinDuration = "HoldOffTunnelMinDuration" - HoldOffTunnelMaxDuration = "HoldOffTunnelMaxDuration" - HoldOffTunnelProtocols = "HoldOffTunnelProtocols" - HoldOffTunnelProbability = "HoldOffTunnelProbability" + HoldOffTunnelProtocolMinDuration = "HoldOffTunnelProtocolMinDuration" + HoldOffTunnelProtocolMaxDuration = "HoldOffTunnelProtocolMaxDuration" + HoldOffTunnelProtocolNames = "HoldOffTunnelProtocolNames" + HoldOffTunnelProtocolProbability = "HoldOffTunnelProtocolProbability" HoldOffFrontingTunnelMinDuration = "HoldOffFrontingTunnelMinDuration" HoldOffFrontingTunnelMaxDuration = "HoldOffFrontingTunnelMaxDuration" HoldOffFrontingTunnelProviderIDs = "HoldOffFrontingTunnelProviderIDs" @@ -816,10 +816,10 @@ var defaultParameters = map[string]struct { CustomHostNameProbability: {value: 0.0, minimum: 0.0}, CustomHostNameLimitProtocols: {value: protocol.TunnelProtocols{}}, - HoldOffTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffTunnelProtocols: {value: protocol.TunnelProtocols{}}, - HoldOffTunnelProbability: {value: 0.0, minimum: 0.0}, + HoldOffTunnelProtocolMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelProtocolMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelProtocolNames: {value: protocol.TunnelProtocols{}}, + HoldOffTunnelProtocolProbability: {value: 0.0, minimum: 0.0}, HoldOffFrontingTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, HoldOffFrontingTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, diff --git a/psiphon/config.go b/psiphon/config.go index b94956dec..cd123c994 100755 --- a/psiphon/config.go +++ b/psiphon/config.go @@ -872,12 +872,12 @@ type Config struct { ConjureSTUNServerAddresses []string ConjureDTLSEmptyInitialPacketProbability *float64 - // HoldOffTunnelMinDurationMilliseconds and other HoldOffTunnel fields are - // for testing purposes. - HoldOffTunnelMinDurationMilliseconds *int - HoldOffTunnelMaxDurationMilliseconds *int - HoldOffTunnelProtocols []string - HoldOffTunnelProbability *float64 + // HoldOffTunnelProtocolMinDurationMilliseconds and other + // HoldOffTunnelProtocol fields are for testing purposes. + HoldOffTunnelProtocolMinDurationMilliseconds *int + HoldOffTunnelProtocolMaxDurationMilliseconds *int + HoldOffTunnelProtocolNames []string + HoldOffTunnelProtocolProbability *float64 // HoldOffFrontingTunnelMinDurationMilliseconds and other // HoldOffFrontingTunnel fields are for testing purposes. @@ -2220,20 +2220,20 @@ func (config *Config) makeConfigParameters() map[string]interface{} { applyParameters[parameters.ConjureDTLSEmptyInitialPacketProbability] = *config.ConjureDTLSEmptyInitialPacketProbability } - if config.HoldOffTunnelMinDurationMilliseconds != nil { - applyParameters[parameters.HoldOffTunnelMinDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelMinDurationMilliseconds) + if config.HoldOffTunnelProtocolMinDurationMilliseconds != nil { + applyParameters[parameters.HoldOffTunnelProtocolMinDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelProtocolMinDurationMilliseconds) } - if config.HoldOffTunnelMaxDurationMilliseconds != nil { - applyParameters[parameters.HoldOffTunnelMaxDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelMaxDurationMilliseconds) + if config.HoldOffTunnelProtocolMaxDurationMilliseconds != nil { + applyParameters[parameters.HoldOffTunnelProtocolMaxDuration] = fmt.Sprintf("%dms", *config.HoldOffTunnelProtocolMaxDurationMilliseconds) } - if len(config.HoldOffTunnelProtocols) > 0 { - applyParameters[parameters.HoldOffTunnelProtocols] = protocol.TunnelProtocols(config.HoldOffTunnelProtocols) + if len(config.HoldOffTunnelProtocolNames) > 0 { + applyParameters[parameters.HoldOffTunnelProtocolNames] = protocol.TunnelProtocols(config.HoldOffTunnelProtocolNames) } - if config.HoldOffTunnelProbability != nil { - applyParameters[parameters.HoldOffTunnelProbability] = *config.HoldOffTunnelProbability + if config.HoldOffTunnelProtocolProbability != nil { + applyParameters[parameters.HoldOffTunnelProtocolProbability] = *config.HoldOffTunnelProtocolProbability } if config.HoldOffFrontingTunnelMinDurationMilliseconds != nil { @@ -3051,26 +3051,26 @@ func (config *Config) setDialParametersHash() { } } - if config.HoldOffTunnelMinDurationMilliseconds != nil { - hash.Write([]byte("HoldOffTunnelMinDurationMilliseconds")) - binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelMinDurationMilliseconds)) + if config.HoldOffTunnelProtocolMinDurationMilliseconds != nil { + hash.Write([]byte("HoldOffTunnelProtocolMinDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelProtocolMinDurationMilliseconds)) } - if config.HoldOffTunnelMaxDurationMilliseconds != nil { - hash.Write([]byte("HoldOffTunnelMaxDurationMilliseconds")) - binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelMaxDurationMilliseconds)) + if config.HoldOffTunnelProtocolMaxDurationMilliseconds != nil { + hash.Write([]byte("HoldOffTunnelProtocolMaxDurationMilliseconds")) + binary.Write(hash, binary.LittleEndian, int64(*config.HoldOffTunnelProtocolMaxDurationMilliseconds)) } - if len(config.HoldOffTunnelProtocols) > 0 { - hash.Write([]byte("HoldOffTunnelProtocols")) - for _, protocol := range config.HoldOffTunnelProtocols { + if len(config.HoldOffTunnelProtocolNames) > 0 { + hash.Write([]byte("HoldOffTunnelProtocolNames")) + for _, protocol := range config.HoldOffTunnelProtocolNames { hash.Write([]byte(protocol)) } } - if config.HoldOffTunnelProbability != nil { - hash.Write([]byte("HoldOffTunnelProbability")) - binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelProbability) + if config.HoldOffTunnelProtocolProbability != nil { + hash.Write([]byte("HoldOffTunnelProtocolProbability")) + binary.Write(hash, binary.LittleEndian, *config.HoldOffTunnelProtocolProbability) } if config.HoldOffFrontingTunnelMinDurationMilliseconds != nil { diff --git a/psiphon/dialParameters.go b/psiphon/dialParameters.go index 6d58d45f2..3a41de80a 100644 --- a/psiphon/dialParameters.go +++ b/psiphon/dialParameters.go @@ -1001,19 +1001,19 @@ func MakeDialParameters( if !isReplay || !replayHoldOffTunnel { - var holdOffTunnelDuration time.Duration + var HoldOffTunnelProtocolDuration time.Duration var HoldOffFrontingTunnelDuration time.Duration var holdOffDirectTunnelDuration time.Duration var holdOffInproxyTunnelDuration time.Duration if common.Contains( - p.TunnelProtocols(parameters.HoldOffTunnelProtocols), dialParams.TunnelProtocol) { + p.TunnelProtocols(parameters.HoldOffTunnelProtocolNames), dialParams.TunnelProtocol) { - if p.WeightedCoinFlip(parameters.HoldOffTunnelProbability) { + if p.WeightedCoinFlip(parameters.HoldOffTunnelProtocolProbability) { - holdOffTunnelDuration = prng.Period( - p.Duration(parameters.HoldOffTunnelMinDuration), - p.Duration(parameters.HoldOffTunnelMaxDuration)) + HoldOffTunnelProtocolDuration = prng.Period( + p.Duration(parameters.HoldOffTunnelProtocolMinDuration), + p.Duration(parameters.HoldOffTunnelProtocolMaxDuration)) } } @@ -1056,7 +1056,7 @@ func MakeDialParameters( // Use the longest hold off duration dialParams.HoldOffTunnelDuration = common.MaxDuration( - holdOffTunnelDuration, + HoldOffTunnelProtocolDuration, HoldOffFrontingTunnelDuration, holdOffDirectTunnelDuration, holdOffInproxyTunnelDuration) diff --git a/psiphon/dialParameters_test.go b/psiphon/dialParameters_test.go index 89acd9765..347913ae3 100644 --- a/psiphon/dialParameters_test.go +++ b/psiphon/dialParameters_test.go @@ -80,7 +80,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { t.Fatalf("error committing configuration file: %s", err) } - holdOffTunnelProtocols := protocol.TunnelProtocols{protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH} + holdOffTunnelProtocolNames := protocol.TunnelProtocols{protocol.TUNNEL_PROTOCOL_OBFUSCATED_SSH} providerID := prng.HexString(8) frontingProviderID := prng.HexString(8) @@ -99,10 +99,10 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { applyParameters := make(map[string]interface{}) applyParameters[parameters.TransformHostNameProbability] = 1.0 applyParameters[parameters.PickUserAgentProbability] = 1.0 - applyParameters[parameters.HoldOffTunnelMinDuration] = "1ms" - applyParameters[parameters.HoldOffTunnelMaxDuration] = "10ms" - applyParameters[parameters.HoldOffTunnelProtocols] = holdOffTunnelProtocols - applyParameters[parameters.HoldOffTunnelProbability] = 1.0 + applyParameters[parameters.HoldOffTunnelProtocolMinDuration] = "1ms" + applyParameters[parameters.HoldOffTunnelProtocolMaxDuration] = "10ms" + applyParameters[parameters.HoldOffTunnelProtocolNames] = holdOffTunnelProtocolNames + applyParameters[parameters.HoldOffTunnelProtocolProbability] = 1.0 applyParameters[parameters.HoldOffFrontingTunnelMinDuration] = "1ms" applyParameters[parameters.HoldOffFrontingTunnelMaxDuration] = "10ms" applyParameters[parameters.HoldOffFrontingTunnelProviderIDs] = []string{frontingProviderID} @@ -258,7 +258,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { t.Fatalf("missing API request fields") } - expectHoldOffTunnelProtocols := common.Contains(holdOffTunnelProtocols, tunnelProtocol) + expectHoldOffTunnelProtocolNames := common.Contains(holdOffTunnelProtocolNames, tunnelProtocol) expectHoldOffFrontingTunnelProviderIDs := protocol.TunnelProtocolUsesFrontedMeek(tunnelProtocol) expectHoldOffDirectTunnelProviderRegion := protocol.TunnelProtocolIsDirect(tunnelProtocol) && common.ContainsAny( @@ -269,7 +269,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { holdOffInproxyTunnelProviderRegions[dialParams.ServerEntry.ProviderID], []string{"", dialParams.ServerEntry.Region}) - if expectHoldOffTunnelProtocols || + if expectHoldOffTunnelProtocolNames || expectHoldOffFrontingTunnelProviderIDs || expectHoldOffDirectTunnelProviderRegion || expectHoldOffInproxyTunnelProviderRegion { From fa8eef7ef6179fa02343212d01c5eda80f26b355 Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 26 Nov 2024 12:54:34 -0500 Subject: [PATCH 5/6] Fix type --- psiphon/common/parameters/parameters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index c4638990a..2c297d56b 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -841,7 +841,7 @@ var defaultParameters = map[string]struct { HoldOffInproxyTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, HoldOffInproxyTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, - HoldOffInproxyTunnelProviderRegions: {value: []string{}}, + HoldOffInproxyTunnelProviderRegions: {value: KeyStrings{}}, HoldOffInproxyTunnelProbability: {value: 0.0, minimum: 0.0}, RestrictInproxyProviderRegions: {value: KeyStrings{}}, From c128408660a817834d7c00d9ab052820a39227ae Mon Sep 17 00:00:00 2001 From: Miro Date: Tue, 26 Nov 2024 15:05:49 -0500 Subject: [PATCH 6/6] Re-add HoldOffTunnel for legacy clients --- psiphon/common/parameters/parameters.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index 2c297d56b..b0bae029e 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -310,6 +310,11 @@ const ( CustomHostNameRegexes = "CustomHostNameRegexes" CustomHostNameProbability = "CustomHostNameProbability" CustomHostNameLimitProtocols = "CustomHostNameLimitProtocols" + HoldOffTunnelMinDuration = "HoldOffTunnelMinDuration" + HoldOffTunnelMaxDuration = "HoldOffTunnelMaxDuration" + HoldOffTunnelProtocols = "HoldOffTunnelProtocols" + HoldOffTunnelFrontingProviderIDs = "HoldOffTunnelFrontingProviderIDs" + HoldOffTunnelProbability = "HoldOffTunnelProbability" HoldOffTunnelProtocolMinDuration = "HoldOffTunnelProtocolMinDuration" HoldOffTunnelProtocolMaxDuration = "HoldOffTunnelProtocolMaxDuration" HoldOffTunnelProtocolNames = "HoldOffTunnelProtocolNames" @@ -816,6 +821,12 @@ var defaultParameters = map[string]struct { CustomHostNameProbability: {value: 0.0, minimum: 0.0}, CustomHostNameLimitProtocols: {value: protocol.TunnelProtocols{}}, + HoldOffTunnelMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, + HoldOffTunnelProtocols: {value: protocol.TunnelProtocols{}}, + HoldOffTunnelFrontingProviderIDs: {value: []string{}}, + HoldOffTunnelProbability: {value: 0.0, minimum: 0.0}, + HoldOffTunnelProtocolMinDuration: {value: time.Duration(0), minimum: time.Duration(0)}, HoldOffTunnelProtocolMaxDuration: {value: time.Duration(0), minimum: time.Duration(0)}, HoldOffTunnelProtocolNames: {value: protocol.TunnelProtocols{}},