Merge "Implement DisconnectingState"

This commit is contained in:
Benedict Wong
2021-01-27 18:13:10 +00:00
committed by Gerrit Code Review
3 changed files with 238 additions and 14 deletions

View File

@@ -122,7 +122,9 @@ public class VcnGatewayConnection extends StateMachine {
private static final int TOKEN_ALL = Integer.MIN_VALUE;
private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
private static final int TEARDOWN_TIMEOUT_SECONDS = 5;
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final int TEARDOWN_TIMEOUT_SECONDS = 5;
private interface EventInfo {}
@@ -412,13 +414,6 @@ public class VcnGatewayConnection extends StateMachine {
*/
private int mCurrentToken = -1;
/**
* The next usable token.
*
* <p>A new token MUST be used for all new IKE sessions.
*/
private int mNextToken = 0;
/**
* The number of unsuccessful attempts since the last successful connection.
*
@@ -440,7 +435,7 @@ public class VcnGatewayConnection extends StateMachine {
* <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and
* Migrating states, null otherwise.
*/
private IkeSession mIkeSession;
private VcnIkeSession mIkeSession;
/**
* The last known child configuration.
@@ -774,7 +769,70 @@ public class VcnGatewayConnection extends StateMachine {
*/
private class DisconnectingState extends ActiveBaseState {
@Override
protected void processStateMsg(Message msg) {}
protected void enterState() throws Exception {
if (mIkeSession == null) {
Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
return;
}
// If underlying network has already been lost, save some time and just kill the session
if (mUnderlying == null) {
// Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down.
mIkeSession.kill();
return;
}
sendMessageDelayed(
EVENT_TEARDOWN_TIMEOUT_EXPIRED,
mCurrentToken,
TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
}
@Override
protected void processStateMsg(Message msg) {
switch (msg.what) {
case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
// If we received a new underlying network, continue.
if (mUnderlying != null) {
break;
}
// Fallthrough; no network exists to send IKE close session requests.
case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
// Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED
mIkeSession.kill();
break;
case EVENT_DISCONNECT_REQUESTED:
teardownNetwork();
String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
// Will trigger EVENT_SESSION_CLOSED immediately.
mIkeSession.kill();
break;
}
// Otherwise we are already in the process of shutting down.
break;
case EVENT_SESSION_CLOSED:
mIkeSession = null;
if (mIsRunning && mUnderlying != null) {
transitionTo(mRetryTimeoutState);
} else {
teardownNetwork();
transitionTo(mDisconnectedState);
}
break;
default:
logUnhandledMessage(msg);
break;
}
}
}
/**
@@ -946,6 +1004,38 @@ public class VcnGatewayConnection extends StateMachine {
mIsRunning = isRunning;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
VcnIkeSession getIkeSession() {
return mIkeSession;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
void setIkeSession(@Nullable VcnIkeSession session) {
mIkeSession = session;
}
private IkeSessionParams buildIkeParams() {
// TODO: Implement this with ConnectingState
return null;
}
private ChildSessionParams buildChildParams() {
// TODO: Implement this with ConnectingState
return null;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
VcnIkeSession buildIkeSession() {
final int token = ++mCurrentToken;
return mDeps.newIkeSession(
mVcnContext,
buildIkeParams(),
buildChildParams(),
new IkeSessionCallbackImpl(token),
new ChildSessionCallbackImpl(token));
}
/** External dependencies used by VcnGatewayConnection, for injection in tests */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies {
@@ -958,19 +1048,67 @@ public class VcnGatewayConnection extends StateMachine {
}
/** Builds a new IkeSession. */
public IkeSession newIkeSession(
public VcnIkeSession newIkeSession(
VcnContext vcnContext,
IkeSessionParams ikeSessionParams,
ChildSessionParams childSessionParams,
IkeSessionCallback ikeSessionCallback,
ChildSessionCallback childSessionCallback) {
return new IkeSession(
vcnContext.getContext(),
return new VcnIkeSession(
vcnContext,
ikeSessionParams,
childSessionParams,
new HandlerExecutor(new Handler(vcnContext.getLooper())),
ikeSessionCallback,
childSessionCallback);
}
}
/** Proxy implementation of IKE session, used for testing. */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class VcnIkeSession {
private final IkeSession mImpl;
public VcnIkeSession(
VcnContext vcnContext,
IkeSessionParams ikeSessionParams,
ChildSessionParams childSessionParams,
IkeSessionCallback ikeSessionCallback,
ChildSessionCallback childSessionCallback) {
mImpl =
new IkeSession(
vcnContext.getContext(),
ikeSessionParams,
childSessionParams,
new HandlerExecutor(new Handler(vcnContext.getLooper())),
ikeSessionCallback,
childSessionCallback);
}
/** Creates a new IKE Child session. */
public void openChildSession(
@NonNull ChildSessionParams childSessionParams,
@NonNull ChildSessionCallback childSessionCallback) {
mImpl.openChildSession(childSessionParams, childSessionCallback);
}
/** Closes an IKE session as identified by the ChildSessionCallback. */
public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) {
mImpl.closeChildSession(childSessionCallback);
}
/** Gracefully closes this IKE Session, waiting for remote acknowledgement. */
public void close() {
mImpl.close();
}
/** Forcibly kills this IKE Session, without waiting for a closure confirmation. */
public void kill() {
mImpl.kill();
}
/** Sets the underlying network used by the IkeSession. */
public void setNetwork(@NonNull Network network) {
mImpl.setNetwork(network);
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.vcn;
import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.verify;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.TimeUnit;
/** Tests for VcnGatewayConnection.DisconnectedState */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnectionTestBase {
@Before
public void setUp() throws Exception {
super.setUp();
mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
mTestLooper.dispatchAll();
}
@Test
public void testIkeSessionClosed() throws Exception {
getIkeSessionCallback().onClosed();
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
}
@Test
public void testTimeoutExpired() throws Exception {
mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
mTestLooper.dispatchAll();
verify(mMockIkeSession).kill();
}
@Test
public void testTeardown() throws Exception {
mGatewayConnection.teardownAsynchronously();
mTestLooper.dispatchAll();
// Should do nothing; already tearing down.
assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
}
}

View File

@@ -17,11 +17,13 @@
package com.android.server.vcn;
import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.content.Context;
@@ -30,6 +32,7 @@ import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.os.ParcelUuid;
@@ -38,6 +41,7 @@ import android.os.test.TestLooper;
import com.android.server.IpSecService;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
import java.util.UUID;
@@ -68,6 +72,7 @@ public class VcnGatewayConnectionTestBase {
@NonNull protected final IpSecService mIpSecSvc;
protected VcnIkeSession mMockIkeSession;
protected VcnGatewayConnection mGatewayConnection;
public VcnGatewayConnectionTestBase() {
@@ -100,6 +105,16 @@ public class VcnGatewayConnectionTestBase {
TEST_IPSEC_TUNNEL_IFACE);
doReturn(resp).when(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any());
mMockIkeSession = mock(VcnIkeSession.class);
doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any());
mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps);
}
protected IkeSessionCallback getIkeSessionCallback() {
ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
verify(mDeps).newIkeSession(any(), any(), any(), captor.capture(), any());
return captor.getValue();
}
}