Merge changes Icf32cb46,I43434c80,I59dc46dc,I7df3f14b,I50f2c0e9

* changes:
  Don't process dup unwanted() when unregistering NetworkAgent
  Add/remove internal addresses from IpSecTunnelInterface
  Populate legacy type in VCN NetworkAgentConfig
  Add TRANSPORT_CELLULAR to VCN filter
  Allow soft-start and opportunistic safe mode
This commit is contained in:
Benedict Wong
2021-04-06 01:19:23 +00:00
committed by Gerrit Code Review
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)) {
final Vcn vcn = mVcns.get(subscriptionGroup);
final int status = vcn.getStatus();
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 {
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
/** Callback for Vcn signals sent up to VcnManagementService. */
public interface VcnCallback {
/** Called by a Vcn to signal that it has entered safe mode. */
void onEnteredSafeMode();
/** Called by a Vcn to signal that its safe mode status has changed. */
void onSafeModeStatusChanged(boolean isInSafeMode);
/** Called by a Vcn to signal that an error occurred. */
void onGatewayConnectionError(
@@ -1004,15 +995,18 @@ public class VcnManagementService extends IVcnManagementService.Stub {
}
@Override
public void onEnteredSafeMode() {
public void onSafeModeStatusChanged(boolean isInSafeMode) {
synchronized (mLock) {
// Ignore if this subscription group doesn't exist anymore
if (!mVcns.containsKey(mSubGroup)) {
return;
}
final int status =
isInSafeMode ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE;
notifyAllPolicyListenersLocked();
notifyAllPermissionedStatusCallbacksLocked(mSubGroup, VCN_STATUS_CODE_SAFE_MODE);
notifyAllPermissionedStatusCallbacksLocked(mSubGroup, status);
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.vcn;
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_INACTIVE;
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;
/**
* 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. */
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 ParcelUuid mSubscriptionGroup;
@NonNull private final Dependencies mDeps;
@@ -233,6 +238,11 @@ public class Vcn extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
if (mCurrentStatus != VCN_STATUS_CODE_ACTIVE
&& mCurrentStatus != VCN_STATUS_CODE_SAFE_MODE) {
return;
}
switch (msg.what) {
case MSG_EVENT_CONFIG_UPDATED:
handleConfigUpdated((VcnConfig) msg.obj);
@@ -246,12 +256,12 @@ public class Vcn extends Handler {
case MSG_EVENT_GATEWAY_CONNECTION_QUIT:
handleGatewayConnectionQuit((VcnGatewayConnectionConfig) msg.obj);
break;
case MSG_EVENT_SAFE_MODE_STATE_CHANGED:
handleSafeModeStatusChanged();
break;
case MSG_CMD_TEARDOWN:
handleTeardown();
break;
case MSG_CMD_ENTER_SAFE_MODE:
handleEnterSafeMode();
break;
default:
Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
}
@@ -263,40 +273,28 @@ public class Vcn extends Handler {
mConfig = config;
// TODO(b/183174340): Remove this once opportunistic safe mode is supported.
if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) {
// VCN is already active - teardown any GatewayConnections whose configs have been
// removed and get all current requests
for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
mVcnGatewayConnections.entrySet()) {
final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
final VcnGatewayConnection gatewayConnection = entry.getValue();
// Teardown any GatewayConnections whose configs have been removed and get all current
// requests
for (final Entry<VcnGatewayConnectionConfig, VcnGatewayConnection> entry :
mVcnGatewayConnections.entrySet()) {
final VcnGatewayConnectionConfig gatewayConnectionConfig = entry.getKey();
final VcnGatewayConnection gatewayConnection = entry.getValue();
// GatewayConnectionConfigs must match exactly (otherwise authentication or
// connection details may have changed).
if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
if (gatewayConnection == null) {
Slog.wtf(
getLogTag(),
"Found gatewayConnectionConfig without GatewayConnection");
} else {
gatewayConnection.teardownAsynchronously();
}
// GatewayConnectionConfigs must match exactly (otherwise authentication or
// connection details may have changed).
if (!mConfig.getGatewayConnectionConfigs().contains(gatewayConnectionConfig)) {
if (gatewayConnection == null) {
Slog.wtf(
getLogTag(), "Found gatewayConnectionConfig without GatewayConnection");
} else {
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() {
@@ -309,21 +307,27 @@ public class Vcn extends Handler {
mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
}
private void handleEnterSafeMode() {
// TODO(b/183174340): Remove this once opportunistic-safe-mode is supported
handleTeardown();
private void handleSafeModeStatusChanged() {
boolean hasSafeModeGatewayConnection = false;
mCurrentStatus = VCN_STATUS_CODE_SAFE_MODE;
mVcnCallback.onEnteredSafeMode();
// If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode
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(
@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 (VDBG) {
Slog.v(
@@ -376,25 +380,23 @@ public class Vcn extends Handler {
mVcnGatewayConnections.remove(config);
// 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
if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) {
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
}
// start a new GatewayConnection). VCN is always alive here, courtesy of the liveness check
// in handleMessage()
mVcnContext.getVcnNetworkProvider().resendAllRequests(mRequestListener);
}
private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
mLastSnapshot = snapshot;
if (mCurrentStatus == VCN_STATUS_CODE_ACTIVE) {
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
}
for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
}
}
private boolean isRequestSatisfiedByGatewayConnectionConfig(
@NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
builder.addTransportType(TRANSPORT_CELLULAR);
builder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
for (int cap : config.getAllExposedCapabilities()) {
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. */
@VisibleForTesting(visibility = Visibility.PACKAGE)
public interface VcnGatewayStatusCallback {
/** Called by a VcnGatewayConnection to indicate that it has entered safe mode. */
void onEnteredSafeMode();
/** Called by a VcnGatewayConnection to indicate that it's safe mode status has changed. */
void onSafeModeStatusChanged();
/** Callback by a VcnGatewayConnection to indicate that an error occurred. */
void onGatewayConnectionError(
@@ -445,8 +447,8 @@ public class Vcn extends Handler {
}
@Override
public void onEnteredSafeMode() {
sendMessage(obtainMessage(MSG_CMD_ENTER_SAFE_MODE));
public void onSafeModeStatusChanged() {
sendMessage(obtainMessage(MSG_EVENT_SAFE_MODE_STATE_CHANGED));
}
@Override

View File

@@ -32,6 +32,7 @@ import static com.android.server.VcnManagementService.VDBG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.IpSecManager;
@@ -44,6 +45,7 @@ import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
@@ -92,6 +94,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* 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;
/**
* 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.
*
@@ -562,8 +574,7 @@ public class VcnGatewayConnection extends StateMachine {
* <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
* otherwise.
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
NetworkAgent mNetworkAgent;
private NetworkAgent mNetworkAgent;
@Nullable private WakeupMessage mTeardownTimeoutAlarm;
@Nullable private WakeupMessage mDisconnectRequestAlarm;
@@ -628,6 +639,14 @@ public class VcnGatewayConnection extends StateMachine {
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.
*
@@ -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) {
Slog.d(TAG, String.format(
"Unexpected event code %d in state %s", what, this.getClass().getSimpleName()));
@@ -1315,8 +1343,7 @@ public class VcnGatewayConnection extends StateMachine {
}
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
mSafeModeTimeoutAlarm = null;
handleSafeModeTimeoutExceeded();
break;
default:
logUnhandledMessage(msg);
@@ -1401,8 +1428,7 @@ public class VcnGatewayConnection extends StateMachine {
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
mSafeModeTimeoutAlarm = null;
handleSafeModeTimeoutExceeded();
break;
default:
logUnhandledMessage(msg);
@@ -1432,30 +1458,35 @@ public class VcnGatewayConnection extends StateMachine {
buildNetworkCapabilities(mConnectionConfig, mUnderlying);
final LinkProperties lp =
buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
final NetworkAgentConfig nac =
new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_MOBILE)
.build();
final NetworkAgent agent =
new NetworkAgent(
mVcnContext.getContext(),
mVcnContext.getLooper(),
mDeps.newNetworkAgent(
mVcnContext,
TAG,
caps,
lp,
Vcn.getNetworkScore(),
new NetworkAgentConfig.Builder().build(),
mVcnContext.getVcnNetworkProvider()) {
@Override
public void onNetworkUnwanted() {
Slog.d(TAG, "NetworkAgent was unwanted");
teardownAsynchronously();
}
@Override
public void onValidationStatus(int status, @Nullable Uri redirectUri) {
if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
clearFailedAttemptCounterAndSafeModeAlarm();
}
}
};
nac,
mVcnContext.getVcnNetworkProvider(),
() -> {
Slog.d(TAG, "NetworkAgent was unwanted");
// If network agent has already been torn down, skip sending the
// disconnect. Unwanted() is always called, even when networkAgents
// are unregistered in teardownNetwork(), so prevent duplicate
// notifications.
if (mNetworkAgent != null) {
teardownAsynchronously();
}
} /* networkUnwantedCallback */,
(status) -> {
if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
clearFailedAttemptCounterAndSafeModeAlarm();
}
} /* validationStatusCallback */);
agent.register();
agent.markConnected();
@@ -1469,6 +1500,11 @@ public class VcnGatewayConnection extends StateMachine {
// Validated connection, clear failed attempt counter
mFailedAttempts = 0;
cancelSafeModeAlarm();
if (mIsInSafeMode) {
mIsInSafeMode = false;
mGatewayStatusCallback.onSafeModeStatusChanged();
}
}
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(
int token,
@NonNull IpSecTunnelInterface tunnelIface,
@@ -1579,16 +1608,17 @@ public class VcnGatewayConnection extends StateMachine {
transformCreatedInfo.direction);
break;
case EVENT_SETUP_COMPLETED:
final VcnChildSessionConfiguration oldChildConfig = mChildConfig;
mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig;
setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig);
setupInterfaceAndNetworkAgent(
mCurrentToken, mTunnelIface, mChildConfig, oldChildConfig);
break;
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
mSafeModeTimeoutAlarm = null;
handleSafeModeTimeoutExceeded();
break;
default:
logUnhandledMessage(msg);
@@ -1626,8 +1656,9 @@ public class VcnGatewayConnection extends StateMachine {
protected void setupInterfaceAndNetworkAgent(
int token,
@NonNull IpSecTunnelInterface tunnelIface,
@NonNull VcnChildSessionConfiguration childConfig) {
setupInterface(token, tunnelIface, childConfig);
@NonNull VcnChildSessionConfiguration childConfig,
@NonNull VcnChildSessionConfiguration oldChildConfig) {
setupInterface(token, tunnelIface, childConfig, oldChildConfig);
if (mNetworkAgent == null) {
mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
@@ -1692,8 +1723,7 @@ public class VcnGatewayConnection extends StateMachine {
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
mSafeModeTimeoutAlarm = null;
handleSafeModeTimeoutExceeded();
break;
default:
logUnhandledMessage(msg);
@@ -1934,6 +1964,16 @@ public class VcnGatewayConnection extends StateMachine {
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)
void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldQuit) {
sendMessageAndAcquireWakeLock(
@@ -2018,6 +2058,38 @@ public class VcnGatewayConnection extends StateMachine {
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. */
public long getElapsedRealTime() {
return SystemClock.elapsedRealtime();

View File

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

View File

@@ -32,6 +32,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
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.verifyNoMoreInteractions;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
@@ -58,19 +61,29 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/** Tests for VcnGatewayConnection.ConnectedState */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
private VcnIkeSession mIkeSession;
private NetworkAgent mNetworkAgent;
@Before
public void setUp() throws Exception {
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);
mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
@@ -159,21 +172,44 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
}
@Test
public void testChildOpenedRegistersNetwork() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
private void triggerChildOpened() {
triggerChildOpened(Collections.singletonList(TEST_INTERNAL_ADDR), TEST_DNS_ADDR);
}
private void triggerChildOpened(List<LinkAddress> internalAddresses, InetAddress dnsAddress) {
final VcnChildSessionConfiguration mMockChildSessionConfig =
mock(VcnChildSessionConfiguration.class);
doReturn(Collections.singletonList(TEST_INTERNAL_ADDR))
.when(mMockChildSessionConfig)
.getInternalAddresses();
doReturn(Collections.singletonList(TEST_DNS_ADDR))
doReturn(internalAddresses).when(mMockChildSessionConfig).getInternalAddresses();
doReturn(Collections.singletonList(dnsAddress))
.when(mMockChildSessionConfig)
.getInternalDnsServers();
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();
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
@@ -182,15 +218,20 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
ArgumentCaptor.forClass(LinkProperties.class);
final ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnMgr)
.registerNetworkAgent(
any(),
any(),
lpCaptor.capture(),
verify(mDeps)
.newNetworkAgent(
eq(mVcnContext),
any(String.class),
ncCaptor.capture(),
lpCaptor.capture(),
anyInt(),
argThat(nac -> nac.getLegacyType() == ConnectivityManager.TYPE_MOBILE),
any(),
any(),
anyInt());
any());
verify(mNetworkAgent).register();
verify(mNetworkAgent).markConnected();
verify(mIpSecSvc)
.addAddressToTunnelInterface(
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
// canceled
mGatewayConnection.mNetworkAgent.onValidationStatus(
NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */);
triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID);
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

View File

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

View File

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

View File

@@ -96,8 +96,9 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
}
@Test
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
public void testSafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent() {
verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent(
mGatewayConnection.mRetryTimeoutState);
}
@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 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.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -42,6 +44,7 @@ import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.ChildSessionCallback;
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 InetAddress TEST_DNS_ADDR =
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 =
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_RESOURCE_ID = 1;
@@ -267,7 +276,12 @@ public class VcnGatewayConnectionTestBase {
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
// state)
final Runnable delayedEvent =
@@ -275,7 +289,11 @@ public class VcnGatewayConnectionTestBase {
delayedEvent.run();
mTestLooper.dispatchAll();
verify(mGatewayStatusCallback).onEnteredSafeMode();
verify(mGatewayStatusCallback).onSafeModeStatusChanged();
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_INTERNET;
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_INACTIVE;
import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
@@ -54,7 +53,6 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -136,6 +134,7 @@ public class VcnTest {
private void startVcnGatewayWithCapabilities(
NetworkRequestListener requestListener, int... netCapabilities) {
final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
requestBuilder.addTransportType(TRANSPORT_CELLULAR);
for (final int netCapability : netCapabilities) {
requestBuilder.addCapability(netCapability);
}
@@ -160,8 +159,7 @@ public class VcnTest {
mTestLooper.dispatchAll();
for (final VcnGatewayConnection gateway : gatewayConnections) {
verify(gateway, status == VCN_STATUS_CODE_ACTIVE ? times(1) : never())
.updateSubscriptionSnapshot(eq(updatedSnapshot));
verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot));
}
}
@@ -202,32 +200,53 @@ public class VcnTest {
private void verifySafeMode(
NetworkRequestListener requestListener,
Set<VcnGatewayConnection> expectedGatewaysTornDown) {
assertEquals(VCN_STATUS_CODE_SAFE_MODE, mVcn.getStatus());
for (final VcnGatewayConnection gatewayConnection : expectedGatewaysTornDown) {
verify(gatewayConnection).teardownAsynchronously();
Set<VcnGatewayConnection> activeGateways,
boolean expectInSafeMode) {
for (VcnGatewayConnection gatewayConnection : activeGateways) {
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
public void testGatewayEnteringSafeModeNotifiesVcn() {
public void testGatewayEnteringAndExitingSafeModeNotifiesVcn() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections =
startGatewaysAndGetGatewayConnections(requestListener);
// Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
// all Gateways
// Doesn't matter which callback this gets, or which VCN is in safe mode - any Gateway
// entering Safemode should trigger safe mode
final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
statusCallback.onEnteredSafeMode();
final VcnGatewayConnection gatewayConnection = gatewayConnections.iterator().next();
doReturn(true).when(gatewayConnection).isInSafeMode();
statusCallback.onSafeModeStatusChanged();
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
public void testGatewayQuit() {
private void verifyGatewayQuit(int status) {
mVcn.setStatus(status);
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections =
new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
@@ -240,7 +259,7 @@ public class VcnTest {
assertEquals(1, mVcn.getVcnGatewayConnections().size());
verify(mVcnNetworkProvider).resendAllRequests(requestListener);
// Verify that the VcnGatewayConnection is restarted
// Verify that the VcnGatewayConnection is restarted if a request exists for it
triggerVcnRequestListeners(requestListener);
mTestLooper.dispatchAll();
assertEquals(2, mVcn.getVcnGatewayConnections().size());
@@ -254,21 +273,13 @@ public class VcnTest {
}
@Test
public void testGatewayQuitWhileInactive() {
final NetworkRequestListener requestListener = verifyAndGetRequestListener();
final Set<VcnGatewayConnection> gatewayConnections =
new ArraySet<>(startGatewaysAndGetGatewayConnections(requestListener));
public void testGatewayQuitReevaluatesRequests() {
verifyGatewayQuit(VCN_STATUS_CODE_ACTIVE);
}
mVcn.teardownAsynchronously();
mTestLooper.dispatchAll();
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
public void testGatewayQuitReevaluatesRequestsInSafeMode() {
verifyGatewayQuit(VCN_STATUS_CODE_SAFE_MODE);
}
@Test
@@ -298,49 +309,4 @@ public class VcnTest {
verify(removedGatewayConnection).teardownAsynchronously();
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());
}
}