Skip to content

Commit

Permalink
Merge pull request #701 from rod-hynes/network-changed
Browse files Browse the repository at this point in the history
Add NetworkChanged
  • Loading branch information
rod-hynes authored Nov 21, 2024
2 parents 5c857bb + 5735128 commit 50a143a
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 6 deletions.
4 changes: 3 additions & 1 deletion MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,9 @@ private PsiphonTunnel(HostService hostService) {
@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);
}
Expand Down
11 changes: 7 additions & 4 deletions MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m
Original file line number Diff line number Diff line change
Expand Up @@ -1549,11 +1549,14 @@ - (void)internetReachabilityChanged:(NSNotification *)note {

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();
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions MobileLibrary/psi/psi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions psiphon/common/inproxy/inproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -438,6 +441,10 @@ func runTestInproxy(doMustUpgrade bool) error {
return true
},

GetCurrentNetworkContext: func() context.Context {
return currentNetworkCtx
},

GetBrokerClient: func() (*BrokerClient, error) {
return brokerClient, nil
},
Expand Down
16 changes: 16 additions & 0 deletions psiphon/common/inproxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
//
Expand Down
51 changes: 51 additions & 0 deletions psiphon/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand 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() {
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 19 additions & 1 deletion psiphon/inproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -195,6 +211,7 @@ type resetBrokerClientReason int
const (
resetBrokerClientReasonInit resetBrokerClientReason = iota + 1
resetBrokerClientReasonTacticsApplied
resetBrokerClientReasonNetworkChanged
resetBrokerClientReasonRoundTripperFailed
resetBrokerClientReasonRoundNoMatch
)
Expand All @@ -220,7 +237,8 @@ func (b *InproxyBrokerClientManager) reset(reason resetBrokerClientReason) error

switch reason {
case resetBrokerClientReasonInit,
resetBrokerClientReasonTacticsApplied:
resetBrokerClientReasonTacticsApplied,
resetBrokerClientReasonNetworkChanged:
b.brokerSelectCount = 0

case resetBrokerClientReasonRoundTripperFailed,
Expand Down

0 comments on commit 50a143a

Please sign in to comment.