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

Merge pull request #701 from rod-hynes/network-changed

Add NetworkChanged
Rod Hynes 1 год назад
Родитель
Сommit
50a143a493

+ 3 - 1
MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java

@@ -208,7 +208,9 @@ public class PsiphonTunnel {
             @Override
             public void onChanged() {
                 try {
-                    reconnectPsiphon();
+                    // networkChanged initiates a reset of all open network
+                    // connections, including a tunnel reconnect.
+                    Psi.networkChanged();
                 } catch (Exception e) {
                     mHostService.onDiagnosticMessage("reconnect error: " + e);
                 }

+ 7 - 4
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m

@@ -1549,11 +1549,14 @@ typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
 
         previousNetworkStatus = atomic_exchange(&self->currentNetworkStatus, networkStatus);
 
-        // Restart if the network status or interface has changed, unless the previous status was
-        // NetworkReachabilityNotReachable, because the tunnel should be waiting for connectivity in
-        // that case.
+        // Signal when the network status or interface has changed, unless the
+        // previous status was NetworkReachabilityNotReachable, because the
+        // tunnel should be waiting for connectivity in that case.
+        //
+        // GoPsiNetworkChanged initiates a reset of all open network
+        // connections, including a tunnel reconnect.
         if ((networkStatus != previousNetworkStatus || interfaceChanged) && previousNetworkStatus != NetworkReachabilityNotReachable) {
-            GoPsiReconnectTunnel();
+            GoPsiNetworkChanged();
         }
     }
 }

+ 12 - 0
MobileLibrary/psi/psi.go

@@ -280,6 +280,18 @@ func ReconnectTunnel() {
 	}
 }
 
+// NetworkChanged initiates a reset of all open network connections, including
+// a tunnel reconnect.
+func NetworkChanged() {
+
+	controllerMutex.Lock()
+	defer controllerMutex.Unlock()
+
+	if controller != nil {
+		controller.NetworkChanged()
+	}
+}
+
 // SetDynamicConfig overrides the sponsor ID and authorizations fields set in
 // the config passed to Start. SetDynamicConfig has no effect if no Controller
 // is started.

+ 7 - 0
psiphon/common/inproxy/inproxy_test.go

