Loading services/core/java/com/android/server/vcn/VcnGatewayConnection.java +152 −14 Original line number Diff line number Diff line Loading @@ -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 {} Loading Loading @@ -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. * Loading @@ -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. Loading Loading @@ -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; } } } /** Loading Loading @@ -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 { Loading @@ -958,13 +1048,34 @@ 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( return new VcnIkeSession( vcnContext, ikeSessionParams, childSessionParams, 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, Loading @@ -972,5 +1083,32 @@ public class VcnGatewayConnection extends StateMachine { 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); } } } tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * 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()); } } tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -68,6 +72,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final IpSecService mIpSecSvc; protected VcnIkeSession mMockIkeSession; protected VcnGatewayConnection mGatewayConnection; public VcnGatewayConnectionTestBase() { Loading Loading @@ -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(); } } Loading
services/core/java/com/android/server/vcn/VcnGatewayConnection.java +152 −14 Original line number Diff line number Diff line Loading @@ -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 {} Loading Loading @@ -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. * Loading @@ -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. Loading Loading @@ -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; } } } /** Loading Loading @@ -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 { Loading @@ -958,13 +1048,34 @@ 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( return new VcnIkeSession( vcnContext, ikeSessionParams, childSessionParams, 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, Loading @@ -972,5 +1083,32 @@ public class VcnGatewayConnection extends StateMachine { 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); } } }
tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * 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()); } }
tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -68,6 +72,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final IpSecService mIpSecSvc; protected VcnIkeSession mMockIkeSession; protected VcnGatewayConnection mGatewayConnection; public VcnGatewayConnectionTestBase() { Loading Loading @@ -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(); } }