blob: 69c21b967917ad8aab5746271e95851dca0ae595 [file] [log] [blame]
/*
* 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 android.net.IpSecManager.DIRECTION_IN;
import static android.net.IpSecManager.DIRECTION_OUT;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
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.Matchers.anyInt;
import static org.mockito.Matchers.eq;
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 android.net.ipsec.ike.exceptions.AuthenticationFailedException;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeInternalException;
import android.net.ipsec.ike.exceptions.TemporaryFailureException;
import android.net.vcn.VcnManager.VcnErrorCode;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Collections;
/** Tests for VcnGatewayConnection.ConnectedState */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
private VcnIkeSession mIkeSession;
@Before
public void setUp() throws Exception {
super.setUp();
mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
mIkeSession = mGatewayConnection.buildIkeSession();
mGatewayConnection.setIkeSession(mIkeSession);
mGatewayConnection.transitionTo(mGatewayConnection.mConnectedState);
mTestLooper.dispatchAll();
}
@Test
public void testEnterStateCreatesNewIkeSession() throws Exception {
verify(mDeps).newIkeSession(any(), any(), any(), any(), any());
}
@Test
public void testEnterStateDoesNotCancelSafeModeAlarm() {
verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
mGatewayConnection
.getUnderlyingNetworkTrackerCallback()
.onSelectedUnderlyingNetworkChanged(null);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
verify(mIkeSession, never()).close();
verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */);
}
@Test
public void testNewNetworkTriggersMigration() throws Exception {
mGatewayConnection
.getUnderlyingNetworkTrackerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
verify(mIkeSession, never()).close();
verify(mIkeSession).setNetwork(TEST_UNDERLYING_NETWORK_RECORD_2.network);
}
@Test
public void testSameNetworkDoesNotTriggerMigration() throws Exception {
mGatewayConnection
.getUnderlyingNetworkTrackerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
}
@Test
public void testCreatedTransformsAreApplied() throws Exception {
for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
mTestLooper.dispatchAll();
verify(mIpSecSvc)
.applyTunnelModeTransform(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
}
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
}
@Test
public void testMigratedTransformsAreApplied() throws Exception {
getChildSessionCallback()
.onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform());
mTestLooper.dispatchAll();
for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
verify(mIpSecSvc)
.applyTunnelModeTransform(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
}
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
}
@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))
.when(mMockChildSessionConfig)
.getInternalAddresses();
doReturn(Collections.singletonList(TEST_DNS_ADDR))
.when(mMockChildSessionConfig)
.getInternalDnsServers();
getChildSessionCallback().onOpened(mMockChildSessionConfig);
mTestLooper.dispatchAll();
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
final ArgumentCaptor<LinkProperties> lpCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
final ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnMgr)
.registerNetworkAgent(
any(),
any(),
lpCaptor.capture(),
ncCaptor.capture(),
anyInt(),
any(),
anyInt());
verify(mIpSecSvc)
.addAddressToTunnelInterface(
eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(TEST_INTERNAL_ADDR), any());
final LinkProperties lp = lpCaptor.getValue();
assertEquals(Collections.singletonList(TEST_INTERNAL_ADDR), lp.getLinkAddresses());
assertEquals(Collections.singletonList(TEST_DNS_ADDR), lp.getDnsServers());
final NetworkCapabilities nc = ncCaptor.getValue();
assertTrue(nc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(nc.hasTransport(TRANSPORT_WIFI));
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);
// The child session was closed without exception, so verify that the GatewayStatusCallback
// was not notified
verifyNoMoreInteractions(mGatewayStatusCallback);
}
@Test
public void testChildSessionClosedExceptionallyNotifiesGatewayStatusCallback()
throws Exception {
final IkeInternalException exception = new IkeInternalException(mock(IOException.class));
getChildSessionCallback().onClosedExceptionally(exception);
mTestLooper.dispatchAll();
verify(mGatewayStatusCallback)
.onGatewayConnectionError(
eq(mConfig.getRequiredUnderlyingCapabilities()),
eq(VCN_ERROR_CODE_INTERNAL_ERROR),
any(),
any());
}
@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();
// Since network never validated, verify mSafeModeTimeoutAlarm not canceled
verifyNoMoreInteractions(mSafeModeTimeoutAlarm);
// IkeSession closed with no error, so verify that the GatewayStatusCallback was not
// notified
verifyNoMoreInteractions(mGatewayStatusCallback);
}
private void verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
IkeException cause, @VcnErrorCode int expectedErrorType) {
getIkeSessionCallback().onClosedExceptionally(cause);
mTestLooper.dispatchAll();
verify(mIkeSession).close();
verify(mGatewayStatusCallback)
.onGatewayConnectionError(
eq(mConfig.getRequiredUnderlyingCapabilities()),
eq(expectedErrorType),
any(),
any());
}
@Test
public void testIkeSessionClosedExceptionallyAuthenticationFailure() throws Exception {
verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
new AuthenticationFailedException("vcn test"), VCN_ERROR_CODE_CONFIG_ERROR);
}
@Test
public void testIkeSessionClosedExceptionallyDnsFailure() throws Exception {
verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
new IkeInternalException(new UnknownHostException()), VCN_ERROR_CODE_NETWORK_ERROR);
}
@Test
public void testIkeSessionClosedExceptionallyInternalFailure() throws Exception {
verifyIkeSessionClosedExceptionalltyNotifiesStatusCallback(
new TemporaryFailureException("vcn test"), VCN_ERROR_CODE_INTERNAL_ERROR);
}
}