@@ -91,6 +91,9 @@ func runTestInproxy(doMustUpgrade bool) error {
 	testNewTacticsTag := "new-tactics-tag"
 	testUnchangedTacticsPayload := []byte(prng.HexString(100))
 
+	currentNetworkCtx, currentNetworkCancelFunc := context.WithCancel(context.Background())
+	defer currentNetworkCancelFunc()
+
 	// TODO: test port mapping
 
 	stunServerAddressSucceededCount := int32(0)
@@ -438,6 +441,10 @@ func runTestInproxy(doMustUpgrade bool) error {
 				return true
 			},
 
+			GetCurrentNetworkContext: func() context.Context {
+				return currentNetworkCtx
+			},
+
 			GetBrokerClient: func() (*BrokerClient, error) {
 				return brokerClient, nil
 			},

+ 16 - 0
psiphon/common/inproxy/proxy.go

@@ -87,6 +87,14 @@ type ProxyConfig struct {
 	// there is network connectivity, and false for shutdown.
 	WaitForNetworkConnectivity func() bool
 
+	// GetCurrentNetworkContext is a callback that returns a context tied to
+	// the lifetime of the host's current active network interface. If the
+	// active network changes, the previous context returned by
+	// GetCurrentNetworkContext should cancel. This context is used to
+	// immediately cancel/close individual connections when the active
+	// network changes.
+	GetCurrentNetworkContext func() context.Context
+
 	// GetBrokerClient provides a BrokerClient which the proxy will use for
 	// making broker requests. If GetBrokerClient returns a shared
 	// BrokerClient instance, the BrokerClient must support multiple,
@@ -510,6 +518,14 @@ func (p *Proxy) proxyOneClient(
 	logAnnounce func() bool,
 	signalAnnounceDone func()) (bool, error) {
 
+	// Cancel/close this connection immediately if the network changes.
+	if p.config.GetCurrentNetworkContext != nil {
+		var cancelFunc context.CancelFunc
+		ctx, cancelFunc = common.MergeContextCancel(
+			ctx, p.config.GetCurrentNetworkContext())
+		defer cancelFunc()
+	}
+
 	// Do not trigger back-off unless the proxy successfully announces and
 	// only then performs poorly.
 	//

+ 51 - 0
psiphon/controller.go

@@ -101,6 +101,10 @@ type Controller struct {
 	inproxyLastStoredTactics                time.Time
 	establishSignalForceTacticsFetch        chan struct{}
 	inproxyClientDialRateLimiter            *rate.Limiter
+
+	currentNetworkMutex      sync.Mutex
+	currentNetworkCtx        context.Context
+	currentNetworkCancelFunc context.CancelFunc
 }
 
 // NewController initializes a new controller.
@@ -177,6 +181,18 @@ func NewController(config *Config) (controller *Controller, err error) {
 		quicTLSClientSessionCache: tls.NewLRUClientSessionCache(0),
 	}
 
+	// Initialize the current network context. This context represents the
+	// lifetime of the host's current active network interface. When
+	// Controller.NetworkChanged is called (by the Android and iOS platform
+	// code), the previous current network interface is considered to be no
+	// longer active and the corresponding current network context is canceled.
+	// Components may use currentNetworkCtx to cancel and close old network
+	// connections and quickly initiate new connections when the active
+	// interface changes.
+
+	controller.currentNetworkCtx, controller.currentNetworkCancelFunc =
+		context.WithCancel(context.Background())
+
 	// Initialize untunneledDialConfig, used by untunneled dials including
 	// remote server list and upgrade downloads.
 	controller.untunneledDialConfig = &DialConfig{
@@ -411,6 +427,9 @@ func (controller *Controller) Run(ctx context.Context) {
 		controller.packetTunnelClient.Stop()
 	}
 
+	// Cleanup current network context
+	controller.currentNetworkCancelFunc()
+
 	// All workers -- runTunnels, establishment workers, and auxilliary
 	// workers such as fetch remote server list and untunneled uprade
 	// download -- operate with the controller run context and will all
@@ -437,6 +456,37 @@ func (controller *Controller) SetDynamicConfig(sponsorID string, authorizations
 	controller.config.SetDynamicConfig(sponsorID, authorizations)
 }
 
+// NetworkChanged initiates a reset of all open network connections, including
+// a tunnel reconnect, if one is running, as well as terminating any in-proxy
+// proxy connections.
+func (controller *Controller) NetworkChanged() {
+
+	// Explicitly reset components that don't use the current network context.
+	controller.TerminateNextActiveTunnel()
+	if controller.inproxyProxyBrokerClientManager != nil {
+		controller.inproxyProxyBrokerClientManager.NetworkChanged()
+	}
+	controller.inproxyClientBrokerClientManager.NetworkChanged()
+
+	controller.currentNetworkMutex.Lock()
+	defer controller.currentNetworkMutex.Unlock()
+
+	// Cancel the previous current network context, which will interrupt any
+	// operations using this context.
+	controller.currentNetworkCancelFunc()
+
+	// Create a new context for the new current network.
+	controller.currentNetworkCtx, controller.currentNetworkCancelFunc =
+		context.WithCancel(context.Background())
+}
+
+func (controller *Controller) getCurrentNetworkContext() context.Context {
+	controller.currentNetworkMutex.Lock()
+	defer controller.currentNetworkMutex.Unlock()
+
+	return controller.currentNetworkCtx
+}
+
 // TerminateNextActiveTunnel terminates the active tunnel, which will initiate
 // establishment of a new tunnel.
 func (controller *Controller) TerminateNextActiveTunnel() {
@@ -2936,6 +2986,7 @@ func (controller *Controller) runInproxyProxy() {
 		Logger:                        NoticeCommonLogger(debugLogging),
 		EnableWebRTCDebugLogging:      debugLogging,
 		WaitForNetworkConnectivity:    controller.inproxyWaitForNetworkConnectivity,
+		GetCurrentNetworkContext:      controller.getCurrentNetworkContext,
 		GetBrokerClient:               controller.inproxyGetProxyBrokerClient,
 		GetBaseAPIParameters:          controller.inproxyGetProxyAPIParameters,
 		MakeWebRTCDialCoordinator:     controller.inproxyMakeProxyWebRTCDialCoordinator,

+ 19 - 1
psiphon/inproxy.go

@@ -114,6 +114,22 @@ func (b *InproxyBrokerClientManager) TacticsApplied() error {
 	return errors.Trace(b.reset(resetBrokerClientReasonTacticsApplied))
 }
 
+// NetworkChanged is called when the active network changes, to trigger a
+// broker client reset.
+func (b *InproxyBrokerClientManager) NetworkChanged() error {
+
+	b.mutex.Lock()
+	defer b.mutex.Unlock()
+
+	// Don't reset when not yet initialized; b.brokerClientInstance is
+	// initialized only on demand.
+	if b.brokerClientInstance == nil {
+		return nil
+	}
+
+	return errors.Trace(b.reset(resetBrokerClientReasonNetworkChanged))
+}
+
 // GetBrokerClient returns the current, shared broker client and its
 // corresponding dial parametrers (for metrics logging). If there is no
 // current broker client, if the network ID differs from the network ID
@@ -195,6 +211,7 @@ type resetBrokerClientReason int
 const (
 	resetBrokerClientReasonInit resetBrokerClientReason = iota + 1
 	resetBrokerClientReasonTacticsApplied
+	resetBrokerClientReasonNetworkChanged
 	resetBrokerClientReasonRoundTripperFailed
 	resetBrokerClientReasonRoundNoMatch
 )
@@ -220,7 +237,8 @@ func (b *InproxyBrokerClientManager) reset(reason resetBrokerClientReason) error
 
 	switch reason {
 	case resetBrokerClientReasonInit,
-		resetBrokerClientReasonTacticsApplied:
+		resetBrokerClientReasonTacticsApplied,
+		resetBrokerClientReasonNetworkChanged:
 		b.brokerSelectCount = 0
 
 	case resetBrokerClientReasonRoundTripperFailed,