Merge "Implement Safemode alarm in VcnGatewayConnection."

This commit is contained in:
Cody Kesting
2021-02-16 22:29:21 +00:00
committed by Gerrit Code Review
7 changed files with 176 additions and 6 deletions

View File

@@ -38,10 +38,12 @@ import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgent.ValidationStatus;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -151,6 +153,9 @@ public class VcnGatewayConnection extends StateMachine {
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM";
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM";
private static final int[] MERGED_CAPABILITIES =
new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
@@ -167,6 +172,9 @@ public class VcnGatewayConnection extends StateMachine {
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final int TEARDOWN_TIMEOUT_SECONDS = 5;
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final int SAFEMODE_TIMEOUT_SECONDS = 30;
private interface EventInfo {}
/**
@@ -409,6 +417,23 @@ public class VcnGatewayConnection extends StateMachine {
// TODO(b/178426520): implement handling of this event
private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
/**
* Sent when this VcnGatewayConnection has entered Safemode.
*
* <p>A VcnGatewayConnection enters Safemode when it takes over {@link
* #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}.
*
* <p>When a VcnGatewayConnection enters safe mode, it will fire {@link
* VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down
* its VcnGatewayConnectin(s).
*
* <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not
* validated yet), and RetryTimeoutState.
*
* @param arg1 The "all" token; this signal is always honored.
*/
private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10;
@VisibleForTesting(visibility = Visibility.PRIVATE)
@NonNull
final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -520,11 +545,13 @@ public class VcnGatewayConnection extends StateMachine {
* <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable
* otherwise.
*/
private NetworkAgent mNetworkAgent;
@VisibleForTesting(visibility = Visibility.PRIVATE)
NetworkAgent mNetworkAgent;
@Nullable private WakeupMessage mTeardownTimeoutAlarm;
@Nullable private WakeupMessage mDisconnectRequestAlarm;
@Nullable private WakeupMessage mRetryTimeoutAlarm;
@Nullable private WakeupMessage mSafemodeTimeoutAlarm;
public VcnGatewayConnection(
@NonNull VcnContext vcnContext,
@@ -611,6 +638,7 @@ public class VcnGatewayConnection extends StateMachine {
cancelTeardownTimeoutAlarm();
cancelDisconnectRequestAlarm();
cancelRetryTimeoutAlarm();
cancelSafemodeAlarm();
mUnderlyingNetworkTracker.teardown();
}
@@ -899,6 +927,30 @@ public class VcnGatewayConnection extends StateMachine {
removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED);
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
void setSafemodeAlarm() {
// Only schedule a NEW alarm if none is already set.
if (mSafemodeTimeoutAlarm != null) {
return;
}
final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL);
mSafemodeTimeoutAlarm =
createScheduledAlarm(
SAFEMODE_TIMEOUT_ALARM,
delayedMessage,
TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
}
private void cancelSafemodeAlarm() {
if (mSafemodeTimeoutAlarm != null) {
mSafemodeTimeoutAlarm.cancel();
mSafemodeTimeoutAlarm = null;
}
removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED);
}
private void sessionLost(int token, @Nullable Exception exception) {
sendMessageAndAcquireWakeLock(
EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception));
@@ -1072,6 +1124,8 @@ public class VcnGatewayConnection extends StateMachine {
if (mIkeSession != null || mNetworkAgent != null) {
Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
}
cancelSafemodeAlarm();
}
@Override
@@ -1095,6 +1149,12 @@ public class VcnGatewayConnection extends StateMachine {
break;
}
}
@Override
protected void exitState() {
// Safe to blindly set up, as it is cancelled and cleared on entering this state
setSafemodeAlarm();
}
}
private abstract class ActiveBaseState extends BaseState {
@@ -1185,6 +1245,10 @@ public class VcnGatewayConnection extends StateMachine {
transitionTo(mDisconnectedState);
}
break;
case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafemode();
mSafemodeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
break;
@@ -1267,6 +1331,10 @@ public class VcnGatewayConnection extends StateMachine {
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafemode();
mSafemodeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
break;
@@ -1310,6 +1378,14 @@ public class VcnGatewayConnection extends StateMachine {
public void unwanted() {
teardownAsynchronously();
}
@Override
public void onValidationStatus(
@ValidationStatus int status, @Nullable Uri redirectUri) {
if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
clearFailedAttemptCounterAndSafeModeAlarm();
}
}
};
agent.register();
@@ -1318,6 +1394,14 @@ public class VcnGatewayConnection extends StateMachine {
return agent;
}
protected void clearFailedAttemptCounterAndSafeModeAlarm() {
mVcnContext.ensureRunningOnLooperThread();
// Validated connection, clear failed attempt counter
mFailedAttempts = 0;
cancelSafemodeAlarm();
}
protected void applyTransform(
int token,
@NonNull IpSecTunnelInterface tunnelIface,
@@ -1397,9 +1481,6 @@ public class VcnGatewayConnection extends StateMachine {
teardownAsynchronously();
}
}
// Successful connection, clear failed attempt counter
mFailedAttempts = 0;
}
@Override
@@ -1436,6 +1517,10 @@ public class VcnGatewayConnection extends StateMachine {
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafemode();
mSafemodeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
break;
@@ -1478,8 +1563,19 @@ public class VcnGatewayConnection extends StateMachine {
mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
} else {
updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);
// mNetworkAgent not null, so the VCN Network has already been established. Clear
// the failed attempt counter and safe mode alarm since this transition is complete.
clearFailedAttemptCounterAndSafeModeAlarm();
}
}
@Override
protected void exitState() {
// Attempt to set the safe mode alarm - this requires the Vcn Network being validated
// while in ConnectedState (which cancels the previous alarm)
setSafemodeAlarm();
}
}
/**
@@ -1526,6 +1622,10 @@ public class VcnGatewayConnection extends StateMachine {
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
break;
case EVENT_SAFEMODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafemode();
mSafemodeTimeoutAlarm = null;
break;
default:
logUnhandledMessage(msg);
break;

View File

@@ -34,8 +34,10 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import androidx.test.filters.SmallTest;
@@ -72,6 +74,11 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
verify(mDeps).newIkeSession(any(), any(), any(), any(), any());
}
@Test
public void testEnterStateDoesNotCancelSafemodeAlarm() {
verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
mGatewayConnection
@@ -122,6 +129,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
@Test
public void testChildOpenedRegistersNetwork() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
final VcnChildSessionConfiguration mMockChildSessionConfig =
mock(VcnChildSessionConfiguration.class);
doReturn(Collections.singletonList(TEST_INTERNAL_ADDR))
@@ -163,24 +173,41 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
for (int cap : mConfig.getAllExposedCapabilities()) {
assertTrue(nc.hasCapability(cap));
}
// 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 */);
verify(mSafemodeTimeoutAlarm).cancel();
}
@Test
public void testChildSessionClosedTriggersDisconnect() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
getChildSessionCallback().onClosed();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
// Since network never validated, verify mSafemodeTimeoutAlarm not canceled
verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
}
@Test
public void testIkeSessionClosedTriggersDisconnect() throws Exception {
// Verify scheduled but not canceled when entering ConnectedState
verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
getIkeSessionCallback().onClosed();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
verify(mIkeSession).close();
verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
// Since network never validated, verify mSafemodeTimeoutAlarm not canceled
verifyNoMoreInteractions(mSafemodeTimeoutAlarm);
}
}

