Merge "Test Data Stall with unknown detection type."
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index ec884d0..f3cd8a9 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -130,14 +130,11 @@
setLastCallback(CallbackState.CAPABILITIES, network, cap);
}
- public void expectLostCallback(Network expectedNetwork) {
- expectCallback(CallbackState.LOST, expectedNetwork, null);
- }
-
public Network expectAvailableCallbackAndGetNetwork() {
final CallbackInfo cb = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
if (cb.state != CallbackState.AVAILABLE) {
- fail("Network is not available");
+ fail("Network is not available. Instead obtained the following callback :"
+ + cb);
}
return cb.network;
}
@@ -152,7 +149,7 @@
do {
final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
if (cb.state == CallbackState.BLOCKED_STATUS) {
- assertEquals(expectBlocked, (Boolean) cb.arg);
+ assertEquals(expectBlocked, cb.arg);
return;
}
} while (System.currentTimeMillis() <= deadline);
@@ -165,10 +162,10 @@
final NetworkCapabilities cap = (NetworkCapabilities) cb.arg;
assertEquals(expectedNetwork, cb.network);
assertEquals(CallbackState.CAPABILITIES, cb.state);
- if (hasCapability) {
- assertTrue(cap.hasCapability(capability));
- } else {
- assertFalse(cap.hasCapability(capability));
+ if (hasCapability != cap.hasCapability(capability)) {
+ fail("NetworkCapabilities callback "
+ + (hasCapability ? "missing expected" : "has unexpected")
+ + " capability. " + cb);
}
}
}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 93a6d91..82b7413 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -39,6 +39,7 @@
static_libs: [
"FrameworksNetCommonTests",
+ "TestNetworkStackLib",
"core-tests-support",
"compatibility-device-util-axt",
"cts-net-utils",
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index baf914f..a7e2bd7 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
new file mode 100644
index 0000000..9be1dc7
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.IkeDerAsn1DnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Explicitly test setting up transport mode Child SA so that devices do not have
+ * FEATURE_IPSEC_TUNNELS will be test covered. Tunnel mode Child SA setup has been tested in
+ * IkeSessionPskTest and authentication method is orthogonal to Child mode.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IkeSessionDigitalSignatureTest extends IkeSessionTestBase {
+ private static final int EXPECTED_AUTH_REQ_FRAG_COUNT = 3;
+
+ private static final String IKE_INIT_RESP =
+ "46B8ECA1E0D72A18BF3FA1C2CB1EE86F21202220000000000000015022000030"
+ + "0000002C010100040300000C0100000C800E0100030000080300000503000008"
+ + "0200000400000008040000022800008800020000328451C8A976CE69E407966A"
+ + "50D7320C4197A15A07267CE1B16BAFF9BDBBDEC1FDCDAAF7175ADF9AA8DB55DB"
+ + "2D70C012D01D914C4EDEF6E8B226868EA1D01B2ED0C4C5C86E6BFE566010EC0C"
+ + "33BA1C93666430B88BDA0470D82CC4F4416F49E3E361E3017C9F27811A66718B"
+ + "389E1800915D776D59AA528A7E1D1B7815D35144290000249FE8FABE7F43D917"
+ + "CE370DE2FD9C22BBC082951AC26C1BA26DE795470F2C25BC2900001C00004004"
+ + "AE388EC86D6D1A470D44142D01AB2E85A7AC14182900001C0000400544A235A4"
+ + "171C884286B170F48FFC181DB428D87D290000080000402E290000100000402F"
+ + "00020003000400050000000800004014";
+ private static final String IKE_AUTH_RESP_FRAG_1 =
+ "46B8ECA1E0D72A18BF3FA1C2CB1EE86F3520232000000001000004E0240004C4"
+ + "00010002DF6750A2D1D5675006F9F6230BB886FFD20CFB973FD04963CFD7A528"
+ + "560598C58CC44178B2FCBBBBB271387AC81A664B7E7F1055B912F8C686E287C9"
+ + "D31684C66339151AB86DA3CF1DA664052FA97687634558A1E9E6B37E16A86BD1"
+ + "68D76DA5E2E1E0B7E98EB662D80D542307015D2BF134EBBBE425D6954FE8C2C4"
+ + "D31D16C16AA0521C3C481F873ECF25BB8B05AC6083775C1821CAAB1E35A3955D"
+ + "85ACC599574142E1DD5B262D6E5365CBF6EBE92FFCC16BC29EC3239456F3B202"
+ + "492551C0F6D752ADCCA56D506D50CC8809EF6BC56EAD005586F7168F76445FD3"
+ + "1366CC62D32C0C19B28210B8F813F97CD6A447C3857EFD6EC483DDA8ACD9870E"
+ + "5A21B9C66F0FA44496C0C3D05E8859A1A4CFC88155D0C411BABC13033DD41FA4"
+ + "AF08CE7734A146687F374F95634D1F26843203CA1FFD05CA3EB150CEA02FBF14"
+ + "712B7A1C9BC7616A086E7FCA059E7D64EFF98DB895B32F8F7002762AF7D12F23"
+ + "31E9DD25174C4CE273E5392BBB48F50B7A3E0187181216265F6A4FC7B91BE0AB"
+ + "C601A580149D4B07411AE99DDB1944B977E86ADC9746605C60A92B569EEFAFFC"
+ + "3A888D187B75D8F13249689FC28EBCD62B5E03AF171F3A561F0DEA3B1A75F531"
+ + "971157DCE1E7BC6E7789FF3E8156015BC9C521EFE48996B41471D33BF09864E4"
+ + "2436E8D7EB6218CDE7716DA754A924B123A63E25585BF27F4AC043A0C4AECE38"
+ + "BB59DD62F5C0EC657206A76CED1BD26262237DA1CA6815435992A825758DEBEC"
+ + "DDF598A22B8242AC4E34E70704DBA7B7B73DC3E067C1C98764F8791F84C99156"
+ + "947D1FFC875F36FCE24B89369C1B5BF1D4C999DCA28E72A528D0E0163C66C067"
+ + "E71B5E0025C13DA93313942F9EDA230B3ADC254821A4CB1A5DC9D0C5F4DC4E8E"
+ + "CE46B7B8C72D3C5923C9B30DF1EF7B4EDEDA8BD05C86CA0162AE1BF8F277878E"
+ + "607401BAA8F06E3EA873FA4C137324C4E0699277CDF649FE7F0F01945EE25FA7"
+ + "0E4A89737E58185B11B4CB52FD5B0497D3E3CD1CEE7B1FBB3E969DB6F4C324A1"
+ + "32DC6A0EA21F41332435FD99140C286F8ABBBA926953ADBEED17D30AAD953909"
+ + "1347EF6D87163D6B1FF32D8B11FFB2E69FAEE7FE913D3826FBA7F9D11E0E3C57"
+ + "27625B37D213710B5DD8965DAEFD3F491E8C029E2BF361039949BADEC31D60AC"
+ + "355F26EE41339C03CC9D9B01C3C7F288F0E9D6DFEE78231BDA9AC10FED135913"
+ + "2836B1A17CE060742B7E5B738A7177CCD59F70337BA251409C377A0FA5333204"
+ + "D8622BA8C06DE0BEF4F32B6D4D77BE9DE977445D8A2A08C5C38341CB7974FBFB"
+ + "22C8F983A7D6CEF068DDB2281E6673453521C831C1826861005AE5F37649BC64"
+ + "0A6360B23284861441A440F1C5AADE1AB53CA63DB17F4C314D493C4C44DE5F20"
+ + "75E084D080F92791F30BDD88373D50AB5A07BC72B0E7FFFA593103964E55603E"
+ + "F7FEB7CA0762A1A7B86B6CCAD88CD6CBC7C6935D21F5F06B2700588A2530E619"
+ + "DA1648AC809F3DDF56ACE5951737568FFEC7E2AB1AA0AE01B03A7F5A29CE73C0"
+ + "5D2801B17CAAD0121082E9952FAB16BA1C386336C62D4CF3A5019CF61609433E"
+ + "1C083237D47C4CF575097F7BF9000EF6B6C497A44E6480154A35669AD276BF05"
+ + "6CC730B4E5962B6AF96CC6D236AE85CEFDA6877173F72D2F614F6696D1F9DF07"
+ + "E107758B0978F69BC9DBE0CCBF252C40A3FDF7CE9104D3344F7B73593CCD73E0";
+ private static final String IKE_AUTH_RESP_FRAG_2 =
+ "46B8ECA1E0D72A18BF3FA1C2CB1EE86F3520232000000001000000F0000000D4"
+ + "00020002155211EA41B37BC5F20568A6AE57038EEE208F94F9B444004F1EF391"
+ + "2CABFCF857B9CD95FAAA9489ED10A3F5C93510820E22E23FC55ED8049E067D72"
+ + "3645C00E1E08611916CE72D7F0A84123B63A8F3B9E78DBBE39967B7BB074AF4D"
+ + "BF2178D991EDBDD01908A14A266D09236DB963B14AC33D894F0F83A580209EFD"
+ + "61875BB56273AA336C22D6A4D890B93E0D42435667830CC32E4F608500E18569"
+ + "3E6C1D88C0B5AE427333C86468E3474DAA4D1506AAB2A4021309A33DD759D0D0"
+ + "A8C98BF7FBEA8109361A9F194D0FD756";
+ private static final String DELETE_IKE_RESP =
+ "46B8ECA1E0D72A18BF3FA1C2CB1EE86F2E202520000000020000004C00000030"
+ + "342842D8DA37C8EFB92ED37C4FBB23CBDC90445137D6A0AF489F9F03641DBA9D"
+ + "02F6F59FD8A7A78C7261CEB8";
+
+ // Using IPv4 for transport mode Child SA. IPv6 is currently infeasible because the IKE server
+ // that generates the test vectors is running in an IPv4 only network.
+ private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS =
+ new IkeTrafficSelector(
+ MIN_PORT,
+ MAX_PORT,
+ InetAddresses.parseNumericAddress("172.58.35.103"),
+ InetAddresses.parseNumericAddress("172.58.35.103"));
+
+ // TODO(b/157510502): Add test for IKE Session setup with transport mode Child in IPv6 network
+
+ private static final String LOCAL_ID_ASN1_DN =
+ "CN=client.test.ike.android.net, O=Android, C=US";
+ private static final String REMOTE_ID_ASN1_DN =
+ "CN=server.test.ike.android.net, O=Android, C=US";
+
+ private static X509Certificate sServerCaCert;
+ private static X509Certificate sClientEndCert;
+ private static X509Certificate sClientIntermediateCaCertOne;
+ private static X509Certificate sClientIntermediateCaCertTwo;
+ private static RSAPrivateKey sClientPrivateKey;
+
+ @BeforeClass
+ public static void setUpCertsBeforeClass() throws Exception {
+ sServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
+ sClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem");
+ sClientIntermediateCaCertOne =
+ CertUtils.createCertFromPemFile("client-a-intermediate-ca-one.pem");
+ sClientIntermediateCaCertTwo =
+ CertUtils.createCertFromPemFile("client-a-intermediate-ca-two.pem");
+ sClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key");
+ }
+
+ private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+ IkeSessionParams ikeParams =
+ new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(remoteAddress.getHostAddress())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+ .setLocalIdentification(
+ new IkeDerAsn1DnIdentification(new X500Principal(LOCAL_ID_ASN1_DN)))
+ .setRemoteIdentification(
+ new IkeDerAsn1DnIdentification(
+ new X500Principal(REMOTE_ID_ASN1_DN)))
+ .setAuthDigitalSignature(
+ sServerCaCert,
+ sClientEndCert,
+ Arrays.asList(
+ sClientIntermediateCaCertOne, sClientIntermediateCaCertTwo),
+ sClientPrivateKey)
+ .build();
+
+ return new IkeSession(
+ sContext,
+ ikeParams,
+ buildTransportModeChildParamsWithTs(
+ TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS),
+ mUserCbExecutor,
+ mIkeSessionCallback,
+ mFirstChildSessionCallback);
+ }
+
+ @Test
+ public void testIkeSessionSetupAndChildSessionSetupWithTransportMode() throws Exception {
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ performSetupIkeAndFirstChildBlocking(
+ IKE_INIT_RESP,
+ EXPECTED_AUTH_REQ_FRAG_COUNT /* expectedReqPktCnt */,
+ true /* expectedAuthUseEncap */,
+ IKE_AUTH_RESP_FRAG_1,
+ IKE_AUTH_RESP_FRAG_2);
+
+ // IKE INIT and IKE AUTH takes two exchanges. Message ID starts from 2
+ int expectedMsgId = 2;
+
+ verifyIkeSessionSetupBlocking();
+ verifyChildSessionSetupBlocking(
+ mFirstChildSessionCallback,
+ Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+ Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+ new ArrayList<LinkAddress>());
+ IpSecTransformCallRecord firstTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord firstTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+ // Close IKE Session
+ ikeSession.close();
+ performCloseIkeBlocking(expectedMsgId++, DELETE_IKE_RESP);
+ verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionMschapV2Test.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionMschapV2Test.java
new file mode 100644
index 0000000..cb77127
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionMschapV2Test.java
@@ -0,0 +1,220 @@
+/*
+ * 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.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Explicitly test setting up transport mode Child SA so that devices do not have
+ * FEATURE_IPSEC_TUNNELS will be test covered. Tunnel mode Child SA setup has been tested in
+ * IkeSessionPskTest and authentication method is orthogonal to Child mode.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IkeSessionMschapV2Test extends IkeSessionTestBase {
+ private static final String IKE_INIT_RESP =
+ "46B8ECA1E0D72A1873F643FF94D249A921202220000000000000015022000030"
+ + "0000002C010100040300000C0100000C800E0080030000080300000203000008"
+ + "0200000200000008040000022800008800020000CC6E71E67E32CED6BCE33FBD"
+ + "A74113867E3FA3AE21C7C9AB44A7F8835DF602BFD6F6528B67FEE39821232380"
+ + "C99E8FFC0A5D767F8F38906DA41946C2299DF18C15FA69BAC08D3EDB32E8C8CA"
+ + "28431831561C04CB0CDE393F817151CD8DAF7A311838411F1C39BFDB5EBCF6A6"
+ + "1DF66DEB067362649D64607D599B56C4227819D0290000241197004CF31AD00F"
+ + "5E0C92E198488D8A2B6F6A25C82762AA49F565BCE9D857D72900001C00004004"
+ + "A0D98FEABBFB92A6C0976EE83D2AACFCCF969A6B2900001C0000400575EBF73F"
+ + "8EE5CC73917DE9D3F91FCD4A16A0444D290000080000402E290000100000402F"
+ + "00020003000400050000000800004014";
+ private static final String IKE_AUTH_RESP_1_FRAG_1 =
+ "46B8ECA1E0D72A1873F643FF94D249A93520232000000001000004E0240004C4"
+ + "00010002C4159CB756773B3F1911F4595107BC505D7A28C72F05182966076679"
+ + "CA68ED92E4BC5CD441C9CB315F2F449A8A521CAFED3C5F285E295FC3791D3415"
+ + "E3BACF66A08410DF4E35F7D88FE40DA28851C91C77A6549E186AC1B7846DF3FA"
+ + "0A347A5ABBCAEE19E70F0EE5966DC6242A115F29523709302EDAD2E36C8F0395"
+ + "CF5C42EC2D2898ECDD8A6AEDD686A70B589A981558667647F32F41E0D8913E94"
+ + "A6693F53E59EA8938037F562CF1DC5E6E2CDC630B5FFB08949E3172249422F7D"
+ + "EA069F9BAD5F96E48BADC7164A9269669AD0DF295A80C54D1D23CEA3F28AC485"
+ + "86D2A9850DA23823037AB7D1577B7B2364C92C36B84238357129EB4A64D33310"
+ + "B95DCD50CD53E78C32EFE7DC1627D9432E9BFDEE130045DE967B19F92A9D1270"
+ + "F1E2C6BFBAA56802F3E63510578EF1ECB6872852F286EEC790AA1FE0CAF391CB"
+ + "E276554922713BA4770CFE71E23F043DC620E22CC02A74F60725D18331B7F2C9"
+ + "276EB6FBB7CBDAA040046D7ECBE1A5D7064E04E542807C5101B941D1C81B9D5E"
+ + "90347B22BD4E638E2EDC98E369B51AA29BDB2CF8AA610D4B893EB83A4650717C"
+ + "38B4D145EE939C18DCEDF6C79933CEB3D7C116B1F188DF9DDD560951B54E4A7D"
+ + "80C999A32AB02BF39D7B498DAD36F1A5CBE2F64557D6401AE9DD6E0CEADA3F90"
+ + "540FE9114BB6B8719C9064796354F4A180A6600CAD092F8302564E409B71ACB7"
+ + "590F19B3AC88E7A606C718D0B97F7E4B4830F11D851C59F2255846DA22E2C805"
+ + "0CA2AF2ACF3B6C769D11B75B5AC9AB82ED3D90014994B1BF6FED58FBEF2D72EF"
+ + "8BDFE51F9A101393A7CA1ACF78FAEBF3E3CC25E09407D1E14AF351A159A13EE3"
+ + "9B919BA8B49942792E7527C2FB6D418C4DF427669A4BF5A1AFBBB973BAF17918"
+ + "9C9D520CAC2283B89A539ECE785EBE48FBB77D880A17D55C84A51F46068A4B87"
+ + "FF48FEEE50E1E034CC8AFF5DA92105F55EC4823E67BDFE942CA8BE0DAECBBD52"
+ + "E8AAF306049DC6C4CF87D987B0AC54FCE92E6AE8507965AAAC6AB8BD3405712F"
+ + "EE170B70BC64BDCBD86D80C7AAAF341131F9A1210D7430B17218413AE1363183"
+ + "5C98FA2428B1E9E987ADC9070E232310A28F4C3163E18366FFB112BADD7C5E0F"
+ + "D13093A7C1428F87856BA0A7E46955589ACA267CE7A04320C4BCDBB60C672404"
+ + "778F8D511AAB09349DAB482445D7F606F28E7FBBB18FC0F4EC0AF04F44C282F9"
+ + "39C6E3B955C84DADEA350667236583069B74F492D600127636FA31F63E560851"
+ + "2FC28B8EA5B4D01D110990B6EA46B9C2E7C7C856C240EF7A8147BA2C4344B85A"
+ + "453C862024B5B6814D13CDEAEF7683D539BB50CAFFC0416F269F2F9EDEC5FA30"
+ + "022FD7B4B186CD2020E7ED8D81ED90822EDD8B76F840DD68F09694CFF9B4F33E"
+ + "11DF4E601A4212881A6D4E9259001705C41E9E23D18A7F3D4A3463649A38211A"
+ + "5A90D0F17739A677C74E23F31C01D60B5A0F1E6A4D44FED9D25BF1E63418E1FC"
+ + "0B19F6F4B71DE53C62B14B82279538A82DD4BE19AB6E00AFC20F124AAB7DF21A"
+ + "42259BE4F40EC69B16917256F23E2C37376311D62E0A3A0EF8C2AD0C090221D5"
+ + "C5ECA08F08178A4D31FFDB150C609827D18AD83C7B0A43AEE0406BD3FB494B53"
+ + "A279FDD6447E234C926AD8CE47FFF779BB45B1FC8457C6E7D257D1359959D977"
+ + "CEF6906A3367DC4D454993EFDC6F1EA94E17EB3DCB00A289346B4CFD7F19B16E";
+ private static final String IKE_AUTH_RESP_1_FRAG_2 =
+ "46B8ECA1E0D72A1873F643FF94D249A935202320000000010000008000000064"
+ + "00020002C61F66025E821A5E69A4DE1F591A2C32C983C3154A5003660137D685"
+ + "A5262B9FDF5EDC699DE4D8BD38F549E3CBD12024B45B4C86561C36C3EED839DA"
+ + "9860C6AA0B764C662D08F1B6A98F68CF6E3038F737C0B415AD8A8B7D702BD92A";
+ private static final String IKE_AUTH_RESP_2 =
+ "46B8ECA1E0D72A1873F643FF94D249A92E202320000000020000008C30000070"
+ + "62B90C2229FD23025BC2FD7FE6341E9EE04B17264CD619BCE18975A5F88BE438"
+ + "D4AD4A5310057255AF568C293A29B10107E3EE3675C10AA2B26404D90C0528CC"
+ + "F7605A86C96A1F2635CCC6CFC90EE65E5C2A2262EB33FE520EB708423A83CB63"
+ + "274ECCBB102AF5DF35742657";
+ private static final String IKE_AUTH_RESP_3 =
+ "46B8ECA1E0D72A1873F643FF94D249A92E202320000000030000004C30000030"
+ + "AB52C3C80123D3432C05AF457CE93C352395F73E861CD49561BA528CFE68D17D"
+ + "78BBF6FC41E81C2B9EA051A2";
+ private static final String IKE_AUTH_RESP_4 =
+ "46B8ECA1E0D72A1873F643FF94D249A92E20232000000004000000CC270000B0"
+ + "8D3342A7AB2666AC754F4B55C5C6B1A61255E62FBCA53D5CDEEDE60DADB7915C"
+ + "7F962076A58BF7D39A05ED1B60FF349B6DE311AF7CEBC72B4BB9723A728A5D3E"
+ + "9E508B2D7A11843D279B56ADA07E608D61F5CA7638F10372A440AD1DCE44E190"
+ + "7B7B7A68B126EBBB86638D667D5B528D233BA8D32D7E0FAC4E1448E87396EEE6"
+ + "0985B79841E1229D7962AACFD8F872722EC8D5B19D4C82D6C4ADCB276127A1A7"
+ + "3FC84CDF85B2299BC96B64AC";
+ private static final String DELETE_IKE_RESP =
+ "46B8ECA1E0D72A1873F643FF94D249A92E202520000000050000004C00000030"
+ + "622CE06C8CB132AA00567E9BC83F58B32BD7DB5130C76E385B306434DA227361"
+ + "D50CC19D408A8D4F36F9697F";
+
+ // This value is align with the test vectors hex that are generated in an IPv4 environment
+ private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS =
+ new IkeTrafficSelector(
+ MIN_PORT,
+ MAX_PORT,
+ InetAddresses.parseNumericAddress("172.58.35.67"),
+ InetAddresses.parseNumericAddress("172.58.35.67"));
+
+ private static final EapSessionConfig EAP_CONFIG =
+ new EapSessionConfig.Builder()
+ .setEapIdentity(EAP_IDENTITY)
+ .setEapMsChapV2Config(EAP_MSCHAPV2_USERNAME, EAP_MSCHAPV2_PASSWORD)
+ .build();
+
+ private static X509Certificate sServerCaCert;
+
+ @BeforeClass
+ public static void setUpCertBeforeClass() throws Exception {
+ sServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
+ }
+
+ private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+ IkeSessionParams ikeParams =
+ new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(remoteAddress.getHostAddress())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+ .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+ .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+ .setAuthEap(sServerCaCert, EAP_CONFIG)
+ .build();
+ return new IkeSession(
+ sContext,
+ ikeParams,
+ buildTransportModeChildParamsWithTs(
+ TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS),
+ mUserCbExecutor,
+ mIkeSessionCallback,
+ mFirstChildSessionCallback);
+ }
+
+ @Test
+ public void testIkeSessionSetupAndChildSessionSetupWithTransportMode() throws Exception {
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ int expectedMsgId = 0;
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ expectedMsgId++,
+ false /* expectedUseEncap */,
+ IKE_INIT_RESP);
+
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ IKE_AUTH_RESP_1_FRAG_1,
+ IKE_AUTH_RESP_1_FRAG_2);
+
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ IKE_AUTH_RESP_2);
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ IKE_AUTH_RESP_3);
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ expectedMsgId++,
+ true /* expectedUseEncap */,
+ IKE_AUTH_RESP_4);
+
+ verifyIkeSessionSetupBlocking();
+ verifyChildSessionSetupBlocking(
+ mFirstChildSessionCallback,
+ Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+ Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+ new ArrayList<LinkAddress>());
+ IpSecTransformCallRecord firstTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord firstTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+ // Close IKE Session
+ ikeSession.close();
+ performCloseIkeBlocking(expectedMsgId++, DELETE_IKE_RESP);
+ verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
index ed67dd1..0509fc0 100644
--- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java
@@ -16,39 +16,34 @@
package android.net.ipsec.ike.cts;
-import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
+import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE;
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.LinkAddress;
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.AfterClass;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
+@AppModeFull(reason = "MANAGE_IPSEC_TUNNELS 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 =
@@ -89,170 +84,278 @@
+ "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) {
+ private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+ IkeSessionParams ikeParams =
+ new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(remoteAddress.getHostAddress())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+ .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+ .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+ .setAuthPsk(IKE_PSK)
+ .build();
return new IkeSession(
sContext,
ikeParams,
- CHILD_PARAMS,
+ buildTunnelModeChildSessionParams(),
mUserCbExecutor,
mIkeSessionCallback,
mFirstChildSessionCallback);
}
+ @BeforeClass
+ public static void setUpTunnelPermissionBeforeClass() throws Exception {
+ // 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 tearDownTunnelPermissionAfterClass() throws Exception {
+ setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
+ }
+
@Test
- public void testIkeSessionSetupAndManageChildSas() throws Exception {
+ public void testIkeSessionSetupAndChildSessionSetupWithTunnelMode() throws Exception {
+ if (!hasTunnelsFeature()) return;
+
// Open IKE Session
- IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
- int expectedMsgId = 0;
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- false /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ performSetupIkeAndFirstChildBlocking(SUCCESS_IKE_INIT_RESP, SUCCESS_IKE_AUTH_RESP);
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- true /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+ // IKE INIT and IKE AUTH takes two exchanges. Message ID starts from 2
+ int expectedMsgId = 2;
- // 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));
+ verifyIkeSessionSetupBlocking();
+ verifyChildSessionSetupBlocking(
+ mFirstChildSessionCallback,
+ Arrays.asList(TUNNEL_MODE_INBOUND_TS),
+ Arrays.asList(TUNNEL_MODE_OUTBOUND_TS),
+ Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR));
- 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());
+ IpSecTransformCallRecord firstTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord firstTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
// Open additional Child Session
TestChildSessionCallback additionalChildCb = new TestChildSessionCallback();
- ikeSession.openChildSession(CHILD_PARAMS, additionalChildCb);
+ ikeSession.openChildSession(buildTunnelModeChildSessionParams(), additionalChildCb);
mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
+ IKE_DETERMINISTIC_INITIATOR_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_CREATE_CHILD_RESP));
+ 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());
+ verifyChildSessionSetupBlocking(
+ additionalChildCb,
+ Arrays.asList(TUNNEL_MODE_INBOUND_TS),
+ Arrays.asList(TUNNEL_MODE_OUTBOUND_TS),
+ new ArrayList<LinkAddress>());
+ IpSecTransformCallRecord additionalTransformRecordA =
+ additionalChildCb.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord additionalTransformRecordB =
+ additionalChildCb.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(additionalTransformRecordA, additionalTransformRecordB);
// Close additional Child Session
ikeSession.closeChildSession(additionalChildCb);
mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
+ IKE_DETERMINISTIC_INITIATOR_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_DELETE_CHILD_RESP));
+ SUCCESS_DELETE_CHILD_RESP);
+ verifyDeleteIpSecTransformPair(
+ additionalChildCb, additionalTransformRecordA, additionalTransformRecordB);
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
+ performCloseIkeBlocking(expectedMsgId++, SUCCESS_DELETE_IKE_RESP);
+ verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
}
@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));
+ public void testIkeSessionSetupAndChildSessionSetupWithTunnelModeV6() throws Exception {
+ if (!hasTunnelsFeature()) return;
- mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
- expectedMsgId++,
- true /* expectedUseEncap */,
- hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
+ final String ikeInitResp =
+ "46B8ECA1E0D72A186F7B6C2CEB77EB9021202220000000000000011822000030"
+ + "0000002C010100040300000C0100000C800E0100030000080300000C03000008"
+ + "0200000500000008040000022800008800020000DABAA04B38B491E2403F2125"
+ + "96ECF1C8EF7B1DC19A422FDD46E1756C826BB3A16404361B775D9950577B5CDF"
+ + "6AAA1642BD1427BDA8BC55354A97C1025E19C1E2EE2DF8A0C9406E545D829F52"
+ + "75695008E3B742984B8DD1770F3514213B0DF3EE8B199416DF200D248115C057"
+ + "1C193E4F96802E5EF48DD99CAC251882A8F7CCC329000024BC6F0F1D3653C2C7"
+ + "679E02CDB6A3B32B2FEE9AF52F0326D4D9AE073D56CE8922290000080000402E"
+ + "290000100000402F00020003000400050000000800004014";
+ final String ikeAuthResp =
+ "46B8ECA1E0D72A186F7B6C2CEB77EB902E202320000000010000015024000134"
+ + "4D115AFDCDAD0310760BB664EB7D405A340869AD6EDF0AAEAD0663A9253DADCB"
+ + "73EBE5CD29D4FA1CDEADE0B94391B5C4CF77BCC1596ACE3CE6A7891E44888FA5"
+ + "46632C0EF4E6193C023C9DC59142C37D1C49D6EF5CD324EC6FC35C89E1721C78"
+ + "91FDCDB723D8062709950F4AA9273D26A54C9C7E86862DBC15F7B6641D2B9BAD"
+ + "E55069008201D12968D97B537B1518FE87B0FFA03C3EE6012C06721B1E2A3F68"
+ + "92108BC4A4F7063F7F94562D8B60F291A1377A836CF12BCDA7E15C1A8F3C77BB"
+ + "6DB7F2C833CCE4CDDED7506536621A3356CE2BC1874E7B1A1A9B447D7DF6AB09"
+ + "638B8AD94A781B28BB91B514B611B24DF8E8A047A10AE27BBF15C754D3D2F792"
+ + "D3E1CCADDAE934C98AE53A8FC3419C88AFF0355564F82A629C998012DA7BB704"
+ + "5307270DF326377E3E1994476902035B";
+ final String deleteIkeResp =
+ "46B8ECA1E0D72A186F7B6C2CEB77EB902E202520000000020000005000000034"
+ + "CF15C299F35688E5140A48B61C95F004121BF8236201415E5CD45BA41AAB16D4"
+ + "90B44B9E6D5D92B5B97D24196A58C73F";
+
+ mLocalAddress = IPV6_ADDRESS_LOCAL;
+ mRemoteAddress = IPV6_ADDRESS_REMOTE;
+
+ // Teardown current test network that uses IPv4 address and set up new network with IPv6
+ // address.
+ tearDownTestNetwork();
+ setUpTestNetwork(mLocalAddress);
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ performSetupIkeAndFirstChildBlocking(
+ ikeInitResp,
+ 1 /* expectedAuthReqPktCnt */,
+ false /* expectedAuthUseEncap */,
+ ikeAuthResp);
+
+ // Local request message ID starts from 2 because there is one IKE_INIT message and a single
+ // IKE_AUTH message.
+ int expectedMsgId = 2;
+
+ verifyIkeSessionSetupBlocking();
+ verifyChildSessionSetupBlocking(
+ mFirstChildSessionCallback,
+ Arrays.asList(TUNNEL_MODE_INBOUND_TS_V6),
+ Arrays.asList(TUNNEL_MODE_OUTBOUND_TS_V6),
+ Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR_V6),
+ Arrays.asList(EXPECTED_DNS_SERVERS_ONE, EXPECTED_DNS_SERVERS_TWO));
+
+ IpSecTransformCallRecord firstTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord firstTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+ // Close IKE Session
+ ikeSession.close();
+ performCloseIkeBlocking(expectedMsgId++, false /* expectedUseEncap */, deleteIkeResp);
+ verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB);
+ }
+
+ @Test
+ public void testIkeSessionKillWithTunnelMode() throws Exception {
+ if (!hasTunnelsFeature()) return;
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ performSetupIkeAndFirstChildBlocking(SUCCESS_IKE_INIT_RESP, SUCCESS_IKE_AUTH_RESP);
ikeSession.kill();
-
mFirstChildSessionCallback.awaitOnClosed();
mIkeSessionCallback.awaitOnClosed();
}
@Test
public void testIkeInitFail() throws Exception {
- String ikeInitFailRespHex =
+ final String ikeInitFailRespHex =
"46B8ECA1E0D72A180000000000000000292022200000000000000024000000080000000E";
// Open IKE Session
- IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
int expectedMsgId = 0;
mTunUtils.awaitReqAndInjectResp(
- IKE_INIT_SPI,
+ IKE_DETERMINISTIC_INITIATOR_SPI,
expectedMsgId++,
false /* expectedUseEncap */,
- hexStringToByteArray(ikeInitFailRespHex));
+ ikeInitFailRespHex);
- IkeException exception = mIkeSessionCallback.awaitOnClosedException();
- assertNotNull(exception);
- assertTrue(exception instanceof IkeProtocolException);
- IkeProtocolException protocolException = (IkeProtocolException) exception;
+ mFirstChildSessionCallback.awaitOnClosed();
+
+ IkeProtocolException protocolException =
+ (IkeProtocolException) mIkeSessionCallback.awaitOnClosedException();
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
+ @Test
+ public void testIkeAuthHandlesAuthFailNotification() throws Exception {
+ final String ikeInitRespHex =
+ "46B8ECA1E0D72A18CF94CE3159486F002120222000000000000001502200"
+ + "00300000002C010100040300000C0100000C800E01000300000803000005"
+ + "0300000802000004000000080400000228000088000200001821AA854691"
+ + "FA3292DF710F0AC149ACBD0CB421608B8796C1912AF04C5B4B23936FDEC4"
+ + "7CB640E3EAFB56BBB562825E87AF68B40E4BAB80A49BAD44407450A4195A"
+ + "1DD54BD99F48D28C9F0FBA315A3401C1C3C4AD55911F514A8DF2D2467C46"
+ + "A73DDC1452AE81336E0F0D5EC896D2E7A77628AF2F9089F48943399DF216"
+ + "EFCD2900002418D2B7E4E6AF0FEFF5962CF8D68F7793B1293FEDE13331D4"
+ + "AB0CE9436C2EE1EC2900001C0000400457BD9AEF5B362A83DD7F3DDAA4A9"
+ + "9B6B4041DAF32900001C000040055A81893582701E44D4B6729A22FE06DE"
+ + "82A03A36290000080000402E290000100000402F00020003000400050000"
+ + "000800004014";
+ final String ikeAuthFailRespHex =
+ "46B8ECA1E0D72A18CF94CE3159486F002E202320000000010000004C2900"
+ + "00301B9E4C8242D3BE62E7F0A537FE8B92C6EAB7153105DA421DCE43A06D"
+ + "AB6E4808BAC0CA1DAD6ADD0A126A41BD";
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ performSetupIkeAndFirstChildBlocking(ikeInitRespHex, ikeAuthFailRespHex);
+
+ mFirstChildSessionCallback.awaitOnClosed();
+ IkeProtocolException protocolException =
+ (IkeProtocolException) mIkeSessionCallback.awaitOnClosedException();
+ assertEquals(ERROR_TYPE_AUTHENTICATION_FAILED, protocolException.getErrorType());
+ assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
+ }
+
+ @Test
+ public void testIkeAuthHandlesFirstChildCreationFail() throws Exception {
+ final String ikeInitRespHex =
+ "46B8ECA1E0D72A182B300285DA19E6452120222000000000000001502200"
+ + "00300000002C010100040300000C0100000C800E01000300000803000005"
+ + "0300000802000004000000080400000228000088000200005C9DE629981F"
+ + "DB1FC45DB6CCF15D076C1F51BD9F63C771DC089F05CCDE6247965D15C616"
+ + "C7B5A62342491715E4D1FEA19326477D24143E8E56AB6AD93F54B19BC32A"
+ + "44BC0A5B5632E57D0A3C43E466E1547D8E4EF65EA4B864A348161666E229"
+ + "84975A486251A17C4F096A6D5CF3DB83874B70324A31AA7ADDE2D73BADD8"
+ + "238029000024CF06260F7C4923295E7C91F2B8479212892DA7A519A0322F"
+ + "F5B2BF570B92972B2900001C00004004C7ACC2C7D58CF8C9F5E953993AF4"
+ + "6CAC976635B42900001C00004005B64B190DFE7BDE8B9B1475EDE67B63D6"
+ + "F1DBBF44290000080000402E290000100000402F00020003000400050000"
+ + "000800004014";
+ final String ikeAuthCreateChildFailHex =
+ "46B8ECA1E0D72A182B300285DA19E6452E202320000000010000008C2400"
+ + "0070386FC9CCC67495A17915D0544390A2963A769F4A42C6FA668CEEC07F"
+ + "EC0C87D681DE34267023DD394F1401B5A563E71002C0CE0928D0ABC0C4570"
+ + "E39C2EDEF820F870AB71BD70A3F3EB5C96CA294B6D3F01677690DCF9F8CFC"
+ + "9584650957573502BA83E32F18207A9ADEB1FA";
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ performSetupIkeAndFirstChildBlocking(ikeInitRespHex, ikeAuthCreateChildFailHex);
+
+ // Even though the child creation failed, the authentication succeeded, so the IKE Session's
+ // onOpened() callback is still expected
+ verifyIkeSessionSetupBlocking();
+
+ // Verify Child Creation failed
+ IkeProtocolException protocolException =
+ (IkeProtocolException) mFirstChildSessionCallback.awaitOnClosedException();
+ assertEquals(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE, protocolException.getErrorType());
+ assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
+
+ ikeSession.kill();
+ mIkeSessionCallback.awaitOnClosed();
+ }
}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java
new file mode 100644
index 0000000..f954fcd
--- /dev/null
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionRekeyTest.java
@@ -0,0 +1,265 @@
+/*
+ * 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 com.android.internal.util.HexDump.hexStringToByteArray;
+
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeSession;
+import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.cts.IkeTunUtils.PortPair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Explicitly test transport mode Child SA so that devices without FEATURE_IPSEC_TUNNELS can be test
+ * covered. Tunnel mode Child SA setup has been tested in IkeSessionPskTest. Rekeying process is
+ * independent from Child SA mode.
+ */
+@RunWith(AndroidJUnit4.class)
+public class IkeSessionRekeyTest extends IkeSessionTestBase {
+ // This value is align with the test vectors hex that are generated in an IPv4 environment
+ private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS =
+ new IkeTrafficSelector(
+ MIN_PORT,
+ MAX_PORT,
+ InetAddresses.parseNumericAddress("172.58.35.40"),
+ InetAddresses.parseNumericAddress("172.58.35.40"));
+
+ private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) {
+ IkeSessionParams ikeParams =
+ new IkeSessionParams.Builder(sContext)
+ .setNetwork(mTunNetwork)
+ .setServerHostname(remoteAddress.getHostAddress())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
+ .setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
+ .setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
+ .setAuthPsk(IKE_PSK)
+ .build();
+ return new IkeSession(
+ sContext,
+ ikeParams,
+ buildTransportModeChildParamsWithTs(
+ TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS),
+ mUserCbExecutor,
+ mIkeSessionCallback,
+ mFirstChildSessionCallback);
+ }
+
+ private byte[] buildInboundPkt(PortPair outPktSrcDestPortPair, String inboundDataHex)
+ throws Exception {
+ // Build inbound packet by flipping the outbound packet addresses and ports
+ return IkeTunUtils.buildIkePacket(
+ mRemoteAddress,
+ mLocalAddress,
+ outPktSrcDestPortPair.dstPort,
+ outPktSrcDestPortPair.srcPort,
+ true /* useEncap */,
+ hexStringToByteArray(inboundDataHex));
+ }
+
+ @Test
+ public void testRekeyIke() throws Exception {
+ final String ikeInitResp =
+ "46B8ECA1E0D72A1866B5248CF6C7472D21202220000000000000015022000030"
+ + "0000002C010100040300000C0100000C800E0100030000080300000C03000008"
+ + "0200000500000008040000022800008800020000920D3E830E7276908209212D"
+ + "E5A7F2A48706CFEF1BE8CB6E3B173B8B4E0D8C2DC626271FF1B13A88619E569E"
+ + "7B03C3ED2C127390749CDC7CDC711D0A8611E4457FFCBC4F0981B3288FBF58EA"
+ + "3E8B70E27E76AE70117FBBCB753660ADDA37EB5EB3A81BED6A374CCB7E132C2A"
+ + "94BFCE402DC76B19C158B533F6B1F2ABF01ACCC329000024B302CA2FB85B6CF4"
+ + "02313381246E3C53828D787F6DFEA6BD62D6405254AEE6242900001C00004004"
+ + "7A1682B06B58596533D00324886EF1F20EF276032900001C00004005BF633E31"
+ + "F9984B29A62E370BB2770FC09BAEA665290000080000402E290000100000402F"
+ + "00020003000400050000000800004014";
+ final String ikeAuthResp =
+ "46B8ECA1E0D72A1866B5248CF6C7472D2E20232000000001000000F0240000D4"
+ + "10166CA8647F56123DE74C17FA5E256043ABF73216C812EE32EE1BB01EAF4A82"
+ + "DC107AB3ADBFEE0DEA5EEE10BDD5D43178F4C975C7C775D252273BB037283C7F"
+ + "236FE34A6BCE4833816897075DB2055B9FFD66DFA45A0A89A8F70AFB59431EED"
+ + "A20602FB614369D12906D3355CF7298A5D25364ABBCC75A9D88E0E6581449FCD"
+ + "4E361A39E00EFD1FD0A69651F63DB46C12470226AA21BA5EFF48FAF0B6DDF61C"
+ + "B0A69392CE559495EEDB4D1C1D80688434D225D57210A424C213F7C993D8A456"
+ + "38153FBD194C5E247B592D1D048DB4C8";
+ final String rekeyIkeCreateReq =
+ "46B8ECA1E0D72A1866B5248CF6C7472D2E202400000000000000013021000114"
+ + "13743670039E308A8409BA5FD47B67F956B36FEE88AC3B70BB5D789B8218A135"
+ + "1B3D83E260E87B3EDB1BF064F09D4DC2611AEDBC99951B4B2DE767BD4AA2ACC3"
+ + "3653549CFC66B75869DF003CDC9A137A9CC27776AD5732B34203E74BE8CA4858"
+ + "1D5C0D9C9CA52D680EB299B4B21C7FA25FFEE174D57015E0FF2EAED653AAD95C"
+ + "071ABE269A8C2C9FBC1188E07550EB992F910D4CA9689E44BA66DE0FABB2BDF9"
+ + "8DD377186DBB25EF9B68B027BB2A27981779D8303D88D7CE860010A42862D50B"
+ + "1E0DBFD3D27C36F14809D7F493B2B96A65534CF98B0C32AD5219AD77F681AC04"
+ + "9D5CB89A0230A91A243FA7F16251B0D9B4B65E7330BEEAC9663EF4578991EAC8"
+ + "46C19EBB726E7D113F1D0D601102C05E";
+ final String rekeyIkeDeleteReq =
+ "46B8ECA1E0D72A1866B5248CF6C7472D2E20250000000001000000502A000034"
+ + "02E40C0C7B1ED977729F705BB9B643FAC513A1070A6EB28ECD2AEA8A441ADC05"
+ + "7841382A7967BBF116AE52496590B2AD";
+ final String deleteIkeReq =
+ "7D3DEDC65407D1FC9361C8CF8C47162A2E20250800000000000000502A000034"
+ + "201915C9E4E9173AA9EE79F3E02FE2D4954B22085C66D164762C34D347C16E9F"
+ + "FC5F7F114428C54D8D915860C57B1BC1";
+ final long newIkeDeterministicInitSpi = Long.parseLong("7D3DEDC65407D1FC", 16);
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ PortPair localRemotePorts = performSetupIkeAndFirstChildBlocking(ikeInitResp, ikeAuthResp);
+
+ // Local request message ID starts from 2 because there is one IKE_INIT message and a single
+ // IKE_AUTH message.
+ int expectedReqMsgId = 2;
+ int expectedRespMsgId = 0;
+
+ verifyIkeSessionSetupBlocking();
+ verifyChildSessionSetupBlocking(
+ mFirstChildSessionCallback,
+ Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+ Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+ new ArrayList<LinkAddress>());
+ IpSecTransformCallRecord firstTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord firstTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB);
+
+ // Inject rekey IKE requests
+ mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeCreateReq));
+ mTunUtils.awaitResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+ mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyIkeDeleteReq));
+ mTunUtils.awaitResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+
+ // IKE has been rekeyed, reset message IDs
+ expectedReqMsgId = 0;
+ expectedRespMsgId = 0;
+
+ // Inject delete IKE request
+ mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
+ mTunUtils.awaitResp(
+ newIkeDeterministicInitSpi, expectedRespMsgId++, true /* expectedUseEncap */);
+
+ verifyDeleteIpSecTransformPair(
+ mFirstChildSessionCallback, firstTransformRecordA, firstTransformRecordB);
+ mFirstChildSessionCallback.awaitOnClosed();
+ mIkeSessionCallback.awaitOnClosed();
+ }
+
+ @Test
+ public void testRekeyTransportModeChildSa() throws Exception {
+ final String ikeInitResp =
+ "46B8ECA1E0D72A18CECD871146CF83A121202220000000000000015022000030"
+ + "0000002C010100040300000C0100000C800E0100030000080300000C03000008"
+ + "0200000500000008040000022800008800020000C4904458957746BCF1C12972"
+ + "1D4E19EB8A584F78DE673053396D167CE0F34552DBC69BA63FE7C673B4CF4A99"
+ + "62481518EE985357876E8C47BAAA0DBE9C40AE47B12E52165874703586E8F786"
+ + "045F72EEEB238C5D1823352BED44B71B3214609276ADC0B3D42DAC820168C4E2"
+ + "660730DAAC92492403288805EBB9053F1AB060DA290000242D9364ACB93519FF"
+ + "8F8B019BAA43A40D699F59714B327B8382216EF427ED52282900001C00004004"
+ + "06D91438A0D6B734E152F76F5CC55A72A2E38A0A2900001C000040052EFF78B3"
+ + "55B37F3CE75AFF26C721B050F892C0D6290000080000402E290000100000402F"
+ + "00020003000400050000000800004014";
+ final String ikeAuthResp =
+ "46B8ECA1E0D72A18CECD871146CF83A12E20232000000001000000F0240000D4"
+ + "A17BC258BA2714CF536663639DD5F665A60C75E93557CD5141990A8CEEDD2017"
+ + "93F5B181C8569FBCD6C2A00198EC2B62D42BEFAC016B8B6BF6A7BC9CEDE3413A"
+ + "6C495A6B8EC941864DC3E08F57D015EA6520C4B05884960B85478FCA53DA5F17"
+ + "9628BB1097DA77461C71837207A9EB80720B3E6E661816EE4E14AC995B5E8441"
+ + "A4C3F9097CC148142BA300076C94A23EC4ADE82B1DD2B121F7E9102860A8C3BF"
+ + "58DDC207285A3176E924C44DE820322524E1AA438EFDFBA781B36084AED80846"
+ + "3B77FCED9682B6E4E476408EF3F1037E";
+ final String rekeyChildCreateReq =
+ "46B8ECA1E0D72A18CECD871146CF83A12E202400000000000000015029000134"
+ + "319D74B6B155B86942143CEC1D29D21F073F24B7BEDC9BFE0F0FDD8BDB5458C0"
+ + "8DB93506E1A43DD0640FE7370C97F9B34FF4EC9B2DB7257A87B75632301FB68A"
+ + "86B54871249534CA3D01C9BEB127B669F46470E1C8AAF72574C3CEEC15B901CF"
+ + "5A0D6ADAE59C3CA64AC8C86689C860FAF9500E608DFE63F2DCD30510FD6FFCD5"
+ + "A50838574132FD1D069BCACD4C7BAF45C9B1A7689FAD132E3F56DBCFAF905A8C"
+ + "4145D4BA1B74A54762F8F43308D94DE05649C49D885121CE30681D51AC1E3E68"
+ + "AB82F9A19B99579AFE257F32DBD1037814DA577379E4F42DEDAC84502E49C933"
+ + "9EA83F6F5DB4401B660CB1681B023B8603D205DFDD1DE86AD8DE22B6B754F30D"
+ + "05EAE81A709C2CEE81386133DC3DC7B5EF8F166E48E54A0722DD0C64F4D00638"
+ + "40F272144C47F6ECED72A248180645DB";
+ final String rekeyChildDeleteReq =
+ "46B8ECA1E0D72A18CECD871146CF83A12E20250000000001000000502A000034"
+ + "02D98DAF0432EBD991CA4F2D89C1E0EFABC6E91A3327A85D8914FB2F1485BE1B"
+ + "8D3415D548F7CE0DC4224E7E9D0D3355";
+ final String deleteIkeReq =
+ "46B8ECA1E0D72A18CECD871146CF83A12E20250000000002000000502A000034"
+ + "095041F4026B4634F04B0AB4F9349484F7BE9AEF03E3733EEE293330043B75D2"
+ + "ABF5F965ED51127629585E1B1BBA787F";
+
+ // Open IKE Session
+ IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress);
+ PortPair localRemotePorts = performSetupIkeAndFirstChildBlocking(ikeInitResp, ikeAuthResp);
+
+ // IKE INIT and IKE AUTH takes two exchanges. Local request message ID starts from 2
+ int expectedReqMsgId = 2;
+ int expectedRespMsgId = 0;
+
+ verifyIkeSessionSetupBlocking();
+ verifyChildSessionSetupBlocking(
+ mFirstChildSessionCallback,
+ Arrays.asList(TRANSPORT_MODE_INBOUND_TS),
+ Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS),
+ new ArrayList<LinkAddress>());
+ IpSecTransformCallRecord oldTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord oldTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(oldTransformRecordA, oldTransformRecordB);
+
+ // Inject rekey Child requests
+ mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildCreateReq));
+ mTunUtils.awaitResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+ mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, rekeyChildDeleteReq));
+ mTunUtils.awaitResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+
+ // Verify IpSecTransforms are renewed
+ IpSecTransformCallRecord newTransformRecordA =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ IpSecTransformCallRecord newTransformRecordB =
+ mFirstChildSessionCallback.awaitNextCreatedIpSecTransform();
+ verifyCreateIpSecTransformPair(newTransformRecordA, newTransformRecordB);
+ verifyDeleteIpSecTransformPair(
+ mFirstChildSessionCallback, oldTransformRecordA, oldTransformRecordB);
+
+ // Inject delete IKE request
+ mTunUtils.injectPacket(buildInboundPkt(localRemotePorts, deleteIkeReq));
+ mTunUtils.awaitResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI, expectedRespMsgId++, true /* expectedUseEncap */);
+
+ verifyDeleteIpSecTransformPair(
+ mFirstChildSessionCallback, newTransformRecordA, newTransformRecordB);
+ mFirstChildSessionCallback.awaitOnClosed();
+ mIkeSessionCallback.awaitOnClosed();
+ }
+}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
index deba8fd..2458b25 100644
--- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java
@@ -15,13 +15,21 @@
package android.net.ipsec.ike.cts;
-import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
+import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.Network;
@@ -32,14 +40,17 @@
import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.TransportModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.cts.IkeTunUtils.PortPair;
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;
@@ -55,6 +66,11 @@
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -65,6 +81,13 @@
*
* <p>Subclasses MUST explicitly call #setUpTestNetwork and #tearDownTestNetwork to be able to use
* the test network
+ *
+ * <p>All IKE Sessions running in test mode will generate SPIs deterministically. That is to say
+ * each IKE Session will always generate the same IKE INIT SPI and test vectors are generated based
+ * on this deterministic IKE SPI. Each test will use different local and remote addresses to avoid
+ * the case that the next test try to allocate the same SPI before the previous test has released
+ * it, since SPI resources are not released in testing thread. Similarly, each test MUST use
+ * different Network instances to avoid sharing the same IkeSocket and hitting IKE SPI collision.
*/
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
@@ -72,13 +95,39 @@
// 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_DNS_SERVERS_ONE =
+ InetAddresses.parseNumericAddress("8.8.8.8");
+ static final InetAddress EXPECTED_DNS_SERVERS_TWO =
+ InetAddresses.parseNumericAddress("8.8.4.4");
+
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 =
+ static final InetAddress EXPECTED_INTERNAL_ADDR_V6 =
+ InetAddresses.parseNumericAddress("2001:db8::2");
+ static final LinkAddress EXPECTED_INTERNAL_LINK_ADDR_V6 =
+ new LinkAddress(EXPECTED_INTERNAL_ADDR_V6, IP6_PREFIX_LEN);
+
+ static final IkeTrafficSelector TUNNEL_MODE_INBOUND_TS =
new IkeTrafficSelector(
MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR, EXPECTED_INTERNAL_ADDR);
+ static final IkeTrafficSelector TUNNEL_MODE_OUTBOUND_TS = DEFAULT_V4_TS;
+ static final IkeTrafficSelector TUNNEL_MODE_INBOUND_TS_V6 =
+ new IkeTrafficSelector(
+ MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR_V6, EXPECTED_INTERNAL_ADDR_V6);
+ static final IkeTrafficSelector TUNNEL_MODE_OUTBOUND_TS_V6 = DEFAULT_V6_TS;
+
+ // This value is align with the test vectors hex that are generated in an IPv4 environment
+ static final IkeTrafficSelector TRANSPORT_MODE_OUTBOUND_TS =
+ new IkeTrafficSelector(
+ MIN_PORT,
+ MAX_PORT,
+ InetAddresses.parseNumericAddress("10.138.0.2"),
+ InetAddresses.parseNumericAddress("10.138.0.2"));
+
+ static final long IKE_DETERMINISTIC_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
// Static state to reduce setup/teardown
static Context sContext = InstrumentationRegistry.getContext();
@@ -116,20 +165,13 @@
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);
+ sTNM = sContext.getSystemService(TestNetworkManager.class);
}
// 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();
@@ -149,14 +191,10 @@
@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;
+ int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP6_PREFIX_LEN;
TestNetworkInterface testIface =
sTNM.createTunInterface(new LinkAddress[] {new LinkAddress(localAddr, prefixLen)});
@@ -176,7 +214,7 @@
mTunFd.close();
}
- private static void setAppOp(int appop, boolean allow) {
+ static void setAppOp(int appop, boolean allow) {
String opName = AppOpsManager.opToName(appop);
for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
String cmd =
@@ -185,9 +223,8 @@
pkg, // Package name
opName, // Appop
(allow ? "allow" : "deny")); // Action
- Log.d("IKE", "CTS setAppOp cmd " + cmd);
- String result = SystemUtil.runShellCommand(cmd);
+ SystemUtil.runShellCommand(cmd);
}
}
@@ -229,6 +266,79 @@
}
}
+ TransportModeChildSessionParams buildTransportModeChildParamsWithTs(
+ IkeTrafficSelector inboundTs, IkeTrafficSelector outboundTs) {
+ return new TransportModeChildSessionParams.Builder()
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+ .addInboundTrafficSelectors(inboundTs)
+ .addOutboundTrafficSelectors(outboundTs)
+ .build();
+ }
+
+ TunnelModeChildSessionParams buildTunnelModeChildSessionParams() {
+ return new TunnelModeChildSessionParams.Builder()
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
+ .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
+ .addInternalAddressRequest(AF_INET)
+ .addInternalAddressRequest(AF_INET6)
+ .build();
+ }
+
+ PortPair performSetupIkeAndFirstChildBlocking(String ikeInitRespHex, String... ikeAuthRespHexes)
+ throws Exception {
+ return performSetupIkeAndFirstChildBlocking(
+ ikeInitRespHex,
+ 1 /* expectedAuthReqPktCnt */,
+ true /*expectedAuthUseEncap*/,
+ ikeAuthRespHexes);
+ }
+
+ PortPair performSetupIkeAndFirstChildBlocking(
+ String ikeInitRespHex, boolean expectedAuthUseEncap, String... ikeAuthRespHexes)
+ throws Exception {
+ return performSetupIkeAndFirstChildBlocking(
+ ikeInitRespHex,
+ 1 /* expectedAuthReqPktCnt */,
+ expectedAuthUseEncap,
+ ikeAuthRespHexes);
+ }
+
+ PortPair performSetupIkeAndFirstChildBlocking(
+ String ikeInitRespHex,
+ int expectedAuthReqPktCnt,
+ boolean expectedAuthUseEncap,
+ String... ikeAuthRespHexes)
+ throws Exception {
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ 0 /* expectedMsgId */,
+ false /* expectedUseEncap */,
+ ikeInitRespHex);
+
+ byte[] ikeAuthReqPkt =
+ mTunUtils
+ .awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI,
+ 1 /* expectedMsgId */,
+ expectedAuthUseEncap,
+ expectedAuthReqPktCnt,
+ ikeAuthRespHexes)
+ .get(0);
+ return IkeTunUtils.getSrcDestPortPair(ikeAuthReqPkt);
+ }
+
+ void performCloseIkeBlocking(int expectedMsgId, String deleteIkeRespHex) throws Exception {
+ performCloseIkeBlocking(expectedMsgId, true /* expectedUseEncap*/, deleteIkeRespHex);
+ }
+
+ void performCloseIkeBlocking(
+ int expectedMsgId, boolean expectedUseEncap, String deleteIkeRespHex) throws Exception {
+ mTunUtils.awaitReqAndInjectResp(
+ IKE_DETERMINISTIC_INITIATOR_SPI, expectedMsgId, expectedUseEncap, deleteIkeRespHex);
+ }
+
+ /** Testing callback that allows caller to block current thread until a method get called */
static class TestIkeSessionCallback implements IkeSessionCallback {
private CompletableFuture<IkeSessionConfiguration> mFutureIkeConfig =
new CompletableFuture<>();
@@ -282,6 +392,7 @@
}
}
+ /** Testing callback that allows caller to block current thread until a method get called */
static class TestChildSessionCallback implements ChildSessionCallback {
private CompletableFuture<ChildSessionConfiguration> mFutureChildConfig =
new CompletableFuture<>();
@@ -366,9 +477,115 @@
this.ipSecTransform = ipSecTransform;
this.direction = direction;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipSecTransform, direction);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof IpSecTransformCallRecord)) return false;
+
+ IpSecTransformCallRecord record = (IpSecTransformCallRecord) o;
+ return ipSecTransform.equals(record.ipSecTransform) && direction == record.direction;
+ }
}
- // TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth
+ void verifyIkeSessionSetupBlocking() throws Exception {
+ 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());
+ }
+
+ void verifyChildSessionSetupBlocking(
+ TestChildSessionCallback childCallback,
+ List<IkeTrafficSelector> expectedInboundTs,
+ List<IkeTrafficSelector> expectedOutboundTs,
+ List<LinkAddress> expectedInternalAddresses)
+ throws Exception {
+ verifyChildSessionSetupBlocking(
+ childCallback,
+ expectedInboundTs,
+ expectedOutboundTs,
+ expectedInternalAddresses,
+ new ArrayList<InetAddress>() /* expectedDnsServers */);
+ }
+
+ void verifyChildSessionSetupBlocking(
+ TestChildSessionCallback childCallback,
+ List<IkeTrafficSelector> expectedInboundTs,
+ List<IkeTrafficSelector> expectedOutboundTs,
+ List<LinkAddress> expectedInternalAddresses,
+ List<InetAddress> expectedDnsServers)
+ throws Exception {
+ ChildSessionConfiguration childConfig = childCallback.awaitChildConfig();
+ assertNotNull(childConfig);
+ assertEquals(expectedInboundTs, childConfig.getInboundTrafficSelectors());
+ assertEquals(expectedOutboundTs, childConfig.getOutboundTrafficSelectors());
+ assertEquals(expectedInternalAddresses, childConfig.getInternalAddresses());
+ assertEquals(expectedDnsServers, childConfig.getInternalDnsServers());
+ assertTrue(childConfig.getInternalSubnets().isEmpty());
+ assertTrue(childConfig.getInternalDhcpServers().isEmpty());
+ }
+
+ void verifyCloseIkeAndChildBlocking(
+ IpSecTransformCallRecord expectedTransformRecordA,
+ IpSecTransformCallRecord expectedTransformRecordB)
+ throws Exception {
+ verifyDeleteIpSecTransformPair(
+ mFirstChildSessionCallback, expectedTransformRecordA, expectedTransformRecordB);
+ mFirstChildSessionCallback.awaitOnClosed();
+ mIkeSessionCallback.awaitOnClosed();
+ }
+
+ static void verifyCreateIpSecTransformPair(
+ IpSecTransformCallRecord transformRecordA, IpSecTransformCallRecord transformRecordB) {
+ IpSecTransform transformA = transformRecordA.ipSecTransform;
+ IpSecTransform transformB = transformRecordB.ipSecTransform;
+
+ assertNotNull(transformA);
+ assertNotNull(transformB);
+
+ Set<Integer> expectedDirections = new HashSet<>();
+ expectedDirections.add(IpSecManager.DIRECTION_IN);
+ expectedDirections.add(IpSecManager.DIRECTION_OUT);
+
+ Set<Integer> resultDirections = new HashSet<>();
+ resultDirections.add(transformRecordA.direction);
+ resultDirections.add(transformRecordB.direction);
+
+ assertEquals(expectedDirections, resultDirections);
+ }
+
+ static void verifyDeleteIpSecTransformPair(
+ TestChildSessionCallback childCb,
+ IpSecTransformCallRecord expectedTransformRecordA,
+ IpSecTransformCallRecord expectedTransformRecordB) {
+ Set<IpSecTransformCallRecord> expectedTransforms = new HashSet<>();
+ expectedTransforms.add(expectedTransformRecordA);
+ expectedTransforms.add(expectedTransformRecordB);
+
+ Set<IpSecTransformCallRecord> resultTransforms = new HashSet<>();
+ resultTransforms.add(childCb.awaitNextDeletedIpSecTransform());
+ resultTransforms.add(childCb.awaitNextDeletedIpSecTransform());
+
+ assertEquals(expectedTransforms, resultTransforms);
+ }
+
+ /** Package private method to check if device has IPsec tunnels feature */
+ static boolean hasTunnelsFeature() {
+ return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS);
+ }
// TODO(b/148689509): Verify hostname based creation
}
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
index f07c710..c70e537 100644
--- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
@@ -54,7 +54,7 @@
static final int SUB_ID = 1;
static final byte[] EAP_IDENTITY = "test@android.net".getBytes();
static final String NETWORK_NAME = "android.net";
- static final String EAP_MSCHAPV2_USERNAME = "username";
+ static final String EAP_MSCHAPV2_USERNAME = "mschapv2user";
static final String EAP_MSCHAPV2_PASSWORD = "password";
static final Inet4Address IPV4_ADDRESS_LOCAL =
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
index 5a8258d..41cbf0b 100644
--- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java
@@ -26,6 +26,8 @@
import static android.net.ipsec.ike.cts.PacketUtils.UdpHeader;
import static android.system.OsConstants.IPPROTO_UDP;
+import static com.android.internal.util.HexDump.hexStringToByteArray;
+
import static org.junit.Assert.fail;
import android.os.ParcelFileDescriptor;
@@ -34,7 +36,10 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
public class IkeTunUtils extends TunUtils {
private static final int PORT_LEN = 2;
@@ -42,63 +47,132 @@
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_FIRST_PAYLOAD_OFFSET = 16;
private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
private static final int IKE_MSG_ID_OFFSET = 20;
+ private static final int IKE_HEADER_LEN = 28;
+ private static final int IKE_FRAG_NUM_OFFSET = 32;
+ private static final int IKE_PAYLOAD_TYPE_SKF = 53;
+
+ private static final int RSP_FLAG_MASK = 0x20;
public IkeTunUtils(ParcelFileDescriptor tunFd) {
super(tunFd);
}
/**
- * Await the expected IKE request and inject an IKE response.
+ * Await the expected IKE request inject an IKE response (or a list of response fragments)
*
- * @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
+ * @param ikeRespDataFragmentsHex IKE response hex (or a list of response fragments) without
+ * IP/UDP headers or NON ESP MARKER.
*/
public byte[] awaitReqAndInjectResp(
- long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap, byte[] respIkePkt)
+ long expectedInitIkeSpi,
+ int expectedMsgId,
+ boolean expectedUseEncap,
+ String... ikeRespDataFragmentsHex)
throws Exception {
- byte[] request =
- awaitIkePacket(
+ return awaitReqAndInjectResp(
expectedInitIkeSpi,
expectedMsgId,
- false /* expectedResp */,
- expectedUseEncap);
+ expectedUseEncap,
+ 1 /* expectedReqPktCnt */,
+ ikeRespDataFragmentsHex)
+ .get(0);
+ }
+
+ /**
+ * Await the expected IKE request (or the list of IKE request fragments) and inject an IKE
+ * response (or a list of response fragments)
+ *
+ * @param ikeRespDataFragmentsHex IKE response hex (or a list of response fragments) without
+ * IP/UDP headers or NON ESP MARKER.
+ */
+ public List<byte[]> awaitReqAndInjectResp(
+ long expectedInitIkeSpi,
+ int expectedMsgId,
+ boolean expectedUseEncap,
+ int expectedReqPktCnt,
+ String... ikeRespDataFragmentsHex)
+ throws Exception {
+ List<byte[]> reqList = new ArrayList<>(expectedReqPktCnt);
+ if (expectedReqPktCnt == 1) {
+ // Expecting one complete IKE packet
+ byte[] req =
+ awaitIkePacket(
+ (pkt) -> {
+ return isExpectedIkePkt(
+ pkt,
+ expectedInitIkeSpi,
+ expectedMsgId,
+ false /* expectedResp */,
+ expectedUseEncap);
+ });
+ reqList.add(req);
+ } else {
+ // Expecting "expectedReqPktCnt" number of request fragments
+ for (int i = 0; i < expectedReqPktCnt; i++) {
+ // IKE Fragment number always starts from 1
+ int expectedFragNum = i + 1;
+ byte[] req =
+ awaitIkePacket(
+ (pkt) -> {
+ return isExpectedIkeFragPkt(
+ pkt,
+ expectedInitIkeSpi,
+ expectedMsgId,
+ false /* expectedResp */,
+ expectedUseEncap,
+ expectedFragNum);
+ });
+ reqList.add(req);
+ }
+ }
+
+ // All request fragments have the same addresses and ports
+ byte[] request = reqList.get(0);
// 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 */);
+ for (String resp : ikeRespDataFragmentsHex) {
+ byte[] response =
+ buildIkePacket(
+ srcAddr,
+ dstAddr,
+ srcPort,
+ dstPort,
+ expectedUseEncap,
+ hexStringToByteArray(resp));
+ injectPacket(response);
+ }
- byte[] response =
- buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, expectedUseEncap, respIkePkt);
- injectPacket(response);
- return request;
+ return reqList;
}
- private byte[] awaitIkePacket(
- long expectedInitIkeSpi,
- int expectedMsgId,
- boolean expectedResp,
- boolean expectedUseEncap)
+ /** Await the expected IKE response */
+ public byte[] awaitResp(long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap)
throws Exception {
+ return awaitIkePacket(
+ (pkt) -> {
+ return isExpectedIkePkt(
+ pkt,
+ expectedInitIkeSpi,
+ expectedMsgId,
+ true /* expectedResp*/,
+ expectedUseEncap);
+ });
+ }
+
+ private byte[] awaitIkePacket(Predicate<byte[]> pktVerifier) 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);
+ byte[] ikePkt = getFirstMatchingPacket(pktVerifier, startIndex);
if (ikePkt != null) {
return ikePkt; // We've found the packet we're looking for.
}
@@ -112,49 +186,51 @@
}
}
- String direction = expectedResp ? "response" : "request";
- fail(
- "No such IKE "
- + direction
- + " found with Initiator SPI "
- + expectedInitIkeSpi
- + " and message ID "
- + expectedMsgId);
+ fail("No matching packet found");
}
- return null;
+
+ throw new IllegalStateException(
+ "Hit an impossible case where fail() didn't throw an exception");
}
- private static boolean isIke(
+ private static boolean isExpectedIkePkt(
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;
- }
- }
- }
+ int ipProtocolOffset = isIpv6(pkt) ? IP6_PROTO_OFFSET : IP4_PROTO_OFFSET;
+ int ikeOffset = getIkeOffset(pkt, expectedUseEncap);
return pkt[ipProtocolOffset] == IPPROTO_UDP
- && areSpiAndMsgIdEqual(
+ && expectedUseEncap == hasNonEspMarker(pkt)
+ && isExpectedSpiAndMsgId(
pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId, expectedResp);
}
+ private static boolean isExpectedIkeFragPkt(
+ byte[] pkt,
+ long expectedInitIkeSpi,
+ int expectedMsgId,
+ boolean expectedResp,
+ boolean expectedUseEncap,
+ int expectedFragNum) {
+ return isExpectedIkePkt(
+ pkt, expectedInitIkeSpi, expectedMsgId, expectedResp, expectedUseEncap)
+ && isExpectedFragNum(pkt, getIkeOffset(pkt, expectedUseEncap), expectedFragNum);
+ }
+
+ private static int getIkeOffset(byte[] pkt, boolean useEncap) {
+ if (isIpv6(pkt)) {
+ // IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap.
+ return IP6_HDRLEN + UDP_HDRLEN;
+ } else {
+ // Use default IPv4 header length (assuming no options)
+ int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
+ return useEncap ? ikeOffset + NON_ESP_MARKER_LEN : ikeOffset;
+ }
+ }
+
private static boolean hasNonEspMarker(byte[] pkt) {
ByteBuffer buffer = ByteBuffer.wrap(pkt);
int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
@@ -168,23 +244,81 @@
return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
}
- private static boolean areSpiAndMsgIdEqual(
+ private static boolean isExpectedSpiAndMsgId(
byte[] pkt,
int ikeOffset,
- long expectedIkeInitSpi,
+ long expectedInitIkeSpi,
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)
+ buffer.mark(); // Mark this position so that later we can reset back here
- // Check message ID.
+ // Check SPI
+ buffer.get(new byte[IKE_INIT_SPI_OFFSET]);
+ long initSpi = buffer.getLong();
+ if (expectedInitIkeSpi != initSpi) {
+ return false;
+ }
+
+ // Check direction
+ buffer.reset();
+ buffer.get(new byte[IKE_IS_RESP_BYTE_OFFSET]);
+ byte flagsByte = buffer.get();
+ boolean isResp = ((flagsByte & RSP_FLAG_MASK) != 0);
+ if (expectedResp != isResp) {
+ return false;
+ }
+
+ // Check message ID
+ buffer.reset();
buffer.get(new byte[IKE_MSG_ID_OFFSET]);
- int msgId = buffer.getInt();
- return expectedMsgId == msgId;
- // TODO: Check SPI and packet direction
+ // Both the expected message ID and the packet's msgId are signed integers, so directly
+ // compare them.
+ int msgId = buffer.getInt();
+ if (expectedMsgId != msgId) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean isExpectedFragNum(byte[] pkt, int ikeOffset, int expectedFragNum) {
+ ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ buffer.get(new byte[ikeOffset]);
+ buffer.mark(); // Mark this position so that later we can reset back here
+
+ // Check if it is a fragment packet
+ buffer.get(new byte[IKE_FIRST_PAYLOAD_OFFSET]);
+ int firstPayload = Byte.toUnsignedInt(buffer.get());
+ if (firstPayload != IKE_PAYLOAD_TYPE_SKF) {
+ return false;
+ }
+
+ // Check fragment number
+ buffer.reset();
+ buffer.get(new byte[IKE_FRAG_NUM_OFFSET]);
+ int fragNum = Short.toUnsignedInt(buffer.getShort());
+ return expectedFragNum == fragNum;
+ }
+
+ public static class PortPair {
+ public final int srcPort;
+ public final int dstPort;
+
+ public PortPair(int sourcePort, int destinationPort) {
+ srcPort = sourcePort;
+ dstPort = destinationPort;
+ }
+ }
+
+ public static PortPair getSrcDestPortPair(byte[] outboundIkePkt) throws Exception {
+ return new PortPair(
+ getPort(outboundIkePkt, true /* shouldGetSource */),
+ getPort(outboundIkePkt, false /* shouldGetSource */));
}
private static InetAddress getAddress(byte[] pkt, boolean shouldGetSource) throws Exception {
@@ -208,7 +342,7 @@
return Short.toUnsignedInt(buffer.getShort());
}
- private static byte[] buildIkePacket(
+ public static byte[] buildIkePacket(
InetAddress srcAddr,
InetAddress dstAddr,
int srcPort,
diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
index cb1d826..5539dbc 100644
--- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
+++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java
@@ -47,7 +47,7 @@
private static final String TAG = TunUtils.class.getSimpleName();
private static final int DATA_BUFFER_LEN = 4096;
- static final int TIMEOUT = 100;
+ static final int TIMEOUT = 500;
static final int IP4_PROTO_OFFSET = 9;
static final int IP6_PROTO_OFFSET = 6;
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalApiTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalApiTest.kt
new file mode 100644
index 0000000..40d0ca6
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalApiTest.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.cts
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.EthernetManager
+import android.net.InetAddresses
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkRequest
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.Uri
+import android.net.dhcp.DhcpDiscoverPacket
+import android.net.dhcp.DhcpPacket
+import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE
+import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_DISCOVER
+import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_REQUEST
+import android.net.dhcp.DhcpRequestPacket
+import android.net.shared.Inet4AddressUtils.getBroadcastAddress
+import android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address
+import android.os.Build
+import android.os.HandlerThread
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.ThrowingRunnable
+import com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DhcpClientPacketFilter
+import com.android.testutils.DhcpOptionFilter
+import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.TapPacketReader
+import com.android.testutils.TestableNetworkCallback
+import fi.iki.elonen.NanoHTTPD
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.Inet4Address
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
+import kotlin.reflect.KClass
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+private const val MAX_PACKET_LENGTH = 1500
+private const val TEST_TIMEOUT_MS = 10_000L
+
+private const val TEST_LEASE_TIMEOUT_SECS = 3600 * 12
+private const val TEST_PREFIX_LENGTH = 24
+
+private const val TEST_LOGIN_URL = "https://login.capport.android.com"
+private const val TEST_VENUE_INFO_URL = "https://venueinfo.capport.android.com"
+private const val TEST_DOMAIN_NAME = "lan"
+private const val TEST_MTU = 1500.toShort()
+
+@AppModeFull(reason = "Instant apps cannot create test networks")
+@RunWith(AndroidJUnit4::class)
+class CaptivePortalApiTest {
+ @JvmField
+ @Rule
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+
+ private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ private val tnm by lazy { context.assertHasService(TestNetworkManager::class.java) }
+ private val eth by lazy { context.assertHasService(EthernetManager::class.java) }
+ private val cm by lazy { context.assertHasService(ConnectivityManager::class.java) }
+
+ private val handlerThread = HandlerThread(CaptivePortalApiTest::class.simpleName)
+ private val serverIpAddr = InetAddresses.parseNumericAddress("192.0.2.222") as Inet4Address
+ private val clientIpAddr = InetAddresses.parseNumericAddress("192.0.2.111") as Inet4Address
+ private val httpServer = HttpServer()
+ private val ethRequest = NetworkRequest.Builder()
+ // ETHERNET|TEST transport networks do not have NET_CAPABILITY_TRUSTED
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_ETHERNET).build()
+ private val ethRequestCb = TestableNetworkCallback()
+
+ private lateinit var iface: TestNetworkInterface
+ private lateinit var reader: TapPacketReader
+ private lateinit var capportUrl: Uri
+
+ private var testSkipped = false
+
+ @Before
+ fun setUp() {
+ // This test requires using a tap interface as the default ethernet interface: skip if there
+ // is already an ethernet interface connected.
+ testSkipped = eth.isAvailable()
+ assumeFalse(testSkipped)
+
+ // Register a request so the network does not get torn down
+ cm.requestNetwork(ethRequest, ethRequestCb)
+ runAsShell(NETWORK_SETTINGS, MANAGE_TEST_NETWORKS) {
+ eth.setIncludeTestInterfaces(true)
+ // Keeping a reference to the test interface also makes sure the ParcelFileDescriptor
+ // does not go out of scope, which would cause it to close the underlying FileDescriptor
+ // in its finalizer.
+ iface = tnm.createTapInterface()
+ }
+
+ handlerThread.start()
+ reader = TapPacketReader(
+ handlerThread.threadHandler,
+ iface.fileDescriptor.fileDescriptor,
+ MAX_PACKET_LENGTH)
+ handlerThread.threadHandler.post { reader.start() }
+ httpServer.start()
+
+ // Pad the listening port to make sure it is always of length 5. This ensures the URL has
+ // always the same length so the test can use constant IP and UDP header lengths.
+ // The maximum port number is 65535 so a length of 5 is always enough.
+ capportUrl = Uri.parse("http://localhost:${httpServer.listeningPort}/testapi.html?par=val")
+ }
+
+ @After
+ fun tearDown() {
+ if (testSkipped) return
+ cm.unregisterNetworkCallback(ethRequestCb)
+
+ runAsShell(NETWORK_SETTINGS) { eth.setIncludeTestInterfaces(false) }
+
+ httpServer.stop()
+ handlerThread.threadHandler.post { reader.stop() }
+ handlerThread.quitSafely()
+
+ iface.fileDescriptor.close()
+ }
+
+ @Test
+ fun testApiCallbacks() {
+ // Handle the DHCP handshake that includes the capport API URL
+ val discover = reader.assertDhcpPacketReceived(
+ DhcpDiscoverPacket::class, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_DISCOVER)
+ reader.sendResponse(makeOfferPacket(discover.clientMac, discover.transactionId))
+
+ val request = reader.assertDhcpPacketReceived(
+ DhcpRequestPacket::class, TEST_TIMEOUT_MS, DHCP_MESSAGE_TYPE_REQUEST)
+ assertEquals(discover.transactionId, request.transactionId)
+ assertEquals(clientIpAddr, request.mRequestedIp)
+ reader.sendResponse(makeAckPacket(request.clientMac, request.transactionId))
+
+ // Expect a request to the capport API
+ val capportReq = httpServer.recordedRequests.poll(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ assertNotNull(capportReq, "The device did not fetch captive portal API data within timeout")
+ assertEquals(capportUrl.path, capportReq.uri)
+ assertEquals(capportUrl.query, capportReq.queryParameterString)
+
+ // Expect network callbacks with capport info
+ val testCb = TestableNetworkCallback(TEST_TIMEOUT_MS)
+ // LinkProperties do not contain captive portal info if the callback is registered without
+ // NETWORK_SETTINGS permissions.
+ val lp = runAsShell(NETWORK_SETTINGS) {
+ cm.registerNetworkCallback(ethRequest, testCb)
+
+ try {
+ val ncCb = testCb.eventuallyExpect<CallbackEntry.CapabilitiesChanged> {
+ it.caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
+ }
+ testCb.eventuallyExpect<CallbackEntry.LinkPropertiesChanged> {
+ it.network == ncCb.network && it.lp.captivePortalData != null
+ }.lp
+ } finally {
+ cm.unregisterNetworkCallback(testCb)
+ }
+ }
+
+ assertEquals(capportUrl, lp.captivePortalApiUrl)
+ with(lp.captivePortalData) {
+ assertNotNull(this)
+ assertTrue(isCaptive)
+ assertEquals(Uri.parse(TEST_LOGIN_URL), userPortalUrl)
+ assertEquals(Uri.parse(TEST_VENUE_INFO_URL), venueInfoUrl)
+ }
+ }
+
+ private fun makeOfferPacket(clientMac: ByteArray, transactionId: Int) =
+ DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, transactionId,
+ false /* broadcast */, serverIpAddr, IPV4_ADDR_ANY /* relayIp */, clientIpAddr,
+ clientMac, TEST_LEASE_TIMEOUT_SECS,
+ getPrefixMaskAsInet4Address(TEST_PREFIX_LENGTH),
+ getBroadcastAddress(clientIpAddr, TEST_PREFIX_LENGTH),
+ listOf(serverIpAddr) /* gateways */, listOf(serverIpAddr) /* dnsServers */,
+ serverIpAddr, TEST_DOMAIN_NAME, null /* hostname */, true /* metered */,
+ TEST_MTU, capportUrl.toString())
+
+ private fun makeAckPacket(clientMac: ByteArray, transactionId: Int) =
+ DhcpPacket.buildAckPacket(DhcpPacket.ENCAP_L2, transactionId,
+ false /* broadcast */, serverIpAddr, IPV4_ADDR_ANY /* relayIp */, clientIpAddr,
+ clientIpAddr /* requestClientIp */, clientMac, TEST_LEASE_TIMEOUT_SECS,
+ getPrefixMaskAsInet4Address(TEST_PREFIX_LENGTH),
+ getBroadcastAddress(clientIpAddr, TEST_PREFIX_LENGTH),
+ listOf(serverIpAddr) /* gateways */, listOf(serverIpAddr) /* dnsServers */,
+ serverIpAddr, TEST_DOMAIN_NAME, null /* hostname */, true /* metered */,
+ TEST_MTU, false /* rapidCommit */, capportUrl.toString())
+
+ private fun parseDhcpPacket(bytes: ByteArray) = DhcpPacket.decodeFullPacket(
+ bytes, MAX_PACKET_LENGTH, DhcpPacket.ENCAP_L2)
+}
+
+/**
+ * A minimal HTTP server running on localhost (loopback), on a random available port.
+ *
+ * The server records each request in [recordedRequests] and will not serve any further request
+ * until the last one is removed from the queue for verification.
+ */
+private class HttpServer : NanoHTTPD("localhost", 0 /* auto-select the port */) {
+ val recordedRequests = ArrayBlockingQueue<IHTTPSession>(1 /* capacity */)
+
+ override fun serve(session: IHTTPSession): Response {
+ recordedRequests.offer(session)
+ return newFixedLengthResponse("""
+ |{
+ | "captive": true,
+ | "user-portal-url": "$TEST_LOGIN_URL",
+ | "venue-info-url": "$TEST_VENUE_INFO_URL"
+ |}
+ """.trimMargin())
+ }
+}
+
+private fun <T : DhcpPacket> TapPacketReader.assertDhcpPacketReceived(
+ packetType: KClass<T>,
+ timeoutMs: Long,
+ type: Byte
+): T {
+ val packetBytes = popPacket(timeoutMs, DhcpClientPacketFilter()
+ .and(DhcpOptionFilter(DHCP_MESSAGE_TYPE, type)))
+ ?: fail("${packetType.simpleName} not received within timeout")
+ val packet = DhcpPacket.decodeFullPacket(packetBytes, packetBytes.size, DhcpPacket.ENCAP_L2)
+ assertTrue(packetType.isInstance(packet),
+ "Expected ${packetType.simpleName} but got ${packet.javaClass.simpleName}")
+ return packetType.java.cast(packet)
+}
+
+private fun <T> Context.assertHasService(manager: Class<T>): T {
+ return getSystemService(manager) ?: fail("Service $manager not found")
+}
+
+/**
+ * Wrapper around runWithShellPermissionIdentity with kotlin-like syntax.
+ */
+private fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
+ var ret: T? = null
+ runWithShellPermissionIdentity(ThrowingRunnable { ret = task() }, *permissions)
+ return ret ?: fail("ThrowingRunnable was not run")
+}
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index 4418e17..0816aba 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -16,6 +16,7 @@
package android.net.cts
+import android.Manifest.permission.CONNECTIVITY_INTERNAL
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
@@ -31,6 +32,7 @@
import android.net.Uri
import android.net.cts.util.CtsNetUtils
import android.net.wifi.WifiManager
+import android.os.Build
import android.os.ConditionVariable
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
@@ -164,7 +166,10 @@
"access."
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
- doAsShell(NETWORK_SETTINGS) { cm.startCaptivePortalApp(network) }
+ val startPortalAppPermission =
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) CONNECTIVITY_INTERNAL
+ else NETWORK_SETTINGS
+ doAsShell(startPortalAppPermission) { cm.startCaptivePortalApp(network) }
assertTrue(portalContentRequestCv.block(TEST_TIMEOUT_MS), "The captive portal login " +
"page was still not fetched ${TEST_TIMEOUT_MS}ms after startCaptivePortalApp.")
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 1ee08ff..d498ed9 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -16,13 +16,17 @@
package android.net.cts;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
-import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_USB_HOST;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
@@ -36,6 +40,16 @@
import static android.system.OsConstants.AF_UNSPEC;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.Instrumentation;
@@ -45,6 +59,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
@@ -59,10 +74,12 @@
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
+import android.net.NetworkUtils;
import android.net.SocketKeepalive;
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Looper;
import android.os.MessageQueue;
@@ -71,15 +88,22 @@
import android.os.VintfRuntimeInfo;
import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
-import android.test.AndroidTestCase;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.ArrayUtils;
import libcore.io.Streams;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
@@ -105,7 +129,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class ConnectivityManagerTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ConnectivityManagerTest {
private static final String TAG = ConnectivityManagerTest.class.getSimpleName();
@@ -117,7 +142,10 @@
private static final int INTERVAL_KEEPALIVE_RETRY_MS = 500;
private static final int MAX_KEEPALIVE_RETRY_COUNT = 3;
private static final int MIN_KEEPALIVE_INTERVAL = 10;
- private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
+
+ // Changing meteredness on wifi involves reconnecting, which can take several seconds (involves
+ // re-associating, DHCP...)
+ private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 30_000;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
// device could have only one interface: data, wifi.
@@ -141,22 +169,19 @@
private PackageManager mPackageManager;
private final HashMap<Integer, NetworkConfig> mNetworks =
new HashMap<Integer, NetworkConfig>();
- boolean mWifiConnectAttempted;
+ boolean mWifiWasDisabled;
private UiAutomation mUiAutomation;
private CtsNetUtils mCtsNetUtils;
- private boolean mShellPermissionIdentityAdopted;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- Looper.prepare();
- mContext = getContext();
+ @Before
+ public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mPackageManager = mContext.getPackageManager();
mCtsNetUtils = new CtsNetUtils(mContext);
- mWifiConnectAttempted = false;
+ mWifiWasDisabled = false;
// Get com.android.internal.R.array.networkAttributes
int resId = mContext.getResources().getIdentifier("networkAttributes", "array", "android");
@@ -173,20 +198,17 @@
} catch (Exception e) {}
}
mUiAutomation = mInstrumentation.getUiAutomation();
- mShellPermissionIdentityAdopted = false;
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
// Return WiFi to its original disabled state after tests that explicitly connect.
- if (mWifiConnectAttempted) {
+ if (mWifiWasDisabled) {
mCtsNetUtils.disconnectFromWifi(null);
}
if (mCtsNetUtils.cellConnectAttempted()) {
mCtsNetUtils.disconnectFromCell();
}
- dropShellPermissionIdentity();
- super.tearDown();
}
/**
@@ -195,13 +217,12 @@
* automatically in tearDown().
*/
private Network ensureWifiConnected() {
- if (mWifiManager.isWifiEnabled()) {
- return mCtsNetUtils.getWifiNetwork();
- }
- mWifiConnectAttempted = true;
+ mWifiWasDisabled = !mWifiManager.isWifiEnabled();
+ // Even if wifi is enabled, the network may not be connected or ready yet
return mCtsNetUtils.connectToWifi();
}
+ @Test
public void testIsNetworkTypeValid() {
assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE));
assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_WIFI));
@@ -231,12 +252,14 @@
}
+ @Test
public void testSetNetworkPreference() {
// getNetworkPreference() and setNetworkPreference() are both deprecated so they do
// not preform any action. Verify they are at least still callable.
mCm.setNetworkPreference(mCm.getNetworkPreference());
}
+ @Test
public void testGetActiveNetworkInfo() {
NetworkInfo ni = mCm.getActiveNetworkInfo();
@@ -245,6 +268,7 @@
assertTrue(ni.getState() == State.CONNECTED);
}
+ @Test
public void testGetActiveNetwork() {
Network network = mCm.getActiveNetwork();
assertNotNull("You must have an active network connection to complete CTS", network);
@@ -257,6 +281,7 @@
assertTrue(ni.getState() == State.CONNECTED);
}
+ @Test
public void testGetNetworkInfo() {
for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE+1; type++) {
if (shouldBeSupported(type)) {
@@ -275,6 +300,7 @@
}
}
+ @Test
public void testGetAllNetworkInfo() {
NetworkInfo[] ni = mCm.getAllNetworkInfo();
assertTrue(ni.length >= MIN_NUM_NETWORK_TYPES);
@@ -298,6 +324,7 @@
* and that they are made from different IP addresses.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testOpenConnection() throws Exception {
boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
&& mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
@@ -377,6 +404,7 @@
} catch (UnsupportedOperationException expected) {}
}
+ @Test
public void testStartUsingNetworkFeature() {
final String invalidateFeature = "invalidateFeature";
@@ -406,6 +434,7 @@
(networkType == ConnectivityManager.TYPE_ETHERNET && shouldEthernetBeSupported());
}
+ @Test
public void testIsNetworkSupported() {
for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
boolean supported = mCm.isNetworkSupported(type);
@@ -417,12 +446,14 @@
}
}
+ @Test
public void testRequestRouteToHost() {
for (int type = -1 ; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
assertRequestRouteToHostUnsupported(type, HOST_ADDRESS);
}
}
+ @Test
public void testTest() {
mCm.getBackgroundDataSetting();
}
@@ -443,6 +474,7 @@
* that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testRegisterNetworkCallback() {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
@@ -484,6 +516,7 @@
* of a {@code NetworkCallback}.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testRegisterNetworkCallback_withPendingIntent() {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
@@ -529,6 +562,7 @@
* see if we get a callback for an INTERNET request.
*/
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
+ @Test
public void testRequestNetworkCallback() {
final TestNetworkCallback callback = new TestNetworkCallback();
mCm.requestNetwork(new NetworkRequest.Builder()
@@ -552,6 +586,7 @@
* fail. Use WIFI and switch Wi-Fi off.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testRequestNetworkCallback_onUnavailable() {
final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
if (previousWifiEnabledState) {
@@ -589,6 +624,7 @@
/** Verify restricted networks cannot be requested. */
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
+ @Test
public void testRestrictedNetworks() {
// Verify we can request unrestricted networks:
NetworkRequest request = new NetworkRequest.Builder()
@@ -710,6 +746,7 @@
* for metered and unmetered networks.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testGetMultipathPreference() throws Exception {
final ContentResolver resolver = mContext.getContentResolver();
ensureWifiConnected();
@@ -878,18 +915,6 @@
keepalivesPerTransport, nc);
}
- private void adoptShellPermissionIdentity() {
- mUiAutomation.adoptShellPermissionIdentity();
- mShellPermissionIdentityAdopted = true;
- }
-
- private void dropShellPermissionIdentity() {
- if (mShellPermissionIdentityAdopted) {
- mUiAutomation.dropShellPermissionIdentity();
- mShellPermissionIdentityAdopted = false;
- }
- }
-
private static boolean isTcpKeepaliveSupportedByKernel() {
final String kVersionString = VintfRuntimeInfo.getKernelVersion();
return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
@@ -924,6 +949,7 @@
* Verifies that version string compare logic returns expected result for various cases.
* Note that only major and minor number are compared.
*/
+ @Test
public void testMajorMinorVersionCompare() {
assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
@@ -943,6 +969,7 @@
* keepalives is set to 0.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testKeepaliveWifiUnsupported() throws Exception {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testKeepaliveUnsupported cannot execute unless device"
@@ -952,32 +979,36 @@
final Network network = ensureWifiConnected();
if (getSupportedKeepalivesForNet(network) != 0) return;
+ final InetAddress srcAddr = getFirstV4Address(network);
+ assumeTrue("This test requires native IPv4", srcAddr != null);
- adoptShellPermissionIdentity();
-
- assertEquals(0, createConcurrentSocketKeepalives(network, 1, 0));
- assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
-
- dropShellPermissionIdentity();
+ runWithShellPermissionIdentity(() -> {
+ assertEquals(0, createConcurrentSocketKeepalives(network, srcAddr, 1, 0));
+ assertEquals(0, createConcurrentSocketKeepalives(network, srcAddr, 0, 1));
+ });
}
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testCreateTcpKeepalive() throws Exception {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testCreateTcpKeepalive cannot execute unless device supports WiFi");
return;
}
- adoptShellPermissionIdentity();
-
final Network network = ensureWifiConnected();
if (getSupportedKeepalivesForNet(network) == 0) return;
+ final InetAddress srcAddr = getFirstV4Address(network);
+ assumeTrue("This test requires native IPv4", srcAddr != null);
+
// If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
// NAT-T keepalive. If keepalive limits from resource overlay is not zero, TCP keepalive
// needs to be supported except if the kernel doesn't support it.
if (!isTcpKeepaliveSupportedByKernel()) {
// Sanity check to ensure the callback result is expected.
- assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
+ runWithShellPermissionIdentity(() -> {
+ assertEquals(0, createConcurrentSocketKeepalives(network, srcAddr, 0, 1));
+ });
Log.i(TAG, "testCreateTcpKeepalive is skipped for kernel "
+ VintfRuntimeInfo.getKernelVersion());
return;
@@ -991,6 +1022,8 @@
// Should able to start keep alive offload when socket is idle.
final Executor executor = mContext.getMainExecutor();
final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+
+ mUiAutomation.adoptShellPermissionIdentity();
try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
sk.start(MIN_KEEPALIVE_INTERVAL);
callback.expectStarted();
@@ -1012,6 +1045,8 @@
// Stop.
sk.stop();
callback.expectStopped();
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
}
// Ensure socket is still connected.
@@ -1040,9 +1075,12 @@
// Should get ERROR_SOCKET_NOT_IDLE because there is still data in the receive queue
// that has not been read.
+ mUiAutomation.adoptShellPermissionIdentity();
try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
sk.start(MIN_KEEPALIVE_INTERVAL);
callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
}
}
}
@@ -1087,7 +1125,7 @@
}
private @NonNull ArrayList<SocketKeepalive> createConcurrentNattSocketKeepalives(
- @NonNull Network network, int requestCount,
+ @NonNull Network network, @NonNull InetAddress srcAddr, int requestCount,
@NonNull TestSocketKeepaliveCallback callback) throws Exception {
final Executor executor = mContext.getMainExecutor();
@@ -1095,7 +1133,6 @@
// Initialize a real NaT-T socket.
final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
final UdpEncapsulationSocket nattSocket = mIpSec.openUdpEncapsulationSocket();
- final InetAddress srcAddr = getFirstV4Address(network);
final InetAddress dstAddr = getAddrByName(TEST_HOST, AF_INET);
assertNotNull(srcAddr);
assertNotNull(dstAddr);
@@ -1136,11 +1173,12 @@
* @return the total number of keepalives created.
*/
private int createConcurrentSocketKeepalives(
- @NonNull Network network, int nattCount, int tcpCount) throws Exception {
+ @NonNull Network network, @NonNull InetAddress srcAddr, int nattCount, int tcpCount)
+ throws Exception {
final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
- kalist.addAll(createConcurrentNattSocketKeepalives(network, nattCount, callback));
+ kalist.addAll(createConcurrentNattSocketKeepalives(network, srcAddr, nattCount, callback));
kalist.addAll(createConcurrentTcpSocketKeepalives(network, tcpCount, callback));
final int ret = kalist.size();
@@ -1160,6 +1198,7 @@
* get leaked after iterations.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testSocketKeepaliveLimitWifi() throws Exception {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testSocketKeepaliveLimitWifi cannot execute unless device"
@@ -1172,33 +1211,39 @@
if (supported == 0) {
return;
}
+ final InetAddress srcAddr = getFirstV4Address(network);
+ assumeTrue("This test requires native IPv4", srcAddr != null);
- adoptShellPermissionIdentity();
+ runWithShellPermissionIdentity(() -> {
+ // Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
+ assertGreaterOrEqual(supported, MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT);
- // Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
- assertGreaterOrEqual(supported, MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT);
-
- // Verifies that Nat-T keepalives can be established.
- assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
- // Verifies that keepalives don't get leaked in second round.
- assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
+ // Verifies that Nat-T keepalives can be established.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr,
+ supported + 1, 0));
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr, supported,
+ 0));
+ });
// If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
// NAT-T keepalive. Test below cases only if TCP keepalive is supported by kernel.
- if (isTcpKeepaliveSupportedByKernel()) {
- assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported + 1));
+ if (!isTcpKeepaliveSupportedByKernel()) return;
+
+ runWithShellPermissionIdentity(() -> {
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr, 0,
+ supported + 1));
// Verifies that different types can be established at the same time.
- assertEquals(supported, createConcurrentSocketKeepalives(network,
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr,
supported / 2, supported - supported / 2));
// Verifies that keepalives don't get leaked in second round.
- assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported));
- assertEquals(supported, createConcurrentSocketKeepalives(network,
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr, 0,
+ supported));
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr,
supported / 2, supported - supported / 2));
- }
-
- dropShellPermissionIdentity();
+ });
}
/**
@@ -1206,6 +1251,7 @@
* don't get leaked after iterations.
*/
@AppModeFull(reason = "Cannot request network in instant app mode")
+ @Test
public void testSocketKeepaliveLimitTelephony() throws Exception {
if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
Log.i(TAG, "testSocketKeepaliveLimitTelephony cannot execute unless device"
@@ -1222,18 +1268,19 @@
final Network network = mCtsNetUtils.connectToCell();
final int supported = getSupportedKeepalivesForNet(network);
+ final InetAddress srcAddr = getFirstV4Address(network);
+ assumeTrue("This test requires native IPv4", srcAddr != null);
- adoptShellPermissionIdentity();
-
- // Verifies that the supported keepalive slots meet minimum requirement.
- assertGreaterOrEqual(supported, MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT);
-
- // Verifies that Nat-T keepalives can be established.
- assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
- // Verifies that keepalives don't get leaked in second round.
- assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
-
- dropShellPermissionIdentity();
+ runWithShellPermissionIdentity(() -> {
+ // Verifies that the supported keepalive slots meet minimum requirement.
+ assertGreaterOrEqual(supported, MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT);
+ // Verifies that Nat-T keepalives can be established.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr,
+ supported + 1, 0));
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, srcAddr, supported,
+ 0));
+ });
}
private int getIntResourceForName(@NonNull String resName) {
@@ -1246,6 +1293,7 @@
* Verifies that the keepalive slots are limited as customized for unprivileged requests.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
public void testSocketKeepaliveUnprivileged() throws Exception {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testSocketKeepaliveUnprivileged cannot execute unless device"
@@ -1258,6 +1306,8 @@
if (supported == 0) {
return;
}
+ final InetAddress srcAddr = getFirstV4Address(network);
+ assumeTrue("This test requires native IPv4", srcAddr != null);
// Resource ID might be shifted on devices that compiled with different symbols.
// Thus, resolve ID at runtime is needed.
@@ -1273,11 +1323,46 @@
final int expectedUnprivileged =
Math.min(allowedUnprivilegedPerUid, supported - reservedPrivilegedSlots);
assertEquals(expectedUnprivileged,
- createConcurrentSocketKeepalives(network, supported + 1, 0));
+ createConcurrentSocketKeepalives(network, srcAddr, supported + 1, 0));
}
private static void assertGreaterOrEqual(long greater, long lesser) {
assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
greater >= lesser);
}
+
+ /**
+ * Verifies that apps are not allowed to access restricted networks even if they declare the
+ * CONNECTIVITY_USE_RESTRICTED_NETWORKS permission in their manifests.
+ * See. b/144679405.
+ */
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testRestrictedNetworkPermission() throws Exception {
+ // Ensure that CONNECTIVITY_USE_RESTRICTED_NETWORKS isn't granted to this package.
+ final PackageInfo app = mPackageManager.getPackageInfo(mContext.getPackageName(),
+ GET_PERMISSIONS);
+ final int index = ArrayUtils.indexOf(
+ app.requestedPermissions, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
+ assertTrue(index >= 0);
+ assertTrue(app.requestedPermissionsFlags[index] != PERMISSION_GRANTED);
+
+ // Ensure that NetworkUtils.queryUserAccess always returns false since this package should
+ // not have netd system permission to call this function.
+ final Network wifiNetwork = ensureWifiConnected();
+ assertFalse(NetworkUtils.queryUserAccess(Binder.getCallingUid(), wifiNetwork.netId));
+
+ // Ensure that this package cannot bind to any restricted network that's currently
+ // connected.
+ Network[] networks = mCm.getAllNetworks();
+ for (Network network : networks) {
+ NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+ if (nc != null && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
+ try {
+ network.bindSocket(new Socket());
+ fail("Bind to restricted network " + network + " unexpectedly succeeded");
+ } catch (IOException expected) {}
+ }
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 1cc49f9..28753ff 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -86,7 +86,6 @@
static final int CANCEL_RETRY_TIMES = 5;
static final int QUERY_TIMES = 10;
static final int NXDOMAIN = 3;
- static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 6_000;
private ContentResolver mCR;
private ConnectivityManager mCM;
@@ -107,32 +106,15 @@
mExecutorInline = (Runnable r) -> r.run();
mCR = getContext().getContentResolver();
mCtsNetUtils = new CtsNetUtils(getContext());
- storePrivateDnsSetting();
+ mCtsNetUtils.storePrivateDnsSetting();
}
@Override
protected void tearDown() throws Exception {
- restorePrivateDnsSetting();
+ mCtsNetUtils.restorePrivateDnsSetting();
super.tearDown();
}
- private void storePrivateDnsSetting() {
- // Store private DNS setting
- mOldMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
- mOldDnsSpecifier = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER);
- }
-
- private void restorePrivateDnsSetting() throws InterruptedException {
- // restore private DNS setting
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldMode);
- if ("hostname".equals(mOldMode)) {
- Settings.Global.putString(
- mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, mOldDnsSpecifier);
- mCtsNetUtils.awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
- mCM.getActiveNetwork(), mOldDnsSpecifier, PRIVATE_DNS_SETTING_TIMEOUT_MS, true);
- }
- }
-
private static String byteArrayToHexString(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int i = 0; i < bytes.length; ++i) {
@@ -416,16 +398,13 @@
final String msg = "RawQuery " + TEST_NX_DOMAIN + " with private DNS";
// Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
// b/144521720
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
- Settings.Global.putString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER, GOOGLE_PRIVATE_DNS_SERVER);
+ mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
for (Network network : getTestableNetworks()) {
final Network networkForPrivateDns =
(network != null) ? network : mCM.getActiveNetwork();
assertNotNull("Can't find network to await private DNS on", networkForPrivateDns);
mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
- networkForPrivateDns, GOOGLE_PRIVATE_DNS_SERVER,
- PRIVATE_DNS_SETTING_TIMEOUT_MS, true);
+ networkForPrivateDns, GOOGLE_PRIVATE_DNS_SERVER, true);
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
executor, null, callback);
@@ -688,9 +667,7 @@
final Network[] testNetworks = getTestableNetworks();
// Set an invalid private DNS server
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
- Settings.Global.putString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER, INVALID_PRIVATE_DNS_SERVER);
+ mCtsNetUtils.setPrivateDnsStrictMode(INVALID_PRIVATE_DNS_SERVER);
final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
for (Network network : testNetworks) {
// This test cannot be ran with null network because we need to explicitly pass a
@@ -699,7 +676,7 @@
// wait for private DNS setting propagating
mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
- network, INVALID_PRIVATE_DNS_SERVER, PRIVATE_DNS_SETTING_TIMEOUT_MS, false);
+ network, INVALID_PRIVATE_DNS_SERVER, false);
final CountDownLatch latch = new CountDownLatch(1);
final DnsResolver.Callback<List<InetAddress>> errorCallback =
diff --git a/tests/cts/net/src/android/net/cts/IkeTunUtils.java b/tests/cts/net/src/android/net/cts/IkeTunUtils.java
new file mode 100644
index 0000000..fc25292
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IkeTunUtils.java
@@ -0,0 +1,188 @@
+/*
+ * 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.cts;
+
+import static android.net.cts.PacketUtils.BytePayload;
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.IpHeader;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
+import static android.net.cts.PacketUtils.UdpHeader;
+import static android.net.cts.PacketUtils.getIpHeader;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import android.os.ParcelFileDescriptor;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+// TODO: Merge this with the version in the IPsec module (IKEv2 library) CTS tests.
+/** An extension of the TunUtils class with IKE-specific packet handling. */
+public class IkeTunUtils extends TunUtils {
+ private static final int PORT_LEN = 2;
+
+ private static final byte[] NON_ESP_MARKER = new byte[] {0, 0, 0, 0};
+
+ private static final int IKE_HEADER_LEN = 28;
+ private static final int IKE_SPI_LEN = 8;
+ private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
+ private static final int IKE_MSG_ID_OFFSET = 20;
+ private static final int IKE_MSG_ID_LEN = 4;
+
+ public IkeTunUtils(ParcelFileDescriptor tunFd) {
+ super(tunFd);
+ }
+
+ /**
+ * Await an 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 encapExpected, byte[] respIkePkt) throws Exception {
+ final byte[] request = awaitIkePacket(expectedInitIkeSpi, expectedMsgId, encapExpected);
+
+ // Build response header by flipping address and port
+ final InetAddress srcAddr = getDstAddress(request);
+ final InetAddress dstAddr = getSrcAddress(request);
+ final int srcPort = getDstPort(request);
+ final int dstPort = getSrcPort(request);
+
+ final byte[] response =
+ buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, encapExpected, respIkePkt);
+ injectPacket(response);
+ return request;
+ }
+
+ private byte[] awaitIkePacket(long expectedInitIkeSpi, int expectedMsgId, boolean expectEncap)
+ throws Exception {
+ return super.awaitPacket(pkt -> isIke(pkt, expectedInitIkeSpi, expectedMsgId, expectEncap));
+ }
+
+ private static boolean isIke(
+ byte[] pkt, long expectedInitIkeSpi, int expectedMsgId, boolean encapExpected) {
+ final int ipProtocolOffset;
+ final int ikeOffset;
+
+ if (isIpv6(pkt)) {
+ ipProtocolOffset = IP6_PROTO_OFFSET;
+ ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
+ } else {
+ if (encapExpected && !hasNonEspMarkerv4(pkt)) {
+ return false;
+ }
+
+ // Use default IPv4 header length (assuming no options)
+ final int encapMarkerLen = encapExpected ? NON_ESP_MARKER.length : 0;
+ ipProtocolOffset = IP4_PROTO_OFFSET;
+ ikeOffset = IP4_HDRLEN + UDP_HDRLEN + encapMarkerLen;
+ }
+
+ return pkt[ipProtocolOffset] == IPPROTO_UDP
+ && areSpiAndMsgIdEqual(pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId);
+ }
+
+ /** Checks if the provided IPv4 packet has a UDP-encapsulation NON-ESP marker */
+ private static boolean hasNonEspMarkerv4(byte[] ipv4Pkt) {
+ final int nonEspMarkerOffset = IP4_HDRLEN + UDP_HDRLEN;
+ if (ipv4Pkt.length < nonEspMarkerOffset + NON_ESP_MARKER.length) {
+ return false;
+ }
+
+ final byte[] nonEspMarker = Arrays.copyOfRange(
+ ipv4Pkt, nonEspMarkerOffset, nonEspMarkerOffset + NON_ESP_MARKER.length);
+ return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
+ }
+
+ private static boolean areSpiAndMsgIdEqual(
+ byte[] pkt, int ikeOffset, long expectedIkeInitSpi, int expectedMsgId) {
+ if (pkt.length <= ikeOffset + IKE_HEADER_LEN) {
+ return false;
+ }
+
+ final ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ final long spi = buffer.getLong(ikeOffset);
+ final int msgId = buffer.getInt(ikeOffset + IKE_MSG_ID_OFFSET);
+
+ return expectedIkeInitSpi == spi && expectedMsgId == msgId;
+ }
+
+ private static InetAddress getSrcAddress(byte[] pkt) throws Exception {
+ return getAddress(pkt, true);
+ }
+
+ private static InetAddress getDstAddress(byte[] pkt) throws Exception {
+ return getAddress(pkt, false);
+ }
+
+ private static InetAddress getAddress(byte[] pkt, boolean getSrcAddr) throws Exception {
+ final int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
+ final int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
+ final int ipOffset = getSrcAddr ? srcIpOffset : srcIpOffset + ipLen;
+
+ if (pkt.length < ipOffset + ipLen) {
+ // Should be impossible; getAddress() is only called with a full IKE request including
+ // the IP and UDP headers.
+ throw new IllegalArgumentException("Packet was too short to contain IP address");
+ }
+
+ return InetAddress.getByAddress(Arrays.copyOfRange(pkt, ipOffset, ipOffset + ipLen));
+ }
+
+ private static int getSrcPort(byte[] pkt) throws Exception {
+ return getPort(pkt, true);
+ }
+
+ private static int getDstPort(byte[] pkt) throws Exception {
+ return getPort(pkt, false);
+ }
+
+ private static int getPort(byte[] pkt, boolean getSrcPort) {
+ final int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
+ final int portOffset = getSrcPort ? srcPortOffset : srcPortOffset + PORT_LEN;
+
+ if (pkt.length < portOffset + PORT_LEN) {
+ // Should be impossible; getPort() is only called with a full IKE request including the
+ // IP and UDP headers.
+ throw new IllegalArgumentException("Packet was too short to contain port");
+ }
+
+ final ByteBuffer buffer = ByteBuffer.wrap(pkt);
+ return Short.toUnsignedInt(buffer.getShort(portOffset));
+ }
+
+ private static byte[] buildIkePacket(
+ InetAddress srcAddr,
+ InetAddress dstAddr,
+ int srcPort,
+ int dstPort,
+ boolean useEncap,
+ byte[] payload)
+ throws Exception {
+ // Append non-ESP marker if encap is enabled
+ if (useEncap) {
+ final ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER.length + payload.length);
+ buffer.put(NON_ESP_MARKER);
+ buffer.put(payload);
+ payload = buffer.array();
+ }
+
+ final UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(payload));
+ final IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
+ return ipPkt.getPacketBytes();
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
new file mode 100644
index 0000000..5cc0cb4
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -0,0 +1,517 @@
+/*
+ * 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.cts;
+
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Ikev2VpnProfile;
+import android.net.IpSecAlgorithm;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.ProxyInfo;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.VpnManager;
+import android.net.cts.util.CtsNetUtils;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)")
+public class Ikev2VpnTest {
+ private static final String TAG = Ikev2VpnTest.class.getSimpleName();
+
+ // Test vectors for IKE negotiation in test mode.
+ private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
+ "46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
+ + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
+ + "100000b8070f159fe5141d8754ca86f72ecc28d66f514927e96cbe9eec0adb42bf2c276a0ab7"
+ + "a97fa93555f4be9218c14e7f286bb28c6b4fb13825a420f2ffc165854f200bab37d69c8963d4"
+ + "0acb831d983163aa50622fd35c182efe882cf54d6106222abcfaa597255d302f1b95ab71c142"
+ + "c279ea5839a180070bff73f9d03fab815f0d5ee2adec7e409d1e35979f8bd92ffd8aab13d1a0"
+ + "0657d816643ae767e9ae84d2ccfa2bcce1a50572be8d3748ae4863c41ae90da16271e014270f"
+ + "77edd5cd2e3299f3ab27d7203f93d770bacf816041cdcecd0f9af249033979da4369cb242dd9"
+ + "6d172e60513ff3db02de63e50eb7d7f596ada55d7946cad0af0669d1f3e2804846ab3f2a930d"
+ + "df56f7f025f25c25ada694e6231abbb87ee8cfd072c8481dc0b0f6b083fdc3bd89b080e49feb"
+ + "0288eef6fdf8a26ee2fc564a11e7385215cf2deaf2a9965638fc279c908ccdf04094988d91a2"
+ + "464b4a8c0326533aff5119ed79ecbd9d99a218b44f506a5eb09351e67da86698b4c58718db25"
+ + "d55f426fb4c76471b27a41fbce00777bc233c7f6e842e39146f466826de94f564cad8b92bfbe"
+ + "87c99c4c7973ec5f1eea8795e7da82819753aa7c4fcfdab77066c56b939330c4b0d354c23f83"
+ + "ea82fa7a64c4b108f1188379ea0eb4918ee009d804100e6bf118771b9058d42141c847d5ec37"
+ + "6e5ec591c71fc9dac01063c2bd31f9c783b28bf1182900002430f3d5de3449462b31dd28bc27"
+ + "297b6ad169bccce4f66c5399c6e0be9120166f2900001c0000400428b8df2e66f69c8584a186"
+ + "c5eac66783551d49b72900001c000040054e7a622e802d5cbfb96d5f30a6e433994370173529"
+ + "0000080000402e290000100000402f00020003000400050000000800004014";
+ private static final String SUCCESSFUL_IKE_INIT_RESP_V6 =
+ "46b8eca1e0d72a1800d9ea1babce26bf2120222000000000000002d0220000300000002c01010004030000"
+ + "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
+ + "100000ea0e6dd9ca5930a9a45c323a41f64bfd8cdef7730f5fbff37d7c377da427f489a42aa8"
+ + "c89233380e6e925990d49de35c2cdcf63a61302c731a4b3569df1ee1bf2457e55a6751838ede"
+ + "abb75cc63ba5c9e4355e8e784f383a5efe8a44727dc14aeaf8dacc2620fb1c8875416dc07739"
+ + "7fe4decc1bd514a9c7d270cf21fd734c63a25c34b30b68686e54e8a198f37f27cb491fe27235"
+ + "fab5476b036d875ccab9a68d65fbf3006197f9bebbf94de0d3802b4fafe1d48d931ce3a1a346"
+ + "2d65bd639e9bd7fa46299650a9dbaf9b324e40b466942d91a59f41ef8042f8474c4850ed0f63"
+ + "e9238949d41cd8bbaea9aefdb65443a6405792839563aa5dc5c36b5ce8326ccf8a94d9622b85"
+ + "038d390d5fc0299e14e1f022966d4ac66515f6108ca04faec44821fe5bbf2ed4f84ff5671219"
+ + "608cb4c36b44a31ba010c9088f8d5ff943bb9ff857f74be1755f57a5783874adc57f42bb174e"
+ + "4ad3215de628707014dbcb1707bd214658118fdd7a42b3e1638b991ce5b812a667f1145be811"
+ + "685e3cd3baf9b18d062657b64c206a4d19a531c252a6a51a04aeaf42c618620cdbab65baca23"
+ + "82c57ed888422aeaacf7f1bc3fe2247ff7e7eaca218b74d7b31d02f2b0afa123f802529e7e6c"
+ + "3259d418290740ddbf55686e26998d7edcbbf895664972fed666f2f20af40503aa2af436ec6d"
+ + "4ec981ab19b9088755d94ae7a7c2066ea331d4e56e290000243fefe5555fce552d57a84e682c"
+ + "d4a6dfb3f2f94a94464d5bec3d88b88e9559642900001c00004004eb4afff764e7b79bca78b1"
+ + "3a89100d36d678ae982900001c00004005d177216a3c26f782076e12570d40bfaaa148822929"
+ + "0000080000402e290000100000402f00020003000400050000000800004014";
+ private static final String SUCCESSFUL_IKE_AUTH_RESP_V4 =
+ "46b8eca1e0d72a18b2b5d9006d47a0022e20232000000001000000e0240000c420a2500a3da4c66fa6929e"
+ + "600f36349ba0e38de14f78a3ad0416cba8c058735712a3d3f9a0a6ed36de09b5e9e02697e7c4"
+ + "2d210ac86cfbd709503cfa51e2eab8cfdc6427d136313c072968f6506a546eb5927164200592"
+ + "6e36a16ee994e63f029432a67bc7d37ca619e1bd6e1678df14853067ecf816b48b81e8746069"
+ + "406363e5aa55f13cb2afda9dbebee94256c29d630b17dd7f1ee52351f92b6e1c3d8551c513f1"
+ + "d74ac52a80b2041397e109fe0aeb3c105b0d4be0ae343a943398764281";
+ private static final String SUCCESSFUL_IKE_AUTH_RESP_V6 =
+ "46b8eca1e0d72a1800d9ea1babce26bf2e20232000000001000000f0240000d4aaf6eaa6c06b50447e6f54"
+ + "827fd8a9d9d6ac8015c1ebb3e8cb03fc6e54b49a107441f50004027cc5021600828026367f03"
+ + "bc425821cd7772ee98637361300c9b76056e874fea2bd4a17212370b291894264d8c023a01d1"
+ + "c3b691fd4b7c0b534e8c95af4c4638e2d125cb21c6267e2507cd745d72e8da109c47b9259c6c"
+ + "57a26f6bc5b337b9b9496d54bdde0333d7a32e6e1335c9ee730c3ecd607a8689aa7b0577b74f"
+ + "3bf437696a9fd5fc0aee3ed346cd9e15d1dda293df89eb388a8719388a60ca7625754de12cdb"
+ + "efe4c886c5c401";
+ private static final long IKE_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
+
+ private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
+ private static final InetAddress LOCAL_OUTER_6 =
+ InetAddress.parseNumericAddress("2001:db8::1");
+
+ private static final int IP4_PREFIX_LEN = 32;
+ private static final int IP6_PREFIX_LEN = 128;
+
+ // TODO: Use IPv6 address when we can generate test vectors (GCE does not allow IPv6 yet).
+ private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2";
+ private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2";
+ private static final String TEST_IDENTITY = "client.cts.android.com";
+ private static final List<String> TEST_ALLOWED_ALGORITHMS =
+ Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM);
+
+ private static final ProxyInfo TEST_PROXY_INFO =
+ ProxyInfo.buildDirectProxy("proxy.cts.android.com", 1234);
+ private static final int TEST_MTU = 1300;
+
+ private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes();
+ private static final String TEST_USER = "username";
+ private static final String TEST_PASSWORD = "pa55w0rd";
+
+ // Static state to reduce setup/teardown
+ private static final Context sContext = InstrumentationRegistry.getContext();
+ private static final ConnectivityManager sCM =
+ (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ private static final VpnManager sVpnMgr =
+ (VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE);
+ private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
+
+ private final X509Certificate mServerRootCa;
+ private final CertificateAndKey mUserCertKey;
+
+ public Ikev2VpnTest() throws Exception {
+ // Build certificates
+ mServerRootCa = generateRandomCertAndKeyPair().cert;
+ mUserCertKey = generateRandomCertAndKeyPair();
+ }
+
+ /**
+ * Sets the given appop using shell commands
+ *
+ * <p>This method must NEVER be called from within a shell permission, as it will attempt to
+ * acquire, and then drop the shell permission identity. This results in the caller losing the
+ * shell permission identity due to these calls not being reference counted.
+ */
+ public void setAppop(int appop, boolean allow) {
+ // Requires shell permission to update appops.
+ runWithShellPermissionIdentity(() -> {
+ mCtsNetUtils.setAppopPrivileged(appop, allow);
+ }, Manifest.permission.MANAGE_TEST_NETWORKS);
+ }
+
+ private Ikev2VpnProfile buildIkev2VpnProfileCommon(
+ Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception {
+ if (isRestrictedToTestNetworks) {
+ builder.restrictToTestNetworks();
+ }
+
+ return builder.setBypassable(true)
+ .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
+ .setProxy(TEST_PROXY_INFO)
+ .setMaxMtu(TEST_MTU)
+ .setMetered(false)
+ .build();
+ }
+
+ private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks)
+ throws Exception {
+ return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks);
+ }
+
+ private Ikev2VpnProfile buildIkev2VpnProfilePsk(
+ String remote, boolean isRestrictedToTestNetworks) throws Exception {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
+
+ return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+ }
+
+ private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
+ throws Exception {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
+
+ return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+ }
+
+ private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
+ throws Exception {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+ .setAuthDigitalSignature(
+ mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
+
+ return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
+ }
+
+ private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
+ assertEquals(TEST_SERVER_ADDR_V6, profile.getServerAddr());
+ assertEquals(TEST_IDENTITY, profile.getUserIdentity());
+ assertEquals(TEST_PROXY_INFO, profile.getProxyInfo());
+ assertEquals(TEST_ALLOWED_ALGORITHMS, profile.getAllowedAlgorithms());
+ assertTrue(profile.isBypassable());
+ assertFalse(profile.isMetered());
+ assertEquals(TEST_MTU, profile.getMaxMtu());
+ assertFalse(profile.isRestrictedToTestNetworks());
+ }
+
+ @Test
+ public void testBuildIkev2VpnProfilePsk() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ final Ikev2VpnProfile profile =
+ buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+
+ checkBasicIkev2VpnProfile(profile);
+ assertArrayEquals(TEST_PSK, profile.getPresharedKey());
+
+ // Verify nothing else is set.
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getServerRootCaCert());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildIkev2VpnProfileUsernamePassword() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ final Ikev2VpnProfile profile =
+ buildIkev2VpnProfileUsernamePassword(false /* isRestrictedToTestNetworks */);
+
+ checkBasicIkev2VpnProfile(profile);
+ assertEquals(TEST_USER, profile.getUsername());
+ assertEquals(TEST_PASSWORD, profile.getPassword());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ // Verify nothing else is set.
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildIkev2VpnProfileDigitalSignature() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ final Ikev2VpnProfile profile =
+ buildIkev2VpnProfileDigitalSignature(false /* isRestrictedToTestNetworks */);
+
+ checkBasicIkev2VpnProfile(profile);
+ assertEquals(mUserCertKey.cert, profile.getUserCert());
+ assertEquals(mUserCertKey.key, profile.getRsaPrivateKey());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ // Verify nothing else is set.
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getPresharedKey());
+ }
+
+ private void verifyProvisionVpnProfile(
+ boolean hasActivateVpn, boolean hasActivatePlatformVpn, boolean expectIntent)
+ throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn);
+ setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn);
+
+ final Ikev2VpnProfile profile =
+ buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+ final Intent intent = sVpnMgr.provisionVpnProfile(profile);
+ assertEquals(expectIntent, intent != null);
+ }
+
+ @Test
+ public void testProvisionVpnProfileNoPreviousConsent() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ verifyProvisionVpnProfile(false /* hasActivateVpn */,
+ false /* hasActivatePlatformVpn */, true /* expectIntent */);
+ }
+
+ @Test
+ public void testProvisionVpnProfilePlatformVpnConsented() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ verifyProvisionVpnProfile(false /* hasActivateVpn */,
+ true /* hasActivatePlatformVpn */, false /* expectIntent */);
+ }
+
+ @Test
+ public void testProvisionVpnProfileVpnServiceConsented() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ verifyProvisionVpnProfile(true /* hasActivateVpn */,
+ false /* hasActivatePlatformVpn */, false /* expectIntent */);
+ }
+
+ @Test
+ public void testProvisionVpnProfileAllPreConsented() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ verifyProvisionVpnProfile(true /* hasActivateVpn */,
+ true /* hasActivatePlatformVpn */, false /* expectIntent */);
+ }
+
+ @Test
+ public void testDeleteVpnProfile() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
+
+ final Ikev2VpnProfile profile =
+ buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
+ assertNull(sVpnMgr.provisionVpnProfile(profile));
+
+ // Verify that deleting the profile works (even without the appop)
+ setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
+ sVpnMgr.deleteProvisionedVpnProfile();
+
+ // Test that the profile was deleted - starting it should throw an IAE.
+ try {
+ setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
+ sVpnMgr.startProvisionedVpnProfile();
+ fail("Expected IllegalArgumentException due to missing profile");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testStartVpnProfileNoPreviousConsent() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
+ setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
+
+ // Make sure the VpnProfile is not provisioned already.
+ sVpnMgr.stopProvisionedVpnProfile();
+
+ try {
+ sVpnMgr.startProvisionedVpnProfile();
+ fail("Expected SecurityException for missing consent");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6)
+ throws Exception {
+ String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
+ String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
+ String authResp = testIpv6 ? SUCCESSFUL_IKE_AUTH_RESP_V6 : SUCCESSFUL_IKE_AUTH_RESP_V4;
+ boolean hasNat = !testIpv6;
+
+ // Requires MANAGE_TEST_NETWORKS to provision a test-mode profile.
+ mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
+
+ final Ikev2VpnProfile profile =
+ buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */);
+ assertNull(sVpnMgr.provisionVpnProfile(profile));
+
+ sVpnMgr.startProvisionedVpnProfile();
+
+ // Inject IKE negotiation
+ int expectedMsgId = 0;
+ tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, false /* isEncap */,
+ HexDump.hexStringToByteArray(initResp));
+ tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, hasNat /* isEncap */,
+ HexDump.hexStringToByteArray(authResp));
+
+ // Verify the VPN network came up
+ final NetworkRequest nr = new NetworkRequest.Builder()
+ .clearCapabilities().addTransportType(TRANSPORT_VPN).build();
+
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ sCM.requestNetwork(nr, cb);
+ cb.waitForAvailable();
+ final Network vpnNetwork = cb.currentNetwork;
+ assertNotNull(vpnNetwork);
+
+ sVpnMgr.stopProvisionedVpnProfile();
+ cb.waitForLost();
+ assertEquals(vpnNetwork, cb.lastLostNetwork);
+ }
+
+ private void doTestStartStopVpnProfile(boolean testIpv6) throws Exception {
+ // Non-final; these variables ensure we clean up properly after our test if we have
+ // allocated test network resources
+ final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class);
+ TestNetworkInterface testIface = null;
+ TestNetworkCallback tunNetworkCallback = null;
+
+ try {
+ // Build underlying test network
+ testIface = tnm.createTunInterface(
+ new LinkAddress[] {
+ new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
+ new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)});
+
+ // Hold on to this callback to ensure network does not get reaped.
+ tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(
+ testIface.getInterfaceName());
+ final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
+
+ checkStartStopVpnProfileBuildsNetworks(tunUtils, testIpv6);
+ } finally {
+ // Make sure to stop the VPN profile. This is safe to call multiple times.
+ sVpnMgr.stopProvisionedVpnProfile();
+
+ if (testIface != null) {
+ testIface.getFileDescriptor().close();
+ }
+
+ if (tunNetworkCallback != null) {
+ sCM.unregisterNetworkCallback(tunNetworkCallback);
+ }
+
+ final Network testNetwork = tunNetworkCallback.currentNetwork;
+ if (testNetwork != null) {
+ tnm.teardownTestNetwork(testNetwork);
+ }
+ }
+ }
+
+ @Test
+ public void testStartStopVpnProfileV4() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ // Requires shell permission to update appops.
+ runWithShellPermissionIdentity(() -> {
+ doTestStartStopVpnProfile(false);
+ });
+ }
+
+ @Test
+ public void testStartStopVpnProfileV6() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+
+ // Requires shell permission to update appops.
+ runWithShellPermissionIdentity(() -> {
+ doTestStartStopVpnProfile(true);
+ });
+ }
+
+ private static class CertificateAndKey {
+ public final X509Certificate cert;
+ public final PrivateKey key;
+
+ CertificateAndKey(X509Certificate cert, PrivateKey key) {
+ this.cert = cert;
+ this.key = key;
+ }
+ }
+
+ private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+ final Date validityBeginDate =
+ new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+ final Date validityEndDate =
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+ // Generate a keypair
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(512);
+ final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ final X500Principal dnName = new X500Principal("CN=test.android.com");
+ final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+ certGen.setSubjectDN(dnName);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(validityBeginDate);
+ certGen.setNotAfter(validityEndDate);
+ certGen.setPublicKey(keyPair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+ return new CertificateAndKey(cert, keyPair.getPrivate());
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 1d83dda..ae38faa 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -18,20 +18,17 @@
import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
import static android.net.IpSecManager.UdpEncapsulationSocket;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
import static android.net.cts.PacketUtils.BytePayload;
import static android.net.cts.PacketUtils.EspHeader;
import static android.net.cts.PacketUtils.IP4_HDRLEN;
import static android.net.cts.PacketUtils.IP6_HDRLEN;
-import static android.net.cts.PacketUtils.Ip4Header;
-import static android.net.cts.PacketUtils.Ip6Header;
import static android.net.cts.PacketUtils.IpHeader;
import static android.net.cts.PacketUtils.UDP_HDRLEN;
import static android.net.cts.PacketUtils.UdpHeader;
+import static android.net.cts.PacketUtils.getIpHeader;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
@@ -40,38 +37,28 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
-import android.app.AppOpsManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.Network;
-import android.net.NetworkRequest;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.cts.PacketUtils.Payload;
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
+import android.net.cts.util.CtsNetUtils;
import android.os.ParcelFileDescriptor;
-import android.os.SystemProperties;
import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.Before;
@@ -114,7 +101,7 @@
private static TunUtils sTunUtils;
private static Context sContext = InstrumentationRegistry.getContext();
- private static IBinder sBinder = new Binder();
+ private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
@BeforeClass
public static void setUpBeforeClass() throws Exception {
@@ -127,7 +114,7 @@
// 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);
+ mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
TestNetworkInterface testIface =
sTNM.createTunInterface(
@@ -137,8 +124,9 @@
});
sTunFd = testIface.getFileDescriptor();
- sTunNetworkCallback = setupAndGetTestNetwork(testIface.getInterfaceName());
- sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
+ sTunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
+ sTunNetworkCallback.waitForAvailable();
+ sTunNetwork = sTunNetworkCallback.currentNetwork;
sTunUtils = new TunUtils(sTunFd);
}
@@ -149,7 +137,7 @@
super.setUp();
// Set to true before every run; some tests flip this.
- setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+ mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
// Clear sTunUtils state
sTunUtils.reset();
@@ -157,7 +145,7 @@
@AfterClass
public static void tearDownAfterClass() throws Exception {
- setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+ mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
sCM.unregisterNetworkCallback(sTunNetworkCallback);
@@ -169,50 +157,12 @@
.dropShellPermissionIdentity();
}
- private static boolean hasTunnelsFeature() {
- return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
- || SystemProperties.getInt("ro.product.first_api_level", 0)
- >= Build.VERSION_CODES.Q;
- }
-
- 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
- SystemUtil.runShellCommand(cmd);
- }
- }
-
- private static TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception {
- // Build a network request
- NetworkRequest nr =
- new NetworkRequest.Builder()
- .clearCapabilities()
- .addTransportType(TRANSPORT_TEST)
- .setNetworkSpecifier(ifname)
- .build();
-
- TestNetworkCallback cb = new TestNetworkCallback();
- sCM.requestNetwork(nr, cb);
-
- // Setup the test network after network request is filed to prevent Network from being
- // reaped due to no requests matching it.
- sTNM.setupTestNetwork(ifname, sBinder);
-
- return cb;
- }
-
@Test
public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
- if (!hasTunnelsFeature()) return;
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Ensure we don't have the appop. Permission is not requested in the Manifest
- setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+ mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try {
@@ -224,10 +174,10 @@
@Test
public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
- if (!hasTunnelsFeature()) return;
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
// Ensure we don't have the appop. Permission is not requested in the Manifest
- setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+ mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try (IpSecManager.SecurityParameterIndex spi =
@@ -253,19 +203,6 @@
public abstract int run(Network ipsecNetwork) throws Exception;
}
- private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
- private final CompletableFuture<Network> futureNetwork = new CompletableFuture<>();
-
- @Override
- public void onAvailable(Network network) {
- futureNetwork.complete(network);
- }
-
- public Network getNetworkBlocking() throws Exception {
- return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- }
- }
-
private int getPacketSize(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
@@ -499,8 +436,6 @@
public void checkTunnelReflected(
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
throws Exception {
- if (!hasTunnelsFeature()) return;
-
InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
@@ -580,7 +515,6 @@
boolean transportInTunnelMode,
IpSecTunnelTestRunnableFactory factory)
throws Exception {
- if (!hasTunnelsFeature()) return;
InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
@@ -648,8 +582,9 @@
mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
// Build the test network
tunnelIface.addAddress(localInner, innerPrefixLen);
- testNetworkCb = setupAndGetTestNetwork(tunnelIface.getInterfaceName());
- Network testNetwork = testNetworkCb.getNetworkBlocking();
+ testNetworkCb = mCtsNetUtils.setupAndGetTestNetwork(tunnelIface.getInterfaceName());
+ testNetworkCb.waitForAvailable();
+ Network testNetwork = testNetworkCb.currentNetwork;
// Check interface was created
assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
@@ -718,18 +653,6 @@
}
}
- private 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);
- }
- }
-
private EspHeader buildTransportModeEspPacket(
int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
@@ -819,134 +742,158 @@
// Transport-in-Tunnel mode tests
@Test
public void testTransportInTunnelModeV4InV4() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, false, true);
checkTunnelInput(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, true, true);
checkTunnelInput(AF_INET, AF_INET, true, true);
}
@Test
public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV4InV6() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET6, false, true);
checkTunnelInput(AF_INET, AF_INET6, false, true);
}
@Test
public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV4() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, false, true);
checkTunnelInput(AF_INET6, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, true, true);
checkTunnelInput(AF_INET6, AF_INET, true, true);
}
@Test
public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
@Test
public void testTransportInTunnelModeV6InV6() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET6, false, true);
checkTunnelInput(AF_INET, AF_INET6, false, true);
}
@Test
public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, true);
}
// Tunnel mode tests
@Test
public void testTunnelV4InV4() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, false, false);
checkTunnelInput(AF_INET, AF_INET, false, false);
}
@Test
public void testTunnelV4InV4Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, false, false);
}
@Test
public void testTunnelV4InV4UdpEncap() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET, true, false);
checkTunnelInput(AF_INET, AF_INET, true, false);
}
@Test
public void testTunnelV4InV4UdpEncapReflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET, true, false);
}
@Test
public void testTunnelV4InV6() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET, AF_INET6, false, false);
checkTunnelInput(AF_INET, AF_INET6, false, false);
}
@Test
public void testTunnelV4InV6Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET, AF_INET6, false, false);
}
@Test
public void testTunnelV6InV4() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, false, false);
checkTunnelInput(AF_INET6, AF_INET, false, false);
}
@Test
public void testTunnelV6InV4Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET6, AF_INET, false, false);
}
@Test
public void testTunnelV6InV4UdpEncap() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET, true, false);
checkTunnelInput(AF_INET6, AF_INET, true, false);
}
@Test
public void testTunnelV6InV4UdpEncapReflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET6, AF_INET, true, false);
}
@Test
public void testTunnelV6InV6() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelOutput(AF_INET6, AF_INET6, false, false);
checkTunnelInput(AF_INET6, AF_INET6, false, false);
}
@Test
public void testTunnelV6InV6Reflected() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
checkTunnelReflected(AF_INET6, AF_INET6, false, false);
}
}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index f123187..985e313 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -41,7 +41,6 @@
private static final String TAG = "MultinetworkNativeApiTest";
static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
- static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 2_000;
/**
* @return 0 on success
@@ -69,7 +68,7 @@
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
mCR = getContext().getContentResolver();
mCtsNetUtils = new CtsNetUtils(getContext());
- storePrivateDnsSetting();
+ mCtsNetUtils.storePrivateDnsSetting();
}
@Override
@@ -77,18 +76,6 @@
super.tearDown();
}
- private void storePrivateDnsSetting() {
- // Store private DNS setting
- mOldMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
- mOldDnsSpecifier = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER);
- }
-
- private void restorePrivateDnsSetting() {
- // restore private DNS setting
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldMode);
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, mOldDnsSpecifier);
- }
-
private Network[] getTestableNetworks() {
final ArrayList<Network> testableNetworks = new ArrayList<Network>();
for (Network network : mCM.getAllNetworks()) {
@@ -239,17 +226,15 @@
// Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
// b/144521720
try {
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
- Settings.Global.putString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER, GOOGLE_PRIVATE_DNS_SERVER);
+ mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
for (Network network : getTestableNetworks()) {
// Wait for private DNS setting to propagate.
mCtsNetUtils.awaitPrivateDnsSetting("NxDomain test wait private DNS setting timeout",
- network, GOOGLE_PRIVATE_DNS_SERVER, PRIVATE_DNS_SETTING_TIMEOUT_MS, true);
+ network, GOOGLE_PRIVATE_DNS_SERVER, true);
runResNnxDomainCheck(network.getNetworkHandle());
}
} finally {
- restorePrivateDnsSetting();
+ mCtsNetUtils.restorePrivateDnsSetting();
}
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index e8af1b3..d118c8a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -87,7 +87,7 @@
verifyNoCapabilities(nr);
}
- @Test
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testTemporarilyNotMeteredCapability() {
assertTrue(new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED).build()
diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java
index 6177827..0aedecb 100644
--- a/tests/cts/net/src/android/net/cts/PacketUtils.java
+++ b/tests/cts/net/src/android/net/cts/PacketUtils.java
@@ -27,6 +27,7 @@
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Arrays;
+
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
@@ -443,6 +444,19 @@
return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
}
+ public 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);
+ }
+ }
+
/*
* Debug printing
*/
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index a030713..adaba9d 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -21,8 +21,8 @@
import static android.net.cts.PacketUtils.IPPROTO_ESP;
import static android.net.cts.PacketUtils.UDP_HDRLEN;
import static android.system.OsConstants.IPPROTO_UDP;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import android.os.ParcelFileDescriptor;
@@ -39,19 +39,18 @@
public class TunUtils {
private static final String TAG = TunUtils.class.getSimpleName();
+ protected static final int IP4_ADDR_OFFSET = 12;
+ protected static final int IP4_ADDR_LEN = 4;
+ protected static final int IP6_ADDR_OFFSET = 8;
+ protected static final int IP6_ADDR_LEN = 16;
+ protected static final int IP4_PROTO_OFFSET = 9;
+ protected static final int IP6_PROTO_OFFSET = 6;
+
private static final int DATA_BUFFER_LEN = 4096;
- private static final int TIMEOUT = 100;
+ private static final int TIMEOUT = 1000;
- private static final int IP4_PROTO_OFFSET = 9;
- private 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;
-
- private final ParcelFileDescriptor mTunFd;
private final List<byte[]> mPackets = new ArrayList<>();
+ private final ParcelFileDescriptor mTunFd;
private final Thread mReaderThread;
public TunUtils(ParcelFileDescriptor tunFd) {
@@ -112,46 +111,15 @@
return null;
}
- /**
- * Checks if the specified bytes were ever sent in plaintext.
- *
- * <p>Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like
- *
- * @param plaintext the plaintext bytes to check for
- * @param startIndex the index in the list to check for
- */
- public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) {
- Predicate<byte[]> verifier =
- (pkt) -> {
- return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext))
- != -1;
- };
- return getFirstMatchingPacket(verifier, startIndex) != null;
- }
-
- public byte[] getEspPacket(int spi, boolean encap, int startIndex) {
- return getFirstMatchingPacket(
- (pkt) -> {
- return isEsp(pkt, spi, encap);
- },
- startIndex);
- }
-
- public byte[] awaitEspPacketNoPlaintext(
- int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+ protected byte[] awaitPacket(Predicate<byte[]> verifier) throws Exception {
long endTime = System.currentTimeMillis() + TIMEOUT;
int startIndex = 0;
synchronized (mPackets) {
while (System.currentTimeMillis() < endTime) {
- byte[] espPkt = getEspPacket(spi, useEncap, startIndex);
- if (espPkt != null) {
- // Validate packet size
- assertEquals(expectedPacketSize, espPkt.length);
-
- // Always check plaintext from start
- assertFalse(hasPlaintextPacket(plaintext, 0));
- return espPkt; // We've found the packet we're looking for.
+ final byte[] pkt = getFirstMatchingPacket(verifier, startIndex);
+ if (pkt != null) {
+ return pkt; // We've found the packet we're looking for.
}
startIndex = mPackets.size();
@@ -162,10 +130,21 @@
mPackets.wait(waitTimeout);
}
}
-
- fail("No such ESP packet found with SPI " + spi);
}
- return null;
+
+ fail("No packet found matching verifier");
+ throw new IllegalStateException("Impossible condition; should have thrown in fail()");
+ }
+
+ public byte[] awaitEspPacketNoPlaintext(
+ int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+ final byte[] espPkt = awaitPacket(
+ (pkt) -> isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
+
+ // Validate packet size
+ assertEquals(expectedPacketSize, espPkt.length);
+
+ return espPkt; // We've found the packet we're looking for.
}
private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
@@ -176,6 +155,24 @@
&& pkt[espOffset + 3] == (byte) (spi & 0xff);
}
+ /**
+ * Variant of isEsp that also fails the test if the provided plaintext is found
+ *
+ * @param pkt the packet bytes to verify
+ * @param spi the expected SPI to look for
+ * @param encap whether encap was enabled, and the packet has a UDP header
+ * @param plaintext the plaintext packet before outbound encryption, which MUST not appear in
+ * the provided packet.
+ */
+ private static boolean isEspFailIfSpecifiedPlaintextFound(
+ byte[] pkt, int spi, boolean encap, byte[] plaintext) {
+ if (Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) != -1) {
+ fail("Banned plaintext packet found");
+ }
+
+ return isEsp(pkt, spi, encap);
+ }
+
private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
if (isIpv6(pkt)) {
// IPv6 UDP encap not supported by kernels; assume non-encap.
@@ -191,7 +188,7 @@
}
}
- private static boolean isIpv6(byte[] pkt) {
+ public static boolean isIpv6(byte[] pkt) {
// First nibble shows IP version. 0x60 for IPv6
return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 824146f..32cdf92 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -17,8 +17,10 @@
package android.net.cts.util;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -26,10 +28,13 @@
import static org.junit.Assert.fail;
import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
@@ -38,7 +43,13 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
+import android.net.TestNetworkManager;
import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.provider.Settings;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
@@ -59,6 +70,7 @@
private static final int SOCKET_TIMEOUT_MS = 2000;
private static final int PRIVATE_DNS_PROBE_MS = 1_000;
+ public static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 6_000;
public static final int HTTP_PORT = 80;
public static final String TEST_HOST = "connectivitycheck.gstatic.com";
public static final String HTTP_REQUEST =
@@ -69,15 +81,65 @@
public static final String NETWORK_CALLBACK_ACTION =
"ConnectivityManagerTest.NetworkCallbackAction";
- private Context mContext;
- private ConnectivityManager mCm;
- private WifiManager mWifiManager;
+ private final IBinder mBinder = new Binder();
+ private final Context mContext;
+ private final ConnectivityManager mCm;
+ private final ContentResolver mCR;
+ private final WifiManager mWifiManager;
private TestNetworkCallback mCellNetworkCallback;
+ private String mOldPrivateDnsMode;
+ private String mOldPrivateDnsSpecifier;
public CtsNetUtils(Context context) {
mContext = context;
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mCR = context.getContentResolver();
+ }
+
+ /** Checks if FEATURE_IPSEC_TUNNELS is enabled on the device */
+ public boolean hasIpsecTunnelsFeature() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ || SystemProperties.getInt("ro.product.first_api_level", 0)
+ >= Build.VERSION_CODES.Q;
+ }
+
+ /**
+ * Sets the given appop using shell commands
+ *
+ * <p>Expects caller to hold the shell permission identity.
+ */
+ public void setAppopPrivileged(int appop, boolean allow) {
+ final String opName = AppOpsManager.opToName(appop);
+ for (final String pkg : new String[] {"com.android.shell", mContext.getPackageName()}) {
+ final String cmd =
+ String.format(
+ "appops set %s %s %s",
+ pkg, // Package name
+ opName, // Appop
+ (allow ? "allow" : "deny")); // Action
+ SystemUtil.runShellCommand(cmd);
+ }
+ }
+
+ /** Sets up a test network using the provided interface name */
+ public TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception {
+ // Build a network request
+ final NetworkRequest nr =
+ new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .setNetworkSpecifier(ifname)
+ .build();
+
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(nr, cb);
+
+ // Setup the test network after network request is filed to prevent Network from being
+ // reaped due to no requests matching it.
+ mContext.getSystemService(TestNetworkManager.class).setupTestNetwork(ifname, mBinder);
+
+ return cb;
}
// Toggle WiFi twice, leaving it in the state it started in
@@ -249,9 +311,51 @@
return s;
}
+ public void storePrivateDnsSetting() {
+ // Store private DNS setting
+ mOldPrivateDnsMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+ mOldPrivateDnsSpecifier = Settings.Global.getString(mCR,
+ Settings.Global.PRIVATE_DNS_SPECIFIER);
+ // It's possible that there is no private DNS default value in Settings.
+ // Give it a proper default mode which is opportunistic mode.
+ if (mOldPrivateDnsMode == null) {
+ mOldPrivateDnsSpecifier = "";
+ mOldPrivateDnsMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+ Settings.Global.putString(mCR,
+ Settings.Global.PRIVATE_DNS_SPECIFIER, mOldPrivateDnsSpecifier);
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
+ }
+ }
+
+ public void restorePrivateDnsSetting() throws InterruptedException {
+ if (mOldPrivateDnsMode == null || mOldPrivateDnsSpecifier == null) {
+ return;
+ }
+ // restore private DNS setting
+ if ("hostname".equals(mOldPrivateDnsMode)) {
+ setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
+ awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
+ mCm.getActiveNetwork(),
+ mOldPrivateDnsSpecifier, true);
+ } else {
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
+ }
+ }
+
+ public void setPrivateDnsStrictMode(String server) {
+ // To reduce flake rate, set PRIVATE_DNS_SPECIFIER before PRIVATE_DNS_MODE. This ensures
+ // that if the previous private DNS mode was not "hostname", the system only sees one
+ // EVENT_PRIVATE_DNS_SETTINGS_CHANGED event instead of two.
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, server);
+ final String mode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+ // If current private DNS mode is "hostname", we only need to set PRIVATE_DNS_SPECIFIER.
+ if (!"hostname".equals(mode)) {
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
+ }
+ }
+
public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
- @NonNull String server, int timeoutMs,
- boolean requiresValidatedServers) throws InterruptedException {
+ @NonNull String server, boolean requiresValidatedServers) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
NetworkCallback callback = new NetworkCallback() {
@@ -266,7 +370,7 @@
}
};
mCm.registerNetworkCallback(request, callback);
- assertTrue(msg, latch.await(timeoutMs, TimeUnit.MILLISECONDS));
+ assertTrue(msg, latch.await(PRIVATE_DNS_SETTING_TIMEOUT_MS, TimeUnit.MILLISECONDS));
mCm.unregisterNetworkCallback(callback);
// Wait some time for NetworkMonitor's private DNS probe to complete. If we do not do
// this, then the test could complete before the NetworkMonitor private DNS probe
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 8665c7e..1055531 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -56,6 +56,7 @@
import android.net.TetheringManager.TetheringInterfaceRegexps;
import android.net.TetheringManager.TetheringRequest;
import android.net.cts.util.CtsNetUtils;
+import android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -714,7 +715,15 @@
mCtsNetUtils.disconnectFromWifi(null);
}
- final Network activeNetwork = mCm.getActiveNetwork();
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ Network activeNetwork = null;
+ try {
+ mCm.registerDefaultNetworkCallback(networkCallback);
+ activeNetwork = networkCallback.waitForAvailable();
+ } finally {
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+
assertNotNull("No active network. Please ensure the device has working mobile data.",
activeNetwork);
final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);