Initial CL for testing IkeSession creation
This commit: -Extend TunUtils for processing IKE packets -Add IkeSessionBaseTest containing common functionality for all IkeSession tests -Add end-to-end test for IKEv2 PSK verifying creating IKE SA, creating child SAs and closing sessions -Add basic tests for error scenarios Bug: 148689509 Test: atest CtsIkeTestCases Change-Id: Ie6c18591ffcc883abbf0484d9a59dfda61b33257
This commit is contained in:
@@ -33,6 +33,7 @@ android_test {
|
||||
"androidx.test.ext.junit",
|
||||
"compatibility-device-util-axt",
|
||||
"ctstestrunner-axt",
|
||||
"net-tests-utils",
|
||||
],
|
||||
|
||||
platform_apis: true,
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.net.ipsec.ike.testutils.CertUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -63,7 +64,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
public final class IkeSessionParamsTest extends IkeSessionTestBase {
|
||||
private static final int HARD_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(20L);
|
||||
private static final int SOFT_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(10L);
|
||||
private static final int DPD_DELAY_SECONDS = (int) TimeUnit.MINUTES.toSeconds(10L);
|
||||
@@ -105,6 +106,9 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
// This address is never used except for setting up the test network
|
||||
setUpTestNetwork(IPV4_ADDRESS_LOCAL);
|
||||
|
||||
mServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
|
||||
mClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem");
|
||||
mClientIntermediateCaCertOne =
|
||||
@@ -114,6 +118,11 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
mClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
tearDownTestNetwork();
|
||||
}
|
||||
|
||||
private static EapSessionConfig.Builder createEapOnlySafeMethodsBuilder() {
|
||||
return new EapSessionConfig.Builder()
|
||||
.setEapIdentity(EAP_IDENTITY)
|
||||
@@ -131,7 +140,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
*/
|
||||
private IkeSessionParams.Builder createIkeParamsBuilderMinimum() {
|
||||
return new IkeSessionParams.Builder(sContext)
|
||||
.setNetwork(sTunNetwork)
|
||||
.setNetwork(mTunNetwork)
|
||||
.setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
|
||||
.addSaProposal(SA_PROPOSAL)
|
||||
.setLocalIdentification(LOCAL_ID)
|
||||
@@ -145,7 +154,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
* @see #createIkeParamsBuilderMinimum
|
||||
*/
|
||||
private void verifyIkeParamsMinimum(IkeSessionParams sessionParams) {
|
||||
assertEquals(sTunNetwork, sessionParams.getNetwork());
|
||||
assertEquals(mTunNetwork, sessionParams.getNetwork());
|
||||
assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
|
||||
assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
|
||||
assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
|
||||
@@ -268,7 +277,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
*/
|
||||
private IkeSessionParams.Builder createIkeParamsBuilderMinimumWithoutAuth() {
|
||||
return new IkeSessionParams.Builder(sContext)
|
||||
.setNetwork(sTunNetwork)
|
||||
.setNetwork(mTunNetwork)
|
||||
.setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
|
||||
.addSaProposal(SA_PROPOSAL)
|
||||
.setLocalIdentification(LOCAL_ID)
|
||||
@@ -282,7 +291,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
|
||||
* @see #createIkeParamsBuilderMinimumWithoutAuth
|
||||
*/
|
||||
private void verifyIkeParamsMinimumWithoutAuth(IkeSessionParams sessionParams) {
|
||||
assertEquals(sTunNetwork, sessionParams.getNetwork());
|
||||
assertEquals(mTunNetwork, sessionParams.getNetwork());
|
||||
assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
|
||||
assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
|
||||
assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* 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 android.net.ipsec.ike.cts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.TestNetworkInterface;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
|
||||
abstract class IkeSessionParamsTestBase extends IkeTestBase {
|
||||
// Static state to reduce setup/teardown
|
||||
static ConnectivityManager sCM;
|
||||
static TestNetworkManager sTNM;
|
||||
static ParcelFileDescriptor sTunFd;
|
||||
static TestNetworkCallback sTunNetworkCallback;
|
||||
static Network sTunNetwork;
|
||||
|
||||
static Context sContext = InstrumentationRegistry.getContext();
|
||||
static IBinder sBinder = new Binder();
|
||||
|
||||
// This method is guaranteed to run in subclasses and will run before subclasses' @BeforeClass
|
||||
// methods.
|
||||
@BeforeClass
|
||||
public static void setUpTestNetworkBeforeClass() throws Exception {
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.adoptShellPermissionIdentity();
|
||||
sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
|
||||
|
||||
TestNetworkInterface testIface =
|
||||
sTNM.createTunInterface(
|
||||
new LinkAddress[] {new LinkAddress(IPV4_ADDRESS_LOCAL, IP4_PREFIX_LEN)});
|
||||
|
||||
sTunFd = testIface.getFileDescriptor();
|
||||
sTunNetworkCallback =
|
||||
TestNetworkUtils.setupAndGetTestNetwork(
|
||||
sCM, sTNM, testIface.getInterfaceName(), sBinder);
|
||||
sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
|
||||
}
|
||||
|
||||
// This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
|
||||
// methods.
|
||||
@AfterClass
|
||||
public static void tearDownTestNetworkAfterClass() throws Exception {
|
||||
sCM.unregisterNetworkCallback(sTunNetworkCallback);
|
||||
|
||||
sTNM.teardownTestNetwork(sTunNetwork);
|
||||
sTunFd.close();
|
||||
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.dropShellPermissionIdentity();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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 android.net.ipsec.ike.cts;
|
||||
|
||||
import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
|
||||
import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
|
||||
import static android.system.OsConstants.AF_INET;
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
|
||||
import static com.android.internal.util.HexDump.hexStringToByteArray;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.net.ipsec.ike.ChildSessionConfiguration;
|
||||
import android.net.ipsec.ike.IkeFqdnIdentification;
|
||||
import android.net.ipsec.ike.IkeSession;
|
||||
import android.net.ipsec.ike.IkeSessionConfiguration;
|
||||
import android.net.ipsec.ike.IkeSessionConnectionInfo;
|
||||
import android.net.ipsec.ike.IkeSessionParams;
|
||||
import android.net.ipsec.ike.TunnelModeChildSessionParams;
|
||||
import android.net.ipsec.ike.exceptions.IkeException;
|
||||
import android.net.ipsec.ike.exceptions.IkeProtocolException;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Arrays;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
|
||||
public class IkeSessionPskTest extends IkeSessionTestBase {
|
||||
// Test vectors for success workflow
|
||||
private static final String SUCCESS_IKE_INIT_RESP =
|
||||
"46B8ECA1E0D72A18B45427679F9245D421202220000000000000015022000030"
|
||||
+ "0000002C010100040300000C0100000C800E0080030000080300000203000008"
|
||||
+ "0200000200000008040000022800008800020000A7AA3435D088EC1A2B7C2A47"
|
||||
+ "1FA1B85F1066C9B2006E7C353FB5B5FDBC2A88347ED2C6F5B7A265D03AE34039"
|
||||
+ "6AAC0145CFCC93F8BDB219DDFF22A603B8856A5DC59B6FAB7F17C5660CF38670"
|
||||
+ "8794FC72F273ADEB7A4F316519794AED6F8AB61F95DFB360FAF18C6C8CABE471"
|
||||
+ "6E18FE215348C2E582171A57FC41146B16C4AFE429000024A634B61C0E5C90C6"
|
||||
+ "8D8818B0955B125A9B1DF47BBD18775710792E651083105C2900001C00004004"
|
||||
+ "406FA3C5685A16B9B72C7F2EEE9993462C619ABE2900001C00004005AF905A87"
|
||||
+ "0A32222AA284A7070585601208A282F0290000080000402E290000100000402F"
|
||||
+ "00020003000400050000000800004014";
|
||||
private static final String SUCCESS_IKE_AUTH_RESP =
|
||||
"46B8ECA1E0D72A18B45427679F9245D42E20232000000001000000EC240000D0"
|
||||
+ "0D06D37198F3F0962DE8170D66F1A9008267F98CDD956D984BDCED2FC7FAF84A"
|
||||
+ "A6664EF25049B46B93C9ED420488E0C172AA6635BF4011C49792EF2B88FE7190"
|
||||
+ "E8859FEEF51724FD20C46E7B9A9C3DC4708EF7005707A18AB747C903ABCEAC5C"
|
||||
+ "6ECF5A5FC13633DCE3844A920ED10EF202F115DBFBB5D6D2D7AB1F34EB08DE7C"
|
||||
+ "A54DCE0A3A582753345CA2D05A0EFDB9DC61E81B2483B7D13EEE0A815D37252C"
|
||||
+ "23D2F29E9C30658227D2BB0C9E1A481EAA80BC6BE9006BEDC13E925A755A0290"
|
||||
+ "AEC4164D29997F52ED7DCC2E";
|
||||
private static final String SUCCESS_CREATE_CHILD_RESP =
|
||||
"46B8ECA1E0D72A18B45427679F9245D42E20242000000002000000CC210000B0"
|
||||
+ "484565D4AF6546274674A8DE339E9C9584EE2326AB9260F41C4D0B6C5B02D1D"
|
||||
+ "2E8394E3CDE3094895F2ACCABCDCA8E82960E5196E9622BD13745FC8D6A2BED"
|
||||
+ "E561FF5D9975421BC463C959A3CBA3478256B6D278159D99B512DDF56AC1658"
|
||||
+ "63C65A986F395FE8B1476124B91F83FD7865304EB95B22CA4DD9601DA7A2533"
|
||||
+ "ABF4B36EB1B8CD09522F6A600032316C74E562E6756D9D49D945854E2ABDC4C"
|
||||
+ "3AF36305353D60D40B58BE44ABF82";
|
||||
private static final String SUCCESS_DELETE_CHILD_RESP =
|
||||
"46B8ECA1E0D72A18B45427679F9245D42E202520000000030000004C2A000030"
|
||||
+ "0C5CEB882DBCA65CE32F4C53909335F1365C91C555316C5E9D9FB553F7AA916"
|
||||
+ "EF3A1D93460B7FABAF0B4B854";
|
||||
private static final String SUCCESS_DELETE_IKE_RESP =
|
||||
"46B8ECA1E0D72A18B45427679F9245D42E202520000000040000004C00000030"
|
||||
+ "9352D71100777B00ABCC6BD7DBEA697827FFAAA48DF9A54D1D68161939F5DC8"
|
||||
+ "6743A7CEB2BE34AC00095A5B8";
|
||||
|
||||
private static final long IKE_INIT_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
|
||||
|
||||
private static final TunnelModeChildSessionParams CHILD_PARAMS =
|
||||
new TunnelModeChildSessionParams.Builder()
|
||||
.addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
|
||||
.addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
|
||||
.addInternalAddressRequest(AF_INET)
|
||||
.addInternalAddressRequest(AF_INET6)
|
||||
.build();
|
||||
|
||||
private IkeSessionParams createIkeSessionParams(InetAddress mRemoteAddress) {
|
||||
return new IkeSessionParams.Builder(sContext)
|
||||
.setNetwork(mTunNetwork)
|
||||
.setServerHostname(mRemoteAddress.getHostAddress())
|
||||
.addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
|
||||
.addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
|
||||
.setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
|
||||
.setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
|
||||
.setAuthPsk(IKE_PSK)
|
||||
.build();
|
||||
}
|
||||
|
||||
private IkeSession openIkeSession(IkeSessionParams ikeParams) {
|
||||
return new IkeSession(
|
||||
sContext,
|
||||
ikeParams,
|
||||
CHILD_PARAMS,
|
||||
mUserCbExecutor,
|
||||
mIkeSessionCallback,
|
||||
mFirstChildSessionCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIkeSessionSetupAndManageChildSas() throws Exception {
|
||||
// Open IKE Session
|
||||
IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
|
||||
int expectedMsgId = 0;
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
false /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
|
||||
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
true /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
|
||||
|
||||
// Verify opening IKE Session
|
||||
IkeSessionConfiguration ikeConfig = mIkeSessionCallback.awaitIkeConfig();
|
||||
assertNotNull(ikeConfig);
|
||||
assertEquals(EXPECTED_REMOTE_APP_VERSION_EMPTY, ikeConfig.getRemoteApplicationVersion());
|
||||
assertTrue(ikeConfig.getRemoteVendorIds().isEmpty());
|
||||
assertTrue(ikeConfig.getPcscfServers().isEmpty());
|
||||
assertTrue(ikeConfig.isIkeExtensionEnabled(EXTENSION_TYPE_FRAGMENTATION));
|
||||
|
||||
IkeSessionConnectionInfo ikeConnectInfo = ikeConfig.getIkeSessionConnectionInfo();
|
||||
assertNotNull(ikeConnectInfo);
|
||||
assertEquals(mLocalAddress, ikeConnectInfo.getLocalAddress());
|
||||
assertEquals(mRemoteAddress, ikeConnectInfo.getRemoteAddress());
|
||||
assertEquals(mTunNetwork, ikeConnectInfo.getNetwork());
|
||||
|
||||
// Verify opening first Child Session
|
||||
ChildSessionConfiguration firstChildConfig = mFirstChildSessionCallback.awaitChildConfig();
|
||||
assertNotNull(firstChildConfig);
|
||||
assertEquals(
|
||||
Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
|
||||
assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
|
||||
assertEquals(
|
||||
Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR),
|
||||
firstChildConfig.getInternalAddresses());
|
||||
assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
|
||||
assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
|
||||
assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
|
||||
|
||||
// Open additional Child Session
|
||||
TestChildSessionCallback additionalChildCb = new TestChildSessionCallback();
|
||||
ikeSession.openChildSession(CHILD_PARAMS, additionalChildCb);
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
true /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_CREATE_CHILD_RESP));
|
||||
|
||||
// Verify opening additional Child Session
|
||||
ChildSessionConfiguration additionalChildConfig = additionalChildCb.awaitChildConfig();
|
||||
assertNotNull(additionalChildConfig);
|
||||
assertEquals(
|
||||
Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
|
||||
assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
|
||||
assertTrue(additionalChildConfig.getInternalAddresses().isEmpty());
|
||||
assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
|
||||
assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
|
||||
assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
|
||||
|
||||
// Close additional Child Session
|
||||
ikeSession.closeChildSession(additionalChildCb);
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
true /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_DELETE_CHILD_RESP));
|
||||
|
||||
additionalChildCb.awaitOnClosed();
|
||||
|
||||
// Close IKE Session
|
||||
ikeSession.close();
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
true /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_DELETE_IKE_RESP));
|
||||
|
||||
mFirstChildSessionCallback.awaitOnClosed();
|
||||
mIkeSessionCallback.awaitOnClosed();
|
||||
|
||||
// TODO: verify IpSecTransform pair is created and deleted
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIkeSessionKill() throws Exception {
|
||||
// Open IKE Session
|
||||
IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
|
||||
int expectedMsgId = 0;
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
false /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
|
||||
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
true /* expectedUseEncap */,
|
||||
hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
|
||||
|
||||
ikeSession.kill();
|
||||
|
||||
mFirstChildSessionCallback.awaitOnClosed();
|
||||
mIkeSessionCallback.awaitOnClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIkeInitFail() throws Exception {
|
||||
String ikeInitFailRespHex =
|
||||
"46B8ECA1E0D72A180000000000000000292022200000000000000024000000080000000E";
|
||||
|
||||
// Open IKE Session
|
||||
IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
|
||||
int expectedMsgId = 0;
|
||||
mTunUtils.awaitReqAndInjectResp(
|
||||
IKE_INIT_SPI,
|
||||
expectedMsgId++,
|
||||
false /* expectedUseEncap */,
|
||||
hexStringToByteArray(ikeInitFailRespHex));
|
||||
|
||||
IkeException exception = mIkeSessionCallback.awaitOnClosedException();
|
||||
assertNotNull(exception);
|
||||
assertTrue(exception instanceof IkeProtocolException);
|
||||
IkeProtocolException protocolException = (IkeProtocolException) exception;
|
||||
assertEquals(ERROR_TYPE_NO_PROPOSAL_CHOSEN, protocolException.getErrorType());
|
||||
assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
|
||||
}
|
||||
|
||||
// TODO(b/148689509): Verify rekey process and handling IKE_AUTH failure
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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 android.net.ipsec.ike.cts;
|
||||
|
||||
import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpSecTransform;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.TestNetworkInterface;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.net.annotations.PolicyDirection;
|
||||
import android.net.ipsec.ike.ChildSessionCallback;
|
||||
import android.net.ipsec.ike.ChildSessionConfiguration;
|
||||
import android.net.ipsec.ike.IkeSessionCallback;
|
||||
import android.net.ipsec.ike.IkeSessionConfiguration;
|
||||
import android.net.ipsec.ike.IkeTrafficSelector;
|
||||
import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback;
|
||||
import android.net.ipsec.ike.exceptions.IkeException;
|
||||
import android.net.ipsec.ike.exceptions.IkeProtocolException;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.compatibility.common.util.SystemUtil;
|
||||
import com.android.testutils.ArrayTrackRecord;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Package private base class for testing IkeSessionParams and IKE exchanges.
|
||||
*
|
||||
* <p>Subclasses MUST explicitly call #setUpTestNetwork and #tearDownTestNetwork to be able to use
|
||||
* the test network
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
|
||||
abstract class IkeSessionTestBase extends IkeTestBase {
|
||||
// Package-wide common expected results that will be shared by all IKE/Child SA creation tests
|
||||
static final String EXPECTED_REMOTE_APP_VERSION_EMPTY = "";
|
||||
static final byte[] EXPECTED_PROTOCOL_ERROR_DATA_NONE = new byte[0];
|
||||
static final InetAddress EXPECTED_INTERNAL_ADDR =
|
||||
InetAddresses.parseNumericAddress("198.51.100.10");
|
||||
static final LinkAddress EXPECTED_INTERNAL_LINK_ADDR =
|
||||
new LinkAddress(EXPECTED_INTERNAL_ADDR, IP4_PREFIX_LEN);
|
||||
static final IkeTrafficSelector EXPECTED_INBOUND_TS =
|
||||
new IkeTrafficSelector(
|
||||
MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR, EXPECTED_INTERNAL_ADDR);
|
||||
|
||||
// Static state to reduce setup/teardown
|
||||
static Context sContext = InstrumentationRegistry.getContext();
|
||||
static ConnectivityManager sCM =
|
||||
(ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
static TestNetworkManager sTNM;
|
||||
|
||||
private static final int TIMEOUT_MS = 500;
|
||||
|
||||
// Constants to be used for providing different IP addresses for each tests
|
||||
private static final byte IP_ADDR_LAST_BYTE_MAX = (byte) 100;
|
||||
private static final byte[] INITIAL_AVAILABLE_IP4_ADDR_LOCAL =
|
||||
InetAddresses.parseNumericAddress("192.0.2.1").getAddress();
|
||||
private static final byte[] INITIAL_AVAILABLE_IP4_ADDR_REMOTE =
|
||||
InetAddresses.parseNumericAddress("198.51.100.1").getAddress();
|
||||
private static final byte[] NEXT_AVAILABLE_IP4_ADDR_LOCAL = INITIAL_AVAILABLE_IP4_ADDR_LOCAL;
|
||||
private static final byte[] NEXT_AVAILABLE_IP4_ADDR_REMOTE = INITIAL_AVAILABLE_IP4_ADDR_REMOTE;
|
||||
|
||||
ParcelFileDescriptor mTunFd;
|
||||
TestNetworkCallback mTunNetworkCallback;
|
||||
Network mTunNetwork;
|
||||
IkeTunUtils mTunUtils;
|
||||
|
||||
InetAddress mLocalAddress;
|
||||
InetAddress mRemoteAddress;
|
||||
|
||||
Executor mUserCbExecutor;
|
||||
TestIkeSessionCallback mIkeSessionCallback;
|
||||
TestChildSessionCallback mFirstChildSessionCallback;
|
||||
|
||||
// This method is guaranteed to run in subclasses and will run before subclasses' @BeforeClass
|
||||
// methods.
|
||||
@BeforeClass
|
||||
public static void setUpPermissionBeforeClass() throws Exception {
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.adoptShellPermissionIdentity();
|
||||
sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
|
||||
|
||||
// Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
|
||||
// a standard permission is insufficient. So we shell out the appop, to give us the
|
||||
// right appop permissions.
|
||||
setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
|
||||
}
|
||||
|
||||
// This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
|
||||
// methods.
|
||||
@AfterClass
|
||||
public static void tearDownPermissionAfterClass() throws Exception {
|
||||
setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
|
||||
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mLocalAddress = getNextAvailableIpv4AddressLocal();
|
||||
mRemoteAddress = getNextAvailableIpv4AddressRemote();
|
||||
setUpTestNetwork(mLocalAddress);
|
||||
|
||||
mUserCbExecutor = Executors.newSingleThreadExecutor();
|
||||
mIkeSessionCallback = new TestIkeSessionCallback();
|
||||
mFirstChildSessionCallback = new TestChildSessionCallback();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
tearDownTestNetwork();
|
||||
|
||||
resetNextAvailableAddress(NEXT_AVAILABLE_IP4_ADDR_LOCAL, INITIAL_AVAILABLE_IP4_ADDR_LOCAL);
|
||||
resetNextAvailableAddress(
|
||||
NEXT_AVAILABLE_IP4_ADDR_REMOTE, INITIAL_AVAILABLE_IP4_ADDR_REMOTE);
|
||||
}
|
||||
|
||||
void setUpTestNetwork(InetAddress localAddr) throws Exception {
|
||||
int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP4_PREFIX_LEN;
|
||||
|
||||
TestNetworkInterface testIface =
|
||||
sTNM.createTunInterface(new LinkAddress[] {new LinkAddress(localAddr, prefixLen)});
|
||||
|
||||
mTunFd = testIface.getFileDescriptor();
|
||||
mTunNetworkCallback =
|
||||
TestNetworkUtils.setupAndGetTestNetwork(
|
||||
sCM, sTNM, testIface.getInterfaceName(), new Binder());
|
||||
mTunNetwork = mTunNetworkCallback.getNetworkBlocking();
|
||||
mTunUtils = new IkeTunUtils(mTunFd);
|
||||
}
|
||||
|
||||
void tearDownTestNetwork() throws Exception {
|
||||
sCM.unregisterNetworkCallback(mTunNetworkCallback);
|
||||
|
||||
sTNM.teardownTestNetwork(mTunNetwork);
|
||||
mTunFd.close();
|
||||
}
|
||||
|
||||
private static void setAppOp(int appop, boolean allow) {
|
||||
String opName = AppOpsManager.opToName(appop);
|
||||
for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
|
||||
String cmd =
|
||||
String.format(
|
||||
"appops set %s %s %s",
|
||||
pkg, // Package name
|
||||
opName, // Appop
|
||||
(allow ? "allow" : "deny")); // Action
|
||||
Log.d("IKE", "CTS setAppOp cmd " + cmd);
|
||||
|
||||
String result = SystemUtil.runShellCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
Inet4Address getNextAvailableIpv4AddressLocal() throws Exception {
|
||||
return (Inet4Address)
|
||||
getNextAvailableAddress(
|
||||
NEXT_AVAILABLE_IP4_ADDR_LOCAL,
|
||||
INITIAL_AVAILABLE_IP4_ADDR_LOCAL,
|
||||
false /* isIp6 */);
|
||||
}
|
||||
|
||||
Inet4Address getNextAvailableIpv4AddressRemote() throws Exception {
|
||||
return (Inet4Address)
|
||||
getNextAvailableAddress(
|
||||
NEXT_AVAILABLE_IP4_ADDR_REMOTE,
|
||||
INITIAL_AVAILABLE_IP4_ADDR_REMOTE,
|
||||
false /* isIp6 */);
|
||||
}
|
||||
|
||||
InetAddress getNextAvailableAddress(
|
||||
byte[] nextAddressBytes, byte[] initialAddressBytes, boolean isIp6) throws Exception {
|
||||
int addressLen = isIp6 ? IP6_ADDRESS_LEN : IP4_ADDRESS_LEN;
|
||||
|
||||
synchronized (nextAddressBytes) {
|
||||
if (nextAddressBytes[addressLen - 1] == IP_ADDR_LAST_BYTE_MAX) {
|
||||
resetNextAvailableAddress(nextAddressBytes, initialAddressBytes);
|
||||
}
|
||||
|
||||
InetAddress address = InetAddress.getByAddress(nextAddressBytes);
|
||||
nextAddressBytes[addressLen - 1]++;
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetNextAvailableAddress(byte[] nextAddressBytes, byte[] initialAddressBytes) {
|
||||
synchronized (nextAddressBytes) {
|
||||
System.arraycopy(
|
||||
nextAddressBytes, 0, initialAddressBytes, 0, initialAddressBytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestIkeSessionCallback implements IkeSessionCallback {
|
||||
private CompletableFuture<IkeSessionConfiguration> mFutureIkeConfig =
|
||||
new CompletableFuture<>();
|
||||
private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
|
||||
private CompletableFuture<IkeException> mFutureOnClosedException =
|
||||
new CompletableFuture<>();
|
||||
|
||||
private int mOnErrorExceptionsCount = 0;
|
||||
private ArrayTrackRecord<IkeProtocolException> mOnErrorExceptionsTrackRecord =
|
||||
new ArrayTrackRecord<>();
|
||||
|
||||
@Override
|
||||
public void onOpened(@NonNull IkeSessionConfiguration sessionConfiguration) {
|
||||
mFutureIkeConfig.complete(sessionConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
mFutureOnClosedCall.complete(true /* unused */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosedExceptionally(@NonNull IkeException exception) {
|
||||
mFutureOnClosedException.complete(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull IkeProtocolException exception) {
|
||||
mOnErrorExceptionsTrackRecord.add(exception);
|
||||
}
|
||||
|
||||
public IkeSessionConfiguration awaitIkeConfig() throws Exception {
|
||||
return mFutureIkeConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public IkeException awaitOnClosedException() throws Exception {
|
||||
return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public IkeProtocolException awaitNextOnErrorException() {
|
||||
return mOnErrorExceptionsTrackRecord.poll(
|
||||
(long) TIMEOUT_MS,
|
||||
mOnErrorExceptionsCount++,
|
||||
(transform) -> {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void awaitOnClosed() throws Exception {
|
||||
mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestChildSessionCallback implements ChildSessionCallback {
|
||||
private CompletableFuture<ChildSessionConfiguration> mFutureChildConfig =
|
||||
new CompletableFuture<>();
|
||||
private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
|
||||
private CompletableFuture<IkeException> mFutureOnClosedException =
|
||||
new CompletableFuture<>();
|
||||
|
||||
private int mCreatedIpSecTransformCount = 0;
|
||||
private int mDeletedIpSecTransformCount = 0;
|
||||
private ArrayTrackRecord<IpSecTransformCallRecord> mCreatedIpSecTransformsTrackRecord =
|
||||
new ArrayTrackRecord<>();
|
||||
private ArrayTrackRecord<IpSecTransformCallRecord> mDeletedIpSecTransformsTrackRecord =
|
||||
new ArrayTrackRecord<>();
|
||||
|
||||
@Override
|
||||
public void onOpened(@NonNull ChildSessionConfiguration sessionConfiguration) {
|
||||
mFutureChildConfig.complete(sessionConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed() {
|
||||
mFutureOnClosedCall.complete(true /* unused */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosedExceptionally(@NonNull IkeException exception) {
|
||||
mFutureOnClosedException.complete(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIpSecTransformCreated(@NonNull IpSecTransform ipSecTransform, int direction) {
|
||||
mCreatedIpSecTransformsTrackRecord.add(
|
||||
new IpSecTransformCallRecord(ipSecTransform, direction));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIpSecTransformDeleted(@NonNull IpSecTransform ipSecTransform, int direction) {
|
||||
mDeletedIpSecTransformsTrackRecord.add(
|
||||
new IpSecTransformCallRecord(ipSecTransform, direction));
|
||||
}
|
||||
|
||||
public ChildSessionConfiguration awaitChildConfig() throws Exception {
|
||||
return mFutureChildConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public IkeException awaitOnClosedException() throws Exception {
|
||||
return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public IpSecTransformCallRecord awaitNextCreatedIpSecTransform() {
|
||||
return mCreatedIpSecTransformsTrackRecord.poll(
|
||||
(long) TIMEOUT_MS,
|
||||
mCreatedIpSecTransformCount++,
|
||||
(transform) -> {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public IpSecTransformCallRecord awaitNextDeletedIpSecTransform() {
|
||||
return mDeletedIpSecTransformsTrackRecord.poll(
|
||||
(long) TIMEOUT_MS,
|
||||
mDeletedIpSecTransformCount++,
|
||||
(transform) -> {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void awaitOnClosed() throws Exception {
|
||||
mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a created or deleted IpSecTransfrom that is provided by
|
||||
* ChildSessionCallback
|
||||
*/
|
||||
static class IpSecTransformCallRecord {
|
||||
public final IpSecTransform ipSecTransform;
|
||||
public final int direction;
|
||||
|
||||
IpSecTransformCallRecord(IpSecTransform ipSecTransform, @PolicyDirection int direction) {
|
||||
this.ipSecTransform = ipSecTransform;
|
||||
this.direction = direction;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth
|
||||
|
||||
// TODO(b/148689509): Verify hostname based creation
|
||||
}
|
||||
@@ -31,13 +31,15 @@ import java.util.Map;
|
||||
|
||||
/** Shared parameters and util methods for testing different components of IKE */
|
||||
abstract class IkeTestBase {
|
||||
private static final int MIN_PORT = 0;
|
||||
private static final int MAX_PORT = 65535;
|
||||
static final int MIN_PORT = 0;
|
||||
static final int MAX_PORT = 65535;
|
||||
private static final int INBOUND_TS_START_PORT = MIN_PORT;
|
||||
private static final int INBOUND_TS_END_PORT = 65520;
|
||||
private static final int OUTBOUND_TS_START_PORT = 16;
|
||||
private static final int OUTBOUND_TS_END_PORT = MAX_PORT;
|
||||
|
||||
static final int IP4_ADDRESS_LEN = 4;
|
||||
static final int IP6_ADDRESS_LEN = 16;
|
||||
static final int IP4_PREFIX_LEN = 32;
|
||||
static final int IP6_PREFIX_LEN = 64;
|
||||
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 android.net.ipsec.ike.cts;
|
||||
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.BytePayload;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.IP4_HDRLEN;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.IP6_HDRLEN;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.Ip4Header;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.Ip6Header;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.IpHeader;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.Payload;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.UDP_HDRLEN;
|
||||
import static android.net.ipsec.ike.cts.PacketUtils.UdpHeader;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class IkeTunUtils extends TunUtils {
|
||||
private static final int PORT_LEN = 2;
|
||||
|
||||
private static final int NON_ESP_MARKER_LEN = 4;
|
||||
private static final byte[] NON_ESP_MARKER = new byte[NON_ESP_MARKER_LEN];
|
||||
|
||||
private static final int IKE_HEADER_LEN = 28;
|
||||
private static final int IKE_INIT_SPI_OFFSET = 0;
|
||||
private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
|
||||
private static final int IKE_MSG_ID_OFFSET = 20;
|
||||
|
||||
public IkeTunUtils(ParcelFileDescriptor tunFd) {
|
||||
super(tunFd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Await the expected IKE request and inject an IKE response.
|
||||
*
|
||||
* @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
|
||||
*/
|
||||
public byte[] awaitReqAndInjectResp(
|
||||
long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap, byte[] respIkePkt)
|
||||
throws Exception {
|
||||
byte[] request =
|
||||
awaitIkePacket(
|
||||
expectedInitIkeSpi,
|
||||
expectedMsgId,
|
||||
false /* expectedResp */,
|
||||
expectedUseEncap);
|
||||
|
||||
// Build response header by flipping address and port
|
||||
InetAddress srcAddr = getAddress(request, false /* shouldGetSource */);
|
||||
InetAddress dstAddr = getAddress(request, true /* shouldGetSource */);
|
||||
int srcPort = getPort(request, false /* shouldGetSource */);
|
||||
int dstPort = getPort(request, true /* shouldGetSource */);
|
||||
|
||||
byte[] response =
|
||||
buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, expectedUseEncap, respIkePkt);
|
||||
injectPacket(response);
|
||||
return request;
|
||||
}
|
||||
|
||||
private byte[] awaitIkePacket(
|
||||
long expectedInitIkeSpi,
|
||||
int expectedMsgId,
|
||||
boolean expectedResp,
|
||||
boolean expectedUseEncap)
|
||||
throws Exception {
|
||||
long endTime = System.currentTimeMillis() + TIMEOUT;
|
||||
int startIndex = 0;
|
||||
synchronized (mPackets) {
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
byte[] ikePkt =
|
||||
getFirstMatchingPacket(
|
||||
(pkt) -> {
|
||||
return isIke(
|
||||
pkt,
|
||||
expectedInitIkeSpi,
|
||||
expectedMsgId,
|
||||
expectedResp,
|
||||
expectedUseEncap);
|
||||
},
|
||||
startIndex);
|
||||
if (ikePkt != null) {
|
||||
return ikePkt; // We've found the packet we're looking for.
|
||||
}
|
||||
|
||||
startIndex = mPackets.size();
|
||||
|
||||
// Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
|
||||
long waitTimeout = endTime - System.currentTimeMillis();
|
||||
if (waitTimeout > 0) {
|
||||
mPackets.wait(waitTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
String direction = expectedResp ? "response" : "request";
|
||||
fail(
|
||||
"No such IKE "
|
||||
+ direction
|
||||
+ " found with Initiator SPI "
|
||||
+ expectedInitIkeSpi
|
||||
+ " and message ID "
|
||||
+ expectedMsgId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isIke(
|
||||
byte[] pkt,
|
||||
long expectedInitIkeSpi,
|
||||
int expectedMsgId,
|
||||
boolean expectedResp,
|
||||
boolean expectedUseEncap) {
|
||||
int ipProtocolOffset = 0;
|
||||
int ikeOffset = 0;
|
||||
if (isIpv6(pkt)) {
|
||||
// IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap.
|
||||
ipProtocolOffset = IP6_PROTO_OFFSET;
|
||||
ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
|
||||
} else {
|
||||
// Use default IPv4 header length (assuming no options)
|
||||
ipProtocolOffset = IP4_PROTO_OFFSET;
|
||||
ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
|
||||
|
||||
if (expectedUseEncap) {
|
||||
if (hasNonEspMarker(pkt)) {
|
||||
ikeOffset += NON_ESP_MARKER_LEN;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pkt[ipProtocolOffset] == IPPROTO_UDP
|
||||
&& areSpiAndMsgIdEqual(
|
||||
pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId, expectedResp);
|
||||
}
|
||||
|
||||
private static boolean hasNonEspMarker(byte[] pkt) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(pkt);
|
||||
int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
|
||||
if (buffer.remaining() < ikeOffset) return false;
|
||||
|
||||
buffer.get(new byte[ikeOffset]); // Skip IP and UDP header
|
||||
byte[] nonEspMarker = new byte[NON_ESP_MARKER_LEN];
|
||||
if (buffer.remaining() < NON_ESP_MARKER_LEN) return false;
|
||||
|
||||
buffer.get(nonEspMarker);
|
||||
return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
|
||||
}
|
||||
|
||||
private static boolean areSpiAndMsgIdEqual(
|
||||
byte[] pkt,
|
||||
int ikeOffset,
|
||||
long expectedIkeInitSpi,
|
||||
int expectedMsgId,
|
||||
boolean expectedResp) {
|
||||
if (pkt.length <= ikeOffset + IKE_HEADER_LEN) return false;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(pkt);
|
||||
buffer.get(new byte[ikeOffset]); // Skip IP, UDP header (and NON_ESP_MARKER)
|
||||
|
||||
// Check message ID.
|
||||
buffer.get(new byte[IKE_MSG_ID_OFFSET]);
|
||||
int msgId = buffer.getInt();
|
||||
return expectedMsgId == msgId;
|
||||
|
||||
// TODO: Check SPI and packet direction
|
||||
}
|
||||
|
||||
private static InetAddress getAddress(byte[] pkt, boolean shouldGetSource) throws Exception {
|
||||
int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
|
||||
int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
|
||||
int ipOffset = shouldGetSource ? srcIpOffset : srcIpOffset + ipLen;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(pkt);
|
||||
buffer.get(new byte[ipOffset]);
|
||||
byte[] ipAddrBytes = new byte[ipLen];
|
||||
buffer.get(ipAddrBytes);
|
||||
return InetAddress.getByAddress(ipAddrBytes);
|
||||
}
|
||||
|
||||
private static int getPort(byte[] pkt, boolean shouldGetSource) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(pkt);
|
||||
int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
|
||||
int portOffset = shouldGetSource ? srcPortOffset : srcPortOffset + PORT_LEN;
|
||||
|
||||
buffer.get(new byte[portOffset]);
|
||||
return Short.toUnsignedInt(buffer.getShort());
|
||||
}
|
||||
|
||||
private static byte[] buildIkePacket(
|
||||
InetAddress srcAddr,
|
||||
InetAddress dstAddr,
|
||||
int srcPort,
|
||||
int dstPort,
|
||||
boolean useEncap,
|
||||
byte[] ikePacket)
|
||||
throws Exception {
|
||||
if (useEncap) {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER_LEN + ikePacket.length);
|
||||
buffer.put(NON_ESP_MARKER);
|
||||
buffer.put(ikePacket);
|
||||
ikePacket = buffer.array();
|
||||
}
|
||||
|
||||
UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(ikePacket));
|
||||
IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
|
||||
return ipPkt.getPacketBytes();
|
||||
}
|
||||
|
||||
private static IpHeader getIpHeader(
|
||||
int protocol, InetAddress src, InetAddress dst, Payload payload) {
|
||||
if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
|
||||
throw new IllegalArgumentException("Invalid src/dst address combination");
|
||||
}
|
||||
|
||||
if (src instanceof Inet6Address) {
|
||||
return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
|
||||
} else {
|
||||
return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,18 +47,18 @@ public class TunUtils {
|
||||
private static final String TAG = TunUtils.class.getSimpleName();
|
||||
|
||||
private static final int DATA_BUFFER_LEN = 4096;
|
||||
private static final int TIMEOUT = 100;
|
||||
static final int TIMEOUT = 100;
|
||||
|
||||
private static final int IP4_PROTO_OFFSET = 9;
|
||||
private static final int IP6_PROTO_OFFSET = 6;
|
||||
static final int IP4_PROTO_OFFSET = 9;
|
||||
static final int IP6_PROTO_OFFSET = 6;
|
||||
|
||||
private static final int IP4_ADDR_OFFSET = 12;
|
||||
private static final int IP4_ADDR_LEN = 4;
|
||||
private static final int IP6_ADDR_OFFSET = 8;
|
||||
private static final int IP6_ADDR_LEN = 16;
|
||||
static final int IP4_ADDR_OFFSET = 12;
|
||||
static final int IP4_ADDR_LEN = 4;
|
||||
static final int IP6_ADDR_OFFSET = 8;
|
||||
static final int IP6_ADDR_LEN = 16;
|
||||
|
||||
final List<byte[]> mPackets = new ArrayList<>();
|
||||
private final ParcelFileDescriptor mTunFd;
|
||||
private final List<byte[]> mPackets = new ArrayList<>();
|
||||
private final Thread mReaderThread;
|
||||
|
||||
public TunUtils(ParcelFileDescriptor tunFd) {
|
||||
@@ -107,7 +107,7 @@ public class TunUtils {
|
||||
return Arrays.copyOf(inBytes, bytesRead);
|
||||
}
|
||||
|
||||
private byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
|
||||
byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
|
||||
synchronized (mPackets) {
|
||||
for (int i = startIndex; i < mPackets.size(); i++) {
|
||||
byte[] pkt = mPackets.get(i);
|
||||
@@ -198,7 +198,7 @@ public class TunUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isIpv6(byte[] pkt) {
|
||||
static boolean isIpv6(byte[] pkt) {
|
||||
// First nibble shows IP version. 0x60 for IPv6
|
||||
return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user