Allow NETWORK_LOST disconnections to retry

This change adds the ability for disconnection requests to specify
whether the full VcnGatewayConnection should be torn down. This is
useful in cases where the cause is an ephemeral failure, and can be
restarted at some later point (eg an underlying network appears, or a
new NetworkRequest is filed that brings the VCN up.)

Bug: 179944275
Test: atest FrameworksVcnTests
Change-Id: Id1813bcbbb80541be97cc383ea9d1fe08554b7ae
This commit is contained in:
Benedict Wong
2021-02-10 15:44:26 -08:00
parent b7cc3c6106
commit 75a32b9401
6 changed files with 126 additions and 34 deletions

View File

@@ -168,6 +168,8 @@ public class VcnGatewayConnection extends StateMachine {
private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: ";
private static final String DISCONNECT_REASON_UNDERLYING_NETWORK_LOST =
"Underlying Network lost";
private static final String DISCONNECT_REASON_NETWORK_AGENT_UNWANTED =
"NetworkAgent was unwanted";
private static final String DISCONNECT_REASON_TEARDOWN = "teardown() called on VcnTunnel";
private static final int TOKEN_ALL = Integer.MIN_VALUE;
@@ -379,13 +381,16 @@ public class VcnGatewayConnection extends StateMachine {
/** The reason why the disconnect was requested. */
@NonNull public final String reason;
EventDisconnectRequestedInfo(@NonNull String reason) {
public final boolean shouldTeardown;
EventDisconnectRequestedInfo(@NonNull String reason, boolean shouldTeardown) {
this.reason = Objects.requireNonNull(reason);
this.shouldTeardown = shouldTeardown;
}
@Override
public int hashCode() {
return Objects.hash(reason);
return Objects.hash(reason, shouldTeardown);
}
@Override
@@ -395,7 +400,7 @@ public class VcnGatewayConnection extends StateMachine {
}
final EventDisconnectRequestedInfo rhs = (EventDisconnectRequestedInfo) other;
return reason.equals(rhs.reason);
return reason.equals(rhs.reason) && shouldTeardown == rhs.shouldTeardown;
}
}
@@ -622,10 +627,8 @@ public class VcnGatewayConnection extends StateMachine {
* <p>Once torn down, this VcnTunnel CANNOT be started again.
*/
public void teardownAsynchronously() {
sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
sendDisconnectRequestedAndAcquireWakelock(
DISCONNECT_REASON_TEARDOWN, true /* shouldTeardown */);
// TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this
// is also called asynchronously when a NetworkAgent becomes unwanted
@@ -892,7 +895,8 @@ public class VcnGatewayConnection extends StateMachine {
TOKEN_ALL,
0 /* arg2 */,
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
DISCONNECT_REASON_UNDERLYING_NETWORK_LOST,
false /* shouldTeardown */));
mDisconnectRequestAlarm =
createScheduledAlarm(
DISCONNECT_REQUEST_ALARM,
@@ -909,7 +913,8 @@ public class VcnGatewayConnection extends StateMachine {
// Cancel any existing disconnect due to previous loss of underlying network
removeEqualMessages(
EVENT_DISCONNECT_REQUESTED,
new EventDisconnectRequestedInfo(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST));
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldTeardown */));
}
private void setRetryTimeoutAlarm(long delay) {
@@ -1041,11 +1046,8 @@ public class VcnGatewayConnection extends StateMachine {
enterState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
sendDisconnectRequestedAndAcquireWakelock(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldTeardown */);
}
}
@@ -1083,11 +1085,8 @@ public class VcnGatewayConnection extends StateMachine {
processStateMsg(msg);
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
sendDisconnectRequestedAndAcquireWakelock(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldTeardown */);
}
// Attempt to release the WakeLock - only possible if the Handler queue is empty
@@ -1104,11 +1103,8 @@ public class VcnGatewayConnection extends StateMachine {
exitState();
} catch (Exception e) {
Slog.wtf(TAG, "Uncaught exception", e);
sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
sendDisconnectRequestedAndAcquireWakelock(
DISCONNECT_REASON_INTERNAL_ERROR + e.toString(), true /* shouldTeardown */);
}
}
@@ -1141,11 +1137,11 @@ public class VcnGatewayConnection extends StateMachine {
}
}
protected void handleDisconnectRequested(String msg) {
protected void handleDisconnectRequested(EventDisconnectRequestedInfo info) {
// TODO(b/180526152): notify VcnStatusCallback for Network loss
Slog.v(TAG, "Tearing down. Cause: " + msg);
mIsRunning = false;
Slog.v(TAG, "Tearing down. Cause: " + info.reason);
mIsRunning = !info.shouldTeardown;
teardownNetwork();
@@ -1200,9 +1196,11 @@ public class VcnGatewayConnection extends StateMachine {
}
break;
case EVENT_DISCONNECT_REQUESTED:
mIsRunning = false;
if (((EventDisconnectRequestedInfo) msg.obj).shouldTeardown) {
mIsRunning = false;
quitNow();
quitNow();
}
break;
default:
logUnhandledMessage(msg);
@@ -1284,10 +1282,11 @@ public class VcnGatewayConnection extends StateMachine {
break;
case EVENT_DISCONNECT_REQUESTED:
EventDisconnectRequestedInfo info = ((EventDisconnectRequestedInfo) msg.obj);
mIsRunning = !info.shouldTeardown;
teardownNetwork();
String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
if (info.reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
// TODO(b/180526152): notify VcnStatusCallback for Network loss
// Will trigger EVENT_SESSION_CLOSED immediately.
@@ -1391,7 +1390,7 @@ public class VcnGatewayConnection extends StateMachine {
transitionTo(mConnectedState);
break;
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
@@ -1438,6 +1437,7 @@ public class VcnGatewayConnection extends StateMachine {
mVcnContext.getVcnNetworkProvider()) {
@Override
public void unwanted() {
Slog.d(TAG, "NetworkAgent was unwanted");
teardownAsynchronously();
}
@@ -1577,7 +1577,7 @@ public class VcnGatewayConnection extends StateMachine {
setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig);
break;
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
@@ -1682,7 +1682,7 @@ public class VcnGatewayConnection extends StateMachine {
transitionTo(mConnectingState);
break;
case EVENT_DISCONNECT_REQUESTED:
handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
handleDisconnectRequested((EventDisconnectRequestedInfo) msg.obj);
break;
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED:
mGatewayStatusCallback.onEnteredSafeMode();
@@ -1924,6 +1924,14 @@ public class VcnGatewayConnection extends StateMachine {
mIkeSession = session;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
void sendDisconnectRequestedAndAcquireWakelock(String reason, boolean shouldTeardown) {
sendMessageAndAcquireWakeLock(
EVENT_DISCONNECT_REQUESTED,
TOKEN_ALL,
new EventDisconnectRequestedInfo(reason, shouldTeardown));
}
private IkeSessionParams buildIkeParams() {
// TODO: Implement this once IkeSessionParams is persisted
return null;

View File

@@ -290,4 +290,22 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
}
@Test
public void testTeardown() throws Exception {
mGatewayConnection.teardownAsynchronously();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
assertFalse(mGatewayConnection.isRunning());
}
@Test
public void testNonTeardownDisconnectRequest() throws Exception {
mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
assertTrue(mGatewayConnection.isRunning());
}
}

View File

@@ -19,6 +19,8 @@ package com.android.server.vcn;
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -111,4 +113,22 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState);
}
@Test
public void testTeardown() throws Exception {
mGatewayConnection.teardownAsynchronously();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
assertFalse(mGatewayConnection.isRunning());
}
@Test
public void testNonTeardownDisconnectRequest() throws Exception {
mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
assertTrue(mGatewayConnection.isRunning());
}
}

View File

@@ -21,7 +21,9 @@ import static android.net.IpSecManager.IpSecTunnelInterface;
import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -101,5 +103,16 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect
assertNull(mGatewayConnection.getCurrentState());
verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
verifySafeModeTimeoutAlarmAndGetCallback(true /* expectCanceled */);
assertFalse(mGatewayConnection.isRunning());
}
@Test
public void testNonTeardownDisconnectRequest() throws Exception {
mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
assertTrue(mGatewayConnection.isRunning());
// No safe mode timer changes expected.
}
}

View File

@@ -18,6 +18,8 @@ package com.android.server.vcn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -79,10 +81,20 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec
// Should do nothing; already tearing down.
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */);
assertFalse(mGatewayConnection.isRunning());
}
@Test
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState);
}
@Test
public void testNonTeardownDisconnectRequest() throws Exception {
mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
assertTrue(mGatewayConnection.isRunning());
}
}

View File

@@ -17,6 +17,9 @@
package com.android.server.vcn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -96,4 +99,22 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect
public void testSafeModeTimeoutNotifiesCallback() {
verifySafeModeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState);
}
@Test
public void testTeardownDisconnectRequest() throws Exception {
mGatewayConnection.teardownAsynchronously();
mTestLooper.dispatchAll();
assertNull(mGatewayConnection.getCurrentState());
assertFalse(mGatewayConnection.isRunning());
}
@Test
public void testNonTeardownDisconnectRequest() throws Exception {
mGatewayConnection.sendDisconnectRequestedAndAcquireWakelock("TEST", false);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
assertTrue(mGatewayConnection.isRunning());
}
}