Merge changes Icf32cb46,I43434c80,I59dc46dc,I7df3f14b,I50f2c0e9 am: e253ce3463 am: 9083a4cf04

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1664800

Change-Id: Ide383ab76488b6d60f9349bfa9efd5264cea167c
This commit is contained in:
Benedict Wong
2021-04-06 01:52:11 +00:00
committed by Automerger Merge Worker
10 changed files with 410 additions and 241 deletions

View File

@@ -542,16 +542,7 @@ public class VcnManagementService extends IVcnManagementService.Stub {
if (mVcns.containsKey(subscriptionGroup)) { if (mVcns.containsKey(subscriptionGroup)) {
final Vcn vcn = mVcns.get(subscriptionGroup); final Vcn vcn = mVcns.get(subscriptionGroup);
final int status = vcn.getStatus();
vcn.updateConfig(config); vcn.updateConfig(config);
// TODO(b/183174340): Remove this once opportunistic-safe-mode is supported
// Only notify VcnStatusCallbacks if this VCN was previously in Safe Mode
if (status == VCN_STATUS_CODE_SAFE_MODE) {
// TODO(b/181789060): invoke asynchronously after Vcn notifies through VcnCallback
notifyAllPermissionedStatusCallbacksLocked(
subscriptionGroup, VCN_STATUS_CODE_ACTIVE);
}
} else { } else {
startVcnLocked(subscriptionGroup, config); startVcnLocked(subscriptionGroup, config);
} }
@@ -941,8 +932,8 @@ public class VcnManagementService extends IVcnManagementService.Stub {
// TODO(b/180452282): Make name more generic and implement directly with VcnManagementService // TODO(b/180452282): Make name more generic and implement directly with VcnManagementService
/** Callback for Vcn signals sent up to VcnManagementService. */ /** Callback for Vcn signals sent up to VcnManagementService. */
public interface VcnCallback { public interface VcnCallback {
/** Called by a Vcn to signal that it has entered safe mode. */ /** Called by a Vcn to signal that its safe mode status has changed. */
void onEnteredSafeMode(); void onSafeModeStatusChanged(boolean isInSafeMode);
/** Called by a Vcn to signal that an error occurred. */ /** Called by a Vcn to signal that an error occurred. */
void onGatewayConnectionError( void onGatewayConnectionError(
@@ -1004,15 +995,18 @@ public class VcnManagementService extends IVcnManagementService.Stub {
} }
@Override @Override
public void onEnteredSafeMode() { public void onSafeModeStatusChanged(boolean isInSafeMode) {
synchronized (mLock) { synchronized (mLock) {
// Ignore if this subscription group doesn't exist anymore // Ignore if this subscription group doesn't exist anymore
if (!mVcns.containsKey(mSubGroup)) { if (!mVcns.containsKey(mSubGroup)) {
return; return;
} }
final int status =
isInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
notifyAllPolicyListenersLocked(); notifyAllPolicyListenersLocked();
notifyAllPermissionedStatusCallbacksLocked(mSubGroup, VCN_STATUS_CODE_SAFE_MODE); notifyAllPermissionedStatusCallbacksLocked(mSubGroup, status);
} }
} }

View File

@@ -17,6 +17,7 @@
package com.android.server.vcn; package com.android.server.vcn;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
@@ -96,17 +97,21 @@ public class Vcn extends Handler {
*/ */
private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3; private static final int MSG_EVENT_GATEWAY_CONNECTION_QUIT = MSG_EVENT_BASE + 3;
/**
* Triggers reevaluation of safe mode conditions.
*
* <p>Upon entering safe mode, the VCN will only provide gateway connections opportunistically,
* leaving the underlying networks marked as NOT_VCN_MANAGED.
*
* <p>Any VcnGatewayConnection in safe mode will result in the entire Vcn instance being put
* into safe mode. Upon receiving this message, the Vcn MUST query all VcnGatewayConnections to
* determine if any are in safe mode.
*/
private static final int MSG_EVENT_SAFE_MODE_STATE_CHANGED = MSG_EVENT_BASE + 4;
/** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
/**
* Causes this VCN to immediately enter safe mode.
*
* <p>Upon entering safe mode, the VCN will unregister its RequestListener, tear down all of its
* VcnGatewayConnections, and notify VcnManagementService that it is in safe mode.
*/
private static final int MSG_CMD_ENTER_SAFE_MODE = MSG_CMD_BASE + 1;
@NonNull private final VcnContext mVcnContext; @NonNull private final VcnContext mVcnContext;
@NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final ParcelUuid mSubscriptionGroup;
@NonNull private final Dependencies mDeps; @NonNull private final Dependencies mDeps;
@@ -233,6 +238,11 @@ public class Vcn extends Handler {
@Override @Override
public void handleMessage(@NonNull Message msg) { public void handleMessage(@NonNull Message msg) {
if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
&& mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
return;
}
switch (msg.what) { switch (msg.what) {
case MSG_EVENT_CONFIG_UPDATED: case MSG_EVENT_CONFIG_UPDATED:
handleConfigUpdated((VcnConfig) msg.obj); handleConfigUpdated((VcnConfig) msg.obj);
@@ -246,12 +256,12 @@ public class Vcn extends Handler {
case MSG_EVENT_GATEWAY_CONNECTION_QUIT: case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj); handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
break; break;
case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
handleSafeModeStatusChanged();
break;
case MSG_CMD_TEARDOWN: case MSG_CMD_TEARDOWN:
handleTeardown(); handleTeardown();
break; break;
case MSG_CMD_ENTER_SAFE_MODE:
handleEnterSafeMode();
break;
default: default:
Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what); Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
} }
@@ -263,40 +273,28 @@ public class Vcn extends Handler {
mConfig = config; mConfig = config;
// TODO(b/183174340): Remove this once opportunistic safe mode is supported. // Teardown any GatewayConnections whose configs have been removed and get all current
if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) { // requests
// VCN is already active - teardown any GatewayConnections whose configs have been for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
// removed and get all current requests mVcnGatewayConnections.entrySet()) {
for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry : final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
mVcnGatewayConnections.entrySet()) { final VcnGatewayConnection gatewayConnection = entry.getValue();
final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
final VcnGatewayConnection gatewayConnection = entry.getValue();
// GatewayConnectionConfigs must match exactly (otherwise authentication or // GatewayConnectionConfigs must match exactly (otherwise authentication or
// connection details may have changed). // connection details may have changed).
if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) { if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
if (gatewayConnection == null) { if (gatewayConnection == null) {
Slog.wtf( Slog.wtf(
getLogTag(), getLogTag(), "Found gatewayConnectionConfig without GatewayConnection");
"Found gatewayConnectionConfig without GatewayConnection"); } else {
} else { gatewayConnection.teardownAsynchronously();
gatewayConnection.teardownAsynchronously();
}
} }
} }
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
// satisfied start a new GatewayConnection)
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
} else if (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE) {
// If this VCN was not previously active, it is exiting Safe Mode. Re-register the
// request listener to get NetworkRequests again (and all cached requests).
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
} else {
// Ignored; VCN was not active; config updates ignored.
return;
} }
mCurrentStatus = VCN_STATUS_CODE_ACTIVE;
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be
// satisfied start a new GatewayConnection)
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
} }
private void handleTeardown() { private void handleTeardown() {
@@ -309,21 +307,27 @@ public class Vcn extends Handler {
mCurrentStatus = VCN_STATUS_CODE_INACTIVE; mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
} }
private void handleEnterSafeMode() { private void handleSafeModeStatusChanged() {
// TODO(b/183174340): Remove this once opportunistic-safe-mode is supported boolean hasSafeModeGatewayConnection = false;
handleTeardown();
mCurrentStatus = VCN_STATUS_CODE_SAFE_MODE; // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
mVcnCallback.onEnteredSafeMode(); for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
if (gatewayConnection.isInSafeMode()) {
hasSafeModeGatewayConnection = true;
break;
}
}
final int oldStatus = mCurrentStatus;
mCurrentStatus =
hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
if (oldStatus != mCurrentStatus) {
mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection);
}
} }
private void handleNetworkRequested( private void handleNetworkRequested(
@NonNull NetworkRequest request, int score, int providerId) { @NonNull NetworkRequest request, int score, int providerId) {
if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE) {
Slog.v(getLogTag(), "Received NetworkRequest while inactive. Ignore for now");
return;
}
if (score > getNetworkScore()) { if (score > getNetworkScore()) {
if (VDBG) { if (VDBG) {
Slog.v( Slog.v(
@@ -376,25 +380,23 @@ public class Vcn extends Handler {
mVcnGatewayConnections.remove(config); mVcnGatewayConnections.remove(config);
// Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied // Trigger a re-evaluation of all NetworkRequests (to make sure any that can be satisfied
// start a new GatewayConnection), but only if the Vcn is still alive // start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) { // in handleMessage()
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener); mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
}
} }
private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
mLastSnapshot = snapshot; mLastSnapshot = snapshot;
if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) { for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
}
} }
} }
private boolean isRequestSatisfiedByGatewayConnectionConfig( private boolean isRequestSatisfiedByGatewayConnectionConfig(
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
builder.addTransportType(TRANSPORT_CELLULAR);
builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
for (int cap : config.getAllExposedCapabilities()) { for (int cap : config.getAllExposedCapabilities()) {
builder.addCapability(cap); builder.addCapability(cap);
@@ -418,8 +420,8 @@ public class Vcn extends Handler {
/** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */ /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
@VisibleForTesting(visibility = Visibility.PACKAGE) @VisibleForTesting(visibility = Visibility.PACKAGE)
public interface VcnGatewayStatusCallback { public interface VcnGatewayStatusCallback {
/** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */ /** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
void onEnteredSafeMode(); void onSafeModeStatusChanged();
/** Callback by a VcnGatewayConnection to indicate that an error occurred. */ /** Callback by a VcnGatewayConnection to indicate that an error occurred. */
void onGatewayConnectionError( void onGatewayConnectionError(
@@ -445,8 +447,8 @@ public class Vcn extends Handler {
} }
@Override @Override
public void onEnteredSafeMode() { public void onSafeModeStatusChanged() {
sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE)); sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
} }
@Override @Override

View File

@@ -32,6 +32,7 @@ import static com.android.server.VcnManagementService.VDBG;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.net.ConnectivityManager;
import android.net.InetAddresses; import android.net.InetAddresses;
import android.net.IpPrefix; import android.net.IpPrefix;
import android.net.IpSecManager; import android.net.IpSecManager;
@@ -44,6 +45,7 @@ import android.net.Network;
import android.net.NetworkAgent; import android.net.NetworkAgent;
import android.net.NetworkAgentConfig; import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier; import android.net.TelephonyNetworkSpecifier;
import android.net.Uri; import android.net.Uri;
@@ -92,6 +94,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/** /**
* A single VCN Gateway Connection, providing a single public-facing VCN network. * A single VCN Gateway Connection, providing a single public-facing VCN network.
@@ -503,6 +506,15 @@ public class VcnGatewayConnection extends StateMachine {
*/ */
private boolean mIsQuitting = false; private boolean mIsQuitting = false;
/**
* Whether the VcnGatewayConnection is in safe mode.
*
* <p>Upon hitting the safe mode timeout, this will be set to {@code true}. In safe mode, this
* VcnGatewayConnection will continue attempting to connect, and if a successful connection is
* made, safe mode will be exited.
*/
private boolean mIsInSafeMode = false;
/** /**
* The token used by the primary/current/active session. * The token used by the primary/current/active session.
* *
@@ -562,8 +574,7 @@ public class VcnGatewayConnection extends StateMachine {
* <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
* otherwise. * otherwise.
*/ */
@VisibleForTesting(visibility = Visibility.PRIVATE) private NetworkAgent mNetworkAgent;
NetworkAgent mNetworkAgent;
@Nullable private WakeupMessage mTeardownTimeoutAlarm; @Nullable private WakeupMessage mTeardownTimeoutAlarm;
@Nullable private WakeupMessage mDisconnectRequestAlarm; @Nullable private WakeupMessage mDisconnectRequestAlarm;
@@ -628,6 +639,14 @@ public class VcnGatewayConnection extends StateMachine {
start(); start();
} }
/** Queries whether this VcnGatewayConnection is in safe mode. */
public boolean isInSafeMode() {
// Accessing internal state; must only be done on looper thread.
mVcnContext.ensureRunningOnLooperThread();
return mIsInSafeMode;
}
/** /**
* Asynchronously tears down this GatewayConnection, and any resources used. * Asynchronously tears down this GatewayConnection, and any resources used.
* *
@@ -1162,6 +1181,15 @@ public class VcnGatewayConnection extends StateMachine {
} }
} }
protected void handleSafeModeTimeoutExceeded() {
mSafeModeTimeoutAlarm = null;
// Connectivity for this GatewayConnection is broken; tear down the Network.
teardownNetwork();
mIsInSafeMode = true;
mGatewayStatusCallback.onSafeModeStatusChanged();
}
protected void logUnexpectedEvent(int what) { protected void logUnexpectedEvent(int what) {
Slog.d(TAG, String.format( Slog.d(TAG, String.format(
"Unexpected event code %d in state %s", what, this.getClass().getSimpleName())); "Unexpected event code %d in state %s", what, this.getClass().getSimpleName()));
@@ -1315,8 +1343,7 @@ public class VcnGatewayConnection extends StateMachine {
} }
break; break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode(); handleSafeModeTimeoutExceeded();
mSafeModeTimeoutAlarm = null;
break; break;
default: default:
logUnhandledMessage(msg); logUnhandledMessage(msg);
@@ -1401,8 +1428,7 @@ public class VcnGatewayConnection extends StateMachine {
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break; break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode(); handleSafeModeTimeoutExceeded();
mSafeModeTimeoutAlarm = null;
break; break;
default: default:
logUnhandledMessage(msg); logUnhandledMessage(msg);
@@ -1432,30 +1458,35 @@ public class VcnGatewayConnection extends StateMachine {
buildNetworkCapabilities(mConnectionConfig, mUnderlying); buildNetworkCapabilities(mConnectionConfig, mUnderlying);
final LinkProperties lp = final LinkProperties lp =
buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
final NetworkAgentConfig nac =
new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_MOBILE)
.build();
final NetworkAgent agent = final NetworkAgent agent =
new NetworkAgent( mDeps.newNetworkAgent(
mVcnContext.getContext(), mVcnContext,
mVcnContext.getLooper(),
TAG, TAG,
caps, caps,
lp, lp,
Vcn.getNetworkScore(), Vcn.getNetworkScore(),
new NetworkAgentConfig.Builder().build(), nac,
mVcnContext.getVcnNetworkProvider()) { mVcnContext.getVcnNetworkProvider(),
@Override () -> {
public void onNetworkUnwanted() { Slog.d(TAG, "NetworkAgent was unwanted");
Slog.d(TAG, "NetworkAgent was unwanted"); // If network agent has already been torn down, skip sending the
teardownAsynchronously(); // disconnect. Unwanted() is always called, even when networkAgents
} // are unregistered in teardownNetwork(), so prevent duplicate
// notifications.
@Override if (mNetworkAgent != null) {
public void onValidationStatus(int status, @Nullable Uri redirectUri) { teardownAsynchronously();
if (status == NetworkAgent.VALIDATION_STATUS_VALID) { }
clearFailedAttemptCounterAndSafeModeAlarm(); } /* networkUnwantedCallback */,
} (status) -> {
} if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
}; clearFailedAttemptCounterAndSafeModeAlarm();
}
} /* validationStatusCallback */);
agent.register(); agent.register();
agent.markConnected(); agent.markConnected();
@@ -1469,6 +1500,11 @@ public class VcnGatewayConnection extends StateMachine {
// Validated connection, clear failed attempt counter // Validated connection, clear failed attempt counter
mFailedAttempts = 0; mFailedAttempts = 0;
cancelSafeModeAlarm(); cancelSafeModeAlarm();
if (mIsInSafeMode) {
mIsInSafeMode = false;
mGatewayStatusCallback.onSafeModeStatusChanged();
}
} }
protected void applyTransform( protected void applyTransform(
@@ -1488,13 +1524,6 @@ public class VcnGatewayConnection extends StateMachine {
} }
} }
protected void setupInterface(
int token,
@NonNull IpSecTunnelInterface tunnelIface,
@NonNull VcnChildSessionConfiguration childConfig) {
setupInterface(token, tunnelIface, childConfig, null);
}
protected void setupInterface( protected void setupInterface(
int token, int token,
@NonNull IpSecTunnelInterface tunnelIface, @NonNull IpSecTunnelInterface tunnelIface,
@@ -1579,16 +1608,17 @@ public class VcnGatewayConnection extends StateMachine {
transformCreatedInfo.direction); transformCreatedInfo.direction);
break; break;
case EVENT_SETUP_COMPLETED: case EVENT_SETUP_COMPLETED:
final VcnChildSessionConfiguration oldChildConfig = mChildConfig;
mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig; mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig;
setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); setupInterfaceAndNetworkAgent(
mCurrentToken, mTunnelIface, mChildConfig, oldChildConfig);
break; break;
case EVENT_DISCONNECT_REQUESTED: case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break; break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode(); handleSafeModeTimeoutExceeded();
mSafeModeTimeoutAlarm = null;
break; break;
default: default:
logUnhandledMessage(msg); logUnhandledMessage(msg);
@@ -1626,8 +1656,9 @@ public class VcnGatewayConnection extends StateMachine {
protected void setupInterfaceAndNetworkAgent( protected void setupInterfaceAndNetworkAgent(
int token, int token,
@NonNull IpSecTunnelInterface tunnelIface, @NonNull IpSecTunnelInterface tunnelIface,
@NonNull VcnChildSessionConfiguration childConfig) { @NonNull VcnChildSessionConfiguration childConfig,
setupInterface(token, tunnelIface, childConfig); @NonNull VcnChildSessionConfiguration oldChildConfig) {
setupInterface(token, tunnelIface, childConfig, oldChildConfig);
if (mNetworkAgent == null) { if (mNetworkAgent == null) {
mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
@@ -1692,8 +1723,7 @@ public class VcnGatewayConnection extends StateMachine {
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj); handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break; break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode(); handleSafeModeTimeoutExceeded();
mSafeModeTimeoutAlarm = null;
break; break;
default: default:
logUnhandledMessage(msg); logUnhandledMessage(msg);
@@ -1934,6 +1964,16 @@ public class VcnGatewayConnection extends StateMachine {
mIkeSession = session; mIkeSession = session;
} }
@VisibleForTesting(visibility = Visibility.PRIVATE)
NetworkAgent getNetworkAgent() {
return mNetworkAgent;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
void setNetworkAgent(@Nullable NetworkAgent networkAgent) {
mNetworkAgent = networkAgent;
}
@VisibleForTesting(visibility = Visibility.PRIVATE) @VisibleForTesting(visibility = Visibility.PRIVATE)
void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) { void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
sendMessageAndAcquireWakeLock( sendMessageAndAcquireWakeLock(
@@ -2018,6 +2058,38 @@ public class VcnGatewayConnection extends StateMachine {
return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable); return new WakeupMessage(vcnContext.getContext(), handler, tag, runnable);
} }
/** Builds a new NetworkAgent. */
public NetworkAgent newNetworkAgent(
@NonNull VcnContext vcnContext,
@NonNull String tag,
@NonNull NetworkCapabilities caps,
@NonNull LinkProperties lp,
@NonNull int score,
@NonNull NetworkAgentConfig nac,
@NonNull NetworkProvider provider,
@NonNull Runnable networkUnwantedCallback,
@NonNull Consumer<Integer> validationStatusCallback) {
return new NetworkAgent(
vcnContext.getContext(),
vcnContext.getLooper(),
tag,
caps,
lp,
score,
nac,
provider) {
@Override
public void onNetworkUnwanted() {
networkUnwantedCallback.run();
}
@Override
public void onValidationStatus(int status, @Nullable Uri redirectUri) {
validationStatusCallback.accept(status);
}
};
}
/** Gets the elapsed real time since boot, in millis. */ /** Gets the elapsed real time since boot, in millis. */
public long getElapsedRealTime() { public long getElapsedRealTime() {
return SystemClock.elapsedRealtime(); return SystemClock.elapsedRealtime();

View File

@@ -536,17 +536,6 @@ public class VcnManagementServiceTest {
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE); verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
} }
@Test
public void testSetVcnConfigInSafeModeNotifiesStatusCallback() throws Exception {
setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */);
mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME);
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_SAFE_MODE);
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
}
@Test @Test
public void testClearVcnConfigRequiresNonSystemServer() throws Exception { public void testClearVcnConfigRequiresNonSystemServer() throws Exception {
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid(); doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
@@ -902,7 +891,9 @@ public class VcnManagementServiceTest {
} }
private void triggerVcnSafeMode( private void triggerVcnSafeMode(
@NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot) @NonNull ParcelUuid subGroup,
@NonNull TelephonySubscriptionSnapshot snapshot,
boolean isInSafeMode)
throws Exception { throws Exception {
verify(mMockDeps) verify(mMockDeps)
.newVcn( .newVcn(
@@ -913,22 +904,32 @@ public class VcnManagementServiceTest {
mVcnCallbackCaptor.capture()); mVcnCallbackCaptor.capture());
VcnCallback vcnCallback = mVcnCallbackCaptor.getValue(); VcnCallback vcnCallback = mVcnCallbackCaptor.getValue();
vcnCallback.onEnteredSafeMode(); vcnCallback.onSafeModeStatusChanged(isInSafeMode);
} }
@Test private void verifyVcnSafeModeChangesNotifiesPolicyListeners(boolean enterSafeMode)
public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception { throws Exception {
TelephonySubscriptionSnapshot snapshot = TelephonySubscriptionSnapshot snapshot =
triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
triggerVcnSafeMode(TEST_UUID_1, snapshot); triggerVcnSafeMode(TEST_UUID_1, snapshot, enterSafeMode);
verify(mMockPolicyListener).onPolicyChanged(); verify(mMockPolicyListener).onPolicyChanged();
} }
private void triggerVcnStatusCallbackOnEnteredSafeMode( @Test
public void testVcnEnteringSafeModeNotifiesPolicyListeners() throws Exception {
verifyVcnSafeModeChangesNotifiesPolicyListeners(true /* enterSafeMode */);
}
@Test
public void testVcnExitingSafeModeNotifiesPolicyListeners() throws Exception {
verifyVcnSafeModeChangesNotifiesPolicyListeners(false /* enterSafeMode */);
}
private void triggerVcnStatusCallbackOnSafeModeStatusChanged(
@NonNull ParcelUuid subGroup, @NonNull ParcelUuid subGroup,
@NonNull String pkgName, @NonNull String pkgName,
int uid, int uid,
@@ -951,12 +952,13 @@ public class VcnManagementServiceTest {
mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName); mVcnMgmtSvc.registerVcnStatusCallback(subGroup, mMockStatusCallback, pkgName);
triggerVcnSafeMode(subGroup, snapshot); triggerVcnSafeMode(subGroup, snapshot, true /* enterSafeMode */);
} }
@Test @Test
public void testVcnStatusCallbackOnEnteredSafeModeWithCarrierPrivileges() throws Exception { public void testVcnStatusCallbackOnSafeModeStatusChangedWithCarrierPrivileges()
triggerVcnStatusCallbackOnEnteredSafeMode( throws Exception {
triggerVcnStatusCallbackOnSafeModeStatusChanged(
TEST_UUID_1, TEST_UUID_1,
TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
TEST_UID, TEST_UID,
@@ -967,8 +969,9 @@ public class VcnManagementServiceTest {
} }
@Test @Test
public void testVcnStatusCallbackOnEnteredSafeModeWithoutCarrierPrivileges() throws Exception { public void testVcnStatusCallbackOnSafeModeStatusChangedWithoutCarrierPrivileges()
triggerVcnStatusCallbackOnEnteredSafeMode( throws Exception {
triggerVcnStatusCallbackOnSafeModeStatusChanged(
TEST_UUID_1, TEST_UUID_1,
TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
TEST_UID, TEST_UID,
@@ -980,8 +983,9 @@ public class VcnManagementServiceTest {
} }
@Test @Test
public void testVcnStatusCallbackOnEnteredSafeModeWithoutLocationPermission() throws Exception { public void testVcnStatusCallbackOnSafeModeStatusChangedWithoutLocationPermission()
triggerVcnStatusCallbackOnEnteredSafeMode( throws Exception {
triggerVcnStatusCallbackOnSafeModeStatusChanged(
TEST_UUID_1, TEST_UUID_1,
TEST_PACKAGE_NAME, TEST_PACKAGE_NAME,
TEST_UID, TEST_UID,

View File

@@ -32,6 +32,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -40,6 +41,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.NetworkAgent; import android.net.NetworkAgent;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
@@ -58,19 +61,29 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/** Tests for VcnGatewayConnection.ConnectedState */ /** Tests for VcnGatewayConnection.ConnectedState */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase { public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
private VcnIkeSession mIkeSession; private VcnIkeSession mIkeSession;
private NetworkAgent mNetworkAgent;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
mNetworkAgent = mock(NetworkAgent.class);
doReturn(mNetworkAgent)
.when(mDeps)
.newNetworkAgent(any(), any(), any(), any(), anyInt(), any(), any(), any(), any());
mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network); mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
@@ -159,21 +172,44 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
} }
@Test private void triggerChildOpened() {
public void testChildOpenedRegistersNetwork() throws Exception { triggerChildOpened(Collections.singletonList(TEST_INTERNAL_ADDR), TEST_DNS_ADDR);
// Verify scheduled but not canceled when entering ConnectedState }
verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
private void triggerChildOpened(List<LinkAddress> internalAddresses, InetAddress dnsAddress) {
final VcnChildSessionConfiguration mMockChildSessionConfig = final VcnChildSessionConfiguration mMockChildSessionConfig =
mock(VcnChildSessionConfiguration.class); mock(VcnChildSessionConfiguration.class);
doReturn(Collections.singletonList(TEST_INTERNAL_ADDR)) doReturn(internalAddresses).when(mMockChildSessionConfig).getInternalAddresses();
.when(mMockChildSessionConfig) doReturn(Collections.singletonList(dnsAddress))
.getInternalAddresses();
doReturn(Collections.singletonList(TEST_DNS_ADDR))
.when(mMockChildSessionConfig) .when(mMockChildSessionConfig)
.getInternalDnsServers(); .getInternalDnsServers();
getChildSessionCallback().onOpened(mMockChildSessionConfig); getChildSessionCallback().onOpened(mMockChildSessionConfig);
}
private void triggerValidation(int status) {
final ArgumentCaptor<Consumer<Integer>> validationCallbackCaptor =
ArgumentCaptor.forClass(Consumer.class);
verify(mDeps)
.newNetworkAgent(
any(),
any(),
any(),
any(),
anyInt(),
any(),
any(),
any(),
validationCallbackCaptor.capture());
validationCallbackCaptor.getValue().accept(status);
}
@Test
public void testChildOpenedRegistersNetwork() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
triggerChildOpened();
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
@@ -182,15 +218,20 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
ArgumentCaptor.forClass(LinkProperties.class); ArgumentCaptor.forClass(LinkProperties.class);
final ArgumentCaptor<NetworkCapabilities> ncCaptor = final ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class); ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnMgr) verify(mDeps)
.registerNetworkAgent( .newNetworkAgent(
any(), eq(mVcnContext),
any(), any(String.class),
lpCaptor.capture(),
ncCaptor.capture(), ncCaptor.capture(),
lpCaptor.capture(),
anyInt(),
argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE),
any(), any(),
any(), any(),
anyInt()); any());
verify(mNetworkAgent).register();
verify(mNetworkAgent).markConnected();
verify(mIpSecSvc) verify(mIpSecSvc)
.addAddressToTunnelInterface( .addAddressToTunnelInterface(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(TEST_INTERNAL_ADDR), any()); eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(TEST_INTERNAL_ADDR), any());
@@ -208,9 +249,78 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
// Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is // Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is
// canceled // canceled
mGatewayConnection.mNetworkAgent.onValidationStatus( triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
verify(mSafeModeTimeoutAlarm).cancel(); verify(mSafeModeTimeoutAlarm).cancel();
assertFalse(mGatewayConnection.isInSafeMode());
}
@Test
public void testInternalAndDnsAddressesChanged() throws Exception {
final List<LinkAddress> startingInternalAddrs =
Arrays.asList(new LinkAddress[] {TEST_INTERNAL_ADDR, TEST_INTERNAL_ADDR_2});
triggerChildOpened(startingInternalAddrs, TEST_DNS_ADDR);
mTestLooper.dispatchAll();
for (LinkAddress addr : startingInternalAddrs) {
verify(mIpSecSvc)
.addAddressToTunnelInterface(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(addr), any());
}
verify(mDeps)
.newNetworkAgent(
any(),
any(),
any(),
argThat(
lp ->
startingInternalAddrs.equals(lp.getLinkAddresses())
&& Collections.singletonList(TEST_DNS_ADDR)
.equals(lp.getDnsServers())),
anyInt(),
any(),
any(),
any(),
any());
// Trigger another connection event, and verify that the addresses change
final List<LinkAddress> newInternalAddrs =
Arrays.asList(new LinkAddress[] {TEST_INTERNAL_ADDR_2, TEST_INTERNAL_ADDR_3});
triggerChildOpened(newInternalAddrs, TEST_DNS_ADDR_2);
mTestLooper.dispatchAll();
// Verify addresses on tunnel network added/removed
for (LinkAddress addr : newInternalAddrs) {
verify(mIpSecSvc)
.addAddressToTunnelInterface(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(addr), any());
}
verify(mIpSecSvc)
.removeAddressFromTunnelInterface(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(TEST_INTERNAL_ADDR), any());
// TODO(b/184579891): Also verify link properties updated and sent when sendLinkProperties
// is mockable
// Verify that IpSecTunnelInterface only created once
verify(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any());
verifyNoMoreInteractions(mIpSecSvc);
}
@Test
public void testSuccessfulConnectionExitsSafeMode() throws Exception {
verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
mGatewayConnection.mConnectedState);
assertTrue(mGatewayConnection.isInSafeMode());
assertFalse(mGatewayConnection.isQuitting());
triggerChildOpened();
mTestLooper.dispatchAll();
triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
assertFalse(mGatewayConnection.isInSafeMode());
} }
@Test @Test

View File

@@ -118,8 +118,9 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
} }
@Test @Test
public void testSafeModeTimeoutNotifiesCallback() { public void testSafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
mGatewayConnection.mConnectingState);
} }
@Test @Test

View File

@@ -86,8 +86,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
} }
@Test @Test
public void testSafeModeTimeoutNotifiesCallback() { public void testSafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
mGatewayConnection.mDisconnectingState);
} }
@Test @Test

View File

@@ -96,8 +96,9 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
} }
@Test @Test
public void testSafeModeTimeoutNotifiesCallback() { public void testSafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
mGatewayConnection.mRetryTimeoutState);
} }
@Test @Test

