From f1ae9bb228599883916b8990ad00cf1a4fcb96c5 Mon Sep 17 00:00:00 2001 From: Rod Hynes Date: Mon, 4 Nov 2024 11:39:41 -0500 Subject: [PATCH] Add tactics parameters for in-proxy incompatible network types - Default to no incompatible network types - Ignore incompatible network types in personal pairing modes - Fix proxy personal pairing mode checks, which incorrectly checked the client mode --- psiphon/common/parameters/parameters.go | 4 +++ psiphon/config.go | 23 ++++++++++++-- psiphon/controller.go | 42 ++++++++++++++++--------- psiphon/dialParameters.go | 16 ++++++++-- psiphon/inproxy.go | 10 +++--- psiphon/utils.go | 10 ------ 6 files changed, 69 insertions(+), 36 deletions(-) diff --git a/psiphon/common/parameters/parameters.go b/psiphon/common/parameters/parameters.go index 5bb6309cd..f23e2e6f3 100644 --- a/psiphon/common/parameters/parameters.go +++ b/psiphon/common/parameters/parameters.go @@ -458,6 +458,8 @@ const ( InproxyFrontingProviderClientMaxRequestTimeouts = "InproxyFrontingProviderClientMaxRequestTimeouts" InproxyFrontingProviderServerMaxRequestTimeouts = "InproxyFrontingProviderServerMaxRequestTimeouts" InproxyProxyOnBrokerClientFailedRetryPeriod = "InproxyProxyOnBrokerClientFailedRetryPeriod" + InproxyProxyIncompatibleNetworkTypes = "InproxyProxyIncompatibleNetworkTypes" + InproxyClientIncompatibleNetworkTypes = "InproxyClientIncompatibleNetworkTypes" // Retired parameters @@ -975,6 +977,8 @@ var defaultParameters = map[string]struct { InproxyFrontingProviderClientMaxRequestTimeouts: {value: KeyDurations{}}, InproxyFrontingProviderServerMaxRequestTimeouts: {value: KeyDurations{}, flags: serverSideOnly}, InproxyProxyOnBrokerClientFailedRetryPeriod: {value: 30 * time.Second, minimum: time.Duration(0)}, + InproxyProxyIncompatibleNetworkTypes: {value: []string{}}, + InproxyClientIncompatibleNetworkTypes: {value: []string{}}, } // IsServerSideOnly indicates if the parameter specified by name is used diff --git a/psiphon/config.go b/psiphon/config.go index 9a0ce900a..990874106 100755 --- a/psiphon/config.go +++ b/psiphon/config.go @@ -1039,6 +1039,8 @@ type Config struct { InproxyClientNoMatchFailoverPersonalProbability *float64 InproxyFrontingProviderClientMaxRequestTimeouts map[string]string InproxyProxyOnBrokerClientFailedRetryPeriodMilliseconds *int + InproxyProxyIncompatibleNetworkTypes []string + InproxyClientIncompatibleNetworkTypes []string InproxySkipAwaitFullyConnected bool InproxyEnableWebRTCDebugLogging bool @@ -1805,13 +1807,20 @@ func (config *Config) SetSignalComponentFailure(signalComponentFailure func()) { config.signalComponentFailure.Store(signalComponentFailure) } -// IsInproxyPersonalPairingMode indicates that the client is in in-proxy +// IsInproxyClientPersonalPairingMode indicates that the client is in in-proxy // personal pairing mode, where connections are made only through in-proxy // proxies with the corresponding personal compartment ID. -func (config *Config) IsInproxyPersonalPairingMode() bool { +func (config *Config) IsInproxyClientPersonalPairingMode() bool { return len(config.InproxyClientPersonalCompartmentID) > 0 } +// IsInproxyProxyPersonalPairingMode indicates that the proxy is in in-proxy +// personal pairing mode, where connections are made only with in-proxy +// clients with the corresponding personal compartment ID. +func (config *Config) IsInproxyProxyPersonalPairingMode() bool { + return len(config.InproxyProxyPersonalCompartmentID) > 0 +} + // OnInproxyMustUpgrade is invoked when the in-proxy broker returns the // MustUpgrade response. When either running a proxy, or when running a // client in personal-pairing mode -- two states that require in-proxy @@ -1823,7 +1832,7 @@ func (config *Config) OnInproxyMustUpgrade() { // protocols; this is another case where in-proxy functionality is // required. - if config.InproxyEnableProxy || config.IsInproxyPersonalPairingMode() { + if config.InproxyEnableProxy || config.IsInproxyClientPersonalPairingMode() { if atomic.CompareAndSwapInt32(&config.inproxyMustUpgradePosted, 0, 1) { NoticeInproxyMustUpgrade() } @@ -2692,6 +2701,14 @@ func (config *Config) makeConfigParameters() map[string]interface{} { applyParameters[parameters.InproxyProxyOnBrokerClientFailedRetryPeriod] = fmt.Sprintf("%dms", *config.InproxyProxyOnBrokerClientFailedRetryPeriodMilliseconds) } + if len(config.InproxyProxyIncompatibleNetworkTypes) > 0 { + applyParameters[parameters.InproxyProxyIncompatibleNetworkTypes] = config.InproxyProxyIncompatibleNetworkTypes + } + + if len(config.InproxyClientIncompatibleNetworkTypes) > 0 { + applyParameters[parameters.InproxyClientIncompatibleNetworkTypes] = config.InproxyClientIncompatibleNetworkTypes + } + // When adding new config dial parameters that may override tactics, also // update setDialParametersHash. diff --git a/psiphon/controller.go b/psiphon/controller.go index 7c0962674..8fe5676f1 100644 --- a/psiphon/controller.go +++ b/psiphon/controller.go @@ -1612,7 +1612,7 @@ func (p *protocolSelectionConstraints) selectProtocol( // // TODO: replace token on fast failure that doesn't reach the broker? - if p.config.IsInproxyPersonalPairingMode() || + if p.config.IsInproxyClientPersonalPairingMode() || p.getLimitTunnelProtocols(connectTunnelCount).IsOnlyInproxyTunnelProtocols() { // Check for missing in-proxy broker request requirements before @@ -1625,7 +1625,7 @@ func (p *protocolSelectionConstraints) selectProtocol( NoticeInfo("in-proxy protocol selection failed: no broker specs") return "", 0, false } - if !p.config.IsInproxyPersonalPairingMode() && + if !p.config.IsInproxyClientPersonalPairingMode() && !haveInproxyCommonCompartmentIDs(p.config) { NoticeInfo("in-proxy protocol selection failed: no common compartment IDs") return "", 0, false @@ -1897,7 +1897,7 @@ func (controller *Controller) launchEstablishing() { // corresponding personal compartment ID, so non-in-proxy tunnel // protocols are disabled. - if controller.config.IsInproxyPersonalPairingMode() { + if controller.config.IsInproxyClientPersonalPairingMode() { if len(controller.protocolSelectionConstraints.initialLimitTunnelProtocols) > 0 { controller.protocolSelectionConstraints.initialLimitTunnelProtocols = @@ -1928,7 +1928,7 @@ func (controller *Controller) launchEstablishing() { // announcement consumption for personal proxies. var workerPoolSize int - if controller.config.IsInproxyPersonalPairingMode() { + if controller.config.IsInproxyClientPersonalPairingMode() { workerPoolSize = p.Int(parameters.InproxyPersonalPairingConnectionWorkerPoolSize) } else { workerPoolSize = p.Int(parameters.ConnectionWorkerPoolSize) @@ -2360,7 +2360,7 @@ loop: controller.establishConnectTunnelCount).IsOnlyInproxyTunnelProtocols() controller.concurrentEstablishTunnelsMutex.Unlock() - if limitInproxyOnly || controller.config.IsInproxyPersonalPairingMode() { + if limitInproxyOnly || controller.config.IsInproxyClientPersonalPairingMode() { // Simply sleep and poll for any imported server entries; // perform one sleep after HasServerEntries, in order to give @@ -2562,7 +2562,7 @@ loop: // tuning/limiting in-proxy usage independent of // LimitTunnelProtocol targeting. - onlyInproxy := controller.config.IsInproxyPersonalPairingMode() + onlyInproxy := controller.config.IsInproxyClientPersonalPairingMode() includeInproxy := onlyInproxy || prng.FlipWeightedCoin(inproxySelectionProbability) selectedProtocol, rateLimitDelay, ok := controller.protocolSelectionConstraints.selectProtocol( @@ -3079,16 +3079,28 @@ func (controller *Controller) inproxyAwaitProxyBrokerSpecs() bool { func (controller *Controller) inproxyWaitForNetworkConnectivity() bool { - // Pause announcing proxies when currently running on an incompatible - // network, such as a non-Psiphon VPN. - emitted := false - isCompatibleNetwork := func() bool { - compatibleNetwork := IsInproxyCompatibleNetworkType(controller.config.GetNetworkID()) - if !compatibleNetwork && !emitted { - NoticeInfo("inproxy proxy: waiting due to incompatible network") - emitted = true + var isCompatibleNetwork func() bool + emittedIncompatibleNetworkNotice := false + + if !controller.config.IsInproxyProxyPersonalPairingMode() { + + // Pause announcing proxies when currently running on an incompatible + // network, such as a non-Psiphon VPN. + + p := controller.config.GetParameters().Get() + incompatibleNetworkTypes := p.Strings(parameters.InproxyProxyIncompatibleNetworkTypes) + p.Close() + + isCompatibleNetwork = func() bool { + compatibleNetwork := !common.Contains( + incompatibleNetworkTypes, + GetNetworkType(controller.config.GetNetworkID())) + if !compatibleNetwork && !emittedIncompatibleNetworkNotice { + NoticeInfo("inproxy proxy: waiting due to incompatible network") + emittedIncompatibleNetworkNotice = true + } + return compatibleNetwork } - return compatibleNetwork } return WaitForNetworkConnectivity( diff --git a/psiphon/dialParameters.go b/psiphon/dialParameters.go index 3e7e9425c..135ca8a5b 100644 --- a/psiphon/dialParameters.go +++ b/psiphon/dialParameters.go @@ -1099,7 +1099,7 @@ func MakeDialParameters( isFronted := protocol.TunnelProtocolUsesFrontedMeek(dialParams.TunnelProtocol) params, err := makeHTTPTransformerParameters( - config.GetParameters().Get(), serverEntry.FrontingProviderID, isFronted) + p, serverEntry.FrontingProviderID, isFronted) if err != nil { return nil, errors.Trace(err) } @@ -1124,8 +1124,18 @@ func MakeDialParameters( // MakeDialParameters, such as in selectProtocol during iteration, // checking here uses the network ID obtained in MakeDialParameters, // and the logged warning is useful for diagnostics. - if !IsInproxyCompatibleNetworkType(dialParams.NetworkID) { - return nil, errors.TraceNew("inproxy protocols skipped on incompatible network") + // + // This check is skipped when in-proxy protocols must be used. + if !config.IsInproxyClientPersonalPairingMode() && + !p.TunnelProtocols(parameters.LimitTunnelProtocols).IsOnlyInproxyTunnelProtocols() { + + incompatibleNetworkTypes := p.Strings(parameters.InproxyClientIncompatibleNetworkTypes) + compatibleNetwork := !common.Contains( + incompatibleNetworkTypes, + GetNetworkType(dialParams.NetworkID)) + if !compatibleNetwork { + return nil, errors.TraceNew("inproxy protocols skipped on incompatible network") + } } // inproxyDialInitialized indicates that the inproxy dial was wired diff --git a/psiphon/inproxy.go b/psiphon/inproxy.go index a75b37c9c..ec00f023c 100644 --- a/psiphon/inproxy.go +++ b/psiphon/inproxy.go @@ -180,7 +180,7 @@ func (b *InproxyBrokerClientManager) resetBrokerClientOnNoMatch( defer p.Close() probability := parameters.InproxyClientNoMatchFailoverProbability - if b.config.IsInproxyPersonalPairingMode() { + if b.config.IsInproxyClientPersonalPairingMode() { probability = parameters.InproxyClientNoMatchFailoverPersonalProbability } if !p.WeightedCoinFlip(probability) { @@ -531,7 +531,7 @@ func NewInproxyBrokerClientInstance( replayUpdateFrequency: p.Duration(parameters.InproxyReplayBrokerUpdateFrequency), } - if isProxy && !config.IsInproxyPersonalPairingMode() { + if isProxy && !config.IsInproxyProxyPersonalPairingMode() { // This retry is applied only for proxies and only in common pairing // mode. See comment in BrokerClientRoundTripperFailed. b.retryOnFailedPeriod = p.Duration(parameters.InproxyProxyOnBrokerClientFailedRetryPeriod) @@ -596,7 +596,7 @@ func getInproxyBrokerSpecs( isProxy bool) parameters.InproxyBrokerSpecsValue { if isProxy { - if config.IsInproxyPersonalPairingMode() { + if config.IsInproxyProxyPersonalPairingMode() { return p.InproxyBrokerSpecs( parameters.InproxyProxyPersonalPairingBrokerSpecs, parameters.InproxyPersonalPairingBrokerSpecs, @@ -608,7 +608,7 @@ func getInproxyBrokerSpecs( parameters.InproxyBrokerSpecs) } } else { - if config.IsInproxyPersonalPairingMode() { + if config.IsInproxyClientPersonalPairingMode() { return p.InproxyBrokerSpecs( parameters.InproxyClientPersonalPairingBrokerSpecs, parameters.InproxyPersonalPairingBrokerSpecs, @@ -915,7 +915,7 @@ func (b *InproxyBrokerClientInstance) BrokerClientRoundTripperFailed(roundTrippe // briefly unavailable. if b.brokerClientManager.isProxy && - !b.config.IsInproxyPersonalPairingMode() && + !b.config.IsInproxyProxyPersonalPairingMode() && b.retryOnFailedPeriod > 0 && !b.lastSuccess.IsZero() && time.Since(b.lastSuccess) <= b.retryOnFailedPeriod { diff --git a/psiphon/utils.go b/psiphon/utils.go index 9a3b2d1bc..b45635abe 100755 --- a/psiphon/utils.go +++ b/psiphon/utils.go @@ -299,13 +299,3 @@ func GetNetworkType(networkID string) string { } return "UNKNOWN" } - -// IsInproxyCompatibleNetworkType indicates if the network type for the given -// network ID is compatible with in-proxy operation. -func IsInproxyCompatibleNetworkType(networkID string) bool { - - // When the network type is "VPN", the outer client (or MobileLibrary) has - // detected that some other, non-Psiphon VPN is active. In this case, - // most in-proxy operations are expected to fail. - return GetNetworkType(networkID) != "VPN" -}