View File

@@ -106,4 +106,9 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
verify(mIkeSession).close();
verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */);
}
@Test
public void testSafemodeTimeoutNotifiesCallback() {
verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
}
}

View File

@@ -48,7 +48,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
.createIpSecTunnelInterface(
DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network);
mGatewayConnection.setTunnelInterface(tunnelIface);
mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
// Don't need to transition to DisconnectedState because it is the starting state
mTestLooper.dispatchAll();
}
@@ -78,6 +79,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
@@ -98,5 +100,6 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
assertNull(mGatewayConnection.getCurrentState());
verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
}
}

View File

@@ -80,4 +80,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
public void testSafemodeTimeoutNotifiesCallback() {
verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
}
}

View File

@@ -91,4 +91,9 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */);
}
@Test
public void testSafemodeTimeoutNotifiesCallback() {
verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
}
}

View File

@@ -20,6 +20,7 @@ import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkR
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.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -50,6 +51,7 @@ import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.test.TestLooper;
import com.android.internal.util.State;
import com.android.internal.util.WakeupMessage;
import com.android.server.IpSecService;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
@@ -108,6 +110,7 @@ public class VcnGatewayConnectionTestBase {
@NonNull protected final WakeupMessage mTeardownTimeoutAlarm;
@NonNull protected final WakeupMessage mDisconnectRequestAlarm;
@NonNull protected final WakeupMessage mRetryTimeoutAlarm;
@NonNull protected final WakeupMessage mSafemodeTimeoutAlarm;
@NonNull protected final IpSecService mIpSecSvc;
@NonNull protected final ConnectivityManager mConnMgr;
@@ -128,6 +131,7 @@ public class VcnGatewayConnectionTestBase {
mTeardownTimeoutAlarm = mock(WakeupMessage.class);
mDisconnectRequestAlarm = mock(WakeupMessage.class);
mRetryTimeoutAlarm = mock(WakeupMessage.class);
mSafemodeTimeoutAlarm = mock(WakeupMessage.class);
mIpSecSvc = mock(IpSecService.class);
setupIpSecManager(mContext, mIpSecSvc);
@@ -150,6 +154,7 @@ public class VcnGatewayConnectionTestBase {
setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM);
setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM);
setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM);
setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM);
doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime();
}
@@ -253,4 +258,24 @@ public class VcnGatewayConnectionTestBase {
delayInMillis,
expectCanceled);
}
protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) {
return verifyWakeupMessageSetUpAndGetCallback(
VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM,
mSafemodeTimeoutAlarm,
TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS),
expectCanceled);
}
protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) {
// Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial
// state)
final Runnable delayedEvent =
verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
delayedEvent.run();
mTestLooper.dispatchAll();
verify(mGatewayStatusCallback).onEnteredSafemode();
assertEquals(expectedState, mGatewayConnection.getCurrentState());
}
}