View File

@@ -21,6 +21,8 @@ import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
@@ -42,6 +44,7 @@ import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkAddress; import android.net.LinkAddress;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.Network; import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.IkeSessionCallback;
@@ -71,8 +74,14 @@ public class VcnGatewayConnectionTestBase {
protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID()); protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
protected static final InetAddress TEST_DNS_ADDR = protected static final InetAddress TEST_DNS_ADDR =
InetAddresses.parseNumericAddress("2001:DB8:0:1::"); InetAddresses.parseNumericAddress("2001:DB8:0:1::");
protected static final InetAddress TEST_DNS_ADDR_2 =
InetAddresses.parseNumericAddress("2001:DB8:0:2::");
protected static final LinkAddress TEST_INTERNAL_ADDR = protected static final LinkAddress TEST_INTERNAL_ADDR =
new LinkAddress(InetAddresses.parseNumericAddress("2001:DB8:0:2::"), 64); new LinkAddress(InetAddresses.parseNumericAddress("2001:DB8:1:1::"), 64);
protected static final LinkAddress TEST_INTERNAL_ADDR_2 =
new LinkAddress(InetAddresses.parseNumericAddress("2001:DB8:1:2::"), 64);
protected static final LinkAddress TEST_INTERNAL_ADDR_3 =
new LinkAddress(InetAddresses.parseNumericAddress("2001:DB8:1:3::"), 64);
protected static final int TEST_IPSEC_SPI_VALUE = 0x1234; protected static final int TEST_IPSEC_SPI_VALUE = 0x1234;
protected static final int TEST_IPSEC_SPI_RESOURCE_ID = 1; protected static final int TEST_IPSEC_SPI_RESOURCE_ID = 1;
@@ -267,7 +276,12 @@ public class VcnGatewayConnectionTestBase {
expectCanceled); expectCanceled);
} }
protected void verifySafeModeTimeoutNotifiesCallback(@NonNull State expectedState) { protected void verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
@NonNull State expectedState) {
// Set a NetworkAgent, and expect it to be unregistered and cleared
final NetworkAgent mockNetworkAgent = mock(NetworkAgent.class);
mGatewayConnection.setNetworkAgent(mockNetworkAgent);
// SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial // SafeMode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
// state) // state)
final Runnable delayedEvent = final Runnable delayedEvent =
@@ -275,7 +289,11 @@ public class VcnGatewayConnectionTestBase {
delayedEvent.run(); delayedEvent.run();
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
verify(mGatewayStatusCallback).onEnteredSafeMode(); verify(mGatewayStatusCallback).onSafeModeStatusChanged();
assertEquals(expectedState, mGatewayConnection.getCurrentState()); assertEquals(expectedState, mGatewayConnection.getCurrentState());
assertTrue(mGatewayConnection.isInSafeMode());
verify(mockNetworkAgent).unregister();
assertNull(mGatewayConnection.getNetworkAgent());
} }
} }

