Merge "Add CtsNetTestCasesMaxTargetSdk30"
diff --git a/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java
new file mode 100644
index 0000000..454bb07
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Icmpv4Header.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv4 header as per https://tools.ietf.org/html/rfc792.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Code | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Icmpv4Header extends Struct {
+ @Field(order = 0, type = Type.U8)
+ public short type;
+ @Field(order = 1, type = Type.U8)
+ public short code;
+ @Field(order = 2, type = Type.S16)
+ public short checksum;
+ public Icmpv4Header(final short type, final short code, final short checksum) {
+ this.type = type;
+ this.code = code;
+ this.checksum = checksum;
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 353fe69..1ad9c35 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -228,6 +228,35 @@
public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81;
public static final int TAG_SYSTEM_DNS = 0xFFFFFF82;
+ /**
+ * A test URL used to override configuration settings and overlays for the network validation
+ * HTTPS URL, when set in {@link android.provider.DeviceConfig} configuration.
+ *
+ * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with
+ * a local test server), and must not be set in production scenarios (as enforced by CTS tests).
+ *
+ * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting.
+ */
+ public static final String TEST_CAPTIVE_PORTAL_HTTPS_URL = "test_captive_portal_https_url";
+ /**
+ * A test URL used to override configuration settings and overlays for the network validation
+ * HTTP URL, when set in {@link android.provider.DeviceConfig} configuration.
+ *
+ * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with
+ * a local test server), and must not be set in production scenarios (as enforced by CTS tests).
+ *
+ * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting.
+ */
+ public static final String TEST_CAPTIVE_PORTAL_HTTP_URL = "test_captive_portal_http_url";
+ /**
+ * Expiration time of the test URL, in ms, relative to {@link System#currentTimeMillis()}.
+ *
+ * <p>After this expiration time, test URLs will be ignored. They will also be ignored if
+ * the expiration time is more than 10 minutes in the future, to avoid misconfiguration
+ * following test runs.
+ */
+ public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time";
+
// TODO: Move to Inet4AddressUtils
// See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues
// for users of both the net-utils-device-common and net-utils-framework-common libraries.
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index 236318d..35d9f31 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -46,9 +46,10 @@
*
* While this will work outside of mainline too, there just is no point to
* using it when the .o and the bpfloader ship in sync with each other.
+ * In which case it's just best to use the default.
*/
#ifndef BPFLOADER_MIN_VER
-#define BPFLOADER_MIN_VER DEFAULT_BPFLOADER_MIN_VER
+#define BPFLOADER_MIN_VER COMPILE_FOR_BPFLOADER_VERSION
#endif
#ifndef BPFLOADER_MAX_VER
@@ -192,8 +193,8 @@
#ifndef DEFAULT_BPF_MAP_UID
#define DEFAULT_BPF_MAP_UID AID_ROOT
-#elif BPFLOADER_MIN_VER < 21u
-#error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.21"
+#elif BPFLOADER_MIN_VER < 28u
+#error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.28"
#endif
#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md) \
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
index 14a0295..02caf07 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -48,6 +48,10 @@
#define DEFAULT_SIZEOF_BPF_MAP_DEF 32 // v0.0 struct: enum (uint sized) + 7 uint
#define DEFAULT_SIZEOF_BPF_PROG_DEF 20 // v0.0 struct: 4 uint + bool + 3 byte alignment pad
+// By default, unless otherwise specified, allow the use of features only supported by v0.28,
+// which first added working support for map uid != root
+#define COMPILE_FOR_BPFLOADER_VERSION 28u
+
/*
* The bpf_{map,prog}_def structures are compiled for different architectures.
* Once by the BPF compiler for the BPF architecture, and once by a C++
@@ -143,7 +147,7 @@
// unsigned int inner_map_idx;
// unsigned int numa_node;
- unsigned int uid; // uid_t
+ unsigned int zero; // uid_t, for compat with old (buggy) bpfloader must be AID_ROOT == 0
unsigned int gid; // gid_t
unsigned int mode; // mode_t
@@ -171,13 +175,15 @@
bool shared; // use empty string as 'file' component of pin path - allows cross .o map sharing
char pad0[3]; // manually pad up to 4 byte alignment, may be used for extensions in the future
+
+ unsigned int uid; // uid_t
};
_Static_assert(sizeof(((struct bpf_map_def *)0)->selinux_context) == 32, "must be 32 bytes");
_Static_assert(sizeof(((struct bpf_map_def *)0)->pin_subdir) == 32, "must be 32 bytes");
// This needs to be updated whenever the above structure definition is expanded.
-_Static_assert(sizeof(struct bpf_map_def) == 116, "sizeof struct bpf_map_def != 116");
+_Static_assert(sizeof(struct bpf_map_def) == 120, "sizeof struct bpf_map_def != 120");
_Static_assert(__alignof__(struct bpf_map_def) == 4, "__alignof__ struct bpf_map_def != 4");
_Static_assert(_Alignof(struct bpf_map_def) == 4, "_Alignof struct bpf_map_def != 4");
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
index 446e881..aa1bfee 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
@@ -17,6 +17,8 @@
package com.android.net.module.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import androidx.test.filters.SmallTest;
@@ -27,16 +29,24 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SharedLogTest {
private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
private static final String TIMESTAMP = "HH:MM:SS";
+ private static final String TAG = "top";
@Test
public void testBasicOperation() {
- final SharedLog logTop = new SharedLog("top");
+ final SharedLog logTop = new SharedLog(TAG);
+ assertTrue(TAG.equals(logTop.getTag()));
+
logTop.mark("first post!");
final SharedLog logLevel2a = logTop.forSubComponent("twoA");
@@ -48,8 +58,10 @@
final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
logTop.log("still logging");
- logLevel3.log("3 >> 2");
+ logLevel2b.e(new Exception("Got another exception"));
+ logLevel3.i("3 >> 2");
logLevel2a.mark("ok: last post");
+ logTop.logf("finished!");
final String[] expected = {
" - MARK first post!",
@@ -59,29 +71,53 @@
" - [twoB] ERROR Wait, here's one: Test",
" - [twoA] WARN second post?",
" - still logging",
+ " - [twoB] ERROR java.lang.Exception: Got another exception",
" - [twoA.three] 3 >> 2",
" - [twoA] MARK ok: last post",
+ " - finished!",
};
// Verify the logs are all there and in the correct order.
- verifyLogLines(expected, logTop);
+ assertDumpLogs(expected, logTop);
// In fact, because they all share the same underlying LocalLog,
// every subcomponent SharedLog's dump() is identical.
- verifyLogLines(expected, logLevel2a);
- verifyLogLines(expected, logLevel2b);
- verifyLogLines(expected, logLevel3);
+ assertDumpLogs(expected, logLevel2a);
+ assertDumpLogs(expected, logLevel2b);
+ assertDumpLogs(expected, logLevel3);
}
- private static void verifyLogLines(String[] expected, SharedLog log) {
+ private static void assertDumpLogs(String[] expected, SharedLog log) {
+ verifyLogLines(expected, dump(log));
+ verifyLogLines(reverse(expected), reverseDump(log));
+ }
+
+ private static String dump(SharedLog log) {
+ return getSharedLogString(pw -> log.dump(null /* fd */, pw, null /* args */));
+ }
+
+ private static String reverseDump(SharedLog log) {
+ return getSharedLogString(pw -> log.reverseDump(pw));
+ }
+
+ private static String[] reverse(String[] ary) {
+ final List<String> ls = new ArrayList<>(Arrays.asList(ary));
+ Collections.reverse(ls);
+ return ls.toArray(new String[ary.length]);
+ }
+
+ private static String getSharedLogString(Consumer<PrintWriter> functor) {
final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
final PrintWriter pw = new PrintWriter(ostream, true);
- log.dump(null, pw, null);
+ functor.accept(pw);
final String dumpOutput = ostream.toString();
- assertTrue(dumpOutput != null);
- assertTrue(!"".equals(dumpOutput));
+ assertNotNull(dumpOutput);
+ assertFalse("".equals(dumpOutput));
+ return dumpOutput;
+ }
- final String[] lines = dumpOutput.split("\n");
+ private static void verifyLogLines(String[] expected, String gottenLogs) {
+ final String[] lines = gottenLogs.split("\n");
assertEquals(expected.length, lines.length);
for (int i = 0; i < expected.length; i++) {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt
index ff8c668..82f1d9b 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/CompatUtil.kt
@@ -17,7 +17,7 @@
package com.android.testutils
import android.net.NetworkSpecifier
-import android.os.Build
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
/**
* Test utility to create [NetworkSpecifier]s on different SDK versions.
@@ -26,7 +26,7 @@
@JvmStatic
fun makeTestNetworkSpecifier(ifName: String): NetworkSpecifier {
// Until R, there was no TestNetworkSpecifier, StringNetworkSpecifier was used instead
- if (isDevSdkInRange(minExclusive = null, maxInclusive = Build.VERSION_CODES.R)) {
+ if (!isAtLeastS()) {
return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
}
// TestNetworkSpecifier is not part of the SDK in some branches using this utility
@@ -37,7 +37,7 @@
@JvmStatic
fun makeEthernetNetworkSpecifier(ifName: String): NetworkSpecifier {
// Until R, there was no EthernetNetworkSpecifier, StringNetworkSpecifier was used instead
- if (isDevSdkInRange(minExclusive = null, maxInclusive = Build.VERSION_CODES.R)) {
+ if (!isAtLeastS()) {
return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
}
// EthernetNetworkSpecifier is not part of the SDK in some branches using this utility
@@ -50,4 +50,4 @@
return Class.forName(clazz)
.getConstructor(String::class.java).newInstance(specifier) as NetworkSpecifier
}
-}
\ No newline at end of file
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
index 3fcf801..21e84da 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -18,58 +18,41 @@
import android.os.Build
import androidx.test.InstrumentationRegistry
-import com.android.modules.utils.build.SdkLevel
-import kotlin.test.fail
+import com.android.modules.utils.build.UnboundedSdkLevel
import org.junit.Assume.assumeTrue
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.util.regex.Pattern
-// TODO: Remove it when Build.VERSION_CODES.SC_V2 is available
-const val SC_V2 = 32
+@Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
+const val SC_V2 = Build.VERSION_CODES.S_V2
private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
+private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean {
+ return (minExclusive == null || !UnboundedSdkLevel.isAtMost(minExclusive)) &&
+ (maxInclusive == null || UnboundedSdkLevel.isAtMost(maxInclusive))
+}
+
/**
- * Returns true if the development SDK version of the device is in the provided range.
+ * Returns true if the development SDK version of the device is in the provided annotation range.
*
- * If the device is not using a release SDK, the development SDK is considered to be higher than
- * [Build.VERSION.SDK_INT].
+ * If the device is not using a release SDK, the development SDK differs from
+ * [Build.VERSION.SDK_INT], and is indicated by the device codenames; see [UnboundedSdkLevel].
*/
-fun isDevSdkInRange(minExclusive: Int?, maxInclusive: Int?): Boolean {
- return (minExclusive == null || isDevSdkAfter(minExclusive)) &&
- (maxInclusive == null || isDevSdkUpTo(maxInclusive))
-}
-
-private fun isDevSdkAfter(minExclusive: Int): Boolean {
- // A development build for T typically has SDK_INT = 30 (R) or SDK_INT = 31 (S), so SDK_INT
- // alone cannot be used to check the SDK version.
- // For recent SDKs that still have development builds used for testing, use SdkLevel utilities
- // instead of SDK_INT.
- return when (minExclusive) {
- // TODO: Use Build.VERSION_CODES.SC_V2 when it is available
- SC_V2 -> SdkLevel.isAtLeastT()
- // TODO: To use SdkLevel.isAtLeastSv2 when available
- Build.VERSION_CODES.S -> fail("Do you expect to ignore the test until T? Use SC_V2 instead")
- Build.VERSION_CODES.R -> SdkLevel.isAtLeastS()
- // Development builds of SDK versions <= R are not used anymore
- else -> Build.VERSION.SDK_INT > minExclusive
- }
-}
-
-private fun isDevSdkUpTo(maxInclusive: Int): Boolean {
- return when (maxInclusive) {
- // TODO: Use Build.VERSION_CODES.SC_V2 when it is available
- SC_V2 -> !SdkLevel.isAtLeastT()
- // TODO: To use SdkLevel.isAtLeastSv2 when available
- Build.VERSION_CODES.S ->
- fail("Do you expect to ignore the test before T? Use SC_V2 instead")
- Build.VERSION_CODES.R -> !SdkLevel.isAtLeastS()
- // Development builds of SDK versions <= R are not used anymore
- else -> Build.VERSION.SDK_INT <= maxInclusive
- }
+fun isDevSdkInRange(
+ ignoreUpTo: DevSdkIgnoreRule.IgnoreUpTo?,
+ ignoreAfter: DevSdkIgnoreRule.IgnoreAfter?
+): Boolean {
+ val minExclusive =
+ if (ignoreUpTo?.value == 0) ignoreUpTo.codename
+ else ignoreUpTo?.value?.toString()
+ val maxInclusive =
+ if (ignoreAfter?.value == 0) ignoreAfter.codename
+ else ignoreAfter?.value?.toString()
+ return isDevSdkInRange(minExclusive, maxInclusive)
}
private fun getMaxTargetSdk(description: Description): Int? {
@@ -86,13 +69,23 @@
* If the device is not using a release SDK, the development SDK is considered to be higher than
* [Build.VERSION.SDK_INT].
*
- * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value.
- * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value.
+ * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this codename or
+ * SDK level.
+ * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this codename or
+ * SDK level.
*/
class DevSdkIgnoreRule @JvmOverloads constructor(
- private val ignoreClassUpTo: Int? = null,
- private val ignoreClassAfter: Int? = null
+ private val ignoreClassUpTo: String? = null,
+ private val ignoreClassAfter: String? = null
) : TestRule {
+ /**
+ * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value.
+ * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value.
+ */
+ @JvmOverloads
+ constructor(ignoreClassUpTo: Int?, ignoreClassAfter: Int? = null) : this(
+ ignoreClassUpTo?.toString(), ignoreClassAfter?.toString())
+
override fun apply(base: Statement, description: Description): Statement {
return IgnoreBySdkStatement(base, description)
}
@@ -103,7 +96,7 @@
* If the device is not using a release SDK, the development SDK is considered to be higher
* than [Build.VERSION.SDK_INT].
*/
- annotation class IgnoreAfter(val value: Int)
+ annotation class IgnoreAfter(val value: Int = 0, val codename: String = "")
/**
* Ignore the test for any development SDK that lower than or equal to [value].
@@ -111,7 +104,7 @@
* If the device is not using a release SDK, the development SDK is considered to be higher
* than [Build.VERSION.SDK_INT].
*/
- annotation class IgnoreUpTo(val value: Int)
+ annotation class IgnoreUpTo(val value: Int = 0, val codename: String = "")
private inner class IgnoreBySdkStatement(
private val base: Statement,
@@ -124,7 +117,7 @@
val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " +
"with SDK ${Build.VERSION.SDK_INT}"
assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
- assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value))
+ assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo, ignoreAfter))
val maxTargetSdk = getMaxTargetSdk(description)
if (maxTargetSdk != null) {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index da7bf97..2e73666 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -52,7 +52,7 @@
val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java)
val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java)
- if (isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value)) AndroidJUnit4(klass) else null
+ if (isDevSdkInRange(ignoreUpTo, ignoreAfter)) AndroidJUnit4(klass) else null
}
override fun run(notifier: RunNotifier) {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
index daf29e4..964c6c6 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketResponder.kt
@@ -54,6 +54,7 @@
*/
fun stop() {
replyThread.interrupt()
+ replyThread.join()
}
private inner class ReplyThread(name: String) : Thread(name) {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
index 4ad93d8..478161d 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/RouterAdvertisementResponder.java
@@ -18,12 +18,15 @@
import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
-import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
@@ -31,6 +34,8 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.MacAddress;
+import android.util.ArrayMap;
+import android.util.Log;
import android.util.Pair;
import com.android.net.module.util.Ipv6Utils;
@@ -38,18 +43,19 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NsHeader;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RdnssOption;
import java.io.IOException;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.Map;
/**
- * RA responder class useful for tests that require a provisioned interface.
+ * ND (RA & NA) responder class useful for tests that require a provisioned IPv6 interface.
+ * TODO: rename to NdResponder
*/
public class RouterAdvertisementResponder extends PacketResponder {
private static final String TAG = "RouterAdvertisementResponder";
@@ -57,26 +63,28 @@
private static final Inet6Address DNS_SERVER =
(Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64");
private final TapPacketReader mPacketReader;
- private final List<Pair<MacAddress, Inet6Address>> mRouterList = new ArrayList<>();
+ // Maps IPv6 address to MacAddress and isRouter boolean.
+ private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>();
public RouterAdvertisementResponder(TapPacketReader packetReader) {
- super(packetReader, RouterAdvertisementResponder::isRouterSolicitation, TAG);
+ super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG);
mPacketReader = packetReader;
}
- private static boolean isRouterSolicitation(byte[] packet) {
+ /** Returns true if the packet is a router solicitation or neighbor solicitation message. */
+ private static boolean isRsOrNs(byte[] packet) {
final ByteBuffer buffer = ByteBuffer.wrap(packet);
final EthernetHeader ethHeader = Struct.parse(EthernetHeader.class, buffer);
if (ethHeader.etherType != ETHER_TYPE_IPV6) {
return false;
}
final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buffer);
- if (ipv6Header.nextHeader != IPPROTO_ICMPV6
- || !ipv6Header.dstIp.equals(IPV6_ADDR_ALL_ROUTERS_MULTICAST)) {
+ if (ipv6Header.nextHeader != IPPROTO_ICMPV6) {
return false;
}
final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buffer);
- return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION;
+ return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION
+ || icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION;
}
/**
@@ -85,7 +93,16 @@
* @param ip the link-local address of the router.
*/
public void addRouterEntry(MacAddress mac, Inet6Address ip) {
- mRouterList.add(new Pair<>(mac, ip));
+ mNeighborMap.put(ip, new Pair<>(mac, true));
+ }
+
+ /**
+ * Adds a new neighbor to be advertised.
+ * @param mac the mac address of the neighbor.
+ * @param ip the link-local address of the neighbor.
+ */
+ public void addNeighborEntry(MacAddress mac, Inet6Address ip) {
+ mNeighborMap.put(ip, new Pair<>(mac, false));
}
private ByteBuffer buildPrefixOption() {
@@ -99,25 +116,72 @@
return RdnssOption.build(3600/*lifetime, must be at least 120*/, DNS_SERVER);
}
+ private ByteBuffer buildSllaOption(MacAddress srcMac) {
+ return LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+ }
+
private ByteBuffer buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp) {
return Ipv6Utils.buildRaPacket(srcMac, dstMac, srcIp, IPV6_ADDR_ALL_NODES_MULTICAST,
(byte) 0 /*M=0, O=0*/, 3600 /*lifetime*/, 0 /*reachableTime, unspecified*/,
- 0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption());
+ 0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption(),
+ buildSllaOption(srcMac));
+ }
+
+ private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) {
+ try {
+ reader.sendResponse(buffer);
+ } catch (IOException e) {
+ // Throwing an exception here will crash the test process. Let's stick to logging, as
+ // the test will fail either way.
+ Log.e(TAG, "Failed to send buffer", e);
+ }
+ }
+
+ private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) {
+ for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) {
+ final boolean isRouter = it.getValue().second;
+ if (!isRouter) {
+ continue;
+ }
+ final ByteBuffer raResponse = buildRaPacket(it.getValue().first, dstMac, it.getKey());
+ sendResponse(reader, raResponse);
+ }
+ }
+
+ private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac,
+ Inet6Address dstIp, Inet6Address targetIp) {
+ final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp);
+ if (neighbor == null) {
+ return;
+ }
+
+ final MacAddress srcMac = neighbor.first;
+ final boolean isRouter = neighbor.second;
+ int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+ if (isRouter) {
+ flags |= NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
+ }
+
+ final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
+ final ByteBuffer naResponse = Ipv6Utils.buildNaPacket(srcMac, dstMac, targetIp, dstIp,
+ flags, targetIp, tlla);
+ sendResponse(reader, naResponse);
}
@Override
protected void replyToPacket(byte[] packet, TapPacketReader reader) {
- final MacAddress srcMac = MacAddress.fromBytes(
- Arrays.copyOfRange(packet, ETHER_SRC_ADDR_OFFSET,
- ETHER_SRC_ADDR_OFFSET + ETHER_ADDR_LEN));
+ final ByteBuffer buf = ByteBuffer.wrap(packet);
+ // Messages are filtered by parent class, so it is safe to assume that packet is either an
+ // RS or NS.
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf);
- for (Pair<MacAddress, Inet6Address> it : mRouterList) {
- final ByteBuffer raResponse = buildRaPacket(it.first, srcMac, it.second);
- try {
- reader.sendResponse(raResponse);
- } catch (IOException e) {
- throw new RuntimeException("Failed to send RA");
- }
+ if (icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION) {
+ replyToRouterSolicitation(reader, ethHdr.srcMac);
+ } else if (icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION) {
+ final NsHeader nsHeader = Struct.parse(NsHeader.class, buf);
+ replyToNeighborSolicitation(reader, ethHdr.srcMac, ipv6Hdr.srcIp, nsHeader.target);
}
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
index f557f18..a4dbd9a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
@@ -19,6 +19,7 @@
package com.android.testutils
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.ExceptionUtils.ThrowingRunnable
import com.android.testutils.ExceptionUtils.ThrowingSupplier
@@ -31,6 +32,23 @@
*/
fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
val autom = InstrumentationRegistry.getInstrumentation().uiAutomation
+
+ // Calls to adoptShellPermissionIdentity do not nest, and dropShellPermissionIdentity drops all
+ // permissions. Thus, nesting calls will almost certainly cause test bugs, On S+, where we can
+ // detect this, refuse to do it.
+ //
+ // TODO: when R is deprecated, we could try to make this work instead.
+ // - Get the list of previously-adopted permissions.
+ // - Adopt the union of the previously-adopted and newly-requested permissions.
+ // - Run the task.
+ // - Adopt the previously-adopted permissions, dropping the ones just adopted.
+ //
+ // This would allow tests (and utility classes, such as the TestCarrierConfigReceiver attempted
+ // in aosp/2106007) to call runAsShell even within a test that has already adopted permissions.
+ if (SdkLevel.isAtLeastS() && !autom.getAdoptedShellPermissions().isNullOrEmpty()) {
+ throw IllegalStateException("adoptShellPermissionIdentity calls must not be nested")
+ }
+
autom.adoptShellPermissionIdentity(*permissions)
try {
return task()