View File

@@ -19,14 +19,13 @@ package com.android.server.vcn;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
@@ -54,7 +53,6 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@@ -136,6 +134,7 @@ public class VcnTest {
private void startVcnGatewayWithCapabilities( private void startVcnGatewayWithCapabilities(
NetworkRequestListener requestListener, int... netCapabilities) { NetworkRequestListener requestListener, int... netCapabilities) {
final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder(); final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
requestBuilder.addTransportType(TRANSPORT_CELLULAR);
for (final int netCapability : netCapabilities) { for (final int netCapability : netCapabilities) {
requestBuilder.addCapability(netCapability); requestBuilder.addCapability(netCapability);
} }
@@ -160,8 +159,7 @@ public class VcnTest {
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
for (final VcnGatewayConnection gateway : gatewayConnections) { for (final VcnGatewayConnection gateway : gatewayConnections) {
verify(gateway, status == VCN_STATUS_CODE_ACTIVE ? times(1) : never()) verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot));
.updateSubscriptionSnapshot(eq(updatedSnapshot));
} }
} }
@@ -202,32 +200,53 @@ public class VcnTest {
private void verifySafeMode( private void verifySafeMode(
NetworkRequestListener requestListener, NetworkRequestListener requestListener,
Set<VcnGatewayConnection> expectedGatewaysTornDown) { Set<VcnGatewayConnection> activeGateways,
assertEquals(VCN_STATUS_CODE_SAFE_MODE, mVcn.getStatus()); boolean expectInSafeMode) {
for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) { for (VcnGatewayConnection gatewayConnection : activeGateways) {
verify(gatewayConnection).teardownAsynchronously(); verify(gatewayConnection, never()).teardownAsynchronously();
} }
verify(mVcnNetworkProvider).unregisterListener(requestListener);
verify(mVcnCallback).onEnteredSafeMode(); assertEquals(
expectInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE,
mVcn.getStatus());
verify(mVcnCallback).onSafeModeStatusChanged(expectInSafeMode);
} }
@Test @Test
public void testGatewayEnteringSafeModeNotifiesVcn() { public void testGatewayEnteringAndExitingSafeModeNotifiesVcn() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener(); final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections = final Set<VcnGatewayConnection> gatewayConnections =
startGatewaysAndGetGatewayConnections(requestListener); startGatewaysAndGetGatewayConnections(requestListener);
// Doesn't matter which callback this gets - any Gateway entering Safemode should shut down // Doesn't matter which callback this gets, or which VCN is in safe mode - any Gateway
// all Gateways // entering Safemode should trigger safe mode
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue(); final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
statusCallback.onEnteredSafeMode(); final VcnGatewayConnection gatewayConnection = gatewayConnections.iterator().next();
doReturn(true).when(gatewayConnection).isInSafeMode();
statusCallback.onSafeModeStatusChanged();
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
verifySafeMode(requestListener, gatewayConnections); verifySafeMode(requestListener, gatewayConnections, true /* expectInSafeMode */);
// Verify that when all GatewayConnections exit safe mode, the VCN also exits safe mode
doReturn(false).when(gatewayConnection).isInSafeMode();
statusCallback.onSafeModeStatusChanged();
mTestLooper.dispatchAll();
verifySafeMode(requestListener, gatewayConnections, false /* expectInSafeMode */);
// Re-trigger, verify safe mode callback does not get fired again for identical state
statusCallback.onSafeModeStatusChanged();
mTestLooper.dispatchAll();
// Expect only once still; from above.
verify(mVcnCallback).onSafeModeStatusChanged(false);
} }
@Test private void verifyGatewayQuit(int status) {
public void testGatewayQuit() { mVcn.setStatus(status);
final NetworkRequestListener requestListener = verifyAndGetRequestListener(); final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections = final Set<VcnGatewayConnection> gatewayConnections =
new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener)); new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
@@ -240,7 +259,7 @@ public class VcnTest {
assertEquals(1, mVcn.getVcnGatewayConnections().size()); assertEquals(1, mVcn.getVcnGatewayConnections().size());
verify(mVcnNetworkProvider).resendAllRequests(requestListener); verify(mVcnNetworkProvider).resendAllRequests(requestListener);
// Verify that the VcnGatewayConnection is restarted // Verify that the VcnGatewayConnection is restarted if a request exists for it
triggerVcnRequestListeners(requestListener); triggerVcnRequestListeners(requestListener);
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
assertEquals(2, mVcn.getVcnGatewayConnections().size()); assertEquals(2, mVcn.getVcnGatewayConnections().size());
@@ -254,21 +273,13 @@ public class VcnTest {
} }
@Test @Test
public void testGatewayQuitWhileInactive() { public void testGatewayQuitReevaluatesRequests() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener(); verifyGatewayQuit(VCN_STATUS_CODE_ACTIVE);
final Set<VcnGatewayConnection> gatewayConnections = }
new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
mVcn.teardownAsynchronously(); @Test
mTestLooper.dispatchAll(); public void testGatewayQuitReevaluatesRequestsInSafeMode() {
verifyGatewayQuit(VCN_STATUS_CODE_SAFE_MODE);
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
statusCallback.onQuit();
mTestLooper.dispatchAll();
// Verify that the VCN requests the networkRequests be resent
assertEquals(1, mVcn.getVcnGatewayConnections().size());
verify(mVcnNetworkProvider, never()).resendAllRequests(requestListener);
} }
@Test @Test
@@ -298,49 +309,4 @@ public class VcnTest {
verify(removedGatewayConnection).teardownAsynchronously(); verify(removedGatewayConnection).teardownAsynchronously();
verify(mVcnNetworkProvider).resendAllRequests(requestListener); verify(mVcnNetworkProvider).resendAllRequests(requestListener);
} }
@Test
public void testUpdateConfigExitsSafeMode() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections =
new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
statusCallback.onEnteredSafeMode();
mTestLooper.dispatchAll();
verifySafeMode(requestListener, gatewayConnections);
doAnswer(invocation -> {
final NetworkRequestListener listener = invocation.getArgument(0);
triggerVcnRequestListeners(listener);
return null;
}).when(mVcnNetworkProvider).registerListener(eq(requestListener));
mVcn.updateConfig(mConfig);
mTestLooper.dispatchAll();
// Registered on start, then re-registered with new configs
verify(mVcnNetworkProvider, times(2)).registerListener(eq(requestListener));
assertEquals(VCN_STATUS_CODE_ACTIVE, mVcn.getStatus());
for (final int[] caps : TEST_CAPS) {
// Expect each gateway connection created only on initial startup
verify(mDeps)
.newVcnGatewayConnection(
eq(mVcnContext),
eq(TEST_SUB_GROUP),
eq(mSubscriptionSnapshot),
argThat(config -> Arrays.equals(caps, config.getExposedCapabilities())),
any());
}
}
@Test
public void testIgnoreNetworkRequestWhileInactive() {
mVcn.setStatus(VCN_STATUS_CODE_INACTIVE);
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
triggerVcnRequestListeners(requestListener);
verify(mDeps, never()).newVcnGatewayConnection(any(), any(), any(), any(), any());
}
} }