Merge "[Thread] add public newbie guide" into main
diff --git a/Cronet/tests/OWNERS b/Cronet/tests/OWNERS
deleted file mode 100644
index a35a789..0000000
--- a/Cronet/tests/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-# Bug component: 31808
-
-set noparent
-file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking_xts
-
-# TODO: Temp ownership to develop cronet CTS
-colibie@google.com #{LAST_RESORT_SUGGESTION}
-prohr@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/Cronet/tests/common/Android.bp b/Cronet/tests/common/Android.bp
deleted file mode 100644
index 703f544..0000000
--- a/Cronet/tests/common/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2023 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.
-//
-
-// Tests in this folder are included both in unit tests and CTS.
-// They must be fast and stable, and exercise public or test APIs.
-
-package {
- default_team: "trendy_team_fwk_core_networking",
- // See: http://go/android-license-faq
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// TODO: Consider merging with ConnectivityCoverageTests which is a collection of all
-// Connectivity tests being used for coverage. This will depend on how far we decide to
-// go with merging NetHttp and Tethering targets.
-android_test {
- name: "NetHttpCoverageTests",
- enforce_default_target_sdk_version: true,
- min_sdk_version: "30",
- test_suites: [
- "general-tests",
- "mts-tethering",
- ],
- static_libs: [
- "modules-utils-native-coverage-listener",
- "CtsNetHttpTestsLib",
- "NetHttpTestsLibPreJarJar",
- ],
- jarjar_rules: ":net-http-test-jarjar-rules",
- compile_multilib: "both", // Include both the 32 and 64 bit versions
- jni_libs: [
- "cronet_aml_components_cronet_android_cronet_tests__testing",
- "cronet_aml_third_party_netty_tcnative_netty_tcnative_so__testing",
- "libnativecoverage",
- ],
- data: [":cronet_javatests_resources"],
-}
diff --git a/Cronet/tests/common/AndroidManifest.xml b/Cronet/tests/common/AndroidManifest.xml
deleted file mode 100644
index b00fc90..0000000
--- a/Cronet/tests/common/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.net.http.tests.coverage">
-
- <!-- NetHttpCoverageTests combines CtsNetHttpTestCases and NetHttpTests targets,
- so permissions and others are declared in their respective manifests -->
- <application tools:replace="android:label"
- android:label="NetHttp coverage tests">
- <uses-library android:name="android.test.runner" />
- </application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.net.http.tests.coverage"
- android:label="NetHttp coverage tests">
- </instrumentation>
-</manifest>
diff --git a/Cronet/tests/common/AndroidTest.xml b/Cronet/tests/common/AndroidTest.xml
deleted file mode 100644
index ae6b65b..0000000
--- a/Cronet/tests/common/AndroidTest.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<configuration description="Runs coverage tests for NetHttp">
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="test-file-name" value="NetHttpCoverageTests.apk" />
- <option name="install-arg" value="-t" />
- </target_preparer>
- <option name="test-tag" value="NetHttpCoverageTests" />
-
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="push-file" key="net" value="/storage/emulated/0/chromium_tests_root/net" />
- <option name="push-file" key="test_server" value="/storage/emulated/0/chromium_tests_root/components/cronet/testing/test_server" />
- </target_preparer>
- <!-- Tethering/Connectivity is a SDK 30+ module -->
- <!-- TODO Switch back to Sdk30 when b/270049141 is fixed -->
- <object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
- <option name="config-descriptor:metadata" key="mainline-param"
- value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.net.http.tests.coverage" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <!-- b/298380508 -->
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider" />
- <!-- b/316559294 -->
- <option name="exclude-filter" value="org.chromium.net.NQETest#testQuicDisabled" />
- <!-- b/316559294 -->
- <option name="exclude-filter" value="org.chromium.net.NQETest#testPrefsWriteRead" />
- <!-- b/316554711-->
- <option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
- <!-- b/316550794 -->
- <option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
- <option name="hidden-api-checks" value="false"/>
- <option name="isolated-storage" value="false"/>
- <option name="orchestrator" value="true"/>
- <option
- name="device-listeners"
- value="com.android.modules.utils.testing.NativeCoverageHackInstrumentationListener" />
- </test>
-</configuration>
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
deleted file mode 100644
index 92b73d9..0000000
--- a/Cronet/tests/cts/Android.bp
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// Copyright (C) 2019 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 {
- default_team: "trendy_team_fwk_core_networking",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_library {
- name: "CtsNetHttpTestsLib",
- defaults: [
- "cts_defaults",
- ],
- sdk_version: "test_current",
- min_sdk_version: "30",
- srcs: [
- "src/**/*.java",
- "src/**/*.kt",
- ],
- static_libs: [
- "androidx.test.ext.junit",
- "ctstestrunner-axt",
- "ctstestserver",
- "hamcrest-library",
- "junit",
- "kotlin-test",
- "mockito-target",
- "net-tests-utils",
- "truth",
- ],
- libs: [
- "android.test.base",
- "androidx.annotation_annotation",
- "framework-connectivity",
- "org.apache.http.legacy",
- ],
- lint: {
- test: true,
- },
-}
-
-android_test {
- name: "CtsNetHttpTestCases",
- defaults: [
- "cts_defaults",
- ],
- enforce_default_target_sdk_version: true,
- sdk_version: "test_current",
- min_sdk_version: "30",
- static_libs: ["CtsNetHttpTestsLib"],
- // Tag this as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "mts-tethering",
- "mcts-tethering",
- ],
-}
diff --git a/Cronet/tests/cts/AndroidManifest.xml b/Cronet/tests/cts/AndroidManifest.xml
deleted file mode 100644
index 26900b2..0000000
--- a/Cronet/tests/cts/AndroidManifest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2019 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.
- */
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.http.cts">
-
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
- <application android:networkSecurityConfig="@xml/network_security_config">
- <uses-library android:name="android.test.runner"/>
- </application>
-
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.net.http.cts"
- android:label="CTS tests of android.net.http">
- </instrumentation>
-</manifest>
diff --git a/Cronet/tests/cts/AndroidTest.xml b/Cronet/tests/cts/AndroidTest.xml
deleted file mode 100644
index e0421fd..0000000
--- a/Cronet/tests/cts/AndroidTest.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 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.
- -->
-<configuration description="Config for CTS Cronet test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="networking" />
- <!-- Instant apps cannot create sockets. See b/264248246 -->
- <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsNetHttpTestCases.apk" />
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.net.http.cts" />
- <option name="runtime-hint" value="10s" />
- </test>
-
- <!-- Only run CtsNetHttpTestCases in MTS if the Tethering Mainline module is installed. -->
- <object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.tethering" />
- </object>
-</configuration>
diff --git a/Cronet/tests/cts/assets/html/hello_world.html b/Cronet/tests/cts/assets/html/hello_world.html
deleted file mode 100644
index ea62ce2..0000000
--- a/Cronet/tests/cts/assets/html/hello_world.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ 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.
- -->
-
-<html>
-<head>
- <title>hello world</title>
-</head>
-<body>
-<h3>hello world</h3><br>
-</body>
-</html>
\ No newline at end of file
diff --git a/Cronet/tests/cts/res/xml/network_security_config.xml b/Cronet/tests/cts/res/xml/network_security_config.xml
deleted file mode 100644
index 7d7530b..0000000
--- a/Cronet/tests/cts/res/xml/network_security_config.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ 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.
- -->
-
-<network-security-config>
- <domain-config cleartextTrafficPermitted="true">
- <domain includeSubdomains="true">localhost</domain>
- </domain-config>
-</network-security-config>
\ No newline at end of file
diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
deleted file mode 100644
index 464862d..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.content.Context
-import android.net.http.BidirectionalStream
-import android.net.http.HttpEngine
-import android.net.http.cts.util.TestBidirectionalStreamCallback
-import android.net.http.cts.util.TestBidirectionalStreamCallback.ResponseStep
-import android.net.http.cts.util.assumeOKStatusCode
-import android.net.http.cts.util.skipIfNoInternetConnection
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.SkipPresubmit
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import org.hamcrest.MatcherAssert
-import org.hamcrest.Matchers
-import org.junit.After
-import org.junit.AssumptionViolatedException
-import org.junit.Before
-import org.junit.runner.RunWith
-
-private const val URL = "https://source.android.com"
-
-/**
- * This tests uses a non-hermetic server. Instead of asserting, assume the next callback. This way,
- * if the request were to fail, the test would just be skipped instead of failing.
- */
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class BidirectionalStreamTest {
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val callback = TestBidirectionalStreamCallback()
- private val httpEngine = HttpEngine.Builder(context).build()
- private var stream: BidirectionalStream? = null
-
- @Before
- fun setUp() {
- skipIfNoInternetConnection(context)
- }
-
- @After
- @Throws(Exception::class)
- fun tearDown() {
- httpEngine.shutdown()
- }
-
- private fun createBidirectionalStreamBuilder(url: String): BidirectionalStream.Builder {
- return httpEngine.newBidirectionalStreamBuilder(url, callback.executor, callback)
- }
-
- @Test
- @Throws(Exception::class)
- @SkipPresubmit(reason = "b/293141085 Confirm non-flaky and move to presubmit after SLO")
- fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
- stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
- stream!!.start()
- // We call to a real server and hence the server may not be reachable, cancel this stream
- // and rethrow the exception before tearDown,
- // otherwise shutdown would fail with active request error.
- try {
- callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
- } catch (e: AssumptionViolatedException) {
- stream!!.cancel()
- callback.blockForDone()
- throw e
- }
-
- val info = callback.mResponseInfo
- assumeOKStatusCode(info)
- MatcherAssert.assertThat(
- "Received byte count must be > 0", info.receivedByteCount, Matchers.greaterThan(0L))
- assertEquals("h2", info.negotiatedProtocol)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_getHttpMethod() {
- val builder = createBidirectionalStreamBuilder(URL)
- val method = "GET"
-
- builder.setHttpMethod(method)
- stream = builder.build()
- assertThat(stream!!.getHttpMethod()).isEqualTo(method)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_hasTrafficStatsTag() {
- val builder = createBidirectionalStreamBuilder(URL)
-
- builder.setTrafficStatsTag(10)
- stream = builder.build()
- assertThat(stream!!.hasTrafficStatsTag()).isTrue()
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_getTrafficStatsTag() {
- val builder = createBidirectionalStreamBuilder(URL)
- val trafficStatsTag = 10
-
- builder.setTrafficStatsTag(trafficStatsTag)
- stream = builder.build()
- assertThat(stream!!.getTrafficStatsTag()).isEqualTo(trafficStatsTag)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_hasTrafficStatsUid() {
- val builder = createBidirectionalStreamBuilder(URL)
-
- builder.setTrafficStatsUid(10)
- stream = builder.build()
- assertThat(stream!!.hasTrafficStatsUid()).isTrue()
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_getTrafficStatsUid() {
- val builder = createBidirectionalStreamBuilder(URL)
- val trafficStatsUid = 10
-
- builder.setTrafficStatsUid(trafficStatsUid)
- stream = builder.build()
- assertThat(stream!!.getTrafficStatsUid()).isEqualTo(trafficStatsUid)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_getHeaders_asList() {
- val builder = createBidirectionalStreamBuilder(URL)
- val expectedHeaders = mapOf(
- "Authorization" to "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
- "Max-Forwards" to "10",
- "X-Client-Data" to "random custom header content").entries.toList()
-
- for (header in expectedHeaders) {
- builder.addHeader(header.key, header.value)
- }
-
- stream = builder.build()
- assertThat(stream!!.getHeaders().getAsList()).containsAtLeastElementsIn(expectedHeaders)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_getHeaders_asMap() {
- val builder = createBidirectionalStreamBuilder(URL)
- val expectedHeaders = mapOf(
- "Authorization" to listOf("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
- "Max-Forwards" to listOf("10"),
- "X-Client-Data" to listOf("random custom header content"))
-
- for (header in expectedHeaders) {
- builder.addHeader(header.key, header.value.get(0))
- }
-
- stream = builder.build()
- assertThat(stream!!.getHeaders().getAsMap()).containsAtLeastEntriesIn(expectedHeaders)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_getPriority() {
- val builder = createBidirectionalStreamBuilder(URL)
- val priority = BidirectionalStream.STREAM_PRIORITY_LOW
-
- builder.setPriority(priority)
- stream = builder.build()
- assertThat(stream!!.getPriority()).isEqualTo(priority)
- }
-
- @Test
- @Throws(Exception::class)
- fun testBidirectionalStream_isDelayRequestHeadersUntilFirstFlushEnabled() {
- val builder = createBidirectionalStreamBuilder(URL)
-
- builder.setDelayRequestHeadersUntilFirstFlushEnabled(true)
- stream = builder.build()
- assertThat(stream!!.isDelayRequestHeadersUntilFirstFlushEnabled()).isTrue()
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
deleted file mode 100644
index 1405ed9..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.content.Context
-import android.net.http.CallbackException
-import android.net.http.HttpEngine
-import android.net.http.cts.util.HttpCtsTestServer
-import android.net.http.cts.util.TestUrlRequestCallback
-import android.net.http.cts.util.TestUrlRequestCallback.FailureType
-import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertSame
-import kotlin.test.assertTrue
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class CallbackExceptionTest {
-
- @Test
- fun testCallbackException_returnsInputParameters() {
- val message = "failed"
- val cause = Throwable("exception")
- val callbackException = object : CallbackException(message, cause) {}
-
- assertEquals(message, callbackException.message)
- assertSame(cause, callbackException.cause)
- }
-
- @Test
- fun testCallbackException_thrownFromUrlRequest() {
- val context: Context = ApplicationProvider.getApplicationContext()
- val server = HttpCtsTestServer(context)
- val httpEngine = HttpEngine.Builder(context).build()
- val callback = TestUrlRequestCallback()
- callback.setFailure(FailureType.THROW_SYNC, ResponseStep.ON_RESPONSE_STARTED)
- val request = httpEngine
- .newUrlRequestBuilder(server.successUrl, callback.executor, callback)
- .build()
-
- request.start()
- callback.blockForDone()
-
- assertTrue(request.isDone)
- assertIs<CallbackException>(callback.mError)
- server.shutdown()
- httpEngine.shutdown()
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
deleted file mode 100644
index 10c7f3c..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.net.http.ConnectionMigrationOptions
-import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_ENABLED
-import android.net.http.ConnectionMigrationOptions.MIGRATION_OPTION_UNSPECIFIED
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class ConnectionMigrationOptionsTest {
-
- @Test
- fun testConnectionMigrationOptions_defaultValues() {
- val options =
- ConnectionMigrationOptions.Builder().build()
-
- assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.allowNonDefaultNetworkUsage)
- assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.defaultNetworkMigration)
- assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.pathDegradationMigration)
- }
-
- @Test
- fun testConnectionMigrationOptions_enableDefaultNetworkMigration_returnSetValue() {
- val options =
- ConnectionMigrationOptions.Builder()
- .setDefaultNetworkMigration(MIGRATION_OPTION_ENABLED)
- .build()
-
- assertEquals(MIGRATION_OPTION_ENABLED, options.defaultNetworkMigration)
- }
-
- @Test
- fun testConnectionMigrationOptions_enablePathDegradationMigration_returnSetValue() {
- val options =
- ConnectionMigrationOptions.Builder()
- .setPathDegradationMigration(MIGRATION_OPTION_ENABLED)
- .build()
-
- assertEquals(MIGRATION_OPTION_ENABLED, options.pathDegradationMigration)
- }
-
- @Test
- fun testConnectionMigrationOptions_allowNonDefaultNetworkUsage_returnSetValue() {
- val options =
- ConnectionMigrationOptions.Builder()
- .setAllowNonDefaultNetworkUsage(MIGRATION_OPTION_ENABLED).build()
-
- assertEquals(MIGRATION_OPTION_ENABLED, options.allowNonDefaultNetworkUsage)
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
deleted file mode 100644
index 56802c6..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.net.http.DnsOptions
-import android.net.http.DnsOptions.DNS_OPTION_ENABLED
-import android.net.http.DnsOptions.DNS_OPTION_UNSPECIFIED
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import java.time.Duration
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class DnsOptionsTest {
-
- @Test
- fun testDnsOptions_defaultValues() {
- val options = DnsOptions.Builder().build()
-
- assertEquals(DNS_OPTION_UNSPECIFIED, options.persistHostCache)
- assertNull(options.persistHostCachePeriod)
- assertEquals(DNS_OPTION_UNSPECIFIED, options.staleDns)
- assertNull(options.staleDnsOptions)
- assertEquals(DNS_OPTION_UNSPECIFIED, options.useHttpStackDnsResolver)
- assertEquals(DNS_OPTION_UNSPECIFIED,
- options.preestablishConnectionsToStaleDnsResults)
- }
-
- @Test
- fun testDnsOptions_persistHostCache_returnSetValue() {
- val options = DnsOptions.Builder()
- .setPersistHostCache(DNS_OPTION_ENABLED)
- .build()
-
- assertEquals(DNS_OPTION_ENABLED, options.persistHostCache)
- }
-
- @Test
- fun testDnsOptions_persistHostCachePeriod_returnSetValue() {
- val period = Duration.ofMillis(12345)
- val options = DnsOptions.Builder().setPersistHostCachePeriod(period).build()
-
- assertEquals(period, options.persistHostCachePeriod)
- }
-
- @Test
- fun testDnsOptions_enableStaleDns_returnSetValue() {
- val options = DnsOptions.Builder()
- .setStaleDns(DNS_OPTION_ENABLED)
- .build()
-
- assertEquals(DNS_OPTION_ENABLED, options.staleDns)
- }
-
- @Test
- fun testDnsOptions_useHttpStackDnsResolver_returnsSetValue() {
- val options = DnsOptions.Builder()
- .setUseHttpStackDnsResolver(DNS_OPTION_ENABLED)
- .build()
-
- assertEquals(DNS_OPTION_ENABLED, options.useHttpStackDnsResolver)
- }
-
- @Test
- fun testDnsOptions_preestablishConnectionsToStaleDnsResults_returnsSetValue() {
- val options = DnsOptions.Builder()
- .setPreestablishConnectionsToStaleDnsResults(DNS_OPTION_ENABLED)
- .build()
-
- assertEquals(DNS_OPTION_ENABLED,
- options.preestablishConnectionsToStaleDnsResults)
- }
-
- @Test
- fun testDnsOptions_setStaleDnsOptions_returnsSetValues() {
- val staleOptions = DnsOptions.StaleDnsOptions.Builder()
- .setAllowCrossNetworkUsage(DNS_OPTION_ENABLED)
- .setFreshLookupTimeout(Duration.ofMillis(1234))
- .build()
- val options = DnsOptions.Builder()
- .setStaleDns(DNS_OPTION_ENABLED)
- .setStaleDnsOptions(staleOptions)
- .build()
-
- assertEquals(DNS_OPTION_ENABLED, options.staleDns)
- assertEquals(staleOptions, options.staleDnsOptions)
- }
-
- @Test
- fun testStaleDnsOptions_defaultValues() {
- val options = DnsOptions.StaleDnsOptions.Builder().build()
-
- assertEquals(DNS_OPTION_UNSPECIFIED, options.allowCrossNetworkUsage)
- assertNull(options.freshLookupTimeout)
- assertNull(options.maxExpiredDelay)
- assertEquals(DNS_OPTION_UNSPECIFIED, options.useStaleOnNameNotResolved)
- }
-
- @Test
- fun testStaleDnsOptions_allowCrossNetworkUsage_returnsSetValue() {
- val options = DnsOptions.StaleDnsOptions.Builder()
- .setAllowCrossNetworkUsage(DNS_OPTION_ENABLED).build()
-
- assertEquals(DNS_OPTION_ENABLED, options.allowCrossNetworkUsage)
- }
-
- @Test
- fun testStaleDnsOptions_freshLookupTimeout_returnsSetValue() {
- val duration = Duration.ofMillis(12345)
- val options = DnsOptions.StaleDnsOptions.Builder().setFreshLookupTimeout(duration).build()
-
- assertNotNull(options.freshLookupTimeout)
- assertEquals(duration, options.freshLookupTimeout!!)
- }
-
- @Test
- fun testStaleDnsOptions_useStaleOnNameNotResolved_returnsSetValue() {
- val options = DnsOptions.StaleDnsOptions.Builder()
- .setUseStaleOnNameNotResolved(DNS_OPTION_ENABLED)
- .build()
-
- assertEquals(DNS_OPTION_ENABLED, options.useStaleOnNameNotResolved)
- }
-
- @Test
- fun testStaleDnsOptions_maxExpiredDelayMillis_returnsSetValue() {
- val duration = Duration.ofMillis(12345)
- val options = DnsOptions.StaleDnsOptions.Builder().setMaxExpiredDelay(duration).build()
-
- assertNotNull(options.maxExpiredDelay)
- assertEquals(duration, options.maxExpiredDelay!!)
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
deleted file mode 100644
index f86ac29..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts;
-
-import static android.net.http.cts.util.TestUtilsKt.assertOKStatusCode;
-import static android.net.http.cts.util.TestUtilsKt.assumeOKStatusCode;
-import static android.net.http.cts.util.TestUtilsKt.skipIfNoInternetConnection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsString;
-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 android.content.Context;
-import android.net.Network;
-import android.net.http.ConnectionMigrationOptions;
-import android.net.http.DnsOptions;
-import android.net.http.HttpEngine;
-import android.net.http.QuicOptions;
-import android.net.http.UrlRequest;
-import android.net.http.UrlResponseInfo;
-import android.net.http.cts.util.HttpCtsTestServer;
-import android.net.http.cts.util.TestUrlRequestCallback;
-import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
-import android.os.Build;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Set;
-
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class HttpEngineTest {
- private static final String HOST = "source.android.com";
- private static final String URL = "https://" + HOST;
-
- private HttpEngine.Builder mEngineBuilder;
- private TestUrlRequestCallback mCallback;
- private HttpCtsTestServer mTestServer;
- private UrlRequest mRequest;
- private HttpEngine mEngine;
- private Context mContext;
-
- @Before
- public void setUp() throws Exception {
- mContext = ApplicationProvider.getApplicationContext();
- skipIfNoInternetConnection(mContext);
- mEngineBuilder = new HttpEngine.Builder(mContext);
- mCallback = new TestUrlRequestCallback();
- mTestServer = new HttpCtsTestServer(mContext);
- }
-
- @After
- public void tearDown() throws Exception {
- if (mRequest != null) {
- mRequest.cancel();
- mCallback.blockForDone();
- }
- if (mEngine != null) {
- mEngine.shutdown();
- }
- if (mTestServer != null) {
- mTestServer.shutdown();
- }
- }
-
- private boolean isQuic(String negotiatedProtocol) {
- return negotiatedProtocol.startsWith("http/2+quic") || negotiatedProtocol.startsWith("h3");
- }
-
- @Test
- public void testHttpEngine_Default() throws Exception {
- mEngine = mEngineBuilder.build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- // This tests uses a non-hermetic server. Instead of asserting, assume the next callback.
- // This way, if the request were to fail, the test would just be skipped instead of failing.
- mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- assertEquals("h2", info.getNegotiatedProtocol());
- }
-
- @Test
- public void testHttpEngine_EnableHttpCache() {
- String url = mTestServer.getCacheableTestDownloadUrl(
- /* downloadId */ "cacheable-download",
- /* numBytes */ 10);
- mEngine =
- mEngineBuilder
- .setStoragePath(mContext.getApplicationInfo().dataDir)
- .setEnableHttpCache(
- HttpEngine.Builder.HTTP_CACHE_DISK, /* maxSize */ 100 * 1024)
- .build();
-
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assumeOKStatusCode(info);
- assertFalse(info.wasCached());
-
- mCallback = new TestUrlRequestCallback();
- builder = mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- assertTrue(info.wasCached());
- }
-
- @Test
- public void testHttpEngine_DisableHttp2() throws Exception {
- mEngine = mEngineBuilder.setEnableHttp2(false).build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- // This tests uses a non-hermetic server. Instead of asserting, assume the next callback.
- // This way, if the request were to fail, the test would just be skipped instead of failing.
- mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- assertEquals("http/1.1", info.getNegotiatedProtocol());
- }
-
- @Test
- public void testHttpEngine_EnablePublicKeyPinningBypassForLocalTrustAnchors() {
- String url = mTestServer.getSuccessUrl();
- // For known hosts, requests should succeed whether we're bypassing the local trust anchor
- // or not.
- mEngine = mEngineBuilder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(false).build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
- mEngine.shutdown();
- mEngine = mEngineBuilder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(true).build();
- mCallback = new TestUrlRequestCallback();
- builder = mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
- // TODO(b/270918920): We should also test with a certificate not present in the device's
- // trusted store.
- // This requires either:
- // * Mocking the underlying CertificateVerifier.
- // * Or, having the server return a root certificate not present in the device's trusted
- // store.
- // The former doesn't make sense for a CTS test as it would depend on the underlying
- // implementation. The latter is something we should support once we write a proper test
- // server.
- }
-
- private byte[] generateSha256() {
- byte[] sha256 = new byte[32];
- Arrays.fill(sha256, (byte) 58);
- return sha256;
- }
-
- private Instant instantInFuture(int secondsIntoFuture) {
- Calendar cal = Calendar.getInstance();
- cal.add(Calendar.SECOND, secondsIntoFuture);
- return cal.getTime().toInstant();
- }
-
- @Test
- public void testHttpEngine_AddPublicKeyPins() {
- // CtsTestServer, when set in SslMode.NO_CLIENT_AUTH (required to trigger
- // certificate verification, needed by this test), uses a certificate that
- // doesn't match the hostname. For this reason, CtsTestServer cannot be used
- // by this test.
- Instant expirationInstant = instantInFuture(/* secondsIntoFuture */ 100);
- boolean includeSubdomains = true;
- Set<byte[]> pinsSha256 = Set.of(generateSha256());
- mEngine = mEngineBuilder.addPublicKeyPins(
- HOST, pinsSha256, includeSubdomains, expirationInstant).build();
-
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
- mCallback.expectCallback(ResponseStep.ON_FAILED);
- assertNotNull("Expected an error", mCallback.mError);
- }
-
- @Test
- public void testHttpEngine_EnableQuic() throws Exception {
- String url = mTestServer.getSuccessUrl();
- mEngine = mEngineBuilder.setEnableQuic(true).addQuicHint(HOST, 443, 443).build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- }
-
- @Test
- public void testHttpEngine_GetDefaultUserAgent() throws Exception {
- assertThat(mEngineBuilder.getDefaultUserAgent(), containsString("AndroidHttpClient"));
- assertThat(mEngineBuilder.getDefaultUserAgent()).contains(HttpEngine.getVersionString());
- }
-
- @Test
- public void testHttpEngine_requestUsesDefaultUserAgent() throws Exception {
- mEngine = mEngineBuilder.build();
-
- String url = mTestServer.getUserAgentUrl();
- UrlRequest request =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
- request.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- String receivedUserAgent = extractUserAgent(mCallback.mResponseAsString);
-
- assertThat(receivedUserAgent).isEqualTo(mEngineBuilder.getDefaultUserAgent());
- }
-
- @Test
- public void testHttpEngine_requestUsesCustomUserAgent() throws Exception {
- String userAgent = "CtsTests User Agent";
- mEngine =
- new HttpEngine.Builder(ApplicationProvider.getApplicationContext())
- .setUserAgent(userAgent)
- .build();
-
- String url = mTestServer.getUserAgentUrl();
- UrlRequest request =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
- request.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- String receivedUserAgent = extractUserAgent(mCallback.mResponseAsString);
-
- assertThat(receivedUserAgent).isEqualTo(userAgent);
- }
-
- private static String extractUserAgent(String userAgentResponseBody) {
- // If someone wants to be evil and have the title HTML tag a part of the user agent,
- // they'll have to fix this method :)
- return userAgentResponseBody.replaceFirst(".*<title>", "").replaceFirst("</title>.*", "");
- }
-
- @Test
- public void testHttpEngine_bindToNetwork() throws Exception {
- // Create a fake Android.net.Network. Since that network doesn't exist, binding to
- // that should end up in a failed request.
- Network mockNetwork = Mockito.mock(Network.class);
- Mockito.when(mockNetwork.getNetworkHandle()).thenReturn(123L);
- String url = mTestServer.getSuccessUrl();
-
- mEngine = mEngineBuilder.build();
- mEngine.bindToNetwork(mockNetwork);
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- mCallback.expectCallback(ResponseStep.ON_FAILED);
- }
-
- @Test
- public void testHttpEngine_unbindFromNetwork() throws Exception {
- // Create a fake Android.net.Network. Since that network doesn't exist, binding to
- // that should end up in a failed request.
- Network mockNetwork = Mockito.mock(Network.class);
- Mockito.when(mockNetwork.getNetworkHandle()).thenReturn(123L);
- String url = mTestServer.getSuccessUrl();
-
- mEngine = mEngineBuilder.build();
- // Bind to the fake network but then unbind. This should result in a successful
- // request.
- mEngine.bindToNetwork(mockNetwork);
- mEngine.bindToNetwork(null);
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- }
-
- @Test
- public void testHttpEngine_setConnectionMigrationOptions_requestSucceeds() {
- ConnectionMigrationOptions options = new ConnectionMigrationOptions.Builder().build();
- mEngine = mEngineBuilder.setConnectionMigrationOptions(options).build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(
- mTestServer.getSuccessUrl(), mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- }
-
- @Test
- public void testHttpEngine_setDnsOptions_requestSucceeds() {
- DnsOptions options = new DnsOptions.Builder().build();
- mEngine = mEngineBuilder.setDnsOptions(options).build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(
- mTestServer.getSuccessUrl(), mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- }
-
- @Test
- public void getVersionString_notEmpty() {
- assertThat(HttpEngine.getVersionString()).isNotEmpty();
- }
-
- @Test
- public void testHttpEngine_SetQuicOptions_RequestSucceedsWithQuic() throws Exception {
- String url = mTestServer.getSuccessUrl();
- QuicOptions options = new QuicOptions.Builder().build();
- mEngine = mEngineBuilder
- .setEnableQuic(true)
- .addQuicHint(HOST, 443, 443)
- .setQuicOptions(options)
- .build();
- UrlRequest.Builder builder =
- mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- mRequest = builder.build();
- mRequest.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
-
- }
-
- @Test
- public void testHttpEngine_enableBrotli_brotliAdvertised() {
- mEngine = mEngineBuilder.setEnableBrotli(true).build();
- mRequest =
- mEngine.newUrlRequestBuilder(
- mTestServer.getEchoHeadersUrl(), mCallback.getExecutor(), mCallback)
- .build();
- mRequest.start();
-
- mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertThat(info.getHeaders().getAsMap().get("x-request-header-Accept-Encoding").toString())
- .contains("br");
- assertOKStatusCode(info);
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
deleted file mode 100644
index cff54b3..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.net.http.HttpEngine
-import android.net.http.NetworkException
-import android.net.http.cts.util.TestUrlRequestCallback
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertSame
-import kotlin.test.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class NetworkExceptionTest {
-
- @Test
- fun testNetworkException_returnsInputParameters() {
- val message = "failed"
- val cause = Throwable("thrown")
- val networkException =
- object : NetworkException(message, cause) {
- override fun getErrorCode() = 0
- override fun isImmediatelyRetryable() = false
- }
-
- assertEquals(message, networkException.message)
- assertSame(cause, networkException.cause)
- }
-
- @Test
- fun testNetworkException_thrownFromUrlRequest() {
- val httpEngine = HttpEngine.Builder(ApplicationProvider.getApplicationContext()).build()
- val callback = TestUrlRequestCallback()
- val request =
- httpEngine.newUrlRequestBuilder("http://localhost", callback.executor, callback).build()
-
- request.start()
- callback.blockForDone()
-
- assertTrue(request.isDone)
- assertIs<NetworkException>(callback.mError)
- httpEngine.shutdown()
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
deleted file mode 100644
index 2705fc3..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.net.http.QuicException
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class QuicExceptionTest {
-
- @Test
- fun testQuicException_returnsInputParameters() {
- val message = "failed"
- val cause = Throwable("thrown")
- val quicException =
- object : QuicException(message, cause) {
- override fun getErrorCode() = 0
- override fun isImmediatelyRetryable() = false
- }
-
- assertEquals(message, quicException.message)
- assertEquals(cause, quicException.cause)
- }
-
- // TODO: add test for QuicException triggered from HttpEngine
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
deleted file mode 100644
index da0b15c..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.net.http.QuicOptions
-import android.os.Build
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.google.common.truth.Truth.assertThat
-import java.time.Duration
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class QuicOptionsTest {
- @Test
- fun testQuicOptions_defaultValues() {
- val quicOptions = QuicOptions.Builder().build()
- assertThat(quicOptions.allowedQuicHosts).isEmpty()
- assertThat(quicOptions.handshakeUserAgent).isNull()
- assertThat(quicOptions.idleConnectionTimeout).isNull()
- assertFalse(quicOptions.hasInMemoryServerConfigsCacheSize())
- assertFailsWith(IllegalStateException::class) {
- quicOptions.inMemoryServerConfigsCacheSize
- }
- }
-
- @Test
- fun testQuicOptions_quicHostAllowlist_returnsAddedValues() {
- val quicOptions = QuicOptions.Builder()
- .addAllowedQuicHost("foo")
- .addAllowedQuicHost("bar")
- .addAllowedQuicHost("foo")
- .addAllowedQuicHost("baz")
- .build()
- assertThat(quicOptions.allowedQuicHosts)
- .containsExactly("foo", "bar", "baz")
- .inOrder()
- }
-
- @Test
- fun testQuicOptions_idleConnectionTimeout_returnsSetValue() {
- val timeout = Duration.ofMinutes(10)
- val quicOptions = QuicOptions.Builder()
- .setIdleConnectionTimeout(timeout)
- .build()
- assertThat(quicOptions.idleConnectionTimeout)
- .isEqualTo(timeout)
- }
-
- @Test
- fun testQuicOptions_inMemoryServerConfigsCacheSize_returnsSetValue() {
- val quicOptions = QuicOptions.Builder()
- .setInMemoryServerConfigsCacheSize(42)
- .build()
- assertTrue(quicOptions.hasInMemoryServerConfigsCacheSize())
- assertThat(quicOptions.inMemoryServerConfigsCacheSize)
- .isEqualTo(42)
- }
-
- @Test
- fun testQuicOptions_handshakeUserAgent_returnsSetValue() {
- val userAgent = "test"
- val quicOptions = QuicOptions.Builder()
- .setHandshakeUserAgent(userAgent)
- .build()
- assertThat(quicOptions.handshakeUserAgent)
- .isEqualTo(userAgent)
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
deleted file mode 100644
index 3c4d134..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2019 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.http.cts;
-
-import static android.net.http.cts.util.TestUtilsKt.assertOKStatusCode;
-import static android.net.http.cts.util.TestUtilsKt.skipIfNoInternetConnection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.net.http.HeaderBlock;
-import android.net.http.HttpEngine;
-import android.net.http.HttpException;
-import android.net.http.InlineExecutionProhibitedException;
-import android.net.http.UploadDataProvider;
-import android.net.http.UrlRequest;
-import android.net.http.UrlRequest.Status;
-import android.net.http.UrlResponseInfo;
-import android.net.http.cts.util.HttpCtsTestServer;
-import android.net.http.cts.util.TestStatusListener;
-import android.net.http.cts.util.TestUrlRequestCallback;
-import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
-import android.net.http.cts.util.UploadDataProviders;
-import android.os.Build;
-import android.webkit.cts.CtsTestServer;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import com.google.common.base.Strings;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.URLEncoder;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class UrlRequestTest {
- private static final Executor DIRECT_EXECUTOR = Runnable::run;
-
- private TestUrlRequestCallback mCallback;
- private HttpCtsTestServer mTestServer;
- private HttpEngine mHttpEngine;
-
- @Before
- public void setUp() throws Exception {
- Context context = ApplicationProvider.getApplicationContext();
- skipIfNoInternetConnection(context);
- HttpEngine.Builder builder = new HttpEngine.Builder(context);
- mHttpEngine = builder.build();
- mCallback = new TestUrlRequestCallback();
- mTestServer = new HttpCtsTestServer(context);
- }
-
- @After
- public void tearDown() throws Exception {
- if (mHttpEngine != null) {
- mHttpEngine.shutdown();
- }
- if (mTestServer != null) {
- mTestServer.shutdown();
- }
- }
-
- private UrlRequest.Builder createUrlRequestBuilder(String url) {
- return mHttpEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
- }
-
- @Test
- public void testUrlRequestGet_CompletesSuccessfully() throws Exception {
- String url = mTestServer.getSuccessUrl();
- UrlRequest request = createUrlRequestBuilder(url).build();
- request.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- assertThat("Received byte count must be > 0", info.getReceivedByteCount(), greaterThan(0L));
- }
-
- @Test
- public void testUrlRequestStatus_InvalidBeforeRequestStarts() throws Exception {
- UrlRequest request = createUrlRequestBuilder(mTestServer.getSuccessUrl()).build();
- // Calling before request is started should give Status.INVALID,
- // since the native adapter is not created.
- TestStatusListener statusListener = new TestStatusListener();
- request.getStatus(statusListener);
- statusListener.expectStatus(Status.INVALID);
- }
-
- @Test
- public void testUrlRequestCancel_CancelCalled() throws Exception {
- UrlRequest request = createUrlRequestBuilder(mTestServer.getSuccessUrl()).build();
- mCallback.setAutoAdvance(false);
-
- request.start();
- mCallback.waitForNextStep();
- assertSame(mCallback.mResponseStep, ResponseStep.ON_RESPONSE_STARTED);
-
- request.cancel();
- mCallback.expectCallback(ResponseStep.ON_CANCELED);
- }
-
- @Test
- public void testUrlRequestPost_EchoRequestBody() {
- String testData = "test";
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoBodyUrl());
-
- UploadDataProvider dataProvider = UploadDataProviders.create(testData);
- builder.setUploadDataProvider(dataProvider, mCallback.getExecutor());
- builder.addHeader("Content-Type", "text/html");
- builder.build().start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
- assertOKStatusCode(mCallback.mResponseInfo);
- assertEquals(testData, mCallback.mResponseAsString);
- }
-
- @Test
- public void testUrlRequestFail_FailedCalled() {
- createUrlRequestBuilder("http://0.0.0.0:0/").build().start();
- mCallback.expectCallback(ResponseStep.ON_FAILED);
- }
-
- @Test
- public void testUrlRequest_directExecutor_allowed() throws InterruptedException {
- TestUrlRequestCallback callback = new TestUrlRequestCallback();
- callback.setAllowDirectExecutor(true);
- UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
- mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
- UploadDataProvider dataProvider = UploadDataProviders.create("test");
- builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR);
- builder.addHeader("Content-Type", "text/plain;charset=UTF-8");
- builder.setDirectExecutorAllowed(true);
- builder.build().start();
- callback.blockForDone();
-
- if (callback.mOnErrorCalled) {
- throw new AssertionError("Expected no exception", callback.mError);
- }
-
- assertEquals(200, callback.mResponseInfo.getHttpStatusCode());
- assertEquals("test", callback.mResponseAsString);
- }
-
- @Test
- public void testUrlRequest_directExecutor_disallowed_uploadDataProvider() throws Exception {
- TestUrlRequestCallback callback = new TestUrlRequestCallback();
- // This applies just locally to the test callback, not to SUT
- callback.setAllowDirectExecutor(true);
-
- UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
- mTestServer.getEchoBodyUrl(), Executors.newSingleThreadExecutor(), callback);
- UploadDataProvider dataProvider = UploadDataProviders.create("test");
-
- builder.setUploadDataProvider(dataProvider, DIRECT_EXECUTOR)
- .addHeader("Content-Type", "text/plain;charset=UTF-8")
- .build()
- .start();
- callback.blockForDone();
-
- assertTrue(callback.mOnErrorCalled);
- assertTrue(callback.mError.getCause() instanceof InlineExecutionProhibitedException);
- }
-
- @Test
- public void testUrlRequest_directExecutor_disallowed_responseCallback() throws Exception {
- TestUrlRequestCallback callback = new TestUrlRequestCallback();
- // This applies just locally to the test callback, not to SUT
- callback.setAllowDirectExecutor(true);
-
- UrlRequest.Builder builder = mHttpEngine.newUrlRequestBuilder(
- mTestServer.getEchoBodyUrl(), DIRECT_EXECUTOR, callback);
- UploadDataProvider dataProvider = UploadDataProviders.create("test");
-
- builder.setUploadDataProvider(dataProvider, Executors.newSingleThreadExecutor())
- .addHeader("Content-Type", "text/plain;charset=UTF-8")
- .build()
- .start();
- callback.blockForDone();
-
- assertTrue(callback.mOnErrorCalled);
- assertTrue(callback.mError.getCause() instanceof InlineExecutionProhibitedException);
- }
-
- @Test
- public void testUrlRequest_nonDirectByteBuffer() throws Exception {
- BlockingQueue<HttpException> onFailedException = new ArrayBlockingQueue<>(1);
-
- UrlRequest request =
- mHttpEngine
- .newUrlRequestBuilder(
- mTestServer.getSuccessUrl(),
- Executors.newSingleThreadExecutor(),
- new StubUrlRequestCallback() {
- @Override
- public void onResponseStarted(
- UrlRequest request, UrlResponseInfo info) {
- // note: allocate, not allocateDirect
- request.read(ByteBuffer.allocate(1024));
- }
-
- @Override
- public void onFailed(
- UrlRequest request,
- UrlResponseInfo info,
- HttpException error) {
- onFailedException.add(error);
- }
- })
- .build();
- request.start();
-
- HttpException e = onFailedException.poll(5, TimeUnit.SECONDS);
- assertNotNull(e);
- assertTrue(e.getCause() instanceof IllegalArgumentException);
- assertTrue(e.getCause().getMessage().contains("direct"));
- }
-
- @Test
- public void testUrlRequest_fullByteBuffer() throws Exception {
- BlockingQueue<HttpException> onFailedException = new ArrayBlockingQueue<>(1);
-
- UrlRequest request =
- mHttpEngine
- .newUrlRequestBuilder(
- mTestServer.getSuccessUrl(),
- Executors.newSingleThreadExecutor(),
- new StubUrlRequestCallback() {
- @Override
- public void onResponseStarted(
- UrlRequest request, UrlResponseInfo info) {
- ByteBuffer bb = ByteBuffer.allocateDirect(1024);
- bb.position(bb.limit());
- request.read(bb);
- }
-
- @Override
- public void onFailed(
- UrlRequest request,
- UrlResponseInfo info,
- HttpException error) {
- onFailedException.add(error);
- }
- })
- .build();
- request.start();
-
- HttpException e = onFailedException.poll(5, TimeUnit.SECONDS);
- assertNotNull(e);
- assertTrue(e.getCause() instanceof IllegalArgumentException);
- assertTrue(e.getCause().getMessage().contains("full"));
- }
-
- @Test
- public void testUrlRequest_redirects() throws Exception {
- int expectedNumRedirects = 5;
- String url =
- mTestServer.getRedirectingAssetUrl("html/hello_world.html", expectedNumRedirects);
-
- UrlRequest request = createUrlRequestBuilder(url).build();
- request.start();
-
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
- UrlResponseInfo info = mCallback.mResponseInfo;
- assertOKStatusCode(info);
- assertThat(mCallback.mResponseAsString).contains("hello world");
- assertThat(info.getUrlChain()).hasSize(expectedNumRedirects + 1);
- assertThat(info.getUrlChain().get(0)).isEqualTo(url);
- assertThat(info.getUrlChain().get(expectedNumRedirects)).isEqualTo(info.getUrl());
- }
-
- @Test
- public void testUrlRequestPost_withRedirect() throws Exception {
- String body = Strings.repeat(
- "Hello, this is a really interesting body, so write this 100 times.", 100);
-
- String redirectUrlParameter =
- URLEncoder.encode(mTestServer.getEchoBodyUrl(), "UTF-8");
- createUrlRequestBuilder(
- String.format(
- "%s/alt_redirect?dest=%s&statusCode=307",
- mTestServer.getBaseUri(),
- redirectUrlParameter))
- .setHttpMethod("POST")
- .addHeader("Content-Type", "text/plain")
- .setUploadDataProvider(
- UploadDataProviders.create(body.getBytes(StandardCharsets.UTF_8)),
- mCallback.getExecutor())
- .build()
- .start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
- assertOKStatusCode(mCallback.mResponseInfo);
- assertThat(mCallback.mResponseAsString).isEqualTo(body);
- }
-
- @Test
- public void testUrlRequest_customHeaders() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoHeadersUrl());
-
- List<Map.Entry<String, String>> expectedHeaders = Arrays.asList(
- Map.entry("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
- Map.entry("Max-Forwards", "10"),
- Map.entry("X-Client-Data", "random custom header content"));
-
- for (Map.Entry<String, String> header : expectedHeaders) {
- builder.addHeader(header.getKey(), header.getValue());
- }
-
- builder.build().start();
- mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
- assertOKStatusCode(mCallback.mResponseInfo);
-
- List<Map.Entry<String, String>> echoedHeaders =
- extractEchoedHeaders(mCallback.mResponseInfo.getHeaders());
-
- // The implementation might decide to add more headers like accepted encodings it handles
- // internally so the server is likely to see more headers than explicitly set
- // by the developer.
- assertThat(echoedHeaders)
- .containsAtLeastElementsIn(expectedHeaders);
- }
-
- @Test
- public void testUrlRequest_getHttpMethod() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final String method = "POST";
-
- builder.setHttpMethod(method);
- UrlRequest request = builder.build();
- assertThat(request.getHttpMethod()).isEqualTo(method);
- }
-
- @Test
- public void testUrlRequest_getHeaders_asList() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final List<Map.Entry<String, String>> expectedHeaders = Arrays.asList(
- Map.entry("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
- Map.entry("Max-Forwards", "10"),
- Map.entry("X-Client-Data", "random custom header content"));
-
- for (Map.Entry<String, String> header : expectedHeaders) {
- builder.addHeader(header.getKey(), header.getValue());
- }
-
- UrlRequest request = builder.build();
- assertThat(request.getHeaders().getAsList()).containsAtLeastElementsIn(expectedHeaders);
- }
-
- @Test
- public void testUrlRequest_getHeaders_asMap() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final Map<String, List<String>> expectedHeaders = Map.of(
- "Authorization", Arrays.asList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
- "Max-Forwards", Arrays.asList("10"),
- "X-Client-Data", Arrays.asList("random custom header content"));
-
- for (Map.Entry<String, List<String>> header : expectedHeaders.entrySet()) {
- builder.addHeader(header.getKey(), header.getValue().get(0));
- }
-
- UrlRequest request = builder.build();
- assertThat(request.getHeaders().getAsMap()).containsAtLeastEntriesIn(expectedHeaders);
- }
-
- @Test
- public void testUrlRequest_isCacheDisabled() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final boolean isCacheDisabled = true;
-
- builder.setCacheDisabled(isCacheDisabled);
- UrlRequest request = builder.build();
- assertThat(request.isCacheDisabled()).isEqualTo(isCacheDisabled);
- }
-
- @Test
- public void testUrlRequest_isDirectExecutorAllowed() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final boolean isDirectExecutorAllowed = true;
-
- builder.setDirectExecutorAllowed(isDirectExecutorAllowed);
- UrlRequest request = builder.build();
- assertThat(request.isDirectExecutorAllowed()).isEqualTo(isDirectExecutorAllowed);
- }
-
- @Test
- public void testUrlRequest_getPriority() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final int priority = UrlRequest.REQUEST_PRIORITY_LOW;
-
- builder.setPriority(priority);
- UrlRequest request = builder.build();
- assertThat(request.getPriority()).isEqualTo(priority);
- }
-
- @Test
- public void testUrlRequest_hasTrafficStatsTag() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-
- builder.setTrafficStatsTag(10);
- UrlRequest request = builder.build();
- assertThat(request.hasTrafficStatsTag()).isEqualTo(true);
- }
-
- @Test
- public void testUrlRequest_getTrafficStatsTag() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final int trafficStatsTag = 10;
-
- builder.setTrafficStatsTag(trafficStatsTag);
- UrlRequest request = builder.build();
- assertThat(request.getTrafficStatsTag()).isEqualTo(trafficStatsTag);
- }
-
- @Test
- public void testUrlRequest_hasTrafficStatsUid() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
-
- builder.setTrafficStatsUid(10);
- UrlRequest request = builder.build();
- assertThat(request.hasTrafficStatsUid()).isEqualTo(true);
- }
-
- @Test
- public void testUrlRequest_getTrafficStatsUid() throws Exception {
- UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl());
- final int trafficStatsUid = 10;
-
- builder.setTrafficStatsUid(trafficStatsUid);
- UrlRequest request = builder.build();
- assertThat(request.getTrafficStatsUid()).isEqualTo(trafficStatsUid);
- }
-
- private static List<Map.Entry<String, String>> extractEchoedHeaders(HeaderBlock headers) {
- return headers.getAsList()
- .stream()
- .flatMap(input -> {
- if (input.getKey().startsWith(CtsTestServer.ECHOED_RESPONSE_HEADER_PREFIX)) {
- String strippedKey =
- input.getKey().substring(
- CtsTestServer.ECHOED_RESPONSE_HEADER_PREFIX.length());
- return Stream.of(Map.entry(strippedKey, input.getValue()));
- } else {
- return Stream.empty();
- }
- })
- .collect(Collectors.toList());
- }
-
- private static class StubUrlRequestCallback implements UrlRequest.Callback {
-
- @Override
- public void onRedirectReceived(
- UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void onReadCompleted(
- UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
- throw new UnsupportedOperationException(error);
- }
-
- @Override
- public void onCanceled(@NonNull UrlRequest request, @Nullable UrlResponseInfo info) {
- throw new UnsupportedOperationException();
- }
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
deleted file mode 100644
index f1b57c6..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts
-
-import android.content.Context
-import android.net.http.HttpEngine
-import android.net.http.cts.util.HttpCtsTestServer
-import android.net.http.cts.util.TestUrlRequestCallback
-import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
-import android.os.Build
-import androidx.test.core.app.ApplicationProvider
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-import org.junit.runner.RunWith
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class UrlResponseInfoTest {
-
- @Test
- fun testUrlResponseInfo_apisReturnCorrectInfo() {
- // start the engine and send a request
- val context: Context = ApplicationProvider.getApplicationContext()
- val server = HttpCtsTestServer(context)
- val httpEngine = HttpEngine.Builder(context).build()
- val callback = TestUrlRequestCallback()
- val url = server.successUrl
- val request = httpEngine.newUrlRequestBuilder(url, callback.executor, callback).build()
-
- request.start()
- callback.expectCallback(ResponseStep.ON_SUCCEEDED)
-
- val info = callback.mResponseInfo
- assertFalse(info.headers.asList.isEmpty())
- assertEquals(200, info.httpStatusCode)
- assertTrue(info.receivedByteCount > 0)
- assertEquals(url, info.url)
- assertEquals(listOf(url), info.urlChain)
- assertFalse(info.wasCached())
-
- // TODO Current test server does not set these values. Uncomment when we use one that does.
- // assertEquals("OK", info.httpStatusText)
- // assertEquals("http/1.1", info.negotiatedProtocol)
-
- // cronet defaults to port 0 when no proxy is specified.
- // This is not a behaviour we want to enforce since null is reasonable too.
- // assertEquals(":0", info.proxyServer)
-
- server.shutdown()
- httpEngine.shutdown()
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt b/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
deleted file mode 100644
index 5196544..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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 android.net.http.cts.util
-
-import android.content.Context
-import android.webkit.cts.CtsTestServer
-import java.net.URI
-import org.apache.http.HttpEntityEnclosingRequest
-import org.apache.http.HttpRequest
-import org.apache.http.HttpResponse
-import org.apache.http.HttpStatus
-import org.apache.http.HttpVersion
-import org.apache.http.message.BasicHttpResponse
-
-private const val ECHO_BODY_PATH = "/echo_body"
-
-/** Extends CtsTestServer to handle POST requests and other test specific requests */
-class HttpCtsTestServer(context: Context) : CtsTestServer(context) {
-
- val echoBodyUrl: String = baseUri + ECHO_BODY_PATH
- val successUrl: String = getAssetUrl("html/hello_world.html")
-
- override fun onPost(req: HttpRequest): HttpResponse? {
- val path = URI.create(req.requestLine.uri).path
- var response: HttpResponse? = null
-
- if (path.startsWith(ECHO_BODY_PATH)) {
- if (req !is HttpEntityEnclosingRequest) {
- return BasicHttpResponse(
- HttpVersion.HTTP_1_0,
- HttpStatus.SC_INTERNAL_SERVER_ERROR,
- "Expected req to be of type HttpEntityEnclosingRequest but got ${req.javaClass}"
- )
- }
-
- response = BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, null)
- response.entity = req.entity
- response.addHeader("Content-Length", req.entity.contentLength.toString())
- }
-
- return response
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
deleted file mode 100644
index 1e7333c..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts.util;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeThat;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.http.BidirectionalStream;
-import android.net.http.HeaderBlock;
-import android.net.http.HttpException;
-import android.net.http.UrlResponseInfo;
-import android.os.ConditionVariable;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-/**
- * Callback that tracks information from different callbacks and has a method to block thread until
- * the stream completes on another thread. Allows to cancel, block stream or throw an exception from
- * an arbitrary step.
- */
-public class TestBidirectionalStreamCallback implements BidirectionalStream.Callback {
- private static final int TIMEOUT_MS = 12_000;
- public UrlResponseInfo mResponseInfo;
- public HttpException mError;
-
- public ResponseStep mResponseStep = ResponseStep.NOTHING;
-
- public boolean mOnErrorCalled;
- public boolean mOnCanceledCalled;
-
- public int mHttpResponseDataLength;
- public String mResponseAsString = "";
-
- public HeaderBlock mTrailers;
-
- private static final int READ_BUFFER_SIZE = 32 * 1024;
-
- // When false, the consumer is responsible for all calls into the stream
- // that advance it.
- private boolean mAutoAdvance = true;
-
- // Conditionally fail on certain steps.
- private FailureType mFailureType = FailureType.NONE;
- private ResponseStep mFailureStep = ResponseStep.NOTHING;
-
- // Signals when the stream is done either successfully or not.
- private final ConditionVariable mDone = new ConditionVariable();
-
- // Signaled on each step when mAutoAdvance is false.
- private final ConditionVariable mReadStepBlock = new ConditionVariable();
- private final ConditionVariable mWriteStepBlock = new ConditionVariable();
-
- // Executor Service for Cronet callbacks.
- private final ExecutorService mExecutorService =
- Executors.newSingleThreadExecutor(new ExecutorThreadFactory());
- private Thread mExecutorThread;
-
- // position() of ByteBuffer prior to read() call.
- private int mBufferPositionBeforeRead;
-
- // Data to write.
- private final ArrayList<WriteBuffer> mWriteBuffers = new ArrayList<WriteBuffer>();
-
- // Buffers that we yet to receive the corresponding onWriteCompleted callback.
- private final ArrayList<WriteBuffer> mWriteBuffersToBeAcked = new ArrayList<WriteBuffer>();
-
- // Whether to use a direct executor.
- private final boolean mUseDirectExecutor;
- private final DirectExecutor mDirectExecutor;
-
- private class ExecutorThreadFactory implements ThreadFactory {
- @Override
- public Thread newThread(Runnable r) {
- mExecutorThread = new Thread(r);
- return mExecutorThread;
- }
- }
-
- private static class WriteBuffer {
- final ByteBuffer mBuffer;
- final boolean mFlush;
-
- WriteBuffer(ByteBuffer buffer, boolean flush) {
- mBuffer = buffer;
- mFlush = flush;
- }
- }
-
- private static class DirectExecutor implements Executor {
- @Override
- public void execute(Runnable task) {
- task.run();
- }
- }
-
- public enum ResponseStep {
- NOTHING,
- ON_STREAM_READY,
- ON_RESPONSE_STARTED,
- ON_READ_COMPLETED,
- ON_WRITE_COMPLETED,
- ON_TRAILERS,
- ON_CANCELED,
- ON_FAILED,
- ON_SUCCEEDED,
- }
-
- public enum FailureType {
- NONE,
- CANCEL_SYNC,
- CANCEL_ASYNC,
- // Same as above, but continues to advance the stream after posting
- // the cancellation task.
- CANCEL_ASYNC_WITHOUT_PAUSE,
- THROW_SYNC
- }
-
- private boolean isTerminalCallback(ResponseStep step) {
- switch (step) {
- case ON_SUCCEEDED:
- case ON_CANCELED:
- case ON_FAILED:
- return true;
- default:
- return false;
- }
- }
-
- public TestBidirectionalStreamCallback() {
- mUseDirectExecutor = false;
- mDirectExecutor = null;
- }
-
- public TestBidirectionalStreamCallback(boolean useDirectExecutor) {
- mUseDirectExecutor = useDirectExecutor;
- mDirectExecutor = new DirectExecutor();
- }
-
- public void setAutoAdvance(boolean autoAdvance) {
- mAutoAdvance = autoAdvance;
- }
-
- public void setFailure(FailureType failureType, ResponseStep failureStep) {
- mFailureStep = failureStep;
- mFailureType = failureType;
- }
-
- public boolean blockForDone() {
- return mDone.block(TIMEOUT_MS);
- }
-
- /**
- * Waits for a terminal callback to complete execution before failing if the callback is not the
- * expected one
- *
- * @param expectedStep the expected callback step
- */
- public void expectCallback(ResponseStep expectedStep) {
- if (isTerminalCallback(expectedStep)) {
- assertTrue(String.format(
- "Request timed out. Expected %s callback. Current callback is %s",
- expectedStep, mResponseStep),
- blockForDone());
- }
- assertSame(expectedStep, mResponseStep);
- }
-
- /**
- * Waits for a terminal callback to complete execution before skipping the test if the callback
- * is not the expected one
- *
- * @param expectedStep the expected callback step
- */
- public void assumeCallback(ResponseStep expectedStep) {
- if (isTerminalCallback(expectedStep)) {
- assumeTrue(
- String.format(
- "Request timed out. Expected %s callback. Current callback is %s",
- expectedStep, mResponseStep),
- blockForDone());
- }
- assumeThat(expectedStep, equalTo(mResponseStep));
- }
-
- public void waitForNextReadStep() {
- mReadStepBlock.block();
- mReadStepBlock.close();
- }
-
- public void waitForNextWriteStep() {
- mWriteStepBlock.block();
- mWriteStepBlock.close();
- }
-
- public Executor getExecutor() {
- if (mUseDirectExecutor) {
- return mDirectExecutor;
- }
- return mExecutorService;
- }
-
- public void shutdownExecutor() {
- if (mUseDirectExecutor) {
- throw new UnsupportedOperationException("DirectExecutor doesn't support shutdown");
- }
- mExecutorService.shutdown();
- }
-
- public void addWriteData(byte[] data) {
- addWriteData(data, true);
- }
-
- public void addWriteData(byte[] data, boolean flush) {
- ByteBuffer writeBuffer = ByteBuffer.allocateDirect(data.length);
- writeBuffer.put(data);
- writeBuffer.flip();
- mWriteBuffers.add(new WriteBuffer(writeBuffer, flush));
- mWriteBuffersToBeAcked.add(new WriteBuffer(writeBuffer, flush));
- }
-
- @Override
- public void onStreamReady(BidirectionalStream stream) {
- checkOnValidThread();
- assertFalse(stream.isDone());
- assertEquals(ResponseStep.NOTHING, mResponseStep);
- assertNull(mError);
- mResponseStep = ResponseStep.ON_STREAM_READY;
- if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
- return;
- }
- startNextWrite(stream);
- }
-
- @Override
- public void onResponseHeadersReceived(BidirectionalStream stream, UrlResponseInfo info) {
- checkOnValidThread();
- assertFalse(stream.isDone());
- assertTrue(
- mResponseStep == ResponseStep.NOTHING
- || mResponseStep == ResponseStep.ON_STREAM_READY
- || mResponseStep == ResponseStep.ON_WRITE_COMPLETED);
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
- mResponseInfo = info;
- if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
- return;
- }
- startNextRead(stream);
- }
-
- @Override
- public void onReadCompleted(
- BidirectionalStream stream,
- UrlResponseInfo info,
- ByteBuffer byteBuffer,
- boolean endOfStream) {
- checkOnValidThread();
- assertFalse(stream.isDone());
- assertTrue(
- mResponseStep == ResponseStep.ON_RESPONSE_STARTED
- || mResponseStep == ResponseStep.ON_READ_COMPLETED
- || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
- || mResponseStep == ResponseStep.ON_TRAILERS);
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_READ_COMPLETED;
- mResponseInfo = info;
-
- final int bytesRead = byteBuffer.position() - mBufferPositionBeforeRead;
- mHttpResponseDataLength += bytesRead;
- final byte[] lastDataReceivedAsBytes = new byte[bytesRead];
- // Rewind byteBuffer.position() to pre-read() position.
- byteBuffer.position(mBufferPositionBeforeRead);
- // This restores byteBuffer.position() to its value on entrance to
- // this function.
- byteBuffer.get(lastDataReceivedAsBytes);
-
- mResponseAsString += new String(lastDataReceivedAsBytes);
-
- if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
- return;
- }
- // Do not read if EOF has been reached.
- if (!endOfStream) {
- startNextRead(stream);
- }
- }
-
- @Override
- public void onWriteCompleted(
- BidirectionalStream stream,
- UrlResponseInfo info,
- ByteBuffer buffer,
- boolean endOfStream) {
- checkOnValidThread();
- assertFalse(stream.isDone());
- assertNull(mError);
- mResponseStep = ResponseStep.ON_WRITE_COMPLETED;
- mResponseInfo = info;
- if (!mWriteBuffersToBeAcked.isEmpty()) {
- assertEquals(buffer, mWriteBuffersToBeAcked.get(0).mBuffer);
- mWriteBuffersToBeAcked.remove(0);
- }
- if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
- return;
- }
- startNextWrite(stream);
- }
-
- @Override
- public void onResponseTrailersReceived(
- BidirectionalStream stream,
- UrlResponseInfo info,
- HeaderBlock trailers) {
- checkOnValidThread();
- assertFalse(stream.isDone());
- assertNull(mError);
- mResponseStep = ResponseStep.ON_TRAILERS;
- mResponseInfo = info;
- mTrailers = trailers;
- if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
- return;
- }
- }
-
- @Override
- public void onSucceeded(BidirectionalStream stream, UrlResponseInfo info) {
- checkOnValidThread();
- assertTrue(stream.isDone());
- assertTrue(
- mResponseStep == ResponseStep.ON_RESPONSE_STARTED
- || mResponseStep == ResponseStep.ON_READ_COMPLETED
- || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
- || mResponseStep == ResponseStep.ON_TRAILERS);
- assertFalse(mOnErrorCalled);
- assertFalse(mOnCanceledCalled);
- assertNull(mError);
- assertEquals(0, mWriteBuffers.size());
- assertEquals(0, mWriteBuffersToBeAcked.size());
-
- mResponseStep = ResponseStep.ON_SUCCEEDED;
- mResponseInfo = info;
- openDone();
- maybeThrowCancelOrPause(stream, mReadStepBlock);
- }
-
- @Override
- public void onFailed(BidirectionalStream stream, UrlResponseInfo info, HttpException error) {
- checkOnValidThread();
- assertTrue(stream.isDone());
- // Shouldn't happen after success.
- assertTrue(mResponseStep != ResponseStep.ON_SUCCEEDED);
- // Should happen at most once for a single stream.
- assertFalse(mOnErrorCalled);
- assertFalse(mOnCanceledCalled);
- assertNull(mError);
- mResponseStep = ResponseStep.ON_FAILED;
- mResponseInfo = info;
-
- mOnErrorCalled = true;
- mError = error;
- openDone();
- maybeThrowCancelOrPause(stream, mReadStepBlock);
- }
-
- @Override
- public void onCanceled(BidirectionalStream stream, UrlResponseInfo info) {
- checkOnValidThread();
- assertTrue(stream.isDone());
- // Should happen at most once for a single stream.
- assertFalse(mOnCanceledCalled);
- assertFalse(mOnErrorCalled);
- assertNull(mError);
- mResponseStep = ResponseStep.ON_CANCELED;
- mResponseInfo = info;
-
- mOnCanceledCalled = true;
- openDone();
- maybeThrowCancelOrPause(stream, mReadStepBlock);
- }
-
- public void startNextRead(BidirectionalStream stream) {
- startNextRead(stream, ByteBuffer.allocateDirect(READ_BUFFER_SIZE));
- }
-
- public void startNextRead(BidirectionalStream stream, ByteBuffer buffer) {
- mBufferPositionBeforeRead = buffer.position();
- stream.read(buffer);
- }
-
- public void startNextWrite(BidirectionalStream stream) {
- if (!mWriteBuffers.isEmpty()) {
- Iterator<WriteBuffer> iterator = mWriteBuffers.iterator();
- while (iterator.hasNext()) {
- WriteBuffer b = iterator.next();
- stream.write(b.mBuffer, !iterator.hasNext());
- iterator.remove();
- if (b.mFlush) {
- stream.flush();
- break;
- }
- }
- }
- }
-
- public boolean isDone() {
- // It's not mentioned by the Android docs, but block(0) seems to block
- // indefinitely, so have to block for one millisecond to get state
- // without blocking.
- return mDone.block(1);
- }
-
- /** Returns the number of pending Writes. */
- public int numPendingWrites() {
- return mWriteBuffers.size();
- }
-
- protected void openDone() {
- mDone.open();
- }
-
- /** Returns {@code false} if the callback should continue to advance the stream. */
- private boolean maybeThrowCancelOrPause(
- final BidirectionalStream stream, ConditionVariable stepBlock) {
- if (mResponseStep != mFailureStep || mFailureType == FailureType.NONE) {
- if (!mAutoAdvance) {
- stepBlock.open();
- return true;
- }
- return false;
- }
-
- if (mFailureType == FailureType.THROW_SYNC) {
- throw new IllegalStateException("Callback Exception.");
- }
- Runnable task =
- new Runnable() {
- @Override
- public void run() {
- stream.cancel();
- }
- };
- if (mFailureType == FailureType.CANCEL_ASYNC
- || mFailureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE) {
- getExecutor().execute(task);
- } else {
- task.run();
- }
- return mFailureType != FailureType.CANCEL_ASYNC_WITHOUT_PAUSE;
- }
-
- /** Checks whether callback methods are invoked on the correct thread. */
- private void checkOnValidThread() {
- if (!mUseDirectExecutor) {
- assertEquals(mExecutorThread, Thread.currentThread());
- }
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
deleted file mode 100644
index 3a4486f..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 android.net.http.cts.util
-
-import android.net.http.UrlRequest.StatusListener
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.TimeUnit
-import org.junit.Assert.assertSame
-
-private const val TIMEOUT_MS = 12000L
-
-/** Test status listener for requests */
-class TestStatusListener : StatusListener {
- private val statusFuture = CompletableFuture<Int>()
-
- override fun onStatus(status: Int) {
- statusFuture.complete(status)
- }
-
- /** Fails if the expected status is not the returned status */
- fun expectStatus(expected: Int) {
- assertSame(expected, statusFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS))
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
deleted file mode 100644
index 28443b7..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * 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 android.net.http.cts.util;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.core.AnyOf.anyOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeThat;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.http.CallbackException;
-import android.net.http.HttpException;
-import android.net.http.InlineExecutionProhibitedException;
-import android.net.http.UrlRequest;
-import android.net.http.UrlResponseInfo;
-import android.os.ConditionVariable;
-import android.os.StrictMode;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Callback that tracks information from different callbacks and has a
- * method to block thread until the request completes on another thread.
- * Allows us to cancel, block request or throw an exception from an arbitrary step.
- */
-public class TestUrlRequestCallback implements UrlRequest.Callback {
- private static final int TIMEOUT_MS = 12_000;
- public ArrayList<UrlResponseInfo> mRedirectResponseInfoList = new ArrayList<>();
- public ArrayList<String> mRedirectUrlList = new ArrayList<>();
- public UrlResponseInfo mResponseInfo;
- public HttpException mError;
-
- public ResponseStep mResponseStep = ResponseStep.NOTHING;
-
- public int mRedirectCount;
- public boolean mOnErrorCalled;
- public boolean mOnCanceledCalled;
-
- public int mHttpResponseDataLength;
- public String mResponseAsString = "";
-
- public int mReadBufferSize = 32 * 1024;
-
- // When false, the consumer is responsible for all calls into the request
- // that advance it.
- private boolean mAutoAdvance = true;
- // Whether an exception is thrown by maybeThrowCancelOrPause().
- private boolean mCallbackExceptionThrown;
-
- // Whether to permit calls on the network thread.
- private boolean mAllowDirectExecutor;
-
- // Whether to stop the executor thread after reaching a terminal method.
- // Terminal methods are (onSucceeded, onFailed or onCancelled)
- private boolean mBlockOnTerminalState;
-
- // Conditionally fail on certain steps.
- private FailureType mFailureType = FailureType.NONE;
- private ResponseStep mFailureStep = ResponseStep.NOTHING;
-
- // Signals when request is done either successfully or not.
- private final ConditionVariable mDone = new ConditionVariable();
-
- // Hangs the calling thread until a terminal method has started executing.
- private final ConditionVariable mWaitForTerminalToStart = new ConditionVariable();
-
- // Signaled on each step when mAutoAdvance is false.
- private final ConditionVariable mStepBlock = new ConditionVariable();
-
- // Executor Service for Http callbacks.
- private final ExecutorService mExecutorService;
- private Thread mExecutorThread;
-
- // position() of ByteBuffer prior to read() call.
- private int mBufferPositionBeforeRead;
-
- private static class ExecutorThreadFactory implements ThreadFactory {
- @Override
- public Thread newThread(final Runnable r) {
- return new Thread(new Runnable() {
- @Override
- public void run() {
- StrictMode.ThreadPolicy threadPolicy = StrictMode.getThreadPolicy();
- try {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectNetwork()
- .penaltyLog()
- .penaltyDeath()
- .build());
- r.run();
- } finally {
- StrictMode.setThreadPolicy(threadPolicy);
- }
- }
- });
- }
- }
-
- public enum ResponseStep {
- NOTHING,
- ON_RECEIVED_REDIRECT,
- ON_RESPONSE_STARTED,
- ON_READ_COMPLETED,
- ON_SUCCEEDED,
- ON_FAILED,
- ON_CANCELED,
- }
-
- public enum FailureType {
- NONE,
- CANCEL_SYNC,
- CANCEL_ASYNC,
- // Same as above, but continues to advance the request after posting
- // the cancellation task.
- CANCEL_ASYNC_WITHOUT_PAUSE,
- THROW_SYNC
- }
-
- private static void assertContains(String expectedSubstring, String actualString) {
- assertNotNull(actualString);
- assertTrue("String [" + actualString + "] doesn't contain substring [" + expectedSubstring
- + "]", actualString.contains(expectedSubstring));
-
- }
-
- /**
- * Set {@code mExecutorThread}.
- */
- private void fillInExecutorThread() {
- mExecutorService.execute(new Runnable() {
- @Override
- public void run() {
- mExecutorThread = Thread.currentThread();
- }
- });
- }
-
- private boolean isTerminalCallback(ResponseStep step) {
- switch (step) {
- case ON_SUCCEEDED:
- case ON_CANCELED:
- case ON_FAILED:
- return true;
- default:
- return false;
- }
- }
-
- /**
- * Create a {@link TestUrlRequestCallback} with a new single-threaded executor.
- */
- public TestUrlRequestCallback() {
- this(Executors.newSingleThreadExecutor(new ExecutorThreadFactory()));
- }
-
- /**
- * Create a {@link TestUrlRequestCallback} using a custom single-threaded executor.
- */
- public TestUrlRequestCallback(ExecutorService executorService) {
- mExecutorService = executorService;
- fillInExecutorThread();
- }
-
- /**
- * This blocks the callback executor thread once it has reached a final state callback.
- * In order to continue execution, this method must be called again and providing {@code false}
- * to continue execution.
- *
- * @param blockOnTerminalState the state to set for the executor thread
- */
- public void setBlockOnTerminalState(boolean blockOnTerminalState) {
- mBlockOnTerminalState = blockOnTerminalState;
- if (!blockOnTerminalState) {
- mDone.open();
- }
- }
-
- public void setAutoAdvance(boolean autoAdvance) {
- mAutoAdvance = autoAdvance;
- }
-
- public void setAllowDirectExecutor(boolean allowed) {
- mAllowDirectExecutor = allowed;
- }
-
- public void setFailure(FailureType failureType, ResponseStep failureStep) {
- mFailureStep = failureStep;
- mFailureType = failureType;
- }
-
- /**
- * Blocks the calling thread till callback execution is done
- *
- * @return true if the condition was opened, false if the call returns because of the timeout.
- */
- public boolean blockForDone() {
- return mDone.block(TIMEOUT_MS);
- }
-
- /**
- * Waits for a terminal callback to complete execution before failing if the callback
- * is not the expected one
- *
- * @param expectedStep the expected callback step
- */
- public void expectCallback(ResponseStep expectedStep) {
- if (isTerminalCallback(expectedStep)) {
- assertTrue("Did not receive terminal callback before timeout", blockForDone());
- }
- assertSame(expectedStep, mResponseStep);
- }
-
- /**
- * Waits for a terminal callback to complete execution before skipping the test if the
- * callback is not the expected one
- *
- * @param expectedStep the expected callback step
- */
- public void assumeCallback(ResponseStep expectedStep) {
- if (isTerminalCallback(expectedStep)) {
- assumeTrue("Did not receive terminal callback before timeout", blockForDone());
- }
- assumeThat(expectedStep, equalTo(mResponseStep));
- }
-
- /**
- * Blocks the calling thread until one of the final states has been called.
- * This is called before the callback has finished executed.
- */
- public void waitForTerminalToStart() {
- mWaitForTerminalToStart.block();
- }
-
- public void waitForNextStep() {
- mStepBlock.block();
- mStepBlock.close();
- }
-
- public ExecutorService getExecutor() {
- return mExecutorService;
- }
-
- public void shutdownExecutor() {
- mExecutorService.shutdown();
- }
-
- /**
- * Shuts down the ExecutorService and waits until it executes all posted
- * tasks.
- */
- public void shutdownExecutorAndWait() {
- mExecutorService.shutdown();
- try {
- // Termination shouldn't take long. Use 1 min which should be more than enough.
- mExecutorService.awaitTermination(1, TimeUnit.MINUTES);
- } catch (InterruptedException e) {
- fail("ExecutorService is interrupted while waiting for termination");
- }
- assertTrue(mExecutorService.isTerminated());
- }
-
- @Override
- public void onRedirectReceived(
- UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
- checkExecutorThread();
- assertFalse(request.isDone());
- assertThat(mResponseStep, anyOf(
- equalTo(ResponseStep.NOTHING),
- equalTo(ResponseStep.ON_RECEIVED_REDIRECT)));
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_RECEIVED_REDIRECT;
- mRedirectUrlList.add(newLocationUrl);
- mRedirectResponseInfoList.add(info);
- ++mRedirectCount;
- if (maybeThrowCancelOrPause(request)) {
- return;
- }
- request.followRedirect();
- }
-
- @Override
- public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
- checkExecutorThread();
- assertFalse(request.isDone());
- assertThat(mResponseStep, anyOf(
- equalTo(ResponseStep.NOTHING),
- equalTo(ResponseStep.ON_RECEIVED_REDIRECT)));
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
- mResponseInfo = info;
- if (maybeThrowCancelOrPause(request)) {
- return;
- }
- startNextRead(request);
- }
-
- @Override
- public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
- checkExecutorThread();
- assertFalse(request.isDone());
- assertThat(mResponseStep, anyOf(
- equalTo(ResponseStep.ON_RESPONSE_STARTED),
- equalTo(ResponseStep.ON_READ_COMPLETED)));
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_READ_COMPLETED;
-
- final byte[] lastDataReceivedAsBytes;
- final int bytesRead = byteBuffer.position() - mBufferPositionBeforeRead;
- mHttpResponseDataLength += bytesRead;
- lastDataReceivedAsBytes = new byte[bytesRead];
- // Rewind |byteBuffer.position()| to pre-read() position.
- byteBuffer.position(mBufferPositionBeforeRead);
- // This restores |byteBuffer.position()| to its value on entrance to
- // this function.
- byteBuffer.get(lastDataReceivedAsBytes);
- mResponseAsString += new String(lastDataReceivedAsBytes);
-
- if (maybeThrowCancelOrPause(request)) {
- return;
- }
- startNextRead(request);
- }
-
- @Override
- public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
- checkExecutorThread();
- assertTrue(request.isDone());
- assertThat(mResponseStep, anyOf(
- equalTo(ResponseStep.ON_RESPONSE_STARTED),
- equalTo(ResponseStep.ON_READ_COMPLETED)));
- assertFalse(mOnErrorCalled);
- assertFalse(mOnCanceledCalled);
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_SUCCEEDED;
- mResponseInfo = info;
- mWaitForTerminalToStart.open();
- if (mBlockOnTerminalState) mDone.block();
- openDone();
- maybeThrowCancelOrPause(request);
- }
-
- @Override
- public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
- // If the failure is because of prohibited direct execution, the test shouldn't fail
- // since the request already did.
- if (error.getCause() instanceof InlineExecutionProhibitedException) {
- mAllowDirectExecutor = true;
- }
- checkExecutorThread();
- assertTrue(request.isDone());
- // Shouldn't happen after success.
- assertNotEquals(ResponseStep.ON_SUCCEEDED, mResponseStep);
- // Should happen at most once for a single request.
- assertFalse(mOnErrorCalled);
- assertFalse(mOnCanceledCalled);
- assertNull(mError);
- if (mCallbackExceptionThrown) {
- assertTrue(error instanceof CallbackException);
- assertContains("Exception received from UrlRequest.Callback", error.getMessage());
- assertNotNull(error.getCause());
- assertTrue(error.getCause() instanceof IllegalStateException);
- assertContains("Listener Exception.", error.getCause().getMessage());
- }
-
- mResponseStep = ResponseStep.ON_FAILED;
- mOnErrorCalled = true;
- mError = error;
- mWaitForTerminalToStart.open();
- if (mBlockOnTerminalState) mDone.block();
- openDone();
- maybeThrowCancelOrPause(request);
- }
-
- @Override
- public void onCanceled(UrlRequest request, UrlResponseInfo info) {
- checkExecutorThread();
- assertTrue(request.isDone());
- // Should happen at most once for a single request.
- assertFalse(mOnCanceledCalled);
- assertFalse(mOnErrorCalled);
- assertNull(mError);
-
- mResponseStep = ResponseStep.ON_CANCELED;
- mOnCanceledCalled = true;
- mWaitForTerminalToStart.open();
- if (mBlockOnTerminalState) mDone.block();
- openDone();
- maybeThrowCancelOrPause(request);
- }
-
- public void startNextRead(UrlRequest request) {
- startNextRead(request, ByteBuffer.allocateDirect(mReadBufferSize));
- }
-
- public void startNextRead(UrlRequest request, ByteBuffer buffer) {
- mBufferPositionBeforeRead = buffer.position();
- request.read(buffer);
- }
-
- public boolean isDone() {
- // It's not mentioned by the Android docs, but block(0) seems to block
- // indefinitely, so have to block for one millisecond to get state
- // without blocking.
- return mDone.block(1);
- }
-
- protected void openDone() {
- mDone.open();
- }
-
- private void checkExecutorThread() {
- if (!mAllowDirectExecutor) {
- assertEquals(mExecutorThread, Thread.currentThread());
- }
- }
-
- /**
- * Returns {@code false} if the listener should continue to advance the
- * request.
- */
- private boolean maybeThrowCancelOrPause(final UrlRequest request) {
- checkExecutorThread();
- if (mResponseStep != mFailureStep || mFailureType == FailureType.NONE) {
- if (!mAutoAdvance) {
- mStepBlock.open();
- return true;
- }
- return false;
- }
-
- if (mFailureType == FailureType.THROW_SYNC) {
- assertFalse(mCallbackExceptionThrown);
- mCallbackExceptionThrown = true;
- throw new IllegalStateException("Listener Exception.");
- }
- Runnable task = new Runnable() {
- @Override
- public void run() {
- request.cancel();
- }
- };
- if (mFailureType == FailureType.CANCEL_ASYNC
- || mFailureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE) {
- getExecutor().execute(task);
- } else {
- task.run();
- }
- return mFailureType != FailureType.CANCEL_ASYNC_WITHOUT_PAUSE;
- }
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
deleted file mode 100644
index 7fc005a..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts.util
-
-import android.content.Context
-import android.net.ConnectivityManager
-import android.net.http.UrlResponseInfo
-import org.hamcrest.Matchers.equalTo
-import org.junit.Assert.assertEquals
-import org.junit.Assume.assumeNotNull
-import org.junit.Assume.assumeThat
-
-fun skipIfNoInternetConnection(context: Context) {
- val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
- assumeNotNull(
- "This test requires a working Internet connection", connectivityManager!!.activeNetwork
- )
-}
-
-fun assertOKStatusCode(info: UrlResponseInfo) {
- assertEquals("Status code must be 200 OK", 200, info.httpStatusCode)
-}
-
-fun assumeOKStatusCode(info: UrlResponseInfo) {
- assumeThat("Status code must be 200 OK", info.httpStatusCode, equalTo(200))
-}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java b/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
deleted file mode 100644
index 3b90fa0..0000000
--- a/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2023 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.http.cts.util;
-
-import android.net.http.UploadDataProvider;
-import android.net.http.UploadDataSink;
-import android.os.ParcelFileDescriptor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.StandardCharsets;
-
-/**
- * Provides implementations of {@link UploadDataProvider} for common use cases. Corresponds to
- * {@code android.net.http.apihelpers.UploadDataProviders} which is not an exposed API.
- */
-public final class UploadDataProviders {
- /**
- * Uploads an entire file.
- *
- * @param file The file to upload
- * @return A new UploadDataProvider for the given file
- */
- public static UploadDataProvider create(final File file) {
- return new FileUploadProvider(() -> new FileInputStream(file).getChannel());
- }
-
- /**
- * Uploads an entire file, closing the descriptor when it is no longer needed.
- *
- * @param fd The file descriptor to upload
- * @throws IllegalArgumentException if {@code fd} is not a file.
- * @return A new UploadDataProvider for the given file descriptor
- */
- public static UploadDataProvider create(final ParcelFileDescriptor fd) {
- return new FileUploadProvider(() -> {
- if (fd.getStatSize() != -1) {
- return new ParcelFileDescriptor.AutoCloseInputStream(fd).getChannel();
- } else {
- fd.close();
- throw new IllegalArgumentException("Not a file: " + fd);
- }
- });
- }
-
- /**
- * Uploads a ByteBuffer, from the current {@code buffer.position()} to {@code buffer.limit()}
- *
- * @param buffer The data to upload
- * @return A new UploadDataProvider for the given buffer
- */
- public static UploadDataProvider create(ByteBuffer buffer) {
- return new ByteBufferUploadProvider(buffer.slice());
- }
-
- /**
- * Uploads {@code length} bytes from {@code data}, starting from {@code offset}
- *
- * @param data Array containing data to upload
- * @param offset Offset within data to start with
- * @param length Number of bytes to upload
- * @return A new UploadDataProvider for the given data
- */
- public static UploadDataProvider create(byte[] data, int offset, int length) {
- return new ByteBufferUploadProvider(ByteBuffer.wrap(data, offset, length).slice());
- }
-
- /**
- * Uploads the contents of {@code data}
- *
- * @param data Array containing data to upload
- * @return A new UploadDataProvider for the given data
- */
- public static UploadDataProvider create(byte[] data) {
- return create(data, 0, data.length);
- }
-
- /**
- * Uploads the UTF-8 representation of {@code data}
- *
- * @param data String containing data to upload
- * @return A new UploadDataProvider for the given data
- */
- public static UploadDataProvider create(String data) {
- return create(data.getBytes(StandardCharsets.UTF_8));
- }
-
- private interface FileChannelProvider {
- FileChannel getChannel() throws IOException;
- }
-
- private static final class FileUploadProvider extends UploadDataProvider {
- private volatile FileChannel mChannel;
- private final FileChannelProvider mProvider;
- /** Guards initialization of {@code mChannel} */
- private final Object mLock = new Object();
-
- private FileUploadProvider(FileChannelProvider provider) {
- this.mProvider = provider;
- }
-
- @Override
- public long getLength() throws IOException {
- return getChannel().size();
- }
-
- @Override
- public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException {
- if (!byteBuffer.hasRemaining()) {
- throw new IllegalStateException("Cronet passed a buffer with no bytes remaining");
- }
- FileChannel channel = getChannel();
- int bytesRead = 0;
- while (bytesRead == 0) {
- int read = channel.read(byteBuffer);
- if (read == -1) {
- break;
- } else {
- bytesRead += read;
- }
- }
- uploadDataSink.onReadSucceeded(false);
- }
-
- @Override
- public void rewind(UploadDataSink uploadDataSink) throws IOException {
- getChannel().position(0);
- uploadDataSink.onRewindSucceeded();
- }
-
- /**
- * Lazily initializes the channel so that a blocking operation isn't performed
- * on a non-executor thread.
- */
- private FileChannel getChannel() throws IOException {
- if (mChannel == null) {
- synchronized (mLock) {
- if (mChannel == null) {
- mChannel = mProvider.getChannel();
- }
- }
- }
- return mChannel;
- }
-
- @Override
- public void close() throws IOException {
- FileChannel channel = mChannel;
- if (channel != null) {
- channel.close();
- }
- }
- }
-
- private static final class ByteBufferUploadProvider extends UploadDataProvider {
- private final ByteBuffer mUploadBuffer;
-
- private ByteBufferUploadProvider(ByteBuffer uploadBuffer) {
- this.mUploadBuffer = uploadBuffer;
- }
-
- @Override
- public long getLength() {
- return mUploadBuffer.limit();
- }
-
- @Override
- public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) {
- if (!byteBuffer.hasRemaining()) {
- throw new IllegalStateException("Cronet passed a buffer with no bytes remaining");
- }
- if (byteBuffer.remaining() >= mUploadBuffer.remaining()) {
- byteBuffer.put(mUploadBuffer);
- } else {
- int oldLimit = mUploadBuffer.limit();
- mUploadBuffer.limit(mUploadBuffer.position() + byteBuffer.remaining());
- byteBuffer.put(mUploadBuffer);
- mUploadBuffer.limit(oldLimit);
- }
- uploadDataSink.onReadSucceeded(false);
- }
-
- @Override
- public void rewind(UploadDataSink uploadDataSink) {
- mUploadBuffer.position(0);
- uploadDataSink.onRewindSucceeded();
- }
- }
-
- // Prevent instantiation
- private UploadDataProviders() {}
-}
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
deleted file mode 100644
index 9486e1f..0000000
--- a/Cronet/tests/mts/Android.bp
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2023 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 {
- default_team: "trendy_team_fwk_core_networking",
- // See: http://go/android-license-faq
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_genrule {
- name: "net-http-test-jarjar-rules",
- tool_files: [
- ":NetHttpTestsLibPreJarJar{.jar}",
- "jarjar_excludes.txt",
- ],
- tools: [
- "jarjar-rules-generator",
- ],
- out: ["net_http_test_jarjar_rules.txt"],
- cmd: "$(location jarjar-rules-generator) " +
- "$(location :NetHttpTestsLibPreJarJar{.jar}) " +
- "--prefix android.net.connectivity " +
- "--excludes $(location jarjar_excludes.txt) " +
- "--output $(out)",
-}
-
-// Library to be used in coverage tests. cronet_java_tests can't be used directly because the common
-// tests need to inherit the NetHttpTests manifest.
-android_library {
- name: "NetHttpTestsLibPreJarJar",
- static_libs: [
- "cronet_aml_api_java",
- "cronet_aml_java__testing",
- "cronet_java_tests",
- ],
- sdk_version: "module_current",
- min_sdk_version: "30",
-}
-
-android_test {
- name: "NetHttpTests",
- defaults: [
- "mts-target-sdk-version-current",
- ],
- static_libs: ["NetHttpTestsLibPreJarJar"],
- jarjar_rules: ":net-http-test-jarjar-rules",
- jni_libs: [
- "cronet_aml_components_cronet_android_cronet__testing",
- "cronet_aml_components_cronet_android_cronet_tests__testing",
- "cronet_aml_third_party_netty_tcnative_netty_tcnative_so__testing",
- ],
- test_suites: [
- "general-tests",
- "mts-tethering",
- ],
- data: [":cronet_javatests_resources"],
-}
diff --git a/Cronet/tests/mts/AndroidManifest.xml b/Cronet/tests/mts/AndroidManifest.xml
deleted file mode 100644
index 2c56e3a..0000000
--- a/Cronet/tests/mts/AndroidManifest.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.http.mts">
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.INTERNET"/>
-
- <application android:networkSecurityConfig="@xml/network_security_config">
- <uses-library android:name="android.test.runner" />
- </application>
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.net.http.mts"
- android:label="MTS tests of android.net.http">
- </instrumentation>
-
-</manifest>
\ No newline at end of file
diff --git a/Cronet/tests/mts/AndroidTest.xml b/Cronet/tests/mts/AndroidTest.xml
deleted file mode 100644
index 5aed655..0000000
--- a/Cronet/tests/mts/AndroidTest.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-<configuration description="Runs NetHttp Mainline Tests.">
- <!-- Only run tests if the device under test is SDK version 30 or above. -->
- <!-- TODO Switch back to Sdk30 when b/270049141 is fixed -->
- <object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
-
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="test-file-name" value="NetHttpTests.apk" />
- </target_preparer>
-
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="push-file" key="net" value="/storage/emulated/0/chromium_tests_root/net" />
- <option name="push-file" key="test_server" value="/storage/emulated/0/chromium_tests_root/components/cronet/testing/test_server" />
- </target_preparer>
-
- <option name="test-tag" value="NetHttpTests" />
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.net.http.mts" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- <!-- b/298380508 -->
- <option name="exclude-filter" value="org.chromium.net.CronetUrlRequestContextTest#testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider" />
- <!-- b/316559294 -->
- <option name="exclude-filter" value="org.chromium.net.NQETest#testQuicDisabled" />
- <!-- b/316559294 -->
- <option name="exclude-filter" value="org.chromium.net.NQETest#testPrefsWriteRead" />
- <!-- b/316554711-->
- <option name="exclude-filter" value="org.chromium.net.NetworkChangesTest" />
- <!-- b/316550794 -->
- <option name="exclude-filter" value="org.chromium.net.impl.CronetLoggerTest#testEngineCreation" />
- <option name="hidden-api-checks" value="false"/>
- <option name="isolated-storage" value="false"/>
- <option name="orchestrator" value="true"/>
- </test>
-
- <!-- Only run NetHttpTests in MTS if the Tethering Mainline module is installed. -->
- <object type="module_controller"
- class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.tethering" />
- </object>
-</configuration>
\ No newline at end of file
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
deleted file mode 100644
index b5cdf6e..0000000
--- a/Cronet/tests/mts/jarjar_excludes.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-# Exclude some test prefixes, as they can't be found after being jarjared.
-com\.android\.testutils\..+
-# jarjar-gen can't handle some kotlin object expression, exclude packages that include them
-androidx\..+
-# don't jarjar netty as it does JNI
-io\.netty\..+
-kotlin\.test\..+
-kotlin\.reflect\..+
-org\.mockito\..+
-# Do not jarjar the api classes
-android\.net\..+
-# cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a
-# separate java base target to depend on.
-org\.chromium\.base\..+
-J\.cronet_tests_N(\$.+)?
-
-# don't jarjar automatically generated FooJni files.
-org\.chromium\.net\..+Jni(\$.+)?
-
-# Do not jarjar the tests and its utils as they also do JNI with cronet_tests.so
-org\.chromium\.net\..*Test.*(\$.+)?
-org\.chromium\.net\.NativeTestServer(\$.+)?
-org\.chromium\.net\.MockUrlRequestJobFactory(\$.+)?
-org\.chromium\.net\.QuicTestServer(\$.+)?
-org\.chromium\.net\.MockCertVerifier(\$.+)?
-org\.chromium\.net\.LogcatCapture(\$.+)?
-org\.chromium\.net\.ReportingCollector(\$.+)?
-org\.chromium\.net\.Http2TestServer(\$.+)?
-org\.chromium\.net\.Http2TestHandler(\$.+)?
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/raw/quicroot.pem b/Cronet/tests/mts/res/raw/quicroot.pem
deleted file mode 100644
index af21b3e..0000000
--- a/Cronet/tests/mts/res/raw/quicroot.pem
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIC/jCCAeagAwIBAgIUXOi6XoxnMUjJg4jeOwRhsdqEqEQwDQYJKoZIhvcNAQEL
-BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTIzMDYwMTExMjcwMFoXDTMz
-MDUyOTExMjcwMFowFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMIIBIjANBgkqhkiG
-9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl9xCMPMIvfmJWz25AG/VtgWbqNs67HXQbXWf
-pDF2wjQpHVOYbfl7Zgly5O+5es1aUbJaGyZ9G6xuYSXKFnnYLoP7M86O05fQQBAj
-K+IE5nO6136ksCAfxCFTFfn4vhPvK8Vba5rqox4WeIXYKvHYSoiHz0ELrnFOHcyN
-Innyze7bLtkMCA1ShHpmvDCR+U3Uj6JwOfoirn29jjU/48/ORha7dcJYtYXk2eGo
-RJfrtIx20tXAaKaGnXOCGYbEVXTeQkQPqKFVzqP7+KYS/Y8eNFV35ugpLNES+44T
-bQ2QruTZdrNRjJkEoyiB/E53a0OUltB/R7Z0L0xstnKfsAf3OwIDAQABo0IwQDAP
-BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUVdXNh2lk
-51/6hMmz0Z+OpIe8+f0wDQYJKoZIhvcNAQELBQADggEBADNg7G8n6DUrQ5doXzm9
-kOp5siX6iPs0zFReXKhIT1Gef63l3tb7AdPedF03aj9XkUt0shhNOGG5SK2k5KBQ
-MJc9muYRCAyo2xMr3rFUQdI5B51SCy5HeAMralgTHXN0Hv+TH04YfRrACVmr+5ke
-pH3bF1gYaT+Zy5/pHJnV5lcwS6/H44g9XXWIopjWCwbfzKxIuWofqL4fiToPSIYu
-MCUI4bKZipcJT5O6rdz/S9lbgYVjOJ4HAoT2icNQqNMMfULKevmF8SdJzfNd35yn
-tAKTROhIE2aQRVCclrjo/T3eyjWGGoJlGmxKbeCf/rXzcn1BRtk/UzLnbUFFlg5l
-axw=
------END CERTIFICATE-----
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/values/cronet-test-rule-configuration.xml b/Cronet/tests/mts/res/values/cronet-test-rule-configuration.xml
deleted file mode 100644
index 48ce420..0000000
--- a/Cronet/tests/mts/res/values/cronet-test-rule-configuration.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 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.
- -->
-
-<resources>
- <bool name="is_running_in_aosp">true</bool>
-</resources>
\ No newline at end of file
diff --git a/Cronet/tests/mts/res/xml/network_security_config.xml b/Cronet/tests/mts/res/xml/network_security_config.xml
deleted file mode 100644
index 32b7171..0000000
--- a/Cronet/tests/mts/res/xml/network_security_config.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ 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.
- -->
-
-<network-security-config>
- <base-config>
- <trust-anchors>
- <certificates src="@raw/quicroot"/>
- <certificates src="system"/>
- </trust-anchors>
- </base-config>
- <!-- Since Android 9 (API 28) cleartext support is disabled by default, this
- causes some of our tests to fail (see crbug/1220357).
- The following configs allow http requests for the domains used in these
- tests.
-
- TODO(stefanoduo): Figure out if we really need to use http for these tests
- -->
- <domain-config cleartextTrafficPermitted="true">
- <!-- Used as the base URL by native test server (net::EmbeddedTestServer) -->
- <domain includeSubdomains="true">127.0.0.1</domain>
- <!-- Used by CronetHttpURLConnectionTest#testIOExceptionInterruptRethrown -->
- <domain includeSubdomains="true">localhost</domain>
- <!-- Used by CronetHttpURLConnectionTest#testBadIP -->
- <domain includeSubdomains="true">0.0.0.0</domain>
- <!-- Used by CronetHttpURLConnectionTest#testSetUseCachesFalse -->
- <domain includeSubdomains="true">host-cache-test-host</domain>
- <!-- Used by CronetHttpURLConnectionTest#testBadHostname -->
- <domain includeSubdomains="true">this-weird-host-name-does-not-exist</domain>
- <!-- Used by CronetUrlRequestContextTest#testHostResolverRules -->
- <domain includeSubdomains="true">some-weird-hostname</domain>
- </domain-config>
-</network-security-config>
\ No newline at end of file
diff --git a/DnsResolver/DnsBpfHelper.cpp b/DnsResolver/DnsBpfHelper.cpp
index de8bef5..0719ade 100644
--- a/DnsResolver/DnsBpfHelper.cpp
+++ b/DnsResolver/DnsBpfHelper.cpp
@@ -69,9 +69,10 @@
// state, making it a trustworthy source. Since this library primarily serves DNS resolvers,
// relying solely on V+ data prevents erroneous blocking of DNS queries.
if (android::modules::sdklevel::IsAtLeastV() && metered) {
- // The background data setting (PENALTY_BOX_MATCH) and unrestricted data usage setting
- // (HAPPY_BOX_MATCH) for individual apps override the system wide Data Saver setting.
- if (uidRules & PENALTY_BOX_MATCH) return true;
+ // The background data setting (PENALTY_BOX_USER_MATCH, PENALTY_BOX_ADMIN_MATCH) and
+ // unrestricted data usage setting (HAPPY_BOX_MATCH) for individual apps override the system
+ // wide Data Saver setting.
+ if (uidRules & (PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH)) return true;
if (uidRules & HAPPY_BOX_MATCH) return false;
auto dataSaverSetting = mDataSaverEnabledMap.readValue(DATA_SAVER_ENABLED_KEY);
diff --git a/DnsResolver/DnsBpfHelperTest.cpp b/DnsResolver/DnsBpfHelperTest.cpp
index 67b5b95..18a5df4 100644
--- a/DnsResolver/DnsBpfHelperTest.cpp
+++ b/DnsResolver/DnsBpfHelperTest.cpp
@@ -158,23 +158,33 @@
}
} testConfigs[]{
// clang-format off
- // enabledRules, dataSaverEnabled, uidRules, blocked
- {NO_MATCH, false, NO_MATCH, false},
- {NO_MATCH, false, PENALTY_BOX_MATCH, true},
- {NO_MATCH, false, HAPPY_BOX_MATCH, false},
- {NO_MATCH, false, PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
- {NO_MATCH, true, NO_MATCH, true},
- {NO_MATCH, true, PENALTY_BOX_MATCH, true},
- {NO_MATCH, true, HAPPY_BOX_MATCH, false},
- {NO_MATCH, true, PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
- {STANDBY_MATCH, false, STANDBY_MATCH, true},
- {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_MATCH, true},
- {STANDBY_MATCH, false, STANDBY_MATCH|HAPPY_BOX_MATCH, true},
- {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
- {STANDBY_MATCH, true, STANDBY_MATCH, true},
- {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_MATCH, true},
- {STANDBY_MATCH, true, STANDBY_MATCH|HAPPY_BOX_MATCH, true},
- {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true},
+ // enabledRules, dataSaverEnabled, uidRules, blocked
+ {NO_MATCH, false, NO_MATCH, false},
+ {NO_MATCH, false, PENALTY_BOX_USER_MATCH, true},
+ {NO_MATCH, false, PENALTY_BOX_ADMIN_MATCH, true},
+ {NO_MATCH, false, PENALTY_BOX_USER_MATCH|PENALTY_BOX_ADMIN_MATCH, true},
+ {NO_MATCH, false, HAPPY_BOX_MATCH, false},
+ {NO_MATCH, false, PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH, true},
+ {NO_MATCH, false, PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH, true},
+ {NO_MATCH, true, NO_MATCH, true},
+ {NO_MATCH, true, PENALTY_BOX_USER_MATCH, true},
+ {NO_MATCH, true, PENALTY_BOX_ADMIN_MATCH, true},
+ {NO_MATCH, true, PENALTY_BOX_USER_MATCH|PENALTY_BOX_ADMIN_MATCH, true},
+ {NO_MATCH, true, HAPPY_BOX_MATCH, false},
+ {NO_MATCH, true, PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH, true},
+ {NO_MATCH, true, PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_USER_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_USER_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_USER_MATCH|HAPPY_BOX_MATCH, true},
+ {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_ADMIN_MATCH|HAPPY_BOX_MATCH, true},
// clang-format on
};
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index 83f798a..6d8ed4a 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -1,10 +1,6 @@
-chiachangwang@google.com
-cken@google.com
jchalard@google.com
junyulai@google.com
-lifr@google.com
lorenzo@google.com
-markchien@google.com
martinwu@google.com
maze@google.com
motomuman@google.com
@@ -12,7 +8,5 @@
prohr@google.com
reminv@google.com
satk@google.com
-waynema@google.com
xiaom@google.com
-yumike@google.com
yuyanghuang@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d8d4c21..4cf93a8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,4 +1,140 @@
{
+ "captiveportal-networkstack-resolve-tethering-mainline-presubmit": [
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
+ "name": "CtsNetTestCasesMaxTargetSdk30",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
+ "name": "CtsNetTestCasesMaxTargetSdk31",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
+ "name": "CtsNetTestCasesMaxTargetSdk33",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
+ {
+ "name": "bpf_existence_test"
+ },
+ {
+ "name": "connectivity_native_test"
+ },
+ {
+ "name": "netd_updatable_unit_test"
+ },
+ {
+ "name": "ConnectivityCoverageTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ]
+ },
+ {
+ "name": "libnetworkstats_test"
+ },
+ {
+ "name": "CtsTetheringTestLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ }
+ ]
+ }
+ ],
+ "captiveportal-networkstack-mainline-presubmit": [
+ // Test with APK modules only, in cases where APEX is not supported, or the other modules
+ // were simply not updated
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
+ }
+ ]
+ }
+ ],
+ "tethering-mainline-presubmit": [
+ // Test with connectivity/tethering module only, to catch integration issues with older versions
+ // of other modules. "new tethering + old NetworkStack" is not a configuration that should
+ // really exist in the field, but there is no strong guarantee, and it is required by MTS
+ // testing for module qualification, where modules are tested independently.
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
+ }
+ ]
+ }
+ ],
"presubmit": [
{
"name": "ConnectivityCoverageTests",
@@ -104,19 +240,6 @@
},
{
"name": "FrameworksNetIntegrationTests"
- },
- // Runs both NetHttpTests and CtsNetHttpTestCases
- {
- "name": "NetHttpCoverageTests",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- // These sometimes take longer than 1 min which is the presubmit timeout
- "exclude-annotation": "androidx.test.filters.LargeTest"
- }
- ]
}
],
"postsubmit": [
@@ -144,9 +267,6 @@
},
{
"name": "FrameworksNetTests"
- },
- {
- "name": "NetHttpCoverageTests"
}
],
"mainline-presubmit": [
@@ -273,18 +393,6 @@
"name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
{
- "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- // These sometimes take longer than 1 min which is the presubmit timeout
- "exclude-annotation": "androidx.test.filters.LargeTest"
- }
- ]
- },
- {
"name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
@@ -332,6 +440,9 @@
"path": "packages/modules/CaptivePortalLogin"
},
{
+ "path": "external/cronet"
+ },
+ {
"path": "vendor/xts/gts-tests/hostsidetests/networkstack"
}
]
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 19bcff9..e84573b 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -126,7 +126,7 @@
// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
cc_library {
name: "libcom_android_networkstack_tethering_util_jni",
- sdk_version: "30",
+ sdk_version: "current",
apex_available: [
"com.android.tethering",
],
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 4bae221..304a6ed 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -113,6 +113,7 @@
prebuilts: [
"current_sdkinfo",
"netbpfload.mainline.rc",
+ "netbpfload.35rc",
"ot-daemon.init.34rc",
],
manifest: "manifest.json",
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 81e18ab..00d9152 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -69,7 +69,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
@@ -1343,19 +1342,6 @@
pw.decreaseIndent();
}
- private <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
- IndentingPrintWriter pw) throws ErrnoException {
- if (map == null) {
- pw.println("No BPF support");
- return;
- }
- if (map.isEmpty()) {
- pw.println("No entries");
- return;
- }
- map.forEach((k, v) -> pw.println(BpfDump.toBase64EncodedString(k, v)));
- }
-
/**
* Dump raw BPF map into the base64 encoded strings "<base64 key>,<base64 value>".
* Allow to dump only one map path once. For test only.
@@ -1375,16 +1361,16 @@
// TODO: dump downstream4 map.
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
try (IBpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
- dumpRawMap(statsMap, pw);
- } catch (ErrnoException | IOException e) {
+ BpfDump.dumpRawMap(statsMap, pw);
+ } catch (IOException e) {
pw.println("Error dumping stats map: " + e);
}
return;
}
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
- dumpRawMap(upstreamMap, pw);
- } catch (ErrnoException | IOException e) {
+ BpfDump.dumpRawMap(upstreamMap, pw);
+ } catch (IOException e) {
pw.println("Error dumping IPv4 map: " + e);
}
return;
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index f696885..120b871 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -74,7 +74,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.BeforeClass;
import java.io.FileDescriptor;
import java.net.Inet4Address;
@@ -144,7 +143,7 @@
private static final Context sContext =
InstrumentationRegistry.getInstrumentation().getContext();
- private static final EthernetManager sEm = sContext.getSystemService(EthernetManager.class);
+ protected static final EthernetManager sEm = sContext.getSystemService(EthernetManager.class);
private static final TetheringManager sTm = sContext.getSystemService(TetheringManager.class);
private static final PackageManager sPackageManager = sContext.getPackageManager();
private static final CtsNetUtils sCtsNetUtils = new CtsNetUtils(sContext);
@@ -168,34 +167,6 @@
return sContext;
}
- @BeforeClass
- public static void setUpOnce() throws Exception {
- // The first test case may experience tethering restart with IP conflict handling.
- // Tethering would cache the last upstreams so that the next enabled tethering avoids
- // picking up the address that is in conflict with the upstreams. To protect subsequent
- // tests, turn tethering on and off before running them.
- MyTetheringEventCallback callback = null;
- TestNetworkInterface testIface = null;
- assumeTrue(sEm != null);
- try {
- // If the physical ethernet interface is available, do nothing.
- if (isInterfaceForTetheringAvailable()) return;
-
- testIface = createTestInterface();
- setIncludeTestInterfaces(true);
-
- callback = enableEthernetTethering(testIface.getInterfaceName(), null);
- callback.awaitUpstreamChanged(true /* throwTimeoutException */);
- } catch (TimeoutException e) {
- Log.d(TAG, "WARNNING " + e);
- } finally {
- maybeCloseTestInterface(testIface);
- maybeUnregisterTetheringEventCallback(callback);
-
- setIncludeTestInterfaces(false);
- }
- }
-
@Before
public void setUp() throws Exception {
mHandlerThread = new HandlerThread(getClass().getSimpleName());
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 4949eaa..c54d1b4 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -59,6 +59,7 @@
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -150,6 +151,35 @@
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
};
+ /** Enable/disable tethering once before running the tests. */
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ // The first test case may experience tethering restart with IP conflict handling.
+ // Tethering would cache the last upstreams so that the next enabled tethering avoids
+ // picking up the address that is in conflict with the upstreams. To protect subsequent
+ // tests, turn tethering on and off before running them.
+ MyTetheringEventCallback callback = null;
+ TestNetworkInterface testIface = null;
+ assumeTrue(sEm != null);
+ try {
+ // If the physical ethernet interface is available, do nothing.
+ if (isInterfaceForTetheringAvailable()) return;
+
+ testIface = createTestInterface();
+ setIncludeTestInterfaces(true);
+
+ callback = enableEthernetTethering(testIface.getInterfaceName(), null);
+ callback.awaitUpstreamChanged(true /* throwTimeoutException */);
+ } catch (TimeoutException e) {
+ Log.d(TAG, "WARNNING " + e);
+ } finally {
+ maybeCloseTestInterface(testIface);
+ maybeUnregisterTetheringEventCallback(callback);
+
+ setIncludeTestInterfaces(false);
+ }
+ }
+
@Test
public void testVirtualEthernetAlreadyExists() throws Exception {
// This test requires manipulating packets. Skip if there is a physical Ethernet connected.
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index d5d71bc..f8aa69f 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -22,19 +22,24 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.net.MacAddress;
import android.os.Build;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -42,10 +47,18 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.io.File;
import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -61,6 +74,17 @@
private BpfMap<TetherDownstream6Key, Tether6Value> mTestMap;
+ private final boolean mShouldTestSingleWriterMap;
+
+ @Parameterized.Parameters
+ public static Collection<Boolean> shouldTestSingleWriterMap() {
+ return Arrays.asList(true, false);
+ }
+
+ public BpfMapTest(boolean shouldTestSingleWriterMap) {
+ mShouldTestSingleWriterMap = shouldTestSingleWriterMap;
+ }
+
@BeforeClass
public static void setupOnce() {
System.loadLibrary(getTetheringJniLibraryName());
@@ -82,11 +106,16 @@
initTestMap();
}
- private void initTestMap() throws Exception {
- mTestMap = new BpfMap<>(
- TETHER_DOWNSTREAM6_FS_PATH,
- TetherDownstream6Key.class, Tether6Value.class);
+ private BpfMap<TetherDownstream6Key, Tether6Value> openTestMap() throws Exception {
+ return mShouldTestSingleWriterMap
+ ? new SingleWriterBpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
+ Tether6Value.class)
+ : new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
+ Tether6Value.class);
+ }
+ private void initTestMap() throws Exception {
+ mTestMap = openTestMap();
mTestMap.forEach((key, value) -> {
try {
assertTrue(mTestMap.deleteEntry(key));
@@ -357,6 +386,25 @@
}
@Test
+ public void testMapContentsCorrectOnOpen() throws Exception {
+ final BpfMap<TetherDownstream6Key, Tether6Value> map1, map2;
+
+ map1 = openTestMap();
+ map1.clear();
+ for (int i = 0; i < mTestData.size(); i++) {
+ map1.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ // We can't close and reopen map1, because close does nothing. Open another map instead.
+ map2 = openTestMap();
+ for (int i = 0; i < mTestData.size(); i++) {
+ assertEquals(mTestData.valueAt(i), map2.getValue(mTestData.keyAt(i)));
+ }
+
+ map1.clear();
+ }
+
+ @Test
public void testInsertOverflow() throws Exception {
final ArrayMap<TetherDownstream6Key, Tether6Value> testData =
new ArrayMap<>();
@@ -396,8 +444,18 @@
}
}
- private static int getNumOpenFds() {
- return new File("/proc/" + Os.getpid() + "/fd").listFiles().length;
+ private static int getNumOpenBpfMapFds() throws Exception {
+ int numFds = 0;
+ File[] openFiles = new File("/proc/self/fd").listFiles();
+ for (int i = 0; i < openFiles.length; i++) {
+ final Path path = openFiles[i].toPath();
+ if (!Files.isSymbolicLink(path)) continue;
+ if ("anon_inode:bpf-map".equals(Files.readSymbolicLink(path).toString())) {
+ numFds++;
+ }
+ }
+ assertNotEquals("Couldn't find any BPF map fds opened by this process", 0, numFds);
+ return numFds;
}
@Test
@@ -406,7 +464,7 @@
// cache, expect that the fd amount is not increased in the iterations.
// See the comment of BpfMap#close.
final int iterations = 1000;
- final int before = getNumOpenFds();
+ final int before = getNumOpenBpfMapFds();
for (int i = 0; i < iterations; i++) {
try (BpfMap<TetherDownstream6Key, Tether6Value> map = new BpfMap<>(
TETHER_DOWNSTREAM6_FS_PATH,
@@ -414,15 +472,74 @@
// do nothing
}
}
- final int after = getNumOpenFds();
+ final int after = getNumOpenBpfMapFds();
// Check that the number of open fds is the same as before.
- // If this exact match becomes flaky, we probably need to distinguish that fd is belong
- // to "bpf-map".
- // ex:
- // $ adb shell ls -all /proc/16196/fd
- // [..] network_stack 64 2022-07-26 22:01:02.300002956 +0800 749 -> anon_inode:bpf-map
- // [..] network_stack 64 2022-07-26 22:01:02.188002956 +0800 75 -> anon_inode:[eventfd]
assertEquals("Fd leak after " + iterations + " iterations: ", before, after);
}
+
+ @Test
+ public void testNullKey() {
+ assertThrows(NullPointerException.class, () ->
+ mTestMap.insertOrReplaceEntry(null, mTestData.valueAt(0)));
+ }
+
+ private void runBenchmarkThread(BpfMap<TetherDownstream6Key, Tether6Value> map,
+ CompletableFuture<Integer> future, int runtimeMs) {
+ int numReads = 0;
+ final Random r = new Random();
+ final long start = SystemClock.elapsedRealtime();
+ final long stop = start + runtimeMs;
+ while (SystemClock.elapsedRealtime() < stop) {
+ try {
+ final Tether6Value v = map.getValue(mTestData.keyAt(r.nextInt(mTestData.size())));
+ assertNotNull(v);
+ numReads++;
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ return;
+ }
+ }
+ future.complete(numReads);
+ }
+
+ @Test
+ public void testSingleWriterCacheEffectiveness() throws Exception {
+ assumeTrue(mShouldTestSingleWriterMap);
+
+ // Ensure the map is not empty.
+ for (int i = 0; i < mTestData.size(); i++) {
+ mTestMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ // Benchmark parameters.
+ final int timeoutMs = 5_000; // Only hit if threads don't complete.
+ final int benchmarkTimeMs = 2_000;
+ final int minReads = 50;
+ // Local testing on cuttlefish suggests that caching is ~10x faster.
+ // Only require 3x to reduce test flakiness.
+ final int expectedSpeedup = 3;
+
+ final BpfMap cachedMap = new SingleWriterBpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+ final BpfMap uncachedMap = new BpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+
+ final CompletableFuture<Integer> cachedResult = new CompletableFuture<>();
+ final CompletableFuture<Integer> uncachedResult = new CompletableFuture<>();
+
+ new Thread(() -> runBenchmarkThread(uncachedMap, uncachedResult, benchmarkTimeMs)).start();
+ new Thread(() -> runBenchmarkThread(cachedMap, cachedResult, benchmarkTimeMs)).start();
+
+ final int cached = cachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+ final int uncached = uncachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+
+ // Uncomment to see benchmark results.
+ // fail("Cached " + cached + ", uncached " + uncached + ": " + cached / uncached +"x");
+
+ assertTrue("Less than " + minReads + "cached reads observed", cached > minReads);
+ assertTrue("Less than " + minReads + "uncached reads observed", uncached > minReads);
+ assertTrue("Cached map not at least " + expectedSpeedup + "x faster",
+ cached > expectedSpeedup * uncached);
+ }
}
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index dfc7699..c3acaad 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -407,6 +407,9 @@
BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+ // BACKGROUND match does not apply to loopback traffic
+ if (skb->ifindex == 1) enabledRules &= ~BACKGROUND_MATCH;
+
UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
@@ -644,7 +647,8 @@
(struct __sk_buff* skb) {
uint32_t sock_uid = bpf_get_socket_uid(skb);
UidOwnerValue* denylistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
- if (denylistMatch) return denylistMatch->rule & PENALTY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+ uint32_t penalty_box = PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH;
+ if (denylistMatch) return denylistMatch->rule & penalty_box ? BPF_MATCH : BPF_NOMATCH;
return BPF_NOMATCH;
}
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index 098147f..8a56b4a 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -181,7 +181,7 @@
enum UidOwnerMatchType : uint32_t {
NO_MATCH = 0,
HAPPY_BOX_MATCH = (1 << 0),
- PENALTY_BOX_MATCH = (1 << 1),
+ PENALTY_BOX_USER_MATCH = (1 << 1),
DOZABLE_MATCH = (1 << 2),
STANDBY_MATCH = (1 << 3),
POWERSAVE_MATCH = (1 << 4),
@@ -192,7 +192,8 @@
OEM_DENY_1_MATCH = (1 << 9),
OEM_DENY_2_MATCH = (1 << 10),
OEM_DENY_3_MATCH = (1 << 11),
- BACKGROUND_MATCH = (1 << 12)
+ BACKGROUND_MATCH = (1 << 12),
+ PENALTY_BOX_ADMIN_MATCH = (1 << 13),
};
// LINT.ThenChange(../framework/src/android/net/BpfNetMapsConstants.java)
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index 56625c5..21be1d3 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -17,7 +17,7 @@
aconfig_declarations {
name: "com.android.net.flags-aconfig",
package: "com.android.net.flags",
- container: "system",
+ container: "com.android.tethering",
srcs: ["flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -25,7 +25,7 @@
aconfig_declarations {
name: "com.android.net.thread.flags-aconfig",
package: "com.android.net.thread.flags",
- container: "system",
+ container: "com.android.tethering",
srcs: ["thread_flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
@@ -33,7 +33,7 @@
aconfig_declarations {
name: "nearby_flags",
package: "com.android.nearby.flags",
- container: "system",
+ container: "com.android.tethering",
srcs: ["nearby_flags.aconfig"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 30931df..6c3e89d 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -1,5 +1,5 @@
package: "com.android.net.flags"
-container: "system"
+container: "com.android.tethering"
# This file contains aconfig flags for FlaggedAPI annotations
# Flags used from platform code must be in under frameworks
@@ -83,3 +83,27 @@
description: "Flag for API to register nsd offload engine"
bug: "301713539"
}
+
+flag {
+ name: "metered_network_firewall_chains"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for metered network firewall chain API"
+ bug: "332628891"
+}
+
+flag {
+ name: "blocked_reason_oem_deny_chains"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for oem deny chains blocked reasons API"
+ bug: "328732146"
+}
+
+flag {
+ name: "blocked_reason_network_restricted"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for BLOCKED_REASON_NETWORK_RESTRICTED API"
+ bug: "339559837"
+}
diff --git a/common/nearby_flags.aconfig b/common/nearby_flags.aconfig
index b733849..55a865b 100644
--- a/common/nearby_flags.aconfig
+++ b/common/nearby_flags.aconfig
@@ -1,5 +1,5 @@
package: "com.android.nearby.flags"
-container: "system"
+container: "com.android.tethering"
flag {
name: "powered_off_finding"
diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig
index 43fc147..43acd1b 100644
--- a/common/thread_flags.aconfig
+++ b/common/thread_flags.aconfig
@@ -1,5 +1,5 @@
package: "com.android.net.thread.flags"
-container: "system"
+container: "com.android.tethering"
flag {
name: "thread_enabled"
diff --git a/framework-t/src/android/net/INetworkStatsService.aidl b/framework-t/src/android/net/INetworkStatsService.aidl
index c86f7fd..7f0c1fe 100644
--- a/framework-t/src/android/net/INetworkStatsService.aidl
+++ b/framework-t/src/android/net/INetworkStatsService.aidl
@@ -101,4 +101,7 @@
* Note that invocation of any interface will be sent to all providers.
*/
void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit);
+
+ /** Clear TrafficStats rate-limit caches. */
+ void clearTrafficStatsRateLimitCaches();
}
diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java
index 4e10a96..70c9bc8 100644
--- a/framework-t/src/android/net/IpSecTransform.java
+++ b/framework-t/src/android/net/IpSecTransform.java
@@ -124,7 +124,7 @@
private IpSecTransform activate()
throws IOException, IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException {
- synchronized (this) {
+ synchronized (mLock) {
try {
IpSecTransformResponse result = getIpSecManager(mContext).createTransform(
mConfig, new Binder(), mContext.getOpPackageName());
@@ -164,20 +164,23 @@
public void close() {
Log.d(TAG, "Removing Transform with Id " + mResourceId);
- // Always safe to attempt cleanup
- if (mResourceId == INVALID_RESOURCE_ID) {
- mCloseGuard.close();
- return;
- }
- try {
- getIpSecManager(mContext).deleteTransform(mResourceId);
- } catch (Exception e) {
- // On close we swallow all random exceptions since failure to close is not
- // actionable by the user.
- Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
- } finally {
- mResourceId = INVALID_RESOURCE_ID;
- mCloseGuard.close();
+ synchronized(mLock) {
+ // Always safe to attempt cleanup
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ mCloseGuard.close();
+ return;
+ }
+
+ try {
+ getIpSecManager(mContext).deleteTransform(mResourceId);
+ } catch (Exception e) {
+ // On close we swallow all random exceptions since failure to close is not
+ // actionable by the user.
+ Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
+ } finally {
+ mResourceId = INVALID_RESOURCE_ID;
+ mCloseGuard.close();
+ }
}
}
@@ -196,14 +199,17 @@
}
private final IpSecConfig mConfig;
- private int mResourceId;
+ private final Object mLock = new Object();
+ private int mResourceId; // Partly guarded by mLock to ensure basic safety, not correctness
private final Context mContext;
private final CloseGuard mCloseGuard = CloseGuard.get();
/** @hide */
@VisibleForTesting
public int getResourceId() {
- return mResourceId;
+ synchronized(mLock) {
+ return mResourceId;
+ }
}
/**
@@ -224,8 +230,10 @@
// TODO: Consider adding check to prevent DDoS attack.
try {
- final IpSecTransformState ipSecTransformState =
- getIpSecManager(mContext).getTransformState(mResourceId);
+ IpSecTransformState ipSecTransformState;
+ synchronized(mLock) {
+ ipSecTransformState = getIpSecManager(mContext).getTransformState(mResourceId);
+ }
executor.execute(
() -> {
callback.onResult(ipSecTransformState);
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index a69b38d..77c8001 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -19,6 +19,7 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -692,6 +693,27 @@
return UNSUPPORTED;
}
+ /** Clear TrafficStats rate-limit caches.
+ *
+ * This is mainly for {@link com.android.server.net.NetworkStatsService} to
+ * clear rate-limit cache to avoid caching for TrafficStats API results.
+ * Tests might get stale values after generating network traffic, which
+ * generally need to wait for cache expiry to get updated values.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ android.Manifest.permission.NETWORK_STACK,
+ android.Manifest.permission.NETWORK_SETTINGS})
+ public static void clearRateLimitCaches() {
+ try {
+ getStatsService().clearTrafficStatsRateLimitCaches();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Return the number of packets transmitted on the specified interface since the interface
* was created. Statistics are measured at the network layer, so both TCP and
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 1001423..b21e22a 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -389,6 +389,7 @@
}
private static final int FIRST_LISTENER_KEY = 1;
+ private static final int DNSSEC_PROTOCOL = 3;
private final INsdServiceConnector mService;
private final Context mContext;
@@ -1754,45 +1755,132 @@
}
}
+ private enum ServiceValidationType {
+ NO_SERVICE,
+ HAS_SERVICE, // A service with a positive port
+ HAS_SERVICE_ZERO_PORT, // A service with a zero port
+ }
+
+ private enum HostValidationType {
+ DEFAULT_HOST, // No host is specified so the default host will be used
+ CUSTOM_HOST, // A custom host with addresses is specified
+ CUSTOM_HOST_NO_ADDRESS, // A custom host without address is specified
+ }
+
+ private enum PublicKeyValidationType {
+ NO_KEY,
+ HAS_KEY,
+ }
+
+ /**
+ * Check if the service is valid for registration and classify it as one of {@link
+ * ServiceValidationType}.
+ */
+ private static ServiceValidationType validateService(NsdServiceInfo serviceInfo) {
+ final boolean hasServiceName = !TextUtils.isEmpty(serviceInfo.getServiceName());
+ final boolean hasServiceType = !TextUtils.isEmpty(serviceInfo.getServiceType());
+ if (!hasServiceName && !hasServiceType && serviceInfo.getPort() == 0) {
+ return ServiceValidationType.NO_SERVICE;
+ }
+ if (!hasServiceName || !hasServiceType) {
+ throw new IllegalArgumentException("The service name or the service type is missing");
+ }
+ if (serviceInfo.getPort() < 0) {
+ throw new IllegalArgumentException("Invalid port");
+ }
+ if (serviceInfo.getPort() == 0) {
+ return ServiceValidationType.HAS_SERVICE_ZERO_PORT;
+ }
+ return ServiceValidationType.HAS_SERVICE;
+ }
+
+ /**
+ * Check if the host is valid for registration and classify it as one of {@link
+ * HostValidationType}.
+ */
+ private static HostValidationType validateHost(NsdServiceInfo serviceInfo) {
+ final boolean hasHostname = !TextUtils.isEmpty(serviceInfo.getHostname());
+ final boolean hasHostAddresses = !CollectionUtils.isEmpty(serviceInfo.getHostAddresses());
+ if (!hasHostname) {
+ // Keep compatible with the legacy behavior: It's allowed to set host
+ // addresses for a service registration although the host addresses
+ // won't be registered. To register the addresses for a host, the
+ // hostname must be specified.
+ return HostValidationType.DEFAULT_HOST;
+ }
+ if (!hasHostAddresses) {
+ return HostValidationType.CUSTOM_HOST_NO_ADDRESS;
+ }
+ return HostValidationType.CUSTOM_HOST;
+ }
+
+ /**
+ * Check if the public key is valid for registration and classify it as one of {@link
+ * PublicKeyValidationType}.
+ *
+ * <p>For simplicity, it only checks if the protocol is DNSSEC and the RDATA is not fewer than 4
+ * bytes. See RFC 3445 Section 3.
+ */
+ private static PublicKeyValidationType validatePublicKey(NsdServiceInfo serviceInfo) {
+ byte[] publicKey = serviceInfo.getPublicKey();
+ if (publicKey == null) {
+ return PublicKeyValidationType.NO_KEY;
+ }
+ if (publicKey.length < 4) {
+ throw new IllegalArgumentException("The public key should be at least 4 bytes long");
+ }
+ int protocol = publicKey[2];
+ if (protocol == DNSSEC_PROTOCOL) {
+ return PublicKeyValidationType.HAS_KEY;
+ }
+ throw new IllegalArgumentException(
+ "The public key's protocol ("
+ + protocol
+ + ") is invalid. It should be DNSSEC_PROTOCOL (3)");
+ }
+
/**
* Check if the {@link NsdServiceInfo} is valid for registration.
*
- * The following can be registered:
- * - A service with an optional host.
- * - A hostname with addresses.
+ * <p>Firstly, check if service, host and public key are all valid respectively. Then check if
+ * the combination of service, host and public key is valid.
*
- * Note that:
- * - When registering a service, the service name, service type and port must be specified. If
- * hostname is specified, the host addresses can optionally be specified.
- * - When registering a host without a service, the addresses must be specified.
+ * <p>If the {@code serviceInfo} is invalid, throw an {@link IllegalArgumentException}
+ * describing the reason.
+ *
+ * <p>There are the invalid combinations of service, host and public key:
+ *
+ * <ul>
+ * <li>Neither service nor host is specified.
+ * <li>No public key is specified and the service has a zero port.
+ * <li>The registration only contains the hostname but addresses are missing.
+ * </ul>
+ *
+ * <p>Keys are used to reserve hostnames or service names while the service/host is temporarily
+ * inactive, so registrations with a key and just a hostname or a service name are acceptable.
*
* @hide
*/
public static void checkServiceInfoForRegistration(NsdServiceInfo serviceInfo) {
Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
- boolean hasServiceName = !TextUtils.isEmpty(serviceInfo.getServiceName());
- boolean hasServiceType = !TextUtils.isEmpty(serviceInfo.getServiceType());
- boolean hasHostname = !TextUtils.isEmpty(serviceInfo.getHostname());
- boolean hasHostAddresses = !CollectionUtils.isEmpty(serviceInfo.getHostAddresses());
- if (serviceInfo.getPort() < 0) {
- throw new IllegalArgumentException("Invalid port");
+ final ServiceValidationType serviceValidation = validateService(serviceInfo);
+ final HostValidationType hostValidation = validateHost(serviceInfo);
+ final PublicKeyValidationType publicKeyValidation = validatePublicKey(serviceInfo);
+
+ if (serviceValidation == ServiceValidationType.NO_SERVICE
+ && hostValidation == HostValidationType.DEFAULT_HOST) {
+ throw new IllegalArgumentException("Nothing to register");
}
-
- if (hasServiceType || hasServiceName || (serviceInfo.getPort() > 0)) {
- if (!(hasServiceType && hasServiceName && (serviceInfo.getPort() > 0))) {
- throw new IllegalArgumentException(
- "The service type, service name or port is missing");
+ if (publicKeyValidation == PublicKeyValidationType.NO_KEY) {
+ if (serviceValidation == ServiceValidationType.HAS_SERVICE_ZERO_PORT) {
+ throw new IllegalArgumentException("The port is missing");
}
- }
-
- if (!hasServiceType && !hasHostname) {
- throw new IllegalArgumentException("No service or host specified in NsdServiceInfo");
- }
-
- if (!hasServiceType && hasHostname && !hasHostAddresses) {
- // TODO: b/317946010 - This may be allowed when it supports registering KEY RR.
- throw new IllegalArgumentException("No host addresses specified in NsdServiceInfo");
+ if (serviceValidation == ServiceValidationType.NO_SERVICE
+ && hostValidation == HostValidationType.CUSTOM_HOST_NO_ADDRESS) {
+ throw new IllegalArgumentException(
+ "The host addresses must be specified unless there is a service");
+ }
}
}
}
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 9491a9c..2f675a9 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -37,6 +37,7 @@
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -69,6 +70,9 @@
private int mPort;
@Nullable
+ private byte[] mPublicKey;
+
+ @Nullable
private Network mNetwork;
private int mInterfaceIndex;
@@ -220,6 +224,40 @@
}
/**
+ * Set the public key RDATA to be advertised in a KEY RR (RFC 2535).
+ *
+ * <p>This is the public key of the key pair used for signing a DNS message (e.g. SRP). Clients
+ * typically don't need this information, but the KEY RR is usually published to claim the use
+ * of the DNS name so that another mDNS advertiser can't take over the ownership during a
+ * temporary power down of the original host device.
+ *
+ * <p>When the public key is set to non-null, exactly one KEY RR will be advertised for each of
+ * the service and host name if they are not null.
+ *
+ * @hide // For Thread only
+ */
+ public void setPublicKey(@Nullable byte[] publicKey) {
+ if (publicKey == null) {
+ mPublicKey = null;
+ return;
+ }
+ mPublicKey = Arrays.copyOf(publicKey, publicKey.length);
+ }
+
+ /**
+ * Get the public key RDATA in the KEY RR (RFC 2535) or {@code null} if no KEY RR exists.
+ *
+ * @hide // For Thread only
+ */
+ @Nullable
+ public byte[] getPublicKey() {
+ if (mPublicKey == null) {
+ return null;
+ }
+ return Arrays.copyOf(mPublicKey, mPublicKey.length);
+ }
+
+ /**
* Unpack txt information from a base-64 encoded byte array.
*
* @param txtRecordsRawBytes The raw base64 encoded byte array.
@@ -622,6 +660,7 @@
}
dest.writeString(mHostname);
dest.writeLong(mExpirationTime != null ? mExpirationTime.getEpochSecond() : -1);
+ dest.writeByteArray(mPublicKey);
}
/** Implement the Parcelable interface */
@@ -654,6 +693,7 @@
info.mHostname = in.readString();
final long seconds = in.readLong();
info.setExpirationTime(seconds < 0 ? null : Instant.ofEpochSecond(seconds));
+ info.mPublicKey = in.createByteArray();
return info;
}
diff --git a/framework/Android.bp b/framework/Android.bp
index 8787167..deb1c5a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -117,7 +117,6 @@
static_libs: [
"httpclient_api",
"httpclient_impl",
- "http_client_logging",
// Framework-connectivity-pre-jarjar is identical to framework-connectivity
// implementation, but without the jarjar rules. However, framework-connectivity
// is not based on framework-connectivity-pre-jarjar, it's rebuilt from source
@@ -147,7 +146,6 @@
],
impl_only_static_libs: [
"httpclient_impl",
- "http_client_logging",
],
}
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 026d8a9..cd7307f 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -51,11 +51,16 @@
field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20
+ field @FlaggedApi("com.android.net.flags.blocked_reason_network_restricted") public static final int BLOCKED_REASON_NETWORK_RESTRICTED = 256; // 0x100
field public static final int BLOCKED_REASON_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.net.flags.blocked_reason_oem_deny_chains") public static final int BLOCKED_REASON_OEM_DENY = 128; // 0x80
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field @FlaggedApi("com.android.net.flags.basic_background_restrictions_enabled") public static final int FIREWALL_CHAIN_BACKGROUND = 6; // 0x6
field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
+ field @FlaggedApi("com.android.net.flags.metered_network_firewall_chains") public static final int FIREWALL_CHAIN_METERED_ALLOW = 10; // 0xa
+ field @FlaggedApi("com.android.net.flags.metered_network_firewall_chains") public static final int FIREWALL_CHAIN_METERED_DENY_ADMIN = 12; // 0xc
+ field @FlaggedApi("com.android.net.flags.metered_network_firewall_chains") public static final int FIREWALL_CHAIN_METERED_DENY_USER = 11; // 0xb
field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7
field public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; // 0x8
field public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; // 0x9
diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
index 5d0fe73..f3773de 100644
--- a/framework/src/android/net/BpfNetMapsConstants.java
+++ b/framework/src/android/net/BpfNetMapsConstants.java
@@ -19,6 +19,9 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -67,7 +70,7 @@
// LINT.IfChange(match_type)
public static final long NO_MATCH = 0;
public static final long HAPPY_BOX_MATCH = (1 << 0);
- public static final long PENALTY_BOX_MATCH = (1 << 1);
+ public static final long PENALTY_BOX_USER_MATCH = (1 << 1);
public static final long DOZABLE_MATCH = (1 << 2);
public static final long STANDBY_MATCH = (1 << 3);
public static final long POWERSAVE_MATCH = (1 << 4);
@@ -79,10 +82,11 @@
public static final long OEM_DENY_2_MATCH = (1 << 10);
public static final long OEM_DENY_3_MATCH = (1 << 11);
public static final long BACKGROUND_MATCH = (1 << 12);
+ public static final long PENALTY_BOX_ADMIN_MATCH = (1 << 13);
public static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
- Pair.create(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH"),
+ Pair.create(PENALTY_BOX_USER_MATCH, "PENALTY_BOX_USER_MATCH"),
Pair.create(DOZABLE_MATCH, "DOZABLE_MATCH"),
Pair.create(STANDBY_MATCH, "STANDBY_MATCH"),
Pair.create(POWERSAVE_MATCH, "POWERSAVE_MATCH"),
@@ -93,11 +97,13 @@
Pair.create(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"),
Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"),
Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"),
- Pair.create(BACKGROUND_MATCH, "BACKGROUND_MATCH")
+ Pair.create(BACKGROUND_MATCH, "BACKGROUND_MATCH"),
+ Pair.create(PENALTY_BOX_ADMIN_MATCH, "PENALTY_BOX_ADMIN_MATCH")
);
/**
- * List of all firewall allow chains.
+ * List of all firewall allow chains that are applied to all networks regardless of meteredness
+ * See {@link #METERED_ALLOW_CHAINS} for allow chains that are only applied to metered networks.
*
* Allow chains mean the firewall denies all uids by default, uids must be explicitly allowed.
*/
@@ -110,7 +116,8 @@
);
/**
- * List of all firewall deny chains.
+ * List of all firewall deny chains that are applied to all networks regardless of meteredness
+ * See {@link #METERED_DENY_CHAINS} for deny chains that are only applied to metered networks.
*
* Deny chains mean the firewall allows all uids by default, uids must be explicitly denied.
*/
@@ -120,5 +127,24 @@
FIREWALL_CHAIN_OEM_DENY_2,
FIREWALL_CHAIN_OEM_DENY_3
);
+
+ /**
+ * List of all firewall allow chains that are only applied to metered networks.
+ * See {@link #ALLOW_CHAINS} for allow chains that are applied to all networks regardless of
+ * meteredness.
+ */
+ public static final List<Integer> METERED_ALLOW_CHAINS = List.of(
+ FIREWALL_CHAIN_METERED_ALLOW
+ );
+
+ /**
+ * List of all firewall deny chains that are only applied to metered networks.
+ * See {@link #DENY_CHAINS} for deny chains that are applied to all networks regardless of
+ * meteredness.
+ */
+ public static final List<Integer> METERED_DENY_CHAINS = List.of(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ FIREWALL_CHAIN_METERED_DENY_ADMIN
+ );
// LINT.ThenChange(../../../../bpf_progs/netd.h)
}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 19ecafb..4099e2a 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -25,18 +25,36 @@
import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.LOW_POWER_STANDBY_MATCH;
import static android.net.BpfNetMapsConstants.MATCH_LIST;
+import static android.net.BpfNetMapsConstants.METERED_ALLOW_CHAINS;
+import static android.net.BpfNetMapsConstants.METERED_DENY_CHAINS;
import static android.net.BpfNetMapsConstants.NO_MATCH;
import static android.net.BpfNetMapsConstants.OEM_DENY_1_MATCH;
import static android.net.BpfNetMapsConstants.OEM_DENY_2_MATCH;
import static android.net.BpfNetMapsConstants.OEM_DENY_3_MATCH;
-import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_ADMIN_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_USER_MATCH;
import static android.net.BpfNetMapsConstants.POWERSAVE_MATCH;
import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -47,12 +65,15 @@
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.system.OsConstants.EINVAL;
+import android.os.Build;
import android.os.Process;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Pair;
+import androidx.annotation.RequiresApi;
+
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.Struct;
@@ -70,6 +91,8 @@
// Note that this class should be put into bootclasspath instead of static libraries.
// Because modules could have different copies of this class if this is statically linked,
// which would be problematic if the definitions in these modules are not synchronized.
+// Note that NetworkStack can not use this before U due to b/326143935
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class BpfNetMapsUtils {
// Bitmaps for calculating whether a given uid is blocked by firewall chains.
private static final long sMaskDropIfSet;
@@ -117,6 +140,12 @@
return OEM_DENY_2_MATCH;
case FIREWALL_CHAIN_OEM_DENY_3:
return OEM_DENY_3_MATCH;
+ case FIREWALL_CHAIN_METERED_ALLOW:
+ return HAPPY_BOX_MATCH;
+ case FIREWALL_CHAIN_METERED_DENY_USER:
+ return PENALTY_BOX_USER_MATCH;
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ return PENALTY_BOX_ADMIN_MATCH;
default:
throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
}
@@ -129,9 +158,9 @@
* DENYLIST means the firewall allows all by default, uids must be explicitly denied
*/
public static boolean isFirewallAllowList(final int chain) {
- if (ALLOW_CHAINS.contains(chain)) {
+ if (ALLOW_CHAINS.contains(chain) || METERED_ALLOW_CHAINS.contains(chain)) {
return true;
- } else if (DENY_CHAINS.contains(chain)) {
+ } else if (DENY_CHAINS.contains(chain) || METERED_DENY_CHAINS.contains(chain)) {
return false;
}
throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
@@ -221,6 +250,67 @@
}
/**
+ * Get blocked reasons for specified uid
+ *
+ * @param uid Target Uid
+ * @return Reasons of network access blocking for an UID
+ */
+ public static int getUidNetworkingBlockedReasons(final int uid,
+ IBpfMap<S32, U32> configurationMap,
+ IBpfMap<S32, UidOwnerValue> uidOwnerMap,
+ IBpfMap<S32, U8> dataSaverEnabledMap
+ ) {
+ final long uidRuleConfig;
+ final long uidMatch;
+ try {
+ uidRuleConfig = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
+ final UidOwnerValue value = uidOwnerMap.getValue(new Struct.S32(uid));
+ uidMatch = (value != null) ? value.rule : 0L;
+ } catch (ErrnoException e) {
+ throw new ServiceSpecificException(e.errno,
+ "Unable to get firewall chain status: " + Os.strerror(e.errno));
+ }
+ final long blockingMatches = (uidRuleConfig & ~uidMatch & sMaskDropIfUnset)
+ | (uidRuleConfig & uidMatch & sMaskDropIfSet);
+
+ int blockedReasons = BLOCKED_REASON_NONE;
+ if ((blockingMatches & POWERSAVE_MATCH) != 0) {
+ blockedReasons |= BLOCKED_REASON_BATTERY_SAVER;
+ }
+ if ((blockingMatches & DOZABLE_MATCH) != 0) {
+ blockedReasons |= BLOCKED_REASON_DOZE;
+ }
+ if ((blockingMatches & STANDBY_MATCH) != 0) {
+ blockedReasons |= BLOCKED_REASON_APP_STANDBY;
+ }
+ if ((blockingMatches & RESTRICTED_MATCH) != 0) {
+ blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
+ }
+ if ((blockingMatches & LOW_POWER_STANDBY_MATCH) != 0) {
+ blockedReasons |= BLOCKED_REASON_LOW_POWER_STANDBY;
+ }
+ if ((blockingMatches & BACKGROUND_MATCH) != 0) {
+ blockedReasons |= BLOCKED_REASON_APP_BACKGROUND;
+ }
+ if ((blockingMatches & (OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)) != 0) {
+ blockedReasons |= BLOCKED_REASON_OEM_DENY;
+ }
+
+ // Metered chains are not enabled by configuration map currently.
+ if ((uidMatch & PENALTY_BOX_USER_MATCH) != 0) {
+ blockedReasons |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ }
+ if ((uidMatch & PENALTY_BOX_ADMIN_MATCH) != 0) {
+ blockedReasons |= BLOCKED_METERED_REASON_ADMIN_DISABLED;
+ }
+ if ((uidMatch & HAPPY_BOX_MATCH) == 0 && getDataSaverEnabled(dataSaverEnabledMap)) {
+ blockedReasons |= BLOCKED_METERED_REASON_DATA_SAVER;
+ }
+
+ return blockedReasons;
+ }
+
+ /**
* Return whether the network is blocked by firewall chains for the given uid.
*
* Note that {@link #getDataSaverEnabled(IBpfMap)} has a latency before V.
@@ -246,27 +336,16 @@
return false;
}
- final long uidRuleConfig;
- final long uidMatch;
- try {
- uidRuleConfig = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
- final UidOwnerValue value = uidOwnerMap.getValue(new Struct.S32(uid));
- uidMatch = (value != null) ? value.rule : 0L;
- } catch (ErrnoException e) {
- throw new ServiceSpecificException(e.errno,
- "Unable to get firewall chain status: " + Os.strerror(e.errno));
+ final int blockedReasons = getUidNetworkingBlockedReasons(
+ uid,
+ configurationMap,
+ uidOwnerMap,
+ dataSaverEnabledMap);
+ if (isNetworkMetered) {
+ return blockedReasons != BLOCKED_REASON_NONE;
+ } else {
+ return (blockedReasons & ~BLOCKED_METERED_REASON_MASK) != BLOCKED_REASON_NONE;
}
-
- final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset);
- final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet);
- if (blockedByAllowChains || blockedByDenyChains) {
- return true;
- }
-
- if (!isNetworkMetered) return false;
- if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true;
- if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false;
- return getDataSaverEnabled(dataSaverEnabledMap);
}
/**
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index b1e636d..4eaf973 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -28,6 +28,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
@@ -128,6 +129,12 @@
"com.android.net.flags.support_is_uid_networking_blocked";
static final String BASIC_BACKGROUND_RESTRICTIONS_ENABLED =
"com.android.net.flags.basic_background_restrictions_enabled";
+ static final String METERED_NETWORK_FIREWALL_CHAINS =
+ "com.android.net.flags.metered_network_firewall_chains";
+ static final String BLOCKED_REASON_OEM_DENY_CHAINS =
+ "com.android.net.flags.blocked_reason_oem_deny_chains";
+ static final String BLOCKED_REASON_NETWORK_RESTRICTED =
+ "com.android.net.flags.blocked_reason_network_restricted";
}
/**
@@ -911,6 +918,30 @@
public static final int BLOCKED_REASON_APP_BACKGROUND = 1 << 6;
/**
+ * Flag to indicate that an app is subject to OEM-specific application restrictions that would
+ * result in its network access being blocked.
+ *
+ * @see #FIREWALL_CHAIN_OEM_DENY_1
+ * @see #FIREWALL_CHAIN_OEM_DENY_2
+ * @see #FIREWALL_CHAIN_OEM_DENY_3
+ * @hide
+ */
+ @FlaggedApi(Flags.BLOCKED_REASON_OEM_DENY_CHAINS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_OEM_DENY = 1 << 7;
+
+ /**
+ * Flag to indicate that an app does not have permission to access the specified network,
+ * for example, because it does not have the {@link android.Manifest.permission#INTERNET}
+ * permission.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.BLOCKED_REASON_NETWORK_RESTRICTED)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_NETWORK_RESTRICTED = 1 << 8;
+
+ /**
* Flag to indicate that an app is subject to Data saver restrictions that would
* result in its metered network access being blocked.
*
@@ -950,6 +981,8 @@
BLOCKED_REASON_LOCKDOWN_VPN,
BLOCKED_REASON_LOW_POWER_STANDBY,
BLOCKED_REASON_APP_BACKGROUND,
+ BLOCKED_REASON_OEM_DENY,
+ BLOCKED_REASON_NETWORK_RESTRICTED,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -1068,6 +1101,61 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9;
+ /**
+ * Firewall chain for allow list on metered networks
+ *
+ * UIDs added to this chain have access to metered networks, unless they're also in one of the
+ * denylist, {@link #FIREWALL_CHAIN_METERED_DENY_USER},
+ * {@link #FIREWALL_CHAIN_METERED_DENY_ADMIN}
+ *
+ * Note that this chain is used from a separate bpf program that is triggered by iptables and
+ * can not be controlled by {@link ConnectivityManager#setFirewallChainEnabled}.
+ *
+ * @hide
+ */
+ // TODO: Merge this chain with data saver and support setFirewallChainEnabled
+ @FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_METERED_ALLOW = 10;
+
+ /**
+ * Firewall chain for user-set restrictions on metered networks
+ *
+ * UIDs added to this chain do not have access to metered networks.
+ * UIDs should be added to this chain based on user settings.
+ * To restrict metered network based on admin configuration (e.g. enterprise policies),
+ * {@link #FIREWALL_CHAIN_METERED_DENY_ADMIN} should be used.
+ * This chain corresponds to {@link #BLOCKED_METERED_REASON_USER_RESTRICTED}
+ *
+ * Note that this chain is used from a separate bpf program that is triggered by iptables and
+ * can not be controlled by {@link ConnectivityManager#setFirewallChainEnabled}.
+ *
+ * @hide
+ */
+ // TODO: Support setFirewallChainEnabled to control this chain
+ @FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_METERED_DENY_USER = 11;
+
+ /**
+ * Firewall chain for admin-set restrictions on metered networks
+ *
+ * UIDs added to this chain do not have access to metered networks.
+ * UIDs should be added to this chain based on admin configuration (e.g. enterprise policies).
+ * To restrict metered network based on user settings, {@link #FIREWALL_CHAIN_METERED_DENY_USER}
+ * should be used.
+ * This chain corresponds to {@link #BLOCKED_METERED_REASON_ADMIN_DISABLED}
+ *
+ * Note that this chain is used from a separate bpf program that is triggered by iptables and
+ * can not be controlled by {@link ConnectivityManager#setFirewallChainEnabled}.
+ *
+ * @hide
+ */
+ // TODO: Support setFirewallChainEnabled to control this chain
+ @FlaggedApi(Flags.METERED_NETWORK_FIREWALL_CHAINS)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int FIREWALL_CHAIN_METERED_DENY_ADMIN = 12;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
@@ -1079,7 +1167,10 @@
FIREWALL_CHAIN_BACKGROUND,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2,
- FIREWALL_CHAIN_OEM_DENY_3
+ FIREWALL_CHAIN_OEM_DENY_3,
+ FIREWALL_CHAIN_METERED_ALLOW,
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ FIREWALL_CHAIN_METERED_DENY_ADMIN
})
public @interface FirewallChain {}
@@ -1117,6 +1208,16 @@
})
public @interface FirewallRule {}
+ /** @hide */
+ public static final long FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS = 1L;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = "FEATURE_", value = {
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS
+ })
+ public @interface ConnectivityManagerFeature {}
+
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1130,6 +1231,14 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ private final Object mEnabledConnectivityManagerFeaturesLock = new Object();
+ // mEnabledConnectivityManagerFeatures is lazy-loaded in this ConnectivityManager instance, but
+ // fetched from ConnectivityService, where it is loaded in ConnectivityService startup, so it
+ // should have consistent values.
+ @GuardedBy("sEnabledConnectivityManagerFeaturesLock")
+ @ConnectivityManagerFeature
+ private Long mEnabledConnectivityManagerFeatures = null;
+
private TetheringManager getTetheringManager() {
synchronized (mTetheringEventCallbacks) {
if (mTetheringManager == null) {
@@ -4439,6 +4548,20 @@
return request;
}
+ private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
+ synchronized (mEnabledConnectivityManagerFeaturesLock) {
+ if (mEnabledConnectivityManagerFeatures == null) {
+ try {
+ mEnabledConnectivityManagerFeatures =
+ mService.getEnabledConnectivityManagerFeatures();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ return (mEnabledConnectivityManagerFeatures & connectivityManagerFeature) != 0;
+ }
+ }
+
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType,
@@ -6065,7 +6188,7 @@
})
public void addUidToMeteredNetworkAllowList(final int uid) {
try {
- mService.updateMeteredNetworkAllowList(uid, true /* add */);
+ mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -6088,7 +6211,7 @@
})
public void removeUidFromMeteredNetworkAllowList(final int uid) {
try {
- mService.updateMeteredNetworkAllowList(uid, false /* remove */);
+ mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DENY);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -6098,10 +6221,17 @@
* Adds the specified UID to the list of UIDs that are not allowed to use background data on
* metered networks. Takes precedence over {@link #addUidToMeteredNetworkAllowList}.
*
+ * On V+, {@link #setUidFirewallRule} should be used with
+ * {@link #FIREWALL_CHAIN_METERED_DENY_USER} or {@link #FIREWALL_CHAIN_METERED_DENY_ADMIN}
+ * based on the reason so that users can receive {@link #BLOCKED_METERED_REASON_USER_RESTRICTED}
+ * or {@link #BLOCKED_METERED_REASON_ADMIN_DISABLED}, respectively.
+ * This API always uses {@link #FIREWALL_CHAIN_METERED_DENY_USER}.
+ *
* @param uid uid of target app
* @throws IllegalStateException if updating deny list failed.
* @hide
*/
+ // TODO(b/332649177): Deprecate this API after V
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
@@ -6110,7 +6240,7 @@
})
public void addUidToMeteredNetworkDenyList(final int uid) {
try {
- mService.updateMeteredNetworkDenyList(uid, true /* add */);
+ mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -6121,10 +6251,17 @@
* networks if background data is not restricted. The deny list takes precedence over the
* allow list.
*
+ * On V+, {@link #setUidFirewallRule} should be used with
+ * {@link #FIREWALL_CHAIN_METERED_DENY_USER} or {@link #FIREWALL_CHAIN_METERED_DENY_ADMIN}
+ * based on the reason so that users can receive {@link #BLOCKED_METERED_REASON_USER_RESTRICTED}
+ * or {@link #BLOCKED_METERED_REASON_ADMIN_DISABLED}, respectively.
+ * This API always uses {@link #FIREWALL_CHAIN_METERED_DENY_USER}.
+ *
* @param uid uid of target app
* @throws IllegalStateException if updating deny list failed.
* @hide
*/
+ // TODO(b/332649177): Deprecate this API after V
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
@@ -6133,7 +6270,7 @@
})
public void removeUidFromMeteredNetworkDenyList(final int uid) {
try {
- mService.updateMeteredNetworkDenyList(uid, false /* remove */);
+ mService.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_ALLOW);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -6191,6 +6328,10 @@
/**
* Enables or disables the specified firewall chain.
*
+ * Note that metered firewall chains can not be controlled by this API.
+ * See {@link #FIREWALL_CHAIN_METERED_ALLOW}, {@link #FIREWALL_CHAIN_METERED_DENY_USER}, and
+ * {@link #FIREWALL_CHAIN_METERED_DENY_ADMIN} for more detail.
+ *
* @param chain target chain.
* @param enable whether the chain should be enabled.
* @throws UnsupportedOperationException if called on pre-T devices.
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index d3a02b9..f9de8ed 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -242,10 +242,6 @@
void setDataSaverEnabled(boolean enable);
- void updateMeteredNetworkAllowList(int uid, boolean add);
-
- void updateMeteredNetworkDenyList(int uid, boolean add);
-
void setUidFirewallRule(int chain, int uid, int rule);
int getUidFirewallRule(int chain, int uid);
@@ -263,4 +259,6 @@
void setTestLowTcpPollingTimerForKeepalive(long timeMs);
IBinder getRoutingCoordinatorService();
+
+ long getEnabledConnectivityManagerFeatures();
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 85b1dac..a5a6723 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -36,6 +36,7 @@
import android.os.Process;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
@@ -749,22 +750,22 @@
* Network capabilities that are expected to be mutable, i.e., can change while a particular
* network is connected.
*/
- private static final long MUTABLE_CAPABILITIES = BitUtils.packBitList(
+ private static final long MUTABLE_CAPABILITIES =
// TRUSTED can change when user explicitly connects to an untrusted network in Settings.
// http://b/18206275
- NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_VALIDATED,
- NET_CAPABILITY_CAPTIVE_PORTAL,
- NET_CAPABILITY_NOT_ROAMING,
- NET_CAPABILITY_FOREGROUND,
- NET_CAPABILITY_NOT_CONGESTED,
- NET_CAPABILITY_NOT_SUSPENDED,
- NET_CAPABILITY_PARTIAL_CONNECTIVITY,
- NET_CAPABILITY_TEMPORARILY_NOT_METERED,
- NET_CAPABILITY_NOT_VCN_MANAGED,
+ (1L << NET_CAPABILITY_TRUSTED) |
+ (1L << NET_CAPABILITY_VALIDATED) |
+ (1L << NET_CAPABILITY_CAPTIVE_PORTAL) |
+ (1L << NET_CAPABILITY_NOT_ROAMING) |
+ (1L << NET_CAPABILITY_FOREGROUND) |
+ (1L << NET_CAPABILITY_NOT_CONGESTED) |
+ (1L << NET_CAPABILITY_NOT_SUSPENDED) |
+ (1L << NET_CAPABILITY_PARTIAL_CONNECTIVITY) |
+ (1L << NET_CAPABILITY_TEMPORARILY_NOT_METERED) |
+ (1L << NET_CAPABILITY_NOT_VCN_MANAGED) |
// The value of NET_CAPABILITY_HEAD_UNIT is 32, which cannot use int to do bit shift,
// otherwise there will be an overflow. Use long to do bit shift instead.
- NET_CAPABILITY_HEAD_UNIT);
+ (1L << NET_CAPABILITY_HEAD_UNIT);
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -784,10 +785,10 @@
/**
* Capabilities that are set by default when the object is constructed.
*/
- private static final long DEFAULT_CAPABILITIES = BitUtils.packBitList(
- NET_CAPABILITY_NOT_RESTRICTED,
- NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_NOT_VPN);
+ private static final long DEFAULT_CAPABILITIES =
+ (1L << NET_CAPABILITY_NOT_RESTRICTED) |
+ (1L << NET_CAPABILITY_TRUSTED) |
+ (1L << NET_CAPABILITY_NOT_VPN);
/**
* Capabilities that are managed by ConnectivityService.
@@ -795,11 +796,10 @@
*/
@VisibleForTesting
public static final long CONNECTIVITY_MANAGED_CAPABILITIES =
- BitUtils.packBitList(
- NET_CAPABILITY_VALIDATED,
- NET_CAPABILITY_CAPTIVE_PORTAL,
- NET_CAPABILITY_FOREGROUND,
- NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ (1L << NET_CAPABILITY_VALIDATED) |
+ (1L << NET_CAPABILITY_CAPTIVE_PORTAL) |
+ (1L << NET_CAPABILITY_FOREGROUND) |
+ (1L << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
/**
* Capabilities that are allowed for all test networks. This list must be set so that it is safe
@@ -808,15 +808,14 @@
* IMS, SUPL, etc.
*/
private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
- BitUtils.packBitList(
- NET_CAPABILITY_NOT_METERED,
- NET_CAPABILITY_TEMPORARILY_NOT_METERED,
- NET_CAPABILITY_NOT_RESTRICTED,
- NET_CAPABILITY_NOT_VPN,
- NET_CAPABILITY_NOT_ROAMING,
- NET_CAPABILITY_NOT_CONGESTED,
- NET_CAPABILITY_NOT_SUSPENDED,
- NET_CAPABILITY_NOT_VCN_MANAGED);
+ (1L << NET_CAPABILITY_NOT_METERED) |
+ (1L << NET_CAPABILITY_TEMPORARILY_NOT_METERED) |
+ (1L << NET_CAPABILITY_NOT_RESTRICTED) |
+ (1L << NET_CAPABILITY_NOT_VPN) |
+ (1L << NET_CAPABILITY_NOT_ROAMING) |
+ (1L << NET_CAPABILITY_NOT_CONGESTED) |
+ (1L << NET_CAPABILITY_NOT_SUSPENDED) |
+ (1L << NET_CAPABILITY_NOT_VCN_MANAGED);
/**
* Extra allowed capabilities for test networks that do not have TRANSPORT_CELLULAR. Test
@@ -824,7 +823,9 @@
* the risk of being used by running apps.
*/
private static final long TEST_NETWORKS_EXTRA_ALLOWED_CAPABILITIES_ON_NON_CELL =
- BitUtils.packBitList(NET_CAPABILITY_CBS, NET_CAPABILITY_DUN, NET_CAPABILITY_RCS);
+ (1L << NET_CAPABILITY_CBS) |
+ (1L << NET_CAPABILITY_DUN) |
+ (1L << NET_CAPABILITY_RCS);
/**
* Adds the given capability to this {@code NetworkCapability} instance.
@@ -843,7 +844,10 @@
// If the given capability was previously added to the list of forbidden capabilities
// then the capability will also be removed from the list of forbidden capabilities.
// TODO: Add forbidden capabilities to the public API
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addCapability is called with invalid capability: " + capability);
+ return this;
+ }
mNetworkCapabilities |= 1L << capability;
// remove from forbidden capability list
mForbiddenNetworkCapabilities &= ~(1L << capability);
@@ -864,7 +868,10 @@
* @hide
*/
public void addForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addForbiddenCapability is called with invalid capability: " + capability);
+ return;
+ }
mForbiddenNetworkCapabilities |= 1L << capability;
mNetworkCapabilities &= ~(1L << capability); // remove from requested capabilities
}
@@ -878,7 +885,10 @@
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "removeCapability is called with invalid capability: " + capability);
+ return this;
+ }
final long mask = ~(1L << capability);
mNetworkCapabilities &= mask;
return this;
@@ -893,7 +903,11 @@
* @hide
*/
public @NonNull NetworkCapabilities removeForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG,
+ "removeForbiddenCapability is called with invalid capability: " + capability);
+ return this;
+ }
mForbiddenNetworkCapabilities &= ~(1L << capability);
return this;
}
@@ -1174,7 +1188,7 @@
* @hide
*/
public void maybeMarkCapabilitiesRestricted() {
- if (NetworkCapabilitiesUtils.inferRestrictedCapability(this)) {
+ if (NetworkCapabilitiesUtils.inferRestrictedCapability(mNetworkCapabilities)) {
removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
@@ -1368,12 +1382,11 @@
* Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST).
*/
private static final long UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
- BitUtils.packBitList(
- TRANSPORT_TEST,
- // Test eth networks are created with EthernetManager#setIncludeTestInterfaces
- TRANSPORT_ETHERNET,
- // Test VPN networks can be created but their UID ranges must be empty.
- TRANSPORT_VPN);
+ (1L << TRANSPORT_TEST) |
+ // Test eth networks are created with EthernetManager#setIncludeTestInterfaces
+ (1L << TRANSPORT_ETHERNET) |
+ // Test VPN networks can be created but their UID ranges must be empty.
+ (1L << TRANSPORT_VPN);
/**
* Adds the given transport type to this {@code NetworkCapability} instance.
@@ -2633,12 +2646,6 @@
return capability >= 0 && capability <= MAX_NET_CAPABILITY;
}
- private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
- if (!isValidCapability(capability)) {
- throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
- }
- }
-
private static boolean isValidEnterpriseId(
@NetworkCapabilities.EnterpriseId int enterpriseId) {
return enterpriseId >= NET_ENTERPRISE_ID_1
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index fae2499..6b18629 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -22,6 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* APF program support capabilities. APF stands for Android Packet Filtering and it is a flexible
* way to drop unwanted network packets to save power.
@@ -102,15 +104,20 @@
&& apfPacketFormat == other.apfPacketFormat;
}
+ @Override
+ public int hashCode() {
+ return Objects.hash(apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
+ }
+
/**
* Determines whether the APF interpreter advertises support for the data buffer access opcodes
* LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and
- * STDW (STore Data Word) support is present from APFv4 on.
+ * STDW (STore Data Word) support is present from APFv3 on.
*
* @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported.
*/
public boolean hasDataAccess() {
- return apfVersionSupported >= 4;
+ return apfVersionSupported > 2;
}
/**
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index a80db85..51df8ab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -99,6 +99,42 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long ENABLE_MATCH_LOCAL_NETWORK = 319212206L;
+ /**
+ * On Android {@link android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM} or higher releases,
+ * network access from apps targeting Android 36 or higher that do not have the
+ * {@link android.Manifest.permission#INTERNET} permission is considered blocked.
+ * This results in API behaviors change for apps without
+ * {@link android.Manifest.permission#INTERNET} permission.
+ * {@link android.net.NetworkInfo} returned from {@link android.net.ConnectivityManager} APIs
+ * always has {@link android.net.NetworkInfo.DetailedState#BLOCKED}.
+ * {@link android.net.ConnectivityManager#getActiveNetwork()} always returns null.
+ * {@link android.net.ConnectivityManager.NetworkCallback#onBlockedStatusChanged()} is always
+ * called with blocked=true.
+ * <p>
+ * For backwards compatibility, apps running on older releases, or targeting older SDK levels,
+ * network access from apps without {@link android.Manifest.permission#INTERNET} permission is
+ * considered not blocked even though apps cannot access any networks.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
+
+ /**
+ * Enable caching for TrafficStats#get* APIs.
+ *
+ * Apps targeting Android V or later or running on Android V or later may take up to several
+ * seconds to see the updated results.
+ * Apps targeting lower android SDKs do not see cached result for backward compatibility,
+ * results of TrafficStats#get* APIs are reflecting network statistics immediately.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE = 74210811L;
+
private ConnectivityCompatChanges() {
}
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 832ac03..576e806 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -28,9 +28,7 @@
import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.cts.BTAdapterUtils;
+import android.bluetooth.test_utils.EnableBluetoothRule;
import android.content.Context;
import android.location.LocationManager;
import android.nearby.BroadcastCallback;
@@ -58,6 +56,7 @@
import com.android.modules.utils.build.SdkLevel;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,6 +75,9 @@
@RunWith(AndroidJUnit4.class)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class NearbyManagerTest {
+
+ @ClassRule static final EnableBluetoothRule sEnableBluetooth = new EnableBluetoothRule();
+
private static final byte[] SALT = new byte[]{1, 2};
private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
private static final byte[] META_DATA_ENCRYPTION_KEY = new byte[14];
@@ -128,8 +130,6 @@
mContext = InstrumentationRegistry.getContext();
mNearbyManager = mContext.getSystemService(NearbyManager.class);
-
- enableBluetooth();
}
@Test
@@ -281,14 +281,6 @@
assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode());
}
- private void enableBluetooth() {
- BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
- BluetoothAdapter bluetoothAdapter = manager.getAdapter();
- if (!bluetoothAdapter.isEnabled()) {
- assertThat(BTAdapterUtils.enableAdapter(bluetoothAdapter, mContext)).isTrue();
- }
- }
-
private void enableLocation() {
LocationManager locationManager = mContext.getSystemService(LocationManager.class);
UserHandle user = Process.myUserHandle();
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index c39b46c..f278695 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -75,3 +75,10 @@
filename: "netbpfload.33rc",
installable: false,
}
+
+prebuilt_etc {
+ name: "netbpfload.35rc",
+ src: "netbpfload.35rc",
+ filename: "netbpfload.35rc",
+ installable: false,
+}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 83bb98c..80df552 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -51,23 +51,23 @@
#include "bpf/BpfUtils.h"
#include "loader.h"
-using android::base::EndsWith;
-using android::bpf::domain;
+namespace android {
+namespace bpf {
+
+using base::StartsWith;
+using base::EndsWith;
using std::string;
-bool exists(const char* const path) {
+static bool exists(const char* const path) {
int v = access(path, F_OK);
- if (!v) {
- ALOGI("%s exists.", path);
- return true;
- }
+ if (!v) return true;
if (errno == ENOENT) return false;
ALOGE("FATAL: access(%s, F_OK) -> %d [%d:%s]", path, v, errno, strerror(errno));
abort(); // can only hit this if permissions (likely selinux) are screwed up
}
-const android::bpf::Location locations[] = {
+const Location locations[] = {
// S+ Tethering mainline module (network_stack): tether offload
{
.dir = "/apex/com.android.tethering/etc/bpf/",
@@ -97,7 +97,7 @@
},
};
-int loadAllElfObjects(const unsigned int bpfloader_ver, const android::bpf::Location& location) {
+static int loadAllElfObjects(const unsigned int bpfloader_ver, const Location& location) {
int retVal = 0;
DIR* dir;
struct dirent* ent;
@@ -111,12 +111,12 @@
progPath += s;
bool critical;
- int ret = android::bpf::loadProg(progPath.c_str(), &critical, bpfloader_ver, location);
+ int ret = loadProg(progPath.c_str(), &critical, bpfloader_ver, location);
if (ret) {
if (critical) retVal = ret;
ALOGE("Failed to load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret));
} else {
- ALOGI("Loaded object: %s", progPath.c_str());
+ ALOGD("Loaded object: %s", progPath.c_str());
}
}
closedir(dir);
@@ -124,7 +124,7 @@
return retVal;
}
-int createSysFsBpfSubDir(const char* const prefix) {
+static int createSysFsBpfSubDir(const char* const prefix) {
if (*prefix) {
mode_t prevUmask = umask(0);
@@ -147,8 +147,8 @@
// Technically 'value' doesn't need to be newline terminated, but it's best
// to include a newline to match 'echo "value" > /proc/sys/...foo' behaviour,
// which is usually how kernel devs test the actual sysctl interfaces.
-int writeProcSysFile(const char *filename, const char *value) {
- android::base::unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC));
+static int writeProcSysFile(const char *filename, const char *value) {
+ base::unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC));
if (fd < 0) {
const int err = errno;
ALOGE("open('%s', O_WRONLY | O_CLOEXEC) -> %s", filename, strerror(err));
@@ -172,7 +172,7 @@
#define APEX_MOUNT_POINT "/apex/com.android.tethering"
const char * const platformBpfLoader = "/system/bin/bpfloader";
-int logTetheringApexVersion(void) {
+static int logTetheringApexVersion(void) {
char * found_blockdev = NULL;
FILE * f = NULL;
char buf[4096];
@@ -198,7 +198,7 @@
f = NULL;
if (!found_blockdev) return 2;
- ALOGD("Found Tethering Apex mounted from blockdev %s", found_blockdev);
+ ALOGV("Found Tethering Apex mounted from blockdev %s", found_blockdev);
f = fopen("/proc/mounts", "re");
if (!f) { free(found_blockdev); return 3; }
@@ -224,12 +224,36 @@
return 0;
}
-int main(int argc, char** argv, char * const envp[]) {
- (void)argc;
- android::base::InitLogging(argv, &android::base::KernelLogger);
+static bool isGSI() {
+ // From //system/gsid/libgsi.cpp IsGsiRunning()
+ return !access("/metadata/gsi/dsu/booted", F_OK);
+}
- ALOGI("NetBpfLoad '%s' starting...", argv[0]);
+static bool hasGSM() {
+ static string ph = base::GetProperty("gsm.current.phone-type", "");
+ static bool gsm = (ph != "");
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("hasGSM(gsm.current.phone-type='%s'): %s", ph.c_str(), gsm ? "true" : "false");
+ }
+ return gsm;
+}
+static bool isTV() {
+ if (hasGSM()) return false; // TVs don't do GSM
+
+ static string key = base::GetProperty("ro.oem.key1", "");
+ static bool tv = StartsWith(key, "ATV00");
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isTV(ro.oem.key1='%s'): %s.", key.c_str(), tv ? "true" : "false");
+ }
+ return tv;
+}
+
+static int doLoad(char** argv, char * const envp[]) {
const int device_api_level = android_get_device_api_level();
const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
@@ -240,9 +264,9 @@
// first in U QPR2 beta~2
const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
- ALOGI("NetBpfLoad api:%d/%d kver:%07x rc:%d%d",
- android_get_application_target_sdk_version(), device_api_level,
- android::bpf::kernelVersion(),
+ ALOGI("NetBpfLoad (%s) api:%d/%d kver:%07x (%s) rc:%d%d",
+ argv[0], android_get_application_target_sdk_version(), device_api_level,
+ kernelVersion(), describeArch(),
has_platform_bpfloader_rc, has_platform_netbpfload_rc);
if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
@@ -262,27 +286,67 @@
return 1;
}
- if (isAtLeastT && !android::bpf::isAtLeastKernelVersion(4, 9, 0)) {
+ // both S and T require kernel 4.9 (and eBpf support)
+ if (isAtLeastT && !isAtLeastKernelVersion(4, 9, 0)) {
ALOGE("Android T requires kernel 4.9.");
return 1;
}
- if (isAtLeastU && !android::bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ // U bumps the kernel requirement up to 4.14
+ if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
ALOGE("Android U requires kernel 4.14.");
return 1;
}
- if (isAtLeastV && !android::bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ // V bumps the kernel requirement up to 4.19
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel419
+ if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
ALOGE("Android V requires kernel 4.19.");
return 1;
}
- if (isAtLeastV && android::bpf::isX86() && !android::bpf::isKernel64Bit()) {
- ALOGE("Android V requires X86 kernel to be 64-bit.");
- return 1;
+ // Technically already required by U, but only enforce on V+
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
+ if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) {
+ ALOGE("Android V+ platform with 32 bit kernel version >= 5.16.0 is unsupported");
+ if (!isTV()) return 1;
}
- if (android::bpf::isUserspace32bit() && android::bpf::isAtLeastKernelVersion(6, 2, 0)) {
+ // Various known ABI layout issues, particularly wrt. bpf and ipsec/xfrm.
+ if (isAtLeastV && isKernel32Bit() && isX86()) {
+ ALOGE("Android V requires X86 kernel to be 64-bit.");
+ if (!isTV()) return 1;
+ }
+
+ if (isAtLeastV) {
+ bool bad = false;
+
+ if (!isLtsKernel()) {
+ ALOGW("Android V only supports LTS kernels.");
+ bad = true;
+ }
+
+#define REQUIRE(maj, min, sub) \
+ if (isKernelVersion(maj, min) && !isAtLeastKernelVersion(maj, min, sub)) { \
+ ALOGW("Android V requires %d.%d kernel to be %d.%d.%d+.", maj, min, maj, min, sub); \
+ bad = true; \
+ }
+
+ REQUIRE(4, 19, 236)
+ REQUIRE(5, 4, 186)
+ REQUIRE(5, 10, 199)
+ REQUIRE(5, 15, 136)
+ REQUIRE(6, 1, 57)
+ REQUIRE(6, 6, 0)
+
+#undef REQUIRE
+
+ if (bad && !isGSI()) {
+ ALOGE("Unsupported kernel version (%07x).", kernelVersion());
+ }
+ }
+
+ if (isUserspace32bit() && isAtLeastKernelVersion(6, 2, 0)) {
/* Android 14/U should only launch on 64-bit kernels
* T launches on 5.10/5.15
* U launches on 5.15/6.1
@@ -298,28 +362,32 @@
* Some of these have userspace or kernel workarounds/hacks.
* Some of them don't...
* We're going to be removing the hacks.
+ * (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
+ * Note: this check/enforcement only applies to *system* userspace code,
+ * it does not affect unprivileged apps, the 32-on-64 compatibility
+ * problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
*
* Additionally the 32-bit kernel jit support is poor,
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
*/
ALOGE("64-bit userspace required on 6.2+ kernels.");
- return 1;
+ if (!isTV()) return 1;
}
// Ensure we can determine the Android build type.
- if (!android::bpf::isEng() && !android::bpf::isUser() && !android::bpf::isUserdebug()) {
+ if (!isEng() && !isUser() && !isUserdebug()) {
ALOGE("Failed to determine the build type: got %s, want 'eng', 'user', or 'userdebug'",
- android::bpf::getBuildType().c_str());
+ getBuildType().c_str());
return 1;
}
- if (false && isAtLeastV) {
+ if (isAtLeastV) {
// Linux 5.16-rc1 changed the default to 2 (disabled but changeable),
// but we need 0 (enabled)
// (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
// pre-5.13, on 5.13+ it depends on CONFIG_BPF_UNPRIV_DEFAULT_OFF)
if (writeProcSysFile("/proc/sys/kernel/unprivileged_bpf_disabled", "0\n") &&
- android::bpf::isAtLeastKernelVersion(5, 13, 0)) return 1;
+ isAtLeastKernelVersion(5, 13, 0)) return 1;
}
if (isAtLeastU) {
@@ -373,14 +441,14 @@
int key = 1;
int value = 123;
- android::base::unique_fd map(
- android::bpf::createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0));
- if (android::bpf::writeToMapEntry(map, &key, &value, BPF_ANY)) {
+ base::unique_fd map(
+ createMap(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 2, 0));
+ if (writeToMapEntry(map, &key, &value, BPF_ANY)) {
ALOGE("Critical kernel bug - failure to write into index 1 of 2 element bpf map array.");
return 1;
}
- if (false && isAtLeastV) {
+ if (isAtLeastV) {
ALOGI("done, transferring control to platform bpfloader.");
const char * args[] = { platformBpfLoader, NULL, };
@@ -392,3 +460,22 @@
ALOGI("mainline done!");
return 0;
}
+
+} // namespace bpf
+} // namespace android
+
+int main(int argc, char** argv, char * const envp[]) {
+ android::base::InitLogging(argv, &android::base::KernelLogger);
+
+ if (argc == 2 && !strcmp(argv[1], "done")) {
+ // we're being re-exec'ed from platform bpfloader to 'finalize' things
+ if (!android::base::SetProperty("bpf.progs_loaded", "1")) {
+ ALOGE("Failed to set bpf.progs_loaded property to 1.");
+ return 125;
+ }
+ ALOGI("success.");
+ return 0;
+ }
+
+ return android::bpf::doLoad(argv, envp);
+}
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
index 9dd0d2a..289b4d7 100644
--- a/netbpfload/loader.cpp
+++ b/netbpfload/loader.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "NetBpfLoader"
+#define LOG_TAG "NetBpfLoad"
#include <errno.h>
#include <fcntl.h>
@@ -512,7 +512,7 @@
ret = readSectionByIdx(elfFile, i, cs_temp.data);
if (ret) return ret;
- ALOGD("Loaded code section %d (%s)", i, name.c_str());
+ ALOGV("Loaded code section %d (%s)", i, name.c_str());
vector<string> csSymNames;
ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
@@ -532,13 +532,13 @@
if (name == (".rel" + oldName)) {
ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
if (ret) return ret;
- ALOGD("Loaded relo section %d (%s)", i, name.c_str());
+ ALOGV("Loaded relo section %d (%s)", i, name.c_str());
}
}
if (cs_temp.data.size() > 0) {
cs.push_back(std::move(cs_temp));
- ALOGD("Adding section %d to cs list", i);
+ ALOGV("Adding section %d to cs list", i);
}
}
return 0;
@@ -736,15 +736,15 @@
domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
if (specified(selinux_context)) {
ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
- md[i].selinux_context, selinux_context, lookupSelinuxContext(selinux_context),
- lookupPinSubdir(selinux_context));
+ md[i].selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
}
domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
if (unrecognized(pin_subdir)) return -ENOTDIR;
if (specified(pin_subdir)) {
ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
- pin_subdir, lookupPinSubdir(pin_subdir));
+ static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
}
// Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
@@ -769,7 +769,7 @@
.max_entries = max_entries,
.map_flags = md[i].map_flags,
};
- if (isAtLeastKernelVersion(4, 14, 0))
+ if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
fd.reset(bpf(BPF_MAP_CREATE, req));
saved_errno = errno;
@@ -869,7 +869,7 @@
// Occasionally might be useful for relocation debugging, but pretty spammy
if (0) {
- ALOGD("applying relo to instruction at byte offset: %llu, "
+ ALOGV("applying relo to instruction at byte offset: %llu, "
"insn offset %d, insn %llx",
(unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
}
@@ -974,13 +974,14 @@
if (specified(selinux_context)) {
ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
- cs[i].prog_def->selinux_context, selinux_context,
+ cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
}
if (specified(pin_subdir)) {
ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
- cs[i].prog_def->pin_subdir, pin_subdir, lookupPinSubdir(pin_subdir));
+ cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
+ lookupPinSubdir(pin_subdir));
}
// strip any potential $foo suffix
@@ -1012,7 +1013,7 @@
.log_size = static_cast<__u32>(log_buf.size()),
.expected_attach_type = cs[i].expected_attach_type,
};
- if (isAtLeastKernelVersion(4, 14, 0))
+ if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
fd.reset(bpf(BPF_PROG_LOAD, req));
@@ -1177,7 +1178,7 @@
}
for (int i = 0; i < (int)mapFds.size(); i++)
- ALOGD("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
+ ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
applyMapRelo(elfFile, mapFds, cs);
diff --git a/netbpfload/netbpfload.35rc b/netbpfload/netbpfload.35rc
new file mode 100644
index 0000000..0fbcb5a
--- /dev/null
+++ b/netbpfload/netbpfload.35rc
@@ -0,0 +1,9 @@
+service bpfloader /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ override
diff --git a/netd/Android.bp b/netd/Android.bp
index eedbdae..fe4d999 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -72,6 +72,8 @@
"BpfHandlerTest.cpp",
"BpfBaseTest.cpp",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libnetd_updatable",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 0d75c05..925ee50 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "BpfHandler"
+#define LOG_TAG "NetdUpdatable"
#include "BpfHandler.h"
@@ -85,31 +85,6 @@
return Status("U+ platform with kernel version < 4.14.0 is unsupported");
}
- if (modules::sdklevel::IsAtLeastV()) {
- // V bumps the kernel requirement up to 4.19
- // see also: //system/netd/tests/kernel_test.cpp TestKernel419
- if (!bpf::isAtLeastKernelVersion(4, 19, 0)) {
- return Status("V+ platform with kernel version < 4.19.0 is unsupported");
- }
-
- // Technically already required by U, but only enforce on V+
- // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
- if (bpf::isKernel32Bit() && bpf::isAtLeastKernelVersion(5, 16, 0)) {
- return Status("V+ platform with 32 bit kernel, version >= 5.16.0 is unsupported");
- }
- }
-
- // Linux 6.1 is highest version supported by U, starting with V new kernels,
- // ie. 6.2+ we are dropping various kernel/system userspace 32-on-64 hacks
- // (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
- // Note: this check/enforcement only applies to *system* userspace code,
- // it does not affect unprivileged apps, the 32-on-64 compatibility
- // problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
- // see also: //system/bpf/bpfloader/BpfLoader.cpp main()
- if (bpf::isUserspace32bit() && bpf::isAtLeastKernelVersion(6, 2, 0)) {
- return Status("32 bit userspace with Kernel version >= 6.2.0 is unsupported");
- }
-
// U mandates this mount point (though it should also be the case on T)
if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
@@ -183,7 +158,8 @@
// Make sure BPF programs are loaded before doing anything
ALOGI("Waiting for BPF programs");
- if (true || !modules::sdklevel::IsAtLeastV()) {
+ // TODO: use !modules::sdklevel::IsAtLeastV() once api finalized
+ if (android_get_device_api_level() < __ANDROID_API_V__) {
waitForNetProgsLoaded();
ALOGI("Networking BPF programs are loaded");
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
index c0ac779..fc91e0c 100644
--- a/remoteauth/service/jni/Android.bp
+++ b/remoteauth/service/jni/Android.bp
@@ -12,7 +12,7 @@
srcs: ["src/lib.rs"],
rustlibs: [
"libbinder_rs",
- "libjni",
+ "libjni_legacy",
"liblazy_static",
"liblog_rust",
"liblogger",
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index c620634..b1925bd 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -76,6 +76,8 @@
"-Wno-unused-parameter",
"-Wthread-safety",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libgmock",
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 42a922d..a92dfaf 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -193,7 +193,8 @@
* @param sentQueryCount The count of sent queries before stop discovery.
*/
public void reportServiceDiscoveryStop(boolean isLegacy, int transactionId, long durationMs,
- int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount) {
+ int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount,
+ boolean isServiceFromCache) {
final Builder builder = makeReportedBuilder(isLegacy, transactionId);
builder.setType(NsdEventType.NET_DISCOVER);
builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STOP);
@@ -202,6 +203,7 @@
builder.setLostCallbackCount(lostCallbackCount);
builder.setFoundServiceCount(servicesCount);
builder.setSentQueryCount(sentQueryCount);
+ builder.setIsKnownService(isServiceFromCache);
mDependencies.statsWrite(builder.build());
}
@@ -247,12 +249,15 @@
* @param isLegacy Whether this call is using legacy backend.
* @param transactionId The transaction id of service resolution.
* @param durationMs The duration before stop resolving the service.
+ * @param sentQueryCount The count of sent queries during resolving.
*/
- public void reportServiceResolutionStop(boolean isLegacy, int transactionId, long durationMs) {
+ public void reportServiceResolutionStop(boolean isLegacy, int transactionId, long durationMs,
+ int sentQueryCount) {
final Builder builder = makeReportedBuilder(isLegacy, transactionId);
builder.setType(NsdEventType.NET_RESOLVE);
builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLUTION_STOP);
builder.setEventDurationMillisec(durationMs);
+ builder.setSentQueryCount(sentQueryCount);
mDependencies.statsWrite(builder.build());
}
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index ea91e64..54b9ced 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -1877,6 +1877,10 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE, "IpsecService#getTransformState");
+ if (transformId == INVALID_RESOURCE_ID) {
+ throw new IllegalStateException("This transform is already closed");
+ }
+
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
TransformRecord transformInfo =
userRecord.mTransformRecords.getResourceOrThrow(transformId);
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index aca386f..f8b0d53 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -201,6 +201,7 @@
private static final int NO_SENT_QUERY_COUNT = 0;
private static final int DISCOVERY_QUERY_SENT_CALLBACK = 1000;
private static final int MAX_SUBTYPE_COUNT = 100;
+ private static final int DNSSEC_PROTOCOL = 3;
private static final SharedLog LOGGER = new SharedLog("serviceDiscovery");
private final Context mContext;
@@ -275,7 +276,7 @@
}
@VisibleForTesting
- static class MdnsListener implements MdnsServiceBrowserListener {
+ abstract static class MdnsListener implements MdnsServiceBrowserListener {
protected final int mClientRequestId;
protected final int mTransactionId;
@NonNull
@@ -321,6 +322,10 @@
@Override
public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) { }
+
+ // Ensure toString gets overridden
+ @NonNull
+ public abstract String toString();
}
private class DiscoveryListener extends MdnsListener {
@@ -351,13 +356,21 @@
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return String.format("DiscoveryListener: serviceType=%s", getListenedServiceType());
+ }
}
private class ResolutionListener extends MdnsListener {
+ private final String mServiceName;
ResolutionListener(int clientRequestId, int transactionId,
- @NonNull String listenServiceType) {
+ @NonNull String listenServiceType, @NonNull String serviceName) {
super(clientRequestId, transactionId, listenServiceType);
+ mServiceName = serviceName;
}
@Override
@@ -373,13 +386,22 @@
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return String.format("ResolutionListener serviceName=%s, serviceType=%s",
+ mServiceName, getListenedServiceType());
+ }
}
private class ServiceInfoListener extends MdnsListener {
+ private final String mServiceName;
ServiceInfoListener(int clientRequestId, int transactionId,
- @NonNull String listenServiceType) {
+ @NonNull String listenServiceType, @NonNull String serviceName) {
super(clientRequestId, transactionId, listenServiceType);
+ this.mServiceName = serviceName;
}
@Override
@@ -410,6 +432,13 @@
mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return String.format("ServiceInfoListener serviceName=%s, serviceType=%s",
+ mServiceName, getListenedServiceType());
+ }
}
private class SocketRequestMonitor implements MdnsSocketProvider.SocketRequestMonitor {
@@ -656,9 +685,12 @@
}
private void storeAdvertiserRequestMap(int clientRequestId, int transactionId,
- ClientInfo clientInfo, @Nullable Network requestedNetwork) {
+ ClientInfo clientInfo, @NonNull NsdServiceInfo serviceInfo) {
+ final String serviceFullName =
+ serviceInfo.getServiceName() + "." + serviceInfo.getServiceType();
clientInfo.mClientRequests.put(clientRequestId, new AdvertiserClientRequest(
- transactionId, requestedNetwork, mClock.elapsedRealtime()));
+ transactionId, serviceInfo.getNetwork(), serviceFullName,
+ mClock.elapsedRealtime()));
mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
updateMulticastLock();
}
@@ -978,6 +1010,17 @@
break;
}
+ if (!checkPublicKey(serviceInfo.getPublicKey())) {
+ Log.e(TAG,
+ "Invalid public key: "
+ + Arrays.toString(serviceInfo.getPublicKey()));
+ clientInfo.onRegisterServiceFailedImmediately(
+ clientRequestId,
+ NsdManager.FAILURE_BAD_PARAMETERS,
+ false /* isLegacy */);
+ break;
+ }
+
Set<String> subtypes = new ArraySet<>(serviceInfo.getSubtypes());
if (typeSubtype != null && typeSubtype.second != null) {
for (String subType : typeSubtype.second) {
@@ -1010,7 +1053,7 @@
mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
mdnsAdvertisingOptions, clientInfo.mUid);
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
- serviceInfo.getNetwork());
+ serviceInfo);
} else {
maybeStartDaemon();
transactionId = getUniqueId();
@@ -1104,9 +1147,12 @@
maybeStartMonitoringSockets();
final MdnsListener listener = new ResolutionListener(clientRequestId,
- transactionId, resolveServiceType);
+ transactionId, resolveServiceType, info.getServiceName());
+ final int ifaceIdx = info.getNetwork() != null
+ ? 0 : info.getInterfaceIndex();
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
+ .setInterfaceIndex(ifaceIdx)
.setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
? AGGRESSIVE_QUERY_MODE
: PASSIVE_QUERY_MODE)
@@ -1204,9 +1250,12 @@
maybeStartMonitoringSockets();
final MdnsListener listener = new ServiceInfoListener(clientRequestId,
- transactionId, resolveServiceType);
+ transactionId, resolveServiceType, info.getServiceName());
+ final int ifIndex = info.getNetwork() != null
+ ? 0 : info.getInterfaceIndex();
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
+ .setInterfaceIndex(ifIndex)
.setQueryMode(mMdnsFeatureFlags.isAggressiveQueryModeEnabled()
? AGGRESSIVE_QUERY_MODE
: PASSIVE_QUERY_MODE)
@@ -1585,6 +1634,12 @@
NsdManager.nameOf(code), transactionId));
switch (code) {
case NsdManager.SERVICE_FOUND:
+ // Set the ServiceFromCache flag only if the service is actually being
+ // retrieved from the cache. This flag should not be overridden by later
+ // service found event, which may not be cached.
+ if (event.mIsServiceFromCache) {
+ request.setServiceFromCache(true);
+ }
clientInfo.onServiceFound(clientRequestId, info, request);
break;
case NsdManager.SERVICE_LOST:
@@ -1788,15 +1843,42 @@
* <p>For now NsdService only allows single-label hostnames conforming to RFC 1035. In other
* words, the hostname should be at most 63 characters long and it only contains letters, digits
* and hyphens.
+ *
+ * <p>Additionally, this allows hostname starting with a digit to support Matter devices. Per
+ * Matter spec 4.3.1.1:
+ *
+ * <p>The target host name SHALL be constructed using one of the available link-layer addresses,
+ * such as a 48-bit device MAC address (for Ethernet and Wi‑Fi) or a 64-bit MAC Extended Address
+ * (for Thread) expressed as a fixed-length twelve-character (or sixteen-character) hexadecimal
+ * string, encoded as ASCII (UTF-8) text using capital letters, e.g., B75AFB458ECD.<domain>.
*/
public static boolean checkHostname(@Nullable String hostname) {
if (hostname == null) {
return true;
}
- String HOSTNAME_REGEX = "^[a-zA-Z]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$";
+ String HOSTNAME_REGEX = "^[a-zA-Z0-9]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$";
return Pattern.compile(HOSTNAME_REGEX).matcher(hostname).matches();
}
+ /**
+ * Checks if the public key is valid.
+ *
+ * <p>For simplicity, it only checks if the protocol is DNSSEC and the RDATA is not fewer than 4
+ * bytes. See RFC 3445 Section 3.
+ *
+ * <p>Message format: flags (2 bytes), protocol (1 byte), algorithm (1 byte), public key.
+ */
+ private static boolean checkPublicKey(@Nullable byte[] publicKey) {
+ if (publicKey == null) {
+ return true;
+ }
+ if (publicKey.length < 4) {
+ return false;
+ }
+ int protocol = publicKey[2];
+ return protocol == DNSSEC_PROTOCOL;
+ }
+
/** Returns {@code true} if {@code subtype} is a valid DNS-SD subtype label. */
private static boolean checkSubtypeLabel(String subtype) {
return Pattern.compile("^" + SUBTYPE_LABEL_REGEX + "$").matcher(subtype).matches();
@@ -2545,6 +2627,17 @@
// Dump state machine logs
mNsdStateMachine.dump(fd, pw, args);
+ // Dump clients
+ pw.println();
+ pw.println("Active clients:");
+ pw.increaseIndent();
+ HandlerUtils.runWithScissorsForDump(mNsdStateMachine.getHandler(), () -> {
+ for (ClientInfo clientInfo : mClients.values()) {
+ pw.println(clientInfo.toString());
+ }
+ }, 10_000);
+ pw.decreaseIndent();
+
// Dump service and clients logs
pw.println();
pw.println("Logs:");
@@ -2617,6 +2710,21 @@
public int getSentQueryCount() {
return mSentQueryCount;
}
+
+ @NonNull
+ @Override
+ public String toString() {
+ return getRequestDescriptor() + " {" + mTransactionId
+ + ", startTime " + mStartTimeMs
+ + ", foundServices " + mFoundServiceCount
+ + ", lostServices " + mLostServiceCount
+ + ", fromCache " + mIsServiceFromCache
+ + ", sentQueries " + mSentQueryCount
+ + "}";
+ }
+
+ @NonNull
+ protected abstract String getRequestDescriptor();
}
private static class LegacyClientRequest extends ClientRequest {
@@ -2626,6 +2734,12 @@
super(transactionId, startTimeMs);
mRequestCode = requestCode;
}
+
+ @NonNull
+ @Override
+ protected String getRequestDescriptor() {
+ return "Legacy (" + mRequestCode + ")";
+ }
}
private abstract static class JavaBackendClientRequest extends ClientRequest {
@@ -2645,9 +2759,20 @@
}
private static class AdvertiserClientRequest extends JavaBackendClientRequest {
+ @NonNull
+ private final String mServiceFullName;
+
private AdvertiserClientRequest(int transactionId, @Nullable Network requestedNetwork,
- long startTimeMs) {
+ @NonNull String serviceFullName, long startTimeMs) {
super(transactionId, requestedNetwork, startTimeMs);
+ mServiceFullName = serviceFullName;
+ }
+
+ @NonNull
+ @Override
+ public String getRequestDescriptor() {
+ return String.format("Advertiser: serviceFullName=%s, net=%s",
+ mServiceFullName, getRequestedNetwork());
}
}
@@ -2660,6 +2785,12 @@
super(transactionId, requestedNetwork, startTimeMs);
mListener = listener;
}
+
+ @NonNull
+ @Override
+ public String getRequestDescriptor() {
+ return String.format("Discovery/%s, net=%s", mListener, getRequestedNetwork());
+ }
}
/* Information tracked per client */
@@ -2704,17 +2835,15 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("mResolvedService ").append(mResolvedService).append("\n");
- sb.append("mIsLegacy ").append(mIsPreSClient).append("\n");
- sb.append("mUseJavaBackend ").append(mUseJavaBackend).append("\n");
- sb.append("mUid ").append(mUid).append("\n");
+ sb.append("mUid ").append(mUid).append(", ");
+ sb.append("mResolvedService ").append(mResolvedService).append(", ");
+ sb.append("mIsLegacy ").append(mIsPreSClient).append(", ");
+ sb.append("mUseJavaBackend ").append(mUseJavaBackend).append(", ");
+ sb.append("mClientRequests:\n");
for (int i = 0; i < mClientRequests.size(); i++) {
int clientRequestId = mClientRequests.keyAt(i);
- sb.append("clientRequestId ")
- .append(clientRequestId)
- .append(" transactionId ").append(mClientRequests.valueAt(i).mTransactionId)
- .append(" type ").append(
- mClientRequests.valueAt(i).getClass().getSimpleName())
+ sb.append(" ").append(clientRequestId)
+ .append(": ").append(mClientRequests.valueAt(i).toString())
.append("\n");
}
return sb.toString();
@@ -2764,10 +2893,12 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
} else if (listener instanceof ResolutionListener) {
mMetrics.reportServiceResolutionStop(false /* isLegacy */, transactionId,
- request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ request.getSentQueryCount());
} else if (listener instanceof ServiceInfoListener) {
mMetrics.reportServiceInfoCallbackUnregistered(transactionId,
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
@@ -2803,12 +2934,14 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- NO_SENT_QUERY_COUNT);
+ NO_SENT_QUERY_COUNT,
+ request.isServiceFromCache());
break;
case NsdManager.RESOLVE_SERVICE:
stopResolveService(transactionId);
mMetrics.reportServiceResolutionStop(true /* isLegacy */, transactionId,
- request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ NO_SENT_QUERY_COUNT);
break;
case NsdManager.REGISTER_SERVICE:
unregisterService(transactionId);
@@ -2925,7 +3058,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
try {
mCb.onStopDiscoverySucceeded(listenerKey);
} catch (RemoteException e) {
@@ -3024,7 +3158,8 @@
mMetrics.reportServiceResolutionStop(
isLegacyClientRequest(request),
request.mTransactionId,
- request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ request.getSentQueryCount());
try {
mCb.onStopResolutionSucceeded(listenerKey);
} catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 98c2d86..42efcac 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -448,10 +448,11 @@
/**
* Get the ID of a conflicting registration due to host, or -1 if none.
*
- * <p>It's valid that multiple registrations from the same user are using the same hostname.
- *
* <p>If there's already another registration with the same hostname requested by another
- * user, this is considered a conflict.
+ * user, this is a conflict.
+ *
+ * <p>If there're two registrations both containing address records using the same hostname,
+ * this is a conflict.
*/
int getConflictingRegistrationDueToHost(@NonNull NsdServiceInfo info, int clientUid) {
if (TextUtils.isEmpty(info.getHostname())) {
@@ -460,10 +461,17 @@
for (int i = 0; i < mPendingRegistrations.size(); i++) {
final Registration otherRegistration = mPendingRegistrations.valueAt(i);
final NsdServiceInfo otherInfo = otherRegistration.getServiceInfo();
+ final int otherServiceId = mPendingRegistrations.keyAt(i);
if (clientUid != otherRegistration.mClientUid
&& MdnsUtils.equalsIgnoreDnsCase(
info.getHostname(), otherInfo.getHostname())) {
- return mPendingRegistrations.keyAt(i);
+ return otherServiceId;
+ }
+ if (!info.getHostAddresses().isEmpty()
+ && !otherInfo.getHostAddresses().isEmpty()
+ && MdnsUtils.equalsIgnoreDnsCase(
+ info.getHostname(), otherInfo.getHostname())) {
+ return otherServiceId;
}
}
return -1;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 7b0c738..0ab7a76 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -241,11 +241,30 @@
}
}
// Request the network for discovery.
+ // This requests sockets on all networks even if the searchOptions have a given interface
+ // index (with getNetwork==null, for local interfaces), and only uses matching interfaces
+ // in that case. While this is a simple solution to only use matching sockets, a better
+ // practice would be to only request the correct socket for discovery.
+ // TODO: avoid requesting extra sockets after migrating P2P and tethering networks to local
+ // NetworkAgents.
socketClient.notifyNetworkRequested(listener, searchOptions.getNetwork(),
new MdnsSocketClientBase.SocketCreationCallback() {
@Override
public void onSocketCreated(@NonNull SocketKey socketKey) {
discoveryExecutor.ensureRunningOnHandlerThread();
+ final int searchInterfaceIndex = searchOptions.getInterfaceIndex();
+ if (searchOptions.getNetwork() == null
+ && searchInterfaceIndex > 0
+ // The interface index in options should only match interfaces that
+ // do not have any Network; a matching Network should be provided
+ // otherwise.
+ && (socketKey.getNetwork() != null
+ || socketKey.getInterfaceIndex() != searchInterfaceIndex)) {
+ sharedLog.i("Skipping " + socketKey + " as ifIndex "
+ + searchInterfaceIndex + " was requested.");
+ return;
+ }
+
// All listeners of the same service types shares the same
// MdnsServiceTypeClient.
MdnsServiceTypeClient serviceTypeClient =
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsKeyRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsKeyRecord.java
new file mode 100644
index 0000000..ba8a56e
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsKeyRecord.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import static com.android.net.module.util.HexDump.toHexString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/** An mDNS "KEY" record, which contains a public key for a name. See RFC 2535. */
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+public class MdnsKeyRecord extends MdnsRecord {
+ @Nullable private byte[] rData;
+
+ public MdnsKeyRecord(@NonNull String[] name, @NonNull MdnsPacketReader reader)
+ throws IOException {
+ this(name, reader, false);
+ }
+
+ public MdnsKeyRecord(@NonNull String[] name, @NonNull MdnsPacketReader reader,
+ boolean isQuestion) throws IOException {
+ super(name, TYPE_KEY, reader, isQuestion);
+ }
+
+ public MdnsKeyRecord(@NonNull String[] name, boolean isUnicast) {
+ super(name, TYPE_KEY,
+ MdnsConstants.QCLASS_INTERNET | (isUnicast ? MdnsConstants.QCLASS_UNICAST : 0),
+ 0L /* receiptTimeMillis */, false /* cacheFlush */, 0L /* ttlMillis */);
+ }
+
+ public MdnsKeyRecord(@NonNull String[] name, long receiptTimeMillis, boolean cacheFlush,
+ long ttlMillis, @Nullable byte[] rData) {
+ super(name, TYPE_KEY, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush,
+ ttlMillis);
+ if (rData != null) {
+ this.rData = Arrays.copyOf(rData, rData.length);
+ }
+ }
+ /** Returns the KEY RDATA in bytes **/
+ public byte[] getRData() {
+ if (rData == null) {
+ return null;
+ }
+ return Arrays.copyOf(rData, rData.length);
+ }
+
+ @Override
+ protected void readData(MdnsPacketReader reader) throws IOException {
+ rData = new byte[reader.getRemaining()];
+ reader.readBytes(rData);
+ }
+
+ @Override
+ protected void writeData(MdnsPacketWriter writer) throws IOException {
+ if (rData != null) {
+ writer.writeBytes(rData);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "KEY: " + toHexString(rData);
+ }
+
+ @Override
+ public int hashCode() {
+ return (super.hashCode() * 31) + Arrays.hashCode(rData);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof MdnsKeyRecord)) {
+ return false;
+ }
+
+ return super.equals(other) && Arrays.equals(rData, ((MdnsKeyRecord) other).rData);
+ }
+}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
index 83ecabc..aef8211 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
@@ -196,6 +196,15 @@
}
}
+ case MdnsRecord.TYPE_KEY: {
+ try {
+ return new MdnsKeyRecord(name, reader, isQuestion);
+ } catch (IOException e) {
+ throw new ParseException(MdnsResponseErrorCode.ERROR_READING_KEY_RDATA,
+ "Failed to read KEY record from mDNS response.", e);
+ }
+ }
+
case MdnsRecord.TYPE_NSEC: {
try {
return new MdnsNsecRecord(name, reader, isQuestion);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
index 1f9f42b..b865319 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -41,6 +41,7 @@
public static final int TYPE_PTR = 0x000C;
public static final int TYPE_SRV = 0x0021;
public static final int TYPE_TXT = 0x0010;
+ public static final int TYPE_KEY = 0x0019;
public static final int TYPE_NSEC = 0x002f;
public static final int TYPE_ANY = 0x00ff;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 073e465..36f3982 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -34,6 +34,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
@@ -87,6 +88,12 @@
private static final String[] DNS_SD_SERVICE_TYPE =
new String[] { "_services", "_dns-sd", "_udp", LOCAL_TLD };
+ private enum RecordConflictType {
+ NO_CONFLICT,
+ CONFLICT,
+ IDENTICAL
+ }
+
@NonNull
private final Random mDelayGenerator = new Random();
// Map of service unique ID -> records for service
@@ -97,6 +104,8 @@
@NonNull
private final Looper mLooper;
@NonNull
+ private final Dependencies mDeps;
+ @NonNull
private final String[] mDeviceHostname;
@NonNull
private final MdnsFeatureFlags mMdnsFeatureFlags;
@@ -111,6 +120,7 @@
@NonNull String[] deviceHostname, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
mDeviceHostname = deviceHostname;
mLooper = looper;
+ mDeps = deps;
mMdnsFeatureFlags = mdnsFeatureFlags;
}
@@ -127,6 +137,10 @@
public Enumeration<InetAddress> getInterfaceInetAddresses(@NonNull NetworkInterface iface) {
return iface.getInetAddresses();
}
+
+ public long elapsedRealTime() {
+ return SystemClock.elapsedRealtime();
+ }
}
private static class RecordInfo<T extends MdnsRecord> {
@@ -140,17 +154,25 @@
public final boolean isSharedName;
/**
- * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast, 0 if never
+ * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast on IPv4, 0
+ * if never
*/
- public long lastAdvertisedTimeMs;
+ public long lastAdvertisedOnIpv4TimeMs;
/**
- * Last time (as per SystemClock.elapsedRealtime) when sent via unicast or multicast,
- * 0 if never
+ * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast on IPv6, 0
+ * if never
*/
- // FIXME: the `lastSentTimeMs` and `lastAdvertisedTimeMs` should be maintained separately
- // for IPv4 and IPv6, because neither IPv4 nor and IPv6 clients can receive replies in
- // different address space.
+ public long lastAdvertisedOnIpv6TimeMs;
+
+ /**
+ * Last time (as per SystemClock.elapsedRealtime) when sent via unicast or multicast, 0 if
+ * never.
+ *
+ * <p>Different from lastAdvertisedOnIpv(4|6)TimeMs, lastSentTimeMs is mainly used for
+ * tracking is a record is ever sent out, no matter unicast/multicast or IPv4/IPv6. It's
+ * unnecessary to maintain two versions (IPv4/IPv6) for it.
+ */
public long lastSentTimeMs;
RecordInfo(NsdServiceInfo serviceInfo, T record, boolean sharedName) {
@@ -169,6 +191,10 @@
public final RecordInfo<MdnsServiceRecord> srvRecord;
@Nullable
public final RecordInfo<MdnsTextRecord> txtRecord;
+ @Nullable
+ public final RecordInfo<MdnsKeyRecord> serviceKeyRecord;
+ @Nullable
+ public final RecordInfo<MdnsKeyRecord> hostKeyRecord;
@NonNull
public final List<RecordInfo<MdnsInetAddressRecord>> addressRecords;
@NonNull
@@ -230,7 +256,6 @@
nameRecordsTtlMillis = DEFAULT_NAME_RECORDS_TTL_MILLIS;
}
- final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType());
final boolean hasCustomHost = !TextUtils.isEmpty(serviceInfo.getHostname());
final String[] hostname =
hasCustomHost
@@ -238,9 +263,11 @@
: deviceHostname;
final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
- if (hasService) {
- final String[] serviceType = splitServiceType(serviceInfo);
- final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
+ final boolean hasService = !TextUtils.isEmpty(serviceInfo.getServiceType());
+ final String[] serviceType = hasService ? splitServiceType(serviceInfo) : null;
+ final String[] serviceName =
+ hasService ? splitFullyQualifiedName(serviceInfo, serviceType) : null;
+ if (hasService && hasSrvRecord(serviceInfo)) {
// Service PTR records
ptrRecords = new ArrayList<>(serviceInfo.getSubtypes().size() + 1);
ptrRecords.add(new RecordInfo<>(
@@ -321,6 +348,36 @@
addressRecords = Collections.emptyList();
}
+ final boolean hasKey = hasKeyRecord(serviceInfo);
+ if (hasKey && hasService) {
+ this.serviceKeyRecord = new RecordInfo<>(
+ serviceInfo,
+ new MdnsKeyRecord(
+ serviceName,
+ 0L /*receiptTimeMillis */,
+ true /* cacheFlush */,
+ nameRecordsTtlMillis,
+ serviceInfo.getPublicKey()),
+ false /* sharedName */);
+ allRecords.add(this.serviceKeyRecord);
+ } else {
+ this.serviceKeyRecord = null;
+ }
+ if (hasKey && hasCustomHost) {
+ this.hostKeyRecord = new RecordInfo<>(
+ serviceInfo,
+ new MdnsKeyRecord(
+ hostname,
+ 0L /*receiptTimeMillis */,
+ true /* cacheFlush */,
+ nameRecordsTtlMillis,
+ serviceInfo.getPublicKey()),
+ false /* sharedName */);
+ allRecords.add(this.hostKeyRecord);
+ } else {
+ this.hostKeyRecord = null;
+ }
+
this.allRecords = Collections.unmodifiableList(allRecords);
this.repliedServiceCount = repliedServiceCount;
this.sentPacketCount = sentPacketCount;
@@ -471,6 +528,22 @@
? inetAddressRecord.getInet6Address()
: inetAddressRecord.getInet4Address()));
}
+
+ List<MdnsKeyRecord> keyRecords = new ArrayList<>();
+ if (registration.serviceKeyRecord != null) {
+ keyRecords.add(registration.serviceKeyRecord.record);
+ }
+ if (registration.hostKeyRecord != null) {
+ keyRecords.add(registration.hostKeyRecord.record);
+ }
+ for (MdnsKeyRecord keyRecord : keyRecords) {
+ probingRecords.add(new MdnsKeyRecord(
+ keyRecord.getName(),
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ keyRecord.getTtl(),
+ keyRecord.getRData()));
+ }
return new MdnsProber.ProbingInfo(serviceId, probingRecords);
}
@@ -577,7 +650,8 @@
*/
@Nullable
public MdnsReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
- final long now = SystemClock.elapsedRealtime();
+ final long now = mDeps.elapsedRealTime();
+ final boolean isQuestionOnIpv4 = src.getAddress() instanceof Inet4Address;
// TODO: b/322142420 - Set<RecordInfo<?>> may contain duplicate records wrapped in different
// RecordInfo<?>s when custom host is enabled.
@@ -595,7 +669,7 @@
null /* serviceSrvRecord */, null /* serviceTxtRecord */,
null /* hostname */,
replyUnicastEnabled, now, answerInfo, additionalAnswerInfo,
- Collections.emptyList())) {
+ Collections.emptyList(), isQuestionOnIpv4)) {
replyUnicast &= question.isUnicastReplyRequested();
}
@@ -607,7 +681,7 @@
registration.srvRecord, registration.txtRecord,
registration.serviceInfo.getHostname(),
replyUnicastEnabled, now,
- answerInfo, additionalAnswerInfo, packet.answers)) {
+ answerInfo, additionalAnswerInfo, packet.answers, isQuestionOnIpv4)) {
replyUnicast &= question.isUnicastReplyRequested();
registration.repliedServiceCount++;
registration.sentPacketCount++;
@@ -685,7 +759,7 @@
// multicast responses. Unicast replies are faster as they do not need to wait for the
// beacon interval on Wi-Fi.
dest = src;
- } else if (src.getAddress() instanceof Inet4Address) {
+ } else if (isQuestionOnIpv4) {
dest = IPV4_SOCKET_ADDR;
} else {
dest = IPV6_SOCKET_ADDR;
@@ -697,7 +771,11 @@
// TODO: consider actual packet send delay after response aggregation
info.lastSentTimeMs = now + delayMs;
if (!replyUnicast) {
- info.lastAdvertisedTimeMs = info.lastSentTimeMs;
+ if (isQuestionOnIpv4) {
+ info.lastAdvertisedOnIpv4TimeMs = info.lastSentTimeMs;
+ } else {
+ info.lastAdvertisedOnIpv6TimeMs = info.lastSentTimeMs;
+ }
}
// Different RecordInfos may the contain the same record
if (!answerRecords.contains(info.record)) {
@@ -729,7 +807,8 @@
@Nullable String hostname,
boolean replyUnicastEnabled, long now, @NonNull Set<RecordInfo<?>> answerInfo,
@NonNull Set<RecordInfo<?>> additionalAnswerInfo,
- @NonNull List<MdnsRecord> knownAnswerRecords) {
+ @NonNull List<MdnsRecord> knownAnswerRecords,
+ boolean isQuestionOnIpv4) {
boolean hasDnsSdPtrRecordAnswer = false;
boolean hasDnsSdSrvRecordAnswer = false;
boolean hasFullyOwnedNameMatch = false;
@@ -778,10 +857,20 @@
// TODO: responses to probe queries should bypass this check and only ensure the
// reply is sent 250ms after the last sent time (RFC 6762 p.15)
- if (!(replyUnicastEnabled && question.isUnicastReplyRequested())
- && info.lastAdvertisedTimeMs > 0L
- && now - info.lastAdvertisedTimeMs < MIN_MULTICAST_REPLY_INTERVAL_MS) {
- continue;
+ if (!(replyUnicastEnabled && question.isUnicastReplyRequested())) {
+ if (isQuestionOnIpv4) { // IPv4
+ if (info.lastAdvertisedOnIpv4TimeMs > 0L
+ && now - info.lastAdvertisedOnIpv4TimeMs
+ < MIN_MULTICAST_REPLY_INTERVAL_MS) {
+ continue;
+ }
+ } else { // IPv6
+ if (info.lastAdvertisedOnIpv6TimeMs > 0L
+ && now - info.lastAdvertisedOnIpv6TimeMs
+ < MIN_MULTICAST_REPLY_INTERVAL_MS) {
+ continue;
+ }
+ }
}
answerInfo.add(info);
@@ -1070,18 +1159,15 @@
Collections.emptyList() /* additionalRecords */);
}
- /** Check if the record is in any service registration */
- private boolean hasInetAddressRecord(@NonNull MdnsInetAddressRecord record) {
- for (int i = 0; i < mServices.size(); i++) {
- final ServiceRegistration registration = mServices.valueAt(i);
- if (registration.exiting) continue;
-
- for (RecordInfo<MdnsInetAddressRecord> localRecord : registration.addressRecords) {
- if (Objects.equals(localRecord.record, record)) {
- return true;
- }
+ /** Check if the record is in a registration */
+ private static boolean hasInetAddressRecord(
+ @NonNull ServiceRegistration registration, @NonNull MdnsInetAddressRecord record) {
+ for (RecordInfo<MdnsInetAddressRecord> localRecord : registration.addressRecords) {
+ if (Objects.equals(localRecord.record, record)) {
+ return true;
}
}
+
return false;
}
@@ -1093,99 +1179,125 @@
* {@link MdnsInterfaceAdvertiser#CONFLICT_HOST}.
*/
public Map<Integer, Integer> getConflictingServices(MdnsPacket packet) {
- // Avoid allocating a new set for each incoming packet: use an empty set by default.
- Map<Integer, Integer> conflicting = Collections.emptyMap();
+ Map<Integer, Integer> conflicting = new ArrayMap<>();
for (MdnsRecord record : packet.answers) {
+ SparseIntArray conflictingWithRecord = new SparseIntArray();
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
if (registration.exiting) continue;
- int conflictType = 0;
+ final RecordConflictType conflictForService =
+ conflictForService(record, registration);
+ final RecordConflictType conflictForHost = conflictForHost(record, registration);
- if (conflictForService(record, registration)) {
- conflictType |= CONFLICT_SERVICE;
+ // Identical record is found in the repository so there won't be a conflict.
+ if (conflictForService == RecordConflictType.IDENTICAL
+ || conflictForHost == RecordConflictType.IDENTICAL) {
+ conflictingWithRecord.clear();
+ break;
}
- if (conflictForHost(record, registration)) {
+ int conflictType = 0;
+ if (conflictForService == RecordConflictType.CONFLICT) {
+ conflictType |= CONFLICT_SERVICE;
+ }
+ if (conflictForHost == RecordConflictType.CONFLICT) {
conflictType |= CONFLICT_HOST;
}
if (conflictType != 0) {
- if (conflicting.isEmpty()) {
- // Conflict was found: use a mutable set
- conflicting = new ArrayMap<>();
- }
final int serviceId = mServices.keyAt(i);
- conflicting.put(serviceId, conflictType);
+ conflictingWithRecord.put(serviceId, conflictType);
}
}
+ for (int i = 0; i < conflictingWithRecord.size(); i++) {
+ final int serviceId = conflictingWithRecord.keyAt(i);
+ final int conflictType = conflictingWithRecord.valueAt(i);
+ final int oldConflictType = conflicting.getOrDefault(serviceId, 0);
+ conflicting.put(serviceId, oldConflictType | conflictType);
+ }
}
return conflicting;
}
-
- private static boolean conflictForService(
+ private static RecordConflictType conflictForService(
@NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
- if (registration.srvRecord == null) {
- return false;
+ String[] fullServiceName;
+ if (registration.srvRecord != null) {
+ fullServiceName = registration.srvRecord.record.getName();
+ } else if (registration.serviceKeyRecord != null) {
+ fullServiceName = registration.serviceKeyRecord.record.getName();
+ } else {
+ return RecordConflictType.NO_CONFLICT;
}
- final RecordInfo<MdnsServiceRecord> srvRecord = registration.srvRecord;
- if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), srvRecord.record.getName())) {
- return false;
+ if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(), fullServiceName)) {
+ return RecordConflictType.NO_CONFLICT;
}
// As per RFC6762 9., it's fine if the "conflict" is an identical record with same
// data.
- if (record instanceof MdnsServiceRecord) {
- final MdnsServiceRecord local = srvRecord.record;
- final MdnsServiceRecord other = (MdnsServiceRecord) record;
- // Note "equals" does not consider TTL or receipt time, as intended here
- if (Objects.equals(local, other)) {
- return false;
- }
+ if (record instanceof MdnsServiceRecord && equals(record, registration.srvRecord)) {
+ return RecordConflictType.IDENTICAL;
+ }
+ if (record instanceof MdnsTextRecord && equals(record, registration.txtRecord)) {
+ return RecordConflictType.IDENTICAL;
+ }
+ if (record instanceof MdnsKeyRecord && equals(record, registration.serviceKeyRecord)) {
+ return RecordConflictType.IDENTICAL;
}
- if (record instanceof MdnsTextRecord) {
- final MdnsTextRecord local = registration.txtRecord.record;
- final MdnsTextRecord other = (MdnsTextRecord) record;
- if (Objects.equals(local, other)) {
- return false;
- }
- }
- return true;
+ return RecordConflictType.CONFLICT;
}
- private boolean conflictForHost(
+ private RecordConflictType conflictForHost(
@NonNull MdnsRecord record, @NonNull ServiceRegistration registration) {
// Only custom hosts are checked. When using the default host, the hostname is derived from
// a UUID and it's supposed to be unique.
if (registration.serviceInfo.getHostname() == null) {
- return false;
+ return RecordConflictType.NO_CONFLICT;
+ }
+
+ // It cannot be a hostname conflict because no record is registered with the hostname.
+ if (registration.addressRecords.isEmpty() && registration.hostKeyRecord == null) {
+ return RecordConflictType.NO_CONFLICT;
}
// The record's name cannot be registered by NsdManager so it's not a conflict.
if (record.getName().length != 2 || !record.getName()[1].equals(LOCAL_TLD)) {
- return false;
+ return RecordConflictType.NO_CONFLICT;
}
// Different names. There won't be a conflict.
if (!MdnsUtils.equalsIgnoreDnsCase(
record.getName()[0], registration.serviceInfo.getHostname())) {
- return false;
+ return RecordConflictType.NO_CONFLICT;
}
- // If this registration has any address record and there's no identical record in the
- // repository, it's a conflict. There will be no conflict if no registration has addresses
- // for that hostname.
- if (record instanceof MdnsInetAddressRecord) {
- if (!registration.addressRecords.isEmpty()) {
- return !hasInetAddressRecord((MdnsInetAddressRecord) record);
- }
+ // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
+ // data.
+ if (record instanceof MdnsInetAddressRecord
+ && hasInetAddressRecord(registration, (MdnsInetAddressRecord) record)) {
+ return RecordConflictType.IDENTICAL;
+ }
+ if (record instanceof MdnsKeyRecord && equals(record, registration.hostKeyRecord)) {
+ return RecordConflictType.IDENTICAL;
}
- return false;
+ // Per RFC 6762 8.1, when a record is being probed, any answer containing a record with that
+ // name, of any type, MUST be considered a conflicting response.
+ if (registration.isProbing) {
+ return RecordConflictType.CONFLICT;
+ }
+ if (record instanceof MdnsInetAddressRecord && !registration.addressRecords.isEmpty()) {
+ return RecordConflictType.CONFLICT;
+ }
+ if (record instanceof MdnsKeyRecord && registration.hostKeyRecord != null) {
+ return RecordConflictType.CONFLICT;
+ }
+
+ return RecordConflictType.NO_CONFLICT;
}
private List<RecordInfo<MdnsInetAddressRecord>> getInetAddressRecordsForHostname(
@@ -1302,10 +1414,11 @@
final ServiceRegistration registration = mServices.get(serviceId);
if (registration == null) return;
- final long now = SystemClock.elapsedRealtime();
+ final long now = mDeps.elapsedRealTime();
for (RecordInfo<?> record : registration.allRecords) {
record.lastSentTimeMs = now;
- record.lastAdvertisedTimeMs = now;
+ record.lastAdvertisedOnIpv4TimeMs = now;
+ record.lastAdvertisedOnIpv6TimeMs = now;
}
registration.sentPacketCount += sentPacketCount;
}
@@ -1370,4 +1483,21 @@
return type;
}
+
+ /** Returns whether there will be an SRV record when registering the {@code info}. */
+ private static boolean hasSrvRecord(@NonNull NsdServiceInfo info) {
+ return info.getPort() > 0;
+ }
+
+ /** Returns whether there will be KEY record(s) when registering the {@code info}. */
+ private static boolean hasKeyRecord(@NonNull NsdServiceInfo info) {
+ return info.getPublicKey() != null;
+ }
+
+ private static boolean equals(@NonNull MdnsRecord record, @Nullable RecordInfo<?> recordInfo) {
+ if (recordInfo == null) {
+ return false;
+ }
+ return Objects.equals(record, recordInfo.record);
+ }
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
index 73a7e3a..f509da2 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
@@ -37,4 +37,5 @@
public static final int ERROR_END_OF_FILE = 12;
public static final int ERROR_READING_NSEC_RDATA = 13;
public static final int ERROR_READING_ANY_RDATA = 14;
+ public static final int ERROR_READING_KEY_RDATA = 15;
}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 086094b..73405ab 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -59,6 +59,7 @@
source.readInt(),
source.readInt() == 1,
source.readParcelable(null),
+ source.readInt(),
source.readString(),
source.readInt() == 1,
source.readInt());
@@ -79,6 +80,8 @@
private final boolean removeExpiredService;
// The target network for searching. Null network means search on all possible interfaces.
@Nullable private final Network mNetwork;
+ // If the target interface does not have a Network, set to the interface index, otherwise unset.
+ private final int mInterfaceIndex;
/** Parcelable constructs for a {@link MdnsSearchOptions}. */
MdnsSearchOptions(
@@ -86,6 +89,7 @@
int queryMode,
boolean removeExpiredService,
@Nullable Network network,
+ int interfaceIndex,
@Nullable String resolveInstanceName,
boolean onlyUseIpv6OnIpv6OnlyNetworks,
int numOfQueriesBeforeBackoff) {
@@ -98,6 +102,7 @@
this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
this.removeExpiredService = removeExpiredService;
mNetwork = network;
+ mInterfaceIndex = interfaceIndex;
this.resolveInstanceName = resolveInstanceName;
}
@@ -148,15 +153,27 @@
}
/**
- * Returns the network which the mdns query should target on.
+ * Returns the network which the mdns query should target.
*
- * @return the target network or null if search on all possible interfaces.
+ * @return the target network or null to search on all possible interfaces.
*/
@Nullable
public Network getNetwork() {
return mNetwork;
}
+
+ /**
+ * Returns the interface index which the mdns query should target.
+ *
+ * This is only set when the service is to be searched on an interface that does not have a
+ * Network, in which case {@link #getNetwork()} returns null.
+ * The interface index as per {@link java.net.NetworkInterface#getIndex}, or 0 if unset.
+ */
+ public int getInterfaceIndex() {
+ return mInterfaceIndex;
+ }
+
/**
* If non-null, queries should try to resolve all records of this specific service, rather than
* discovering all services.
@@ -177,6 +194,7 @@
out.writeInt(queryMode);
out.writeInt(removeExpiredService ? 1 : 0);
out.writeParcelable(mNetwork, 0);
+ out.writeInt(mInterfaceIndex);
out.writeString(resolveInstanceName);
out.writeInt(onlyUseIpv6OnIpv6OnlyNetworks ? 1 : 0);
out.writeInt(numOfQueriesBeforeBackoff);
@@ -190,6 +208,7 @@
private int numOfQueriesBeforeBackoff = 3;
private boolean removeExpiredService;
private Network mNetwork;
+ private int mInterfaceIndex;
private String resolveInstanceName;
private Builder() {
@@ -278,6 +297,16 @@
return this;
}
+ /**
+ * Set the interface index to use for the query, if not querying on a {@link Network}.
+ *
+ * @see MdnsSearchOptions#getInterfaceIndex()
+ */
+ public Builder setInterfaceIndex(int index) {
+ mInterfaceIndex = index;
+ return this;
+ }
+
/** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
public MdnsSearchOptions build() {
return new MdnsSearchOptions(
@@ -285,6 +314,7 @@
queryMode,
removeExpiredService,
mNetwork,
+ mInterfaceIndex,
resolveInstanceName,
onlyUseIpv6OnIpv6OnlyNetworks,
numOfQueriesBeforeBackoff);
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index 0b54fdd..cadc04d 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -250,6 +250,17 @@
return mTrackingInterfaces.containsKey(ifaceName);
}
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Nullable
+ protected String getHwAddress(@NonNull final String ifaceName) {
+ if (!hasInterface(ifaceName)) {
+ return null;
+ }
+
+ NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
+ return iface.mHwAddress;
+ }
+
@VisibleForTesting
static class NetworkInterfaceState {
final String name;
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 9c8fd99..71f289e 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -131,6 +131,10 @@
// returned when a tethered interface is requested; until then, it remains in client mode. Its
// current mode is reflected in mTetheringInterfaceMode.
private String mTetheringInterface;
+ // If the tethering interface is in server mode, it is not tracked by factory. The HW address
+ // must be maintained by the EthernetTracker. Its current mode is reflected in
+ // mTetheringInterfaceMode.
+ private String mTetheringInterfaceHwAddr;
private int mTetheringInterfaceMode = INTERFACE_MODE_CLIENT;
// Tracks whether clients were notified that the tethered interface is available
private boolean mTetheredInterfaceWasAvailable = false;
@@ -582,6 +586,7 @@
removeInterface(iface);
if (iface.equals(mTetheringInterface)) {
mTetheringInterface = null;
+ mTetheringInterfaceHwAddr = null;
}
broadcastInterfaceStateChange(iface);
}
@@ -610,13 +615,14 @@
return;
}
+ final String hwAddress = config.hwAddr;
+
if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
maybeUpdateServerModeInterfaceState(iface, true);
+ mTetheringInterfaceHwAddr = hwAddress;
return;
}
- final String hwAddress = config.hwAddr;
-
NetworkCapabilities nc = mNetworkCapabilities.get(iface);
if (nc == null) {
// Try to resolve using mac address
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 5e98ee1..5323392 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STATS_PROVIDER;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
@@ -50,12 +51,18 @@
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.TrafficStats.KB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.TrafficStats.TYPE_RX_BYTES;
+import static android.net.TrafficStats.TYPE_RX_PACKETS;
+import static android.net.TrafficStats.TYPE_TX_BYTES;
+import static android.net.TrafficStats.TYPE_TX_PACKETS;
import static android.net.TrafficStats.UID_TETHERING;
import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
import static android.os.Trace.TRACE_TAG_NETWORK;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static android.system.OsConstants.ENOENT;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -64,6 +71,7 @@
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.net.module.util.DeviceConfigUtils.getDeviceConfigPropertyInt;
import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_PERIODIC;
@@ -84,6 +92,7 @@
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.app.usage.NetworkStatsManager;
import android.content.ApexEnvironment;
import android.content.BroadcastReceiver;
@@ -299,6 +308,12 @@
static final String NETSTATS_FASTDATAINPUT_SUCCESSES_COUNTER_NAME = "fastdatainput.successes";
static final String NETSTATS_FASTDATAINPUT_FALLBACKS_COUNTER_NAME = "fastdatainput.fallbacks";
+ static final String TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME =
+ "trafficstats_cache_expiry_duration_ms";
+ static final String TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME = "trafficstats_cache_max_entries";
+ static final int DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS = 1000;
+ static final int DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES = 400;
+
private final Context mContext;
private final NetworkStatsFactory mStatsFactory;
private final AlarmManager mAlarmManager;
@@ -454,6 +469,15 @@
private long mLastStatsSessionPoll;
+ private final TrafficStatsRateLimitCache mTrafficStatsTotalCache;
+ private final TrafficStatsRateLimitCache mTrafficStatsIfaceCache;
+ private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
+ static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
+ "trafficstats_rate_limit_cache_enabled_flag";
+ private final boolean mAlwaysUseTrafficStatsRateLimitCache;
+ private final int mTrafficStatsRateLimitCacheExpiryDuration;
+ private final int mTrafficStatsRateLimitCacheMaxEntries;
+
private final Object mOpenSessionCallsLock = new Object();
/**
@@ -643,6 +667,19 @@
mEventLogger = null;
}
+ mAlwaysUseTrafficStatsRateLimitCache =
+ mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
+ mTrafficStatsRateLimitCacheExpiryDuration =
+ mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
+ mTrafficStatsRateLimitCacheMaxEntries =
+ mDeps.getTrafficStatsRateLimitCacheMaxEntries();
+ mTrafficStatsTotalCache = new TrafficStatsRateLimitCache(mClock,
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
+ mTrafficStatsIfaceCache = new TrafficStatsRateLimitCache(mClock,
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
+ mTrafficStatsUidCache = new TrafficStatsRateLimitCache(mClock,
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
+
// TODO: Remove bpfNetMaps creation and always start SkDestroyListener
// Following code is for the experiment to verify the SkDestroyListener refactoring. Based
// on the experiment flag, BpfNetMaps starts C SkDestroyListener (existing code) or
@@ -696,7 +733,7 @@
* Get the count of import legacy target attempts.
*/
public int getImportLegacyTargetAttempts() {
- return DeviceConfigUtils.getDeviceConfigPropertyInt(
+ return getDeviceConfigPropertyInt(
DeviceConfig.NAMESPACE_TETHERING,
NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS,
DEFAULT_NETSTATS_IMPORT_LEGACY_TARGET_ATTEMPTS);
@@ -706,7 +743,7 @@
* Get the count of using FastDataInput target attempts.
*/
public int getUseFastDataInputTargetAttempts() {
- return DeviceConfigUtils.getDeviceConfigPropertyInt(
+ return getDeviceConfigPropertyInt(
DeviceConfig.NAMESPACE_TETHERING,
NETSTATS_FASTDATAINPUT_TARGET_ATTEMPTS, 0);
}
@@ -888,6 +925,83 @@
return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
ctx, CONFIG_ENABLE_NETWORK_STATS_EVENT_LOGGER);
}
+
+ /**
+ * Get whether TrafficStats rate-limit cache is always applied.
+ *
+ * This method should only be called once in the constructor,
+ * to ensure that the code does not need to deal with flag values changing at runtime.
+ */
+ public boolean alwaysUseTrafficStatsRateLimitCache(@NonNull Context ctx) {
+ return SdkLevel.isAtLeastV() && DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG);
+ }
+
+ /**
+ * Get TrafficStats rate-limit cache expiry.
+ *
+ * This method should only be called once in the constructor,
+ * to ensure that the code does not need to deal with flag values changing at runtime.
+ */
+ public int getTrafficStatsRateLimitCacheExpiryDuration() {
+ return getDeviceConfigPropertyInt(
+ NAMESPACE_TETHERING, TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
+ DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS);
+ }
+
+ /**
+ * Get TrafficStats rate-limit cache max entries.
+ *
+ * This method should only be called once in the constructor,
+ * to ensure that the code does not need to deal with flag values changing at runtime.
+ */
+ public int getTrafficStatsRateLimitCacheMaxEntries() {
+ return getDeviceConfigPropertyInt(
+ NAMESPACE_TETHERING, TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME,
+ DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES);
+ }
+
+ /**
+ * Wrapper method for {@link CompatChanges#isChangeEnabled(long, int)}
+ */
+ public boolean isChangeEnabled(final long changeId, final int uid) {
+ return CompatChanges.isChangeEnabled(changeId, uid);
+ }
+
+ /**
+ * Retrieves native network total statistics.
+ *
+ * @return A NetworkStats.Entry containing the native statistics, or
+ * null if an error occurs.
+ */
+ @Nullable
+ public NetworkStats.Entry nativeGetTotalStat() {
+ return NetworkStatsService.nativeGetTotalStat();
+ }
+
+ /**
+ * Retrieves native network interface statistics for the specified interface.
+ *
+ * @param iface The name of the network interface to query.
+ * @return A NetworkStats.Entry containing the native statistics for the interface, or
+ * null if an error occurs.
+ */
+ @Nullable
+ public NetworkStats.Entry nativeGetIfaceStat(String iface) {
+ return NetworkStatsService.nativeGetIfaceStat(iface);
+ }
+
+ /**
+ * Retrieves native network uid statistics for the specified uid.
+ *
+ * @param uid The uid of the application to query.
+ * @return A NetworkStats.Entry containing the native statistics for the uid, or
+ * null if an error occurs.
+ */
+ @Nullable
+ public NetworkStats.Entry nativeGetUidStat(int uid) {
+ return NetworkStatsService.nativeGetUidStat(uid);
+ }
}
/**
@@ -1983,53 +2097,108 @@
if (callingUid != android.os.Process.SYSTEM_UID && callingUid != uid) {
return UNSUPPORTED;
}
- return getEntryValueForType(nativeGetUidStat(uid), type);
+ if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
+
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, callingUid)) {
+ final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
+ () -> mDeps.nativeGetUidStat(uid));
+ return getEntryValueForType(entry, type);
+ }
+
+ return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
+ }
+
+ @Nullable
+ private NetworkStats.Entry getIfaceStatsInternal(@NonNull String iface) {
+ final NetworkStats.Entry entry = mDeps.nativeGetIfaceStat(iface);
+ if (entry == null) {
+ return null;
+ }
+ // When tethering offload is in use, nativeIfaceStats does not contain usage from
+ // offload, add it back here. Note that the included statistics might be stale
+ // since polling newest stats from hardware might impact system health and not
+ // suitable for TrafficStats API use cases.
+ entry.add(getProviderIfaceStats(iface));
+ return entry;
}
@Override
public long getIfaceStats(@NonNull String iface, int type) {
Objects.requireNonNull(iface);
- final NetworkStats.Entry entry = nativeGetIfaceStat(iface);
- final long value = getEntryValueForType(entry, type);
- if (value == UNSUPPORTED) {
- return UNSUPPORTED;
- } else {
- // When tethering offload is in use, nativeIfaceStats does not contain usage from
- // offload, add it back here. Note that the included statistics might be stale
- // since polling newest stats from hardware might impact system health and not
- // suitable for TrafficStats API use cases.
- entry.add(getProviderIfaceStats(iface));
+ if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
+
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
+ () -> getIfaceStatsInternal(iface));
return getEntryValueForType(entry, type);
}
+
+ return getEntryValueForType(getIfaceStatsInternal(iface), type);
}
private long getEntryValueForType(@Nullable NetworkStats.Entry entry, int type) {
if (entry == null) return UNSUPPORTED;
+ if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
switch (type) {
- case TrafficStats.TYPE_RX_BYTES:
+ case TYPE_RX_BYTES:
return entry.rxBytes;
- case TrafficStats.TYPE_TX_BYTES:
- return entry.txBytes;
- case TrafficStats.TYPE_RX_PACKETS:
+ case TYPE_RX_PACKETS:
return entry.rxPackets;
- case TrafficStats.TYPE_TX_PACKETS:
+ case TYPE_TX_BYTES:
+ return entry.txBytes;
+ case TYPE_TX_PACKETS:
return entry.txPackets;
default:
- return UNSUPPORTED;
+ throw new IllegalStateException("Bug: Invalid type: "
+ + type + " should not reach here.");
}
}
+ private boolean isEntryValueTypeValid(int type) {
+ switch (type) {
+ case TYPE_RX_BYTES:
+ case TYPE_RX_PACKETS:
+ case TYPE_TX_BYTES:
+ case TYPE_TX_PACKETS:
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ @Nullable
+ private NetworkStats.Entry getTotalStatsInternal() {
+ final NetworkStats.Entry entry = mDeps.nativeGetTotalStat();
+ if (entry == null) {
+ return null;
+ }
+ entry.add(getProviderIfaceStats(IFACE_ALL));
+ return entry;
+ }
+
@Override
public long getTotalStats(int type) {
- final NetworkStats.Entry entry = nativeGetTotalStat();
- final long value = getEntryValueForType(entry, type);
- if (value == UNSUPPORTED) {
- return UNSUPPORTED;
- } else {
- // Refer to comment in getIfaceStats
- entry.add(getProviderIfaceStats(IFACE_ALL));
+ if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(
+ IFACE_ALL, UID_ALL, () -> getTotalStatsInternal());
return getEntryValueForType(entry, type);
}
+
+ return getEntryValueForType(getTotalStatsInternal(), type);
+ }
+
+ @Override
+ public void clearTrafficStatsRateLimitCaches() {
+ PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+ mTrafficStatsUidCache.clear();
+ mTrafficStatsIfaceCache.clear();
+ mTrafficStatsTotalCache.clear();
}
private NetworkStats.Entry getProviderIfaceStats(@Nullable String iface) {
@@ -2785,6 +2954,13 @@
} catch (IOException e) {
pw.println("(failed to dump FastDataInput counters)");
}
+ pw.print("trafficstats.cache.alwaysuse", mAlwaysUseTrafficStatsRateLimitCache);
+ pw.println();
+ pw.print(TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
+ mTrafficStatsRateLimitCacheExpiryDuration);
+ pw.println();
+ pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME, mTrafficStatsRateLimitCacheMaxEntries);
+ pw.println();
pw.decreaseIndent();
diff --git a/service/Android.bp b/service/Android.bp
index 1d74efc..1dd09a9 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -188,7 +188,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V14-java",
+ "dnsresolver_aidl_interface-V15-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-ip",
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 4214bc9..c07d050 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -114,7 +114,8 @@
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- if (false && modules::sdklevel::IsAtLeastV()) {
+ // TODO: use modules::sdklevel::IsAtLeastV() once api finalized
+ if (android_get_device_api_level() >= __ANDROID_API_V__) {
V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
} else {
V("/sys/fs/bpf/net_shared", S_IFDIR|01777, SYSTEM, SYSTEM, "fs_bpf_net_shared", DIR);
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 42c1628..a30735a 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -23,17 +23,17 @@
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED;
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH;
-import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.IIF_MATCH;
import static android.net.BpfNetMapsConstants.INGRESS_DISCARD_MAP_PATH;
import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
-import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
import static android.net.BpfNetMapsUtils.isFirewallAllowList;
import static android.net.BpfNetMapsUtils.matchToString;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.INetd.PERMISSION_INTERNET;
@@ -55,6 +55,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
@@ -71,6 +72,7 @@
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
@@ -187,7 +189,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
CONFIGURATION_MAP_PATH, S32.class, U32.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open netd configuration map", e);
@@ -197,7 +199,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid owner map", e);
@@ -207,7 +209,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getUidPermissionMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
UID_PERMISSION_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid permission map", e);
@@ -217,6 +219,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
try {
+ // Cannot use SingleWriterBpfMap because it's written by ClatCoordinator as well.
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -227,7 +230,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open data saver enabled map", e);
@@ -237,7 +240,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
try {
- return new BpfMap<>(INGRESS_DISCARD_MAP_PATH,
+ return new SingleWriterBpfMap<>(INGRESS_DISCARD_MAP_PATH,
IngressDiscardKey.class, IngressDiscardValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open ingress discard map", e);
@@ -446,62 +449,6 @@
}
/**
- * Add naughty app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
- public void addNaughtyApp(final int uid) {
- throwIfPreT("addNaughtyApp is not available on pre-T devices");
-
- addRule(uid, PENALTY_BOX_MATCH, "addNaughtyApp");
- }
-
- /**
- * Remove naughty app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
- public void removeNaughtyApp(final int uid) {
- throwIfPreT("removeNaughtyApp is not available on pre-T devices");
-
- removeRule(uid, PENALTY_BOX_MATCH, "removeNaughtyApp");
- }
-
- /**
- * Add nice app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
- public void addNiceApp(final int uid) {
- throwIfPreT("addNiceApp is not available on pre-T devices");
-
- addRule(uid, HAPPY_BOX_MATCH, "addNiceApp");
- }
-
- /**
- * Remove nice app bandwidth rule for specific app
- *
- * @param uid uid of target app
- * @throws ServiceSpecificException in case of failure, with an error code indicating the
- * cause of the failure.
- */
- @RequiresApi(Build.VERSION_CODES.TIRAMISU)
- public void removeNiceApp(final int uid) {
- throwIfPreT("removeNiceApp is not available on pre-T devices");
-
- removeRule(uid, HAPPY_BOX_MATCH, "removeNiceApp");
- }
-
- /**
* Set target firewall child chain
*
* @param childChain target chain to enable
@@ -637,6 +584,7 @@
return BpfNetMapsUtils.getUidRule(sUidOwnerMap, childChain, uid);
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private Set<Integer> getUidsMatchEnabled(final int childChain) throws ErrnoException {
final long match = getMatchByFirewallChain(childChain);
Set<Integer> uids = new ArraySet<>();
@@ -665,6 +613,7 @@
* @param childChain target chain
* @return Set of uids
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public Set<Integer> getUidsWithAllowRuleOnAllowListChain(final int childChain)
throws ErrnoException {
if (!isFirewallAllowList(childChain)) {
@@ -686,6 +635,7 @@
* @param childChain target chain
* @return Set of uids
*/
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
public Set<Integer> getUidsWithDenyRuleOnDenyListChain(final int childChain)
throws ErrnoException {
if (isFirewallAllowList(childChain)) {
@@ -859,6 +809,28 @@
}
/**
+ * Get granted permissions for specified uid. If uid is not in the map, this method returns
+ * {@link android.net.INetd.PERMISSION_INTERNET} since this is a default permission.
+ * See {@link #setNetPermForUids}
+ *
+ * @param uid target uid
+ * @return granted permissions.
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public int getNetPermForUid(final int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ try {
+ // Key of uid permission map is appId
+ // TODO: Rename map name
+ final U8 permissions = sUidPermissionMap.getValue(new S32(appId));
+ return permissions != null ? permissions.val : PERMISSION_INTERNET;
+ } catch (ErrnoException e) {
+ Log.wtf(TAG, "Failed to get permission for uid: " + uid);
+ return PERMISSION_INTERNET;
+ }
+ }
+
+ /**
* Set Data Saver enabled or disabled
*
* @param enable whether Data Saver is enabled or disabled.
@@ -919,6 +891,30 @@
}
/**
+ * Get blocked reasons for specified uid
+ *
+ * @param uid Target Uid
+ * @return Reasons of network access blocking for an UID
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public int getUidNetworkingBlockedReasons(final int uid) {
+ return BpfNetMapsUtils.getUidNetworkingBlockedReasons(uid,
+ sConfigurationMap, sUidOwnerMap, sDataSaverEnabledMap);
+ }
+
+ /**
+ * Return whether the network access of specified uid is blocked on metered networks
+ *
+ * @param uid The target uid.
+ * @return True if the network access is blocked on metered networks. Otherwise, false
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public boolean isUidRestrictedOnMeteredNetworks(final int uid) {
+ final int blockedReasons = getUidNetworkingBlockedReasons(uid);
+ return (blockedReasons & BLOCKED_METERED_REASON_MASK) != BLOCKED_REASON_NONE;
+ }
+
+ /*
* Return whether the network is blocked by firewall chains for the given uid.
*
* Note that {@link #getDataSaverEnabled()} has a latency before V.
@@ -999,6 +995,7 @@
return sj.toString();
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void dumpOwnerMatchConfig(final IndentingPrintWriter pw) {
try {
final long match = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index b1ae019..d3b7f79 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -24,6 +24,8 @@
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.BpfNetMapsConstants.METERED_ALLOW_CHAINS;
+import static android.net.BpfNetMapsConstants.METERED_DENY_CHAINS;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
@@ -34,8 +36,10 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
@@ -62,6 +66,7 @@
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
@@ -99,6 +104,7 @@
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_MATCH_LOCAL_NETWORK;
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION;
+import static android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION;
import static android.os.Process.INVALID_UID;
import static android.os.Process.VPN_UID;
import static android.system.OsConstants.ETH_P_ALL;
@@ -116,6 +122,7 @@
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
+import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
@@ -396,6 +403,8 @@
private static final String NETWORK_ARG = "networks";
private static final String REQUEST_ARG = "requests";
private static final String TRAFFICCONTROLLER_ARG = "trafficcontroller";
+ public static final String CLATEGRESS4RAWBPFMAP_ARG = "clatEgress4RawBpfMap";
+ public static final String CLATINGRESS6RAWBPFMAP_ARG = "clatIngress6RawBpfMap";
private static final boolean DBG = true;
private static final boolean DDBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -480,9 +489,33 @@
private final boolean mRequestRestrictedWifiEnabled;
private final boolean mBackgroundFirewallChainEnabled;
+ private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+
/**
- * Stale copy of uid blocked reasons provided by NPMS. As long as they are accessed only in
- * internal handler thread, they don't need a lock.
+ * Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
+ * Key is uid based on mAsUid of registered networkRequestInfo
+ * Value is count of registered networkRequestInfo
+ *
+ * This is necessary because when a firewall chain is enabled or disabled, that affects all UIDs
+ * on the system, not just UIDs on that firewall chain. For example, entering doze mode affects
+ * all UIDs that are not on the dozable chain. ConnectivityService doesn't know which UIDs are
+ * running. But it only needs to send onBlockedStatusChanged to UIDs that have at least one
+ * NetworkCallback registered.
+ *
+ * UIDs are added to this list on the binder thread when processing requestNetwork and similar
+ * IPCs. They are removed from this list on the handler thread, when the callback unregistration
+ * is fully processed. They cannot be unregistered when the unregister IPC is processed because
+ * sometimes requests are unregistered on the handler thread.
+ */
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private final SparseIntArray mBlockedStatusTrackingUids = new SparseIntArray();
+
+ /**
+ * Stale copy of UID blocked reasons. This is used to send onBlockedStatusChanged
+ * callbacks. This is only used on the handler thread, so it does not require a lock.
+ * On U-, the blocked reasons come from NPMS.
+ * On V+, the blocked reasons come from the BPF map contents and only maintains blocked reasons
+ * of uids that register network callbacks.
*/
private final SparseIntArray mUidBlockedReasons = new SparseIntArray();
@@ -550,6 +583,8 @@
* default network for each app.
* Order ints passed to netd must be in the 0~999 range. Larger values code for
* a lower priority, see {@link NativeUidRangeConfig}.
+ * Note that only the highest priority preference is applied if the uid is the target of
+ * multiple preferences.
*
* Requests that don't code for a per-app preference use PREFERENCE_ORDER_INVALID.
* The default request uses PREFERENCE_ORDER_DEFAULT.
@@ -784,11 +819,10 @@
private static final int EVENT_SET_PROFILE_NETWORK_PREFERENCE = 50;
/**
- * Event to specify that reasons for why an uid is blocked changed.
- * arg1 = uid
- * arg2 = blockedReasons
+ * Event to update blocked reasons for uids.
+ * obj = List of Pair(uid, blockedReasons)
*/
- private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;
+ private static final int EVENT_BLOCKED_REASONS_CHANGED = 51;
/**
* Event to register a new network offer
@@ -849,6 +883,18 @@
private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
/**
+ * Event to update firewall socket destroy reasons for uids.
+ * obj = List of Pair(uid, socketDestroyReasons)
+ */
+ private static final int EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS = 62;
+
+ /**
+ * Event to clear firewall socket destroy reasons for all uids.
+ * arg1 = socketDestroyReason
+ */
+ private static final int EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS = 63;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -992,14 +1038,19 @@
// Flag to enable the feature of closing frozen app sockets.
private final boolean mDestroyFrozenSockets;
- // Flag to optimize closing frozen app sockets by waiting for the cellular modem to wake up.
- private final boolean mDelayDestroyFrozenSockets;
+ // Flag to optimize closing app sockets by waiting for the cellular modem to wake up.
+ private final boolean mDelayDestroySockets;
// Flag to allow SysUI to receive connectivity reports for wifi picker UI.
private final boolean mAllowSysUiConnectivityReports;
// Uids that ConnectivityService is pending to close sockets of.
- private final Set<Integer> mPendingFrozenUids = new ArraySet<>();
+ // Key is uid and value is reasons of socket destroy
+ private final SparseIntArray mDestroySocketPendingUids = new SparseIntArray();
+
+ private static final int DESTROY_SOCKET_REASON_NONE = 0;
+ private static final int DESTROY_SOCKET_REASON_FROZEN = 1 << 0;
+ private static final int DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND = 1 << 1;
// Flag to drop packets to VPN addresses ingressing via non-VPN interfaces.
private final boolean mIngressToVpnAddressFiltering;
@@ -1801,6 +1852,8 @@
&& mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut(
context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
+ mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
+ ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -1817,7 +1870,12 @@
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
// reading existing policy from disk.
- mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
+ // If shouldTrackUidsForBlockedStatusCallbacks() is true (On V+), ConnectivityService
+ // updates blocked reasons when firewall chain and data saver status is updated based on
+ // bpf map contents instead of receiving callbacks from NPMS
+ if (!shouldTrackUidsForBlockedStatusCallbacks()) {
+ mPolicyManager.registerNetworkPolicyCallback(null, mPolicyCallback);
+ }
final PowerManager powerManager = (PowerManager) context.getSystemService(
Context.POWER_SERVICE);
@@ -1948,8 +2006,7 @@
mDestroyFrozenSockets = mDeps.isAtLeastV() || (mDeps.isAtLeastU()
&& mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION));
- mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
- && mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
+ mDelayDestroySockets = mDeps.isFeatureNotChickenedOut(context, DELAY_DESTROY_SOCKETS);
mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut(
mContext, ALLOW_SYSUI_CONNECTIVITY_REPORTS);
if (mDestroyFrozenSockets) {
@@ -2225,6 +2282,11 @@
return nai;
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private boolean hasInternetPermission(final int uid) {
+ return (mBpfNetMaps.getNetPermForUid(uid) & PERMISSION_INTERNET) != 0;
+ }
+
/**
* Check if UID should be blocked from using the specified network.
*/
@@ -2239,7 +2301,13 @@
try {
final boolean metered = nc == null ? true : nc.isMetered();
if (mDeps.isAtLeastV()) {
- return mBpfNetMaps.isUidNetworkingBlocked(uid, metered);
+ final boolean hasInternetPermission = hasInternetPermission(uid);
+ final boolean blockedByUidRules = mBpfNetMaps.isUidNetworkingBlocked(uid, metered);
+ if (mDeps.isChangeEnabled(NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, uid)) {
+ return blockedByUidRules || !hasInternetPermission;
+ } else {
+ return hasInternetPermission && blockedByUidRules;
+ }
} else {
return mPolicyManager.isUidNetworkingBlocked(uid, metered);
}
@@ -2320,7 +2388,7 @@
final int uid = mDeps.getCallingUid();
final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
if (nai == null) return null;
- final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false);
+ final NetworkInfo networkInfo = getFilteredNetworkInfo(nai, uid, false /* ignoreBlocked */);
maybeLogBlockedNetworkInfo(networkInfo, uid);
return networkInfo;
}
@@ -3294,14 +3362,38 @@
private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {
@Override
public void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
- uid, blockedReasons));
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ Log.wtf(TAG, "Received unexpected NetworkPolicy callback");
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, blockedReasons))));
}
};
- private void handleUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
- maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
- setUidBlockedReasons(uid, blockedReasons);
+ private boolean shouldTrackUidsForBlockedStatusCallbacks() {
+ return mDeps.isAtLeastV();
+ }
+
+ @VisibleForTesting
+ void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
+ for (Pair<Integer, Integer> reasons: reasonsList) {
+ final int uid = reasons.first;
+ final int blockedReasons = reasons.second;
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ synchronized (mBlockedStatusTrackingUids) {
+ if (mBlockedStatusTrackingUids.get(uid) == 0) {
+ // This uid is not tracked anymore.
+ // This can happen if the network request is unregistered while
+ // EVENT_BLOCKED_REASONS_CHANGED is posted but not processed yet.
+ continue;
+ }
+ }
+ }
+ maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
+ setUidBlockedReasons(uid, blockedReasons);
+ }
}
static final class UidFrozenStateChangedArgs {
@@ -3332,44 +3424,92 @@
return !mNetworkActivityTracker.isDefaultNetworkActive();
}
- private void handleFrozenUids(int[] uids, int[] frozenStates) {
- final ArraySet<Integer> ownerUids = new ArraySet<>();
+ private boolean shouldTrackFirewallDestroySocketReasons() {
+ return mDeps.isAtLeastV();
+ }
- for (int i = 0; i < uids.length; i++) {
- if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
- ownerUids.add(uids[i]);
- } else {
- mPendingFrozenUids.remove(uids[i]);
- }
- }
-
- if (ownerUids.isEmpty()) {
- return;
- }
-
- if (mDelayDestroyFrozenSockets && isCellNetworkIdle()) {
- // Delay closing sockets to avoid waking the cell modem up.
- // Wi-Fi network state is not considered since waking Wi-Fi modem up is much cheaper
- // than waking cell modem up.
- mPendingFrozenUids.addAll(ownerUids);
+ private void updateDestroySocketReasons(final int uid, final int reason,
+ final boolean addReason) {
+ final int destroyReasons = mDestroySocketPendingUids.get(uid, DESTROY_SOCKET_REASON_NONE);
+ if (addReason) {
+ mDestroySocketPendingUids.put(uid, destroyReasons | reason);
} else {
- try {
- mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
- } catch (SocketException | InterruptedIOException | ErrnoException e) {
- loge("Exception in socket destroy: " + e);
+ final int newDestroyReasons = destroyReasons & ~reason;
+ if (newDestroyReasons == DESTROY_SOCKET_REASON_NONE) {
+ mDestroySocketPendingUids.delete(uid);
+ } else {
+ mDestroySocketPendingUids.put(uid, newDestroyReasons);
}
}
}
- private void closePendingFrozenSockets() {
+ private void handleFrozenUids(int[] uids, int[] frozenStates) {
+ ensureRunningOnConnectivityServiceThread();
+ for (int i = 0; i < uids.length; i++) {
+ final int uid = uids[i];
+ final boolean addReason = frozenStates[i] == UID_FROZEN_STATE_FROZEN;
+ updateDestroySocketReasons(uid, DESTROY_SOCKET_REASON_FROZEN, addReason);
+ }
+
+ if (!mDelayDestroySockets || !isCellNetworkIdle()) {
+ destroyPendingSockets();
+ }
+ }
+
+ private void handleUpdateFirewallDestroySocketReasons(
+ List<Pair<Integer, Integer>> reasonsList) {
+ if (!shouldTrackFirewallDestroySocketReasons()) {
+ Log.wtf(TAG, "handleUpdateFirewallDestroySocketReasons is called unexpectedly");
+ return;
+ }
ensureRunningOnConnectivityServiceThread();
- try {
- mDeps.destroyLiveTcpSocketsByOwnerUids(mPendingFrozenUids);
- } catch (SocketException | InterruptedIOException | ErrnoException e) {
- loge("Failed to close pending frozen app sockets: " + e);
+ for (Pair<Integer, Integer> uidSocketDestroyReasons: reasonsList) {
+ final int uid = uidSocketDestroyReasons.first;
+ final int reasons = uidSocketDestroyReasons.second;
+ final boolean destroyByFirewallBackground =
+ (reasons & DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND)
+ != DESTROY_SOCKET_REASON_NONE;
+ updateDestroySocketReasons(uid, DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND,
+ destroyByFirewallBackground);
}
- mPendingFrozenUids.clear();
+
+ if (!mDelayDestroySockets || !isCellNetworkIdle()) {
+ destroyPendingSockets();
+ }
+ }
+
+ private void handleClearFirewallDestroySocketReasons(final int reason) {
+ if (!shouldTrackFirewallDestroySocketReasons()) {
+ Log.wtf(TAG, "handleClearFirewallDestroySocketReasons is called uexpectedly");
+ return;
+ }
+ ensureRunningOnConnectivityServiceThread();
+
+ // Unset reason from all pending uids
+ for (int i = mDestroySocketPendingUids.size() - 1; i >= 0; i--) {
+ final int uid = mDestroySocketPendingUids.keyAt(i);
+ updateDestroySocketReasons(uid, reason, false /* addReason */);
+ }
+ }
+
+ private void destroyPendingSockets() {
+ ensureRunningOnConnectivityServiceThread();
+ if (mDestroySocketPendingUids.size() == 0) {
+ return;
+ }
+
+ Set<Integer> uids = new ArraySet<>();
+ for (int i = 0; i < mDestroySocketPendingUids.size(); i++) {
+ uids.add(mDestroySocketPendingUids.keyAt(i));
+ }
+
+ try {
+ mDeps.destroyLiveTcpSocketsByOwnerUids(uids);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ loge("Failed to destroy sockets: " + e);
+ }
+ mDestroySocketPendingUids.clear();
}
private void handleReportNetworkActivity(final NetworkActivityParams params) {
@@ -3386,40 +3526,42 @@
isCellNetworkActivity = params.label == TRANSPORT_CELLULAR;
}
- if (mDelayDestroyFrozenSockets
- && params.isActive
- && isCellNetworkActivity
- && !mPendingFrozenUids.isEmpty()) {
- closePendingFrozenSockets();
+ if (mDelayDestroySockets && params.isActive && isCellNetworkActivity) {
+ destroyPendingSockets();
}
}
/**
- * If the cellular network is no longer the default network, close pending frozen sockets.
+ * If the cellular network is no longer the default network, destroy pending sockets.
*
* @param newNetwork new default network
* @param oldNetwork old default network
*/
- private void maybeClosePendingFrozenSockets(NetworkAgentInfo newNetwork,
+ private void maybeDestroyPendingSockets(NetworkAgentInfo newNetwork,
NetworkAgentInfo oldNetwork) {
final boolean isOldNetworkCellular = oldNetwork != null
&& oldNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
final boolean isNewNetworkCellular = newNetwork != null
&& newNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
- if (isOldNetworkCellular
- && !isNewNetworkCellular
- && !mPendingFrozenUids.isEmpty()) {
- closePendingFrozenSockets();
+ if (isOldNetworkCellular && !isNewNetworkCellular) {
+ destroyPendingSockets();
}
}
- private void dumpCloseFrozenAppSockets(IndentingPrintWriter pw) {
- pw.println("CloseFrozenAppSockets:");
+ private void dumpDestroySockets(IndentingPrintWriter pw) {
+ pw.println("DestroySockets:");
pw.increaseIndent();
pw.print("mDestroyFrozenSockets="); pw.println(mDestroyFrozenSockets);
- pw.print("mDelayDestroyFrozenSockets="); pw.println(mDelayDestroyFrozenSockets);
- pw.print("mPendingFrozenUids="); pw.println(mPendingFrozenUids);
+ pw.print("mDelayDestroySockets="); pw.println(mDelayDestroySockets);
+ pw.print("mDestroySocketPendingUids:");
+ pw.increaseIndent();
+ for (int i = 0; i < mDestroySocketPendingUids.size(); i++) {
+ final int uid = mDestroySocketPendingUids.keyAt(i);
+ final int reasons = mDestroySocketPendingUids.valueAt(i);
+ pw.print(uid + ": reasons=" + reasons);
+ }
+ pw.decreaseIndent();
pw.decreaseIndent();
}
@@ -3445,9 +3587,6 @@
@VisibleForTesting
static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
- @VisibleForTesting
- static final String DELAY_DESTROY_FROZEN_SOCKETS_VERSION =
- "delay_destroy_frozen_sockets_version";
@VisibleForTesting
public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
@@ -4008,6 +4147,12 @@
boolean verbose = !CollectionUtils.contains(args, SHORT_ARG);
dumpTrafficController(pw, fd, verbose);
return;
+ } else if (CollectionUtils.contains(args, CLATEGRESS4RAWBPFMAP_ARG)) {
+ dumpClatBpfRawMap(pw, true /* isEgress4Map */);
+ return;
+ } else if (CollectionUtils.contains(args, CLATINGRESS6RAWBPFMAP_ARG)) {
+ dumpClatBpfRawMap(pw, false /* isEgress4Map */);
+ return;
}
pw.println("NetworkProviders for:");
@@ -4081,7 +4226,7 @@
dumpAvoidBadWifiSettings(pw);
pw.println();
- dumpCloseFrozenAppSockets(pw);
+ dumpDestroySockets(pw);
pw.println();
dumpBpfProgramStatus(pw);
@@ -4267,6 +4412,15 @@
}
}
+ private void dumpClatBpfRawMap(IndentingPrintWriter pw, boolean isEgress4Map) {
+ for (NetworkAgentInfo nai : networksSortedById()) {
+ if (nai.clatd != null) {
+ nai.clatd.dumpRawBpfMap(pw, isEgress4Map);
+ break;
+ }
+ }
+ }
+
private void dumpAllRequestInfoLogsToLogcat() {
try (PrintWriter logPw = new PrintWriter(new Writer() {
@Override
@@ -4644,7 +4798,15 @@
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
- log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
+ final String statusMsg;
+ if (valid) {
+ statusMsg = "passed";
+ } else if (!TextUtils.isEmpty(redirectUrl)) {
+ statusMsg = "detected a portal";
+ } else {
+ statusMsg = "failed";
+ }
+ log(nai.toShortString() + " validation " + statusMsg + logMsg);
}
if (valid != wasValidated) {
final FullScore oldScore = nai.getScore();
@@ -5194,7 +5356,7 @@
if (mDefaultRequest == nri) {
mNetworkActivityTracker.updateDefaultNetwork(null /* newNetwork */, nai);
- maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
+ maybeDestroyPendingSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -5471,6 +5633,12 @@
// If there is an active request, then for sure there is a satisfier.
nri.getSatisfier().addRequest(activeRequest);
}
+
+ if (shouldTrackUidsForBlockedStatusCallbacks()
+ && isAppRequest(nri)
+ && !nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Registered nri is not tracked for sending blocked status: " + nri);
+ }
}
if (mFlags.noRematchAllRequestsOnRegister()) {
@@ -5670,6 +5838,11 @@
}
private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+ handleRemoveNetworkRequest(nri, true /* untrackUids */);
+ }
+
+ private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri,
+ final boolean untrackUids) {
ensureRunningOnConnectivityServiceThread();
for (final NetworkRequest req : nri.mRequests) {
if (null == mNetworkRequests.remove(req)) {
@@ -5704,7 +5877,9 @@
}
}
- nri.mPerUidCounter.decrementCount(nri.mUid);
+ if (untrackUids) {
+ maybeUntrackUidAndClearBlockedReasons(nri);
+ }
mNetworkRequestInfoLogs.log("RELEASE " + nri);
checkNrisConsistency(nri);
@@ -5726,12 +5901,17 @@
}
private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
+ handleRemoveNetworkRequests(nris, true /* untrackUids */);
+ }
+
+ private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris,
+ final boolean untrackUids) {
for (final NetworkRequestInfo nri : nris) {
if (mDefaultRequest == nri) {
// Make sure we never remove the default request.
continue;
}
- handleRemoveNetworkRequest(nri);
+ handleRemoveNetworkRequest(nri, untrackUids);
}
}
@@ -6471,8 +6651,8 @@
handlePrivateDnsValidationUpdate(
(PrivateDnsValidationUpdate) msg.obj);
break;
- case EVENT_UID_BLOCKED_REASON_CHANGED:
- handleUidBlockedReasonChanged(msg.arg1, msg.arg2);
+ case EVENT_BLOCKED_REASONS_CHANGED:
+ handleBlockedReasonsChanged((List) msg.obj);
break;
case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
@@ -6521,6 +6701,12 @@
UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
handleFrozenUids(args.mUids, args.mFrozenStates);
break;
+ case EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleUpdateFirewallDestroySocketReasons((List) msg.obj);
+ break;
+ case EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS:
+ handleClearFirewallDestroySocketReasons(msg.arg1);
+ break;
}
}
}
@@ -7337,6 +7523,11 @@
// maximum limit of registered callbacks per UID.
final int mAsUid;
+ // Flag to indicate that uid of this nri is tracked for sending blocked status callbacks.
+ // It is always true on V+ if mMessenger != null. As such, it's not strictly necessary.
+ // it's used only as a safeguard to avoid double counting or leaking.
+ boolean mUidTrackedForBlockedStatus;
+
// Preference order of this request.
final int mPreferenceOrder;
@@ -7386,7 +7577,6 @@
mUid = mDeps.getCallingUid();
mAsUid = asUid;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
/**
* Location sensitive data not included in pending intent. Only included in
* {@link NetworkCallback}.
@@ -7420,7 +7610,6 @@
mAsUid = asUid;
mPendingIntent = null;
mPerUidCounter = getRequestCounter(this);
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
@@ -7460,9 +7649,9 @@
mAsUid = nri.mAsUid;
mPendingIntent = nri.mPendingIntent;
mPerUidCounter = nri.mPerUidCounter;
- mPerUidCounter.incrementCountOrThrow(mUid);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
+ mUidTrackedForBlockedStatus = nri.mUidTrackedForBlockedStatus;
mPreferenceOrder = PREFERENCE_ORDER_INVALID;
linkDeathRecipient();
}
@@ -7550,7 +7739,8 @@
+ " " + mRequests
+ (mPendingIntent == null ? "" : " to trigger " + mPendingIntent)
+ " callback flags: " + mCallbackFlags
- + " order: " + mPreferenceOrder;
+ + " order: " + mPreferenceOrder
+ + " isUidTracked: " + mUidTrackedForBlockedStatus;
}
}
@@ -7765,13 +7955,6 @@
throw new IllegalArgumentException("Bad timeout specified");
}
- final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
- nextNetworkRequestId(), reqType);
- final NetworkRequestInfo nri = getNriToRegister(
- asUid, networkRequest, messenger, binder, callbackFlags,
- callingAttributionTag);
- if (DBG) log("requestNetwork for " + nri);
-
// For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were
// copied from the default request above. (This is necessary to ensure, for example, that
// the callback does not leak sensitive information to unprivileged apps.) Check that the
@@ -7783,7 +7966,13 @@
+ networkCapabilities + " vs. " + defaultNc);
}
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
+ final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
+ nextNetworkRequestId(), reqType);
+ final NetworkRequestInfo nri = getNriToRegister(
+ asUid, networkRequest, messenger, binder, callbackFlags,
+ callingAttributionTag);
+ if (DBG) log("requestNetwork for " + nri);
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST, nri);
if (timeoutMs > 0) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST,
nri), timeoutMs);
@@ -7953,6 +8142,13 @@
// Policy already enforced.
return;
}
+ if (mDeps.isAtLeastV()) {
+ if (mBpfNetMaps.isUidRestrictedOnMeteredNetworks(uid)) {
+ // If UID is restricted, don't allow them to bring up metered APNs.
+ networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
+ }
+ return;
+ }
final long ident = Binder.clearCallingIdentity();
try {
if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) {
@@ -7985,8 +8181,7 @@
NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, networkRequest, operation,
callingAttributionTag);
if (DBG) log("pendingRequest for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
- nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, nri);
return networkRequest;
}
@@ -8025,6 +8220,77 @@
return true;
}
+ private boolean isAppRequest(NetworkRequestInfo nri) {
+ return nri.mMessenger != null || nri.mPendingIntent != null;
+ }
+
+ private void trackUidAndMaybePostCurrentBlockedReason(final NetworkRequestInfo nri) {
+ if (!isAppRequest(nri)) {
+ Log.wtf(TAG, "trackUidAndMaybePostCurrentBlockedReason is called for non app"
+ + "request: " + nri);
+ return;
+ }
+ nri.mPerUidCounter.incrementCountOrThrow(nri.mUid);
+
+ // If nri.mMessenger is null, this nri does not have NetworkCallback so ConnectivityService
+ // does not need to send onBlockedStatusChanged callback for this uid and does not need to
+ // track the uid in mBlockedStatusTrackingUids
+ if (!shouldTrackUidsForBlockedStatusCallbacks() || nri.mMessenger == null) {
+ return;
+ }
+ if (nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Nri is already tracked for sending blocked status: " + nri);
+ return;
+ }
+ nri.mUidTrackedForBlockedStatus = true;
+ synchronized (mBlockedStatusTrackingUids) {
+ final int uid = nri.mAsUid;
+ final int count = mBlockedStatusTrackingUids.get(uid, 0);
+ if (count == 0) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)))));
+ }
+ mBlockedStatusTrackingUids.put(uid, count + 1);
+ }
+ }
+
+ private void trackUidAndRegisterNetworkRequest(final int event, NetworkRequestInfo nri) {
+ // Post the update of the UID's blocked reasons before posting the message that registers
+ // the callback. This is necessary because if the callback immediately matches a request,
+ // the onBlockedStatusChanged must be called with the correct blocked reasons.
+ // Also, once trackUidAndMaybePostCurrentBlockedReason is called, the register network
+ // request event must be posted, because otherwise the counter for uid will never be
+ // decremented.
+ trackUidAndMaybePostCurrentBlockedReason(nri);
+ mHandler.sendMessage(mHandler.obtainMessage(event, nri));
+ }
+
+ private void maybeUntrackUidAndClearBlockedReasons(final NetworkRequestInfo nri) {
+ if (!isAppRequest(nri)) {
+ // Not an app request.
+ return;
+ }
+ nri.mPerUidCounter.decrementCount(nri.mUid);
+
+ if (!shouldTrackUidsForBlockedStatusCallbacks() || nri.mMessenger == null) {
+ return;
+ }
+ if (!nri.mUidTrackedForBlockedStatus) {
+ Log.wtf(TAG, "Nri is not tracked for sending blocked status: " + nri);
+ return;
+ }
+ nri.mUidTrackedForBlockedStatus = false;
+ synchronized (mBlockedStatusTrackingUids) {
+ final int count = mBlockedStatusTrackingUids.get(nri.mAsUid);
+ if (count > 1) {
+ mBlockedStatusTrackingUids.put(nri.mAsUid, count - 1);
+ } else {
+ mBlockedStatusTrackingUids.delete(nri.mAsUid);
+ mUidBlockedReasons.delete(nri.mAsUid);
+ }
+ }
+ }
+
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder,
@@ -8054,7 +8320,7 @@
callingAttributionTag);
if (VDBG) log("listenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER, nri);
return networkRequest;
}
@@ -8079,8 +8345,7 @@
callingAttributionTag);
if (VDBG) log("pendingListenForNetwork for " + nri);
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri));
+ trackUidAndRegisterNetworkRequest(EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri);
}
/** Returns the next Network provider ID. */
@@ -10063,7 +10328,7 @@
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDefaultNetwork(newDefaultNetwork, oldDefaultNetwork);
- maybeClosePendingFrozenSockets(newDefaultNetwork, oldDefaultNetwork);
+ maybeDestroyPendingSockets(newDefaultNetwork, oldDefaultNetwork);
mProxyTracker.setDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
resetHttpProxyForNonDefaultNetwork(oldDefaultNetwork);
@@ -11035,7 +11300,7 @@
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
- getBlockedState(blockedReasons, metered, vpnBlocked));
+ getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
}
// Notify the requests on this NAI that the network is now lingered.
@@ -11044,7 +11309,21 @@
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
}
- private static int getBlockedState(int reasons, boolean metered, boolean vpnBlocked) {
+ private int getPermissionBlockedState(final int uid, final int reasons) {
+ // Before V, the blocked reasons come from NPMS, and that code already behaves as if the
+ // change was disabled: apps without the internet permission will never be told they are
+ // blocked.
+ if (!mDeps.isAtLeastV()) return reasons;
+
+ if (hasInternetPermission(uid)) return reasons;
+
+ return mDeps.isChangeEnabled(NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, uid)
+ ? reasons | BLOCKED_REASON_NETWORK_RESTRICTED
+ : BLOCKED_REASON_NONE;
+ }
+
+ private int getBlockedState(int uid, int reasons, boolean metered, boolean vpnBlocked) {
+ reasons = getPermissionBlockedState(uid, reasons);
if (!metered) reasons &= ~BLOCKED_METERED_REASON_MASK;
return vpnBlocked
? reasons | BLOCKED_REASON_LOCKDOWN_VPN
@@ -11085,8 +11364,10 @@
? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
: oldVpnBlocked;
- final int oldBlockedState = getBlockedState(blockedReasons, oldMetered, oldVpnBlocked);
- final int newBlockedState = getBlockedState(blockedReasons, newMetered, newVpnBlocked);
+ final int oldBlockedState = getBlockedState(
+ nri.mAsUid, blockedReasons, oldMetered, oldVpnBlocked);
+ final int newBlockedState = getBlockedState(
+ nri.mAsUid, blockedReasons, newMetered, newVpnBlocked);
if (oldBlockedState != newBlockedState) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
newBlockedState);
@@ -11105,8 +11386,9 @@
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
final int oldBlockedState = getBlockedState(
- mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
- final int newBlockedState = getBlockedState(blockedReasons, metered, vpnBlocked);
+ uid, mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
+ final int newBlockedState =
+ getBlockedState(uid, blockedReasons, metered, vpnBlocked);
if (oldBlockedState == newBlockedState) {
continue;
}
@@ -12142,6 +12424,7 @@
// handleRegisterConnectivityDiagnosticsCallback(). nri will be cleaned up as part of the
// callback's binder death.
final NetworkRequestInfo nri = new NetworkRequestInfo(callingUid, requestWithId);
+ nri.mPerUidCounter.incrementCountOrThrow(nri.mUid);
final ConnectivityDiagnosticsCallbackInfo cbInfo =
new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
@@ -12737,8 +13020,8 @@
if (um.isManagedProfile(profile.getIdentifier())) {
return true;
}
- if (mDeps.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
- return false;
+
+ return mDeps.isAtLeastT() && dpm.getDeviceOwnerComponentOnAnyUser() != null;
}
/**
@@ -13236,7 +13519,20 @@
final ArraySet<NetworkRequestInfo> perAppCallbackRequestsToUpdate =
getPerAppCallbackRequestsToUpdate();
final ArraySet<NetworkRequestInfo> nrisToRegister = new ArraySet<>(nris);
- handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate);
+ // This method does not need to modify perUidCounter and mBlockedStatusTrackingUids because:
+ // - |nris| only contains per-app network requests created by ConnectivityService which
+ // are internal requests and have no messenger and are not associated with any callbacks,
+ // and so do not need to be tracked in perUidCounter and mBlockedStatusTrackingUids.
+ // - The requests in perAppCallbackRequestsToUpdate are removed, modified, and re-added,
+ // but the same number of requests is removed and re-added, and none of the requests
+ // changes mUid and mAsUid, so the perUidCounter and mBlockedStatusTrackingUids before
+ // and after this method remains the same. Re-adding the requests does not modify
+ // perUidCounter and mBlockedStatusTrackingUids (that is done when the app registers the
+ // request), so removing them must not modify perUidCounter and mBlockedStatusTrackingUids
+ // either.
+ // TODO(b/341228979): Modify nris in place instead of removing them and re-adding them
+ handleRemoveNetworkRequests(perAppCallbackRequestsToUpdate,
+ false /* untrackUids */);
nrisToRegister.addAll(
createPerAppCallbackRequestsToRegister(perAppCallbackRequestsToUpdate));
handleRegisterNetworkRequests(nrisToRegister);
@@ -13471,40 +13767,17 @@
throw new IllegalStateException(e);
}
- try {
- mBpfNetMaps.setDataSaverEnabled(enable);
- } catch (ServiceSpecificException | UnsupportedOperationException e) {
- Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
- }
- }
-
- @Override
- public void updateMeteredNetworkAllowList(final int uid, final boolean add) {
- enforceNetworkStackOrSettingsPermission();
-
- try {
- if (add) {
- mBpfNetMaps.addNiceApp(uid);
- } else {
- mBpfNetMaps.removeNiceApp(uid);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setDataSaverEnabled(enable);
+ } catch (ServiceSpecificException | UnsupportedOperationException e) {
+ Log.e(TAG, "Failed to set data saver " + enable + " : " + e);
+ return;
}
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
- }
- @Override
- public void updateMeteredNetworkDenyList(final int uid, final boolean add) {
- enforceNetworkStackOrSettingsPermission();
-
- try {
- if (add) {
- mBpfNetMaps.addNaughtyApp(uid);
- } else {
- mBpfNetMaps.removeNaughtyApp(uid);
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
}
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
}
}
@@ -13540,10 +13813,20 @@
throw new IllegalArgumentException("setUidFirewallRule with invalid rule: " + rule);
}
- try {
- mBpfNetMaps.setUidRule(chain, uid, firewallRule);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setUidRule(chain, uid, firewallRule);
+ } catch (ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ if (shouldTrackUidsForBlockedStatusCallbacks()
+ && mBlockedStatusTrackingUids.get(uid, 0) != 0) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ List.of(new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)))));
+ }
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ maybePostFirewallDestroySocketReasons(chain, Set.of(uid));
+ }
}
}
@@ -13567,6 +13850,8 @@
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3:
+ case ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER:
+ case ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN:
defaultRule = FIREWALL_RULE_ALLOW;
break;
case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
@@ -13574,6 +13859,7 @@
case ConnectivityManager.FIREWALL_CHAIN_RESTRICTED:
case ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY:
case ConnectivityManager.FIREWALL_CHAIN_BACKGROUND:
+ case ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW:
defaultRule = FIREWALL_RULE_DENY;
break;
default:
@@ -13584,23 +13870,41 @@
return rule;
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private Set<Integer> getUidsOnFirewallChain(final int chain) throws ErrnoException {
+ if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
+ return mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
+ } else {
+ return mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private void closeSocketsForFirewallChainLocked(final int chain)
throws ErrnoException, SocketException, InterruptedIOException {
+ final Set<Integer> uidsOnChain = getUidsOnFirewallChain(chain);
if (BpfNetMapsUtils.isFirewallAllowList(chain)) {
// Allowlist means the firewall denies all by default, uids must be explicitly allowed
// So, close all non-system socket owned by uids that are not explicitly allowed
Set<Range<Integer>> ranges = new ArraySet<>();
ranges.add(new Range<>(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE));
- final Set<Integer> exemptUids = mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
- mDeps.destroyLiveTcpSockets(ranges, exemptUids);
+ mDeps.destroyLiveTcpSockets(ranges, uidsOnChain /* exemptUids */);
} else {
// Denylist means the firewall allows all by default, uids must be explicitly denied
// So, close socket owned by uids that are explicitly denied
- final Set<Integer> ownerUids = mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
- mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+ mDeps.destroyLiveTcpSocketsByOwnerUids(uidsOnChain /* ownerUids */);
}
}
+ private void maybePostClearFirewallDestroySocketReasons(int chain) {
+ if (chain != FIREWALL_CHAIN_BACKGROUND) {
+ // TODO (b/300681644): Support other firewall chains
+ return;
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CLEAR_FIREWALL_DESTROY_SOCKET_REASONS,
+ DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND, 0 /* arg2 */));
+ }
+
@Override
public void setFirewallChainEnabled(final int chain, final boolean enable) {
enforceNetworkStackOrSettingsPermission();
@@ -13610,11 +13914,27 @@
+ " the feature is disabled.");
return;
}
+ if (METERED_ALLOW_CHAINS.contains(chain) || METERED_DENY_CHAINS.contains(chain)) {
+ // Metered chains are used from a separate bpf program that is triggered by iptables
+ // and can not be controlled by setFirewallChainEnabled.
+ throw new UnsupportedOperationException(
+ "Chain (" + chain + ") can not be controlled by setFirewallChainEnabled");
+ }
- try {
- mBpfNetMaps.setChildChain(chain, enable);
- } catch (ServiceSpecificException e) {
- throw new IllegalStateException(e);
+ synchronized (mBlockedStatusTrackingUids) {
+ try {
+ mBpfNetMaps.setChildChain(chain, enable);
+ } catch (ServiceSpecificException e) {
+ throw new IllegalStateException(e);
+ }
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
+ if (shouldTrackFirewallDestroySocketReasons() && !enable) {
+ // Clear destroy socket reasons so that CS does not destroy sockets of apps that
+ // have network access.
+ maybePostClearFirewallDestroySocketReasons(chain);
+ }
}
if (mDeps.isAtLeastU() && enable) {
@@ -13626,10 +13946,58 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private void updateTrackingUidsBlockedReasons() {
+ if (mBlockedStatusTrackingUids.size() == 0) {
+ return;
+ }
+ final ArrayList<Pair<Integer, Integer>> uidBlockedReasonsList = new ArrayList<>();
+ for (int i = 0; i < mBlockedStatusTrackingUids.size(); i++) {
+ final int uid = mBlockedStatusTrackingUids.keyAt(i);
+ uidBlockedReasonsList.add(
+ new Pair<>(uid, mBpfNetMaps.getUidNetworkingBlockedReasons(uid)));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_BLOCKED_REASONS_CHANGED,
+ uidBlockedReasonsList));
+ }
+
+ private int getFirewallDestroySocketReasons(final int blockedReasons) {
+ int destroySocketReasons = DESTROY_SOCKET_REASON_NONE;
+ if ((blockedReasons & BLOCKED_REASON_APP_BACKGROUND) != BLOCKED_REASON_NONE) {
+ destroySocketReasons |= DESTROY_SOCKET_REASON_FIREWALL_BACKGROUND;
+ }
+ return destroySocketReasons;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @GuardedBy("mBlockedStatusTrackingUids")
+ private void maybePostFirewallDestroySocketReasons(int chain, Set<Integer> uids) {
+ if (chain != FIREWALL_CHAIN_BACKGROUND) {
+ // TODO (b/300681644): Support other firewall chains
+ return;
+ }
+ final ArrayList<Pair<Integer, Integer>> reasonsList = new ArrayList<>();
+ for (int uid: uids) {
+ final int blockedReasons = mBpfNetMaps.getUidNetworkingBlockedReasons(uid);
+ final int destroySocketReaons = getFirewallDestroySocketReasons(blockedReasons);
+ reasonsList.add(new Pair<>(uid, destroySocketReaons));
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UPDATE_FIREWALL_DESTROY_SOCKET_REASONS,
+ reasonsList));
+ }
+
@Override
public boolean getFirewallChainEnabled(final int chain) {
enforceNetworkStackOrSettingsPermission();
+ if (METERED_ALLOW_CHAINS.contains(chain) || METERED_DENY_CHAINS.contains(chain)) {
+ // Metered chains are used from a separate bpf program that is triggered by iptables
+ // and can not be controlled by setFirewallChainEnabled.
+ throw new UnsupportedOperationException(
+ "getFirewallChainEnabled can not return status of chain (" + chain + ")");
+ }
+
return mBpfNetMaps.isChainEnabled(chain);
}
@@ -13643,7 +14011,31 @@
return;
}
- mBpfNetMaps.replaceUidChain(chain, uids);
+ synchronized (mBlockedStatusTrackingUids) {
+ // replaceFirewallChain removes uids that are currently on the chain and put |uids| on
+ // the chain.
+ // So this method could change blocked reasons of uids that are currently on chain +
+ // |uids|.
+ final Set<Integer> affectedUids = new ArraySet<>();
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ try {
+ affectedUids.addAll(getUidsOnFirewallChain(chain));
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to get uids on chain(" + chain + "): " + e);
+ }
+ for (final int uid: uids) {
+ affectedUids.add(uid);
+ }
+ }
+
+ mBpfNetMaps.replaceUidChain(chain, uids);
+ if (shouldTrackUidsForBlockedStatusCallbacks()) {
+ updateTrackingUidsBlockedReasons();
+ }
+ if (shouldTrackFirewallDestroySocketReasons()) {
+ maybePostFirewallDestroySocketReasons(chain, affectedUids);
+ }
+ }
}
@Override
@@ -13657,4 +14049,16 @@
enforceNetworkStackPermission(mContext);
return mRoutingCoordinatorService;
}
+
+ @Override
+ public long getEnabledConnectivityManagerFeatures() {
+ long features = 0;
+ // The bitmask must be built based on final properties initialized in the constructor, to
+ // ensure that it does not change over time and is always consistent between
+ // ConnectivityManager and ConnectivityService.
+ if (mUseDeclaredMethodsForCallbacksEnabled) {
+ features |= ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS;
+ }
+ return features;
+ }
}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index eea16bf..d886182 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -41,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
@@ -399,6 +400,10 @@
mCookieTagMap = mDeps.getBpfCookieTagMap();
}
+ // Note that this may only be called on a brand new v4-* interface,
+ // because it uses bpfmap.insertEntry() which fails if entry exists,
+ // and because the value includes (initialized to 0) byte/packet
+ // counters, so a replace (instead of insert) would wipe those stats.
private void maybeStartBpf(final ClatdTracker tracker) {
if (mIngressMap == null || mEgressMap == null) return;
@@ -520,31 +525,6 @@
}
}
- private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
- ParcelFileDescriptor writeSock6) {
- if (tunFd != null) {
- try {
- tunFd.close();
- } catch (IOException e) {
- Log.e(TAG, "Fail to close tun file descriptor " + e);
- }
- }
- if (readSock6 != null) {
- try {
- readSock6.close();
- } catch (IOException e) {
- Log.e(TAG, "Fail to close read socket " + e);
- }
- }
- if (writeSock6 != null) {
- try {
- writeSock6.close();
- } catch (IOException e) {
- Log.e(TAG, "Fail to close write socket " + e);
- }
- }
- }
-
private void tagSocketAsClat(long cookie) throws IOException {
if (mCookieTagMap == null) {
throw new IOException("Cookie tag map is not initialized");
@@ -600,42 +580,6 @@
throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
}
- // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
- final String v4Str;
- try {
- v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
- } catch (IOException e) {
- throw new IOException("no IPv4 addresses were available for clat: " + e);
- }
-
- final Inet4Address v4;
- try {
- v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
- } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
- throw new IOException("Invalid IPv4 address " + v4Str);
- }
-
- // [2] Generate a checksum-neutral IID.
- final Integer fwmark = getFwmark(netId);
- final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
- final String v6Str;
- try {
- v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark);
- } catch (IOException e) {
- throw new IOException("no IPv6 addresses were available for clat: " + e);
- }
-
- final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
- final Inet6Address v6;
- try {
- v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
- } catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
- throw new IOException("Invalid IPv6 address " + v6Str);
- }
-
- // [3] Open, configure and bring up the tun interface.
- // Create the v4-... tun interface.
-
// Initialize all required file descriptors with null pointer. This makes the following
// error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
// if any valid ones, in error handling.
@@ -643,145 +587,111 @@
ParcelFileDescriptor readSock6 = null;
ParcelFileDescriptor writeSock6 = null;
- final String tunIface = CLAT_PREFIX + iface;
- try {
- tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
- } catch (IOException e) {
- throw new IOException("Create tun interface " + tunIface + " failed: " + e);
- }
+ long cookie = 0;
+ boolean isSocketTagged = false;
- final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
- if (tunIfIndex == INVALID_IFINDEX) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Fail to get interface index for interface " + tunIface);
- }
+ try {
+ // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
+ final String v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING,
+ INIT_V4ADDR_PREFIX_LEN);
+ final Inet4Address v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str);
- // disable IPv6 on it - failing to do so is not a critical error
- try {
- mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
- } catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
- }
+ // [2] Generate a checksum-neutral IID.
+ final Integer fwmark = getFwmark(netId);
+ final String pfx96Str = nat64Prefix.getAddress().getHostAddress();
+ final String v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark);
+ final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress();
+ final Inet6Address v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str);
- // Detect ipv4 mtu.
- final int detectedMtu;
- try {
- detectedMtu = mDeps.detectMtu(pfx96Str,
- ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
- } catch (IOException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
- }
- final int mtu = adjustMtu(detectedMtu);
- Log.i(TAG, "detected ipv4 mtu of " + detectedMtu + " adjusted to " + mtu);
+ // [3] Open and configure local 464xlat read/write sockets.
+ // Opens a packet socket to receive IPv6 packets in clatd.
- // Config tun interface mtu, address and bring up.
- try {
- mNetd.interfaceSetMtu(tunIface, mtu);
- } catch (RemoteException | ServiceSpecificException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
- }
- final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
- ifConfig.ifName = tunIface;
- ifConfig.ipv4Addr = v4Str;
- ifConfig.prefixLength = 32;
- ifConfig.hwAddr = "";
- ifConfig.flags = new String[] {IF_STATE_UP};
- try {
- mNetd.interfaceSetCfg(ifConfig);
- } catch (RemoteException | ServiceSpecificException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
- + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
- }
-
- // [4] Open and configure local 464xlat read/write sockets.
- // Opens a packet socket to receive IPv6 packets in clatd.
- try {
// Use a JNI call to get native file descriptor instead of Os.socket() because we would
// like to use ParcelFileDescriptor to manage file descriptor. But ctor
// ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
// descriptor to initialize ParcelFileDescriptor object instead.
readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
- } catch (IOException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Open packet socket failed: " + e);
- }
- // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
- try {
+ // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
// Use a JNI call to get native file descriptor instead of Os.socket(). See above
// reason why we use jniOpenPacketSocket6().
writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
- } catch (IOException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Open raw socket failed: " + e);
- }
- final int ifIndex = mDeps.getInterfaceIndex(iface);
- if (ifIndex == INVALID_IFINDEX) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("Fail to get interface index for interface " + iface);
- }
+ final int ifIndex = mDeps.getInterfaceIndex(iface);
+ if (ifIndex == INVALID_IFINDEX) {
+ throw new IOException("Fail to get interface index for interface " + iface);
+ }
- // Start translating packets to the new prefix.
- try {
+ // Start translating packets to the new prefix.
mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
- } catch (IOException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("add anycast sockopt failed: " + e);
- }
-
- // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
- final long cookie;
- try {
+ // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting.
cookie = mDeps.getSocketCookie(writeSock6.getFileDescriptor());
tagSocketAsClat(cookie);
- } catch (IOException e) {
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("tag raw socket failed: " + e);
- }
-
- // Update our packet socket filter to reflect the new 464xlat IP address.
- try {
+ isSocketTagged = true;
+ // Update our packet socket filter to reflect the new 464xlat IP address.
mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
- } catch (IOException e) {
- try {
- untagSocket(cookie);
- } catch (IOException e2) {
- Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
- }
- maybeCleanUp(tunFd, readSock6, writeSock6);
- throw new IOException("configure packet socket failed: " + e);
- }
- // [5] Start clatd.
- final int pid;
- try {
- pid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(),
- writeSock6.getFileDescriptor(), iface, pfx96Str, v4Str, v6Str);
- } catch (IOException e) {
- try {
- untagSocket(cookie);
- } catch (IOException e2) {
- Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+ // [4] Open, configure and bring up the tun interface.
+ // Create the v4-... tun interface.
+ final String tunIface = CLAT_PREFIX + iface;
+ tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
+ final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
+ if (tunIfIndex == INVALID_IFINDEX) {
+ throw new IOException("Fail to get interface index for interface " + tunIface);
}
- throw new IOException("Error start clatd on " + iface + ": " + e);
- } finally {
+ // disable IPv6 on it - failing to do so is not a critical error
+ mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
+ // Detect ipv4 mtu.
+ final int detectedMtu = mDeps.detectMtu(pfx96Str,
+ ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+ final int mtu = adjustMtu(detectedMtu);
+ Log.i(TAG, "detected ipv4 mtu of " + detectedMtu + " adjusted to " + mtu);
+ // Config tun interface mtu, address and bring up.
+ mNetd.interfaceSetMtu(tunIface, mtu);
+ final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+ ifConfig.ifName = tunIface;
+ ifConfig.ipv4Addr = v4Str;
+ ifConfig.prefixLength = 32;
+ ifConfig.hwAddr = "";
+ ifConfig.flags = new String[] {IF_STATE_UP};
+ mNetd.interfaceSetCfg(ifConfig);
+
+ // [5] Start clatd.
+ final int pid = mDeps.startClatd(tunFd.getFileDescriptor(),
+ readSock6.getFileDescriptor(), writeSock6.getFileDescriptor(), iface, pfx96Str,
+ v4Str, v6Str);
// The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
- // Close these file descriptor stubs which are unused anymore.
- maybeCleanUp(tunFd, readSock6, writeSock6);
+ // Close these file descriptor stubs in finally block.
+
+ // [6] Initialize and store clatd tracker object.
+ mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
+ pid, cookie);
+
+ // [7] Start BPF
+ maybeStartBpf(mClatdTracker);
+
+ return v6Str;
+ } catch (IOException | RemoteException | ServiceSpecificException | ClassCastException
+ | IllegalArgumentException | NullPointerException e) {
+ if (isSocketTagged) {
+ try {
+ untagSocket(cookie);
+ } catch (IOException e2) {
+ Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
+ }
+ }
+ throw new IOException("Failed to start clat ", e);
+ } finally {
+ if (tunFd != null) {
+ tunFd.close();
+ }
+ if (readSock6 != null) {
+ readSock6.close();
+ }
+ if (writeSock6 != null) {
+ writeSock6.close();
+ }
}
-
- // [6] Initialize and store clatd tracker object.
- mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
- pid, cookie);
-
- // [7] Start BPF
- maybeStartBpf(mClatdTracker);
-
- return v6Str;
}
private void maybeStopBpf(final ClatdTracker tracker) {
@@ -885,6 +795,29 @@
}
/**
+ * Dump raw BPF map into base64 encoded strings {@literal "<base64 key>,<base64 value>"}.
+ * Allow to dump only one map in each call. For test only.
+ *
+ * @param pw print writer.
+ * @param isEgress4Map whether to dump the egress4 map (true) or the ingress6 map (false).
+ *
+ * Usage:
+ * $ dumpsys connectivity {clatEgress4RawBpfMap|clatIngress6RawBpfMap}
+ *
+ * Output:
+ * {@literal <base64 encoded key #1>,<base64 encoded value #1>}
+ * {@literal <base64 encoded key #2>,<base64 encoded value #2>}
+ * ..
+ */
+ public void dumpRawMap(@NonNull IndentingPrintWriter pw, boolean isEgress4Map) {
+ if (isEgress4Map) {
+ BpfDump.dumpRawMap(mEgressMap, pw);
+ } else {
+ BpfDump.dumpRawMap(mIngressMap, pw);
+ }
+ }
+
+ /**
* Dump the coordinator information.
*
* @param pw print writer.
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 176307d..1ee1ed7 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -44,6 +44,11 @@
public static final String BACKGROUND_FIREWALL_CHAIN = "background_firewall_chain";
+ public static final String DELAY_DESTROY_SOCKETS = "delay_destroy_sockets";
+
+ public static final String USE_DECLARED_METHODS_FOR_CALLBACKS =
+ "use_declared_methods_for_callbacks";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 8e6854a..ac02229 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -390,6 +390,7 @@
: new String[0]; // Off
paramsParcel.transportTypes = nc.getTransportTypes();
paramsParcel.meteredNetwork = nc.isMetered();
+ paramsParcel.interfaceNames = lp.getAllInterfaceNames().toArray(new String[0]);
// Prepare to track the validation status of the DNS servers in the
// resolver config when private DNS is in opportunistic or strict mode.
if (useTls) {
@@ -403,13 +404,14 @@
}
Log.d(TAG, String.format("sendDnsConfigurationForNetwork(%d, %s, %s, %d, %d, %d, %d, "
- + "%d, %d, %s, %s, %s, %b)", paramsParcel.netId,
+ + "%d, %d, %s, %s, %s, %b, %s)", paramsParcel.netId,
Arrays.toString(paramsParcel.servers), Arrays.toString(paramsParcel.domains),
paramsParcel.sampleValiditySeconds, paramsParcel.successThreshold,
paramsParcel.minSamples, paramsParcel.maxSamples, paramsParcel.baseTimeoutMsec,
paramsParcel.retryCount, paramsParcel.tlsName,
Arrays.toString(paramsParcel.tlsServers),
- Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork));
+ Arrays.toString(paramsParcel.transportTypes), paramsParcel.meteredNetwork,
+ Arrays.toString(paramsParcel.interfaceNames)));
try {
mDnsResolver.setResolverConfiguration(paramsParcel);
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 15d6adb..9c2b9e8 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -64,6 +64,8 @@
return "/sys/fs/bpf/net_shared/map_" + which + "_map";
}
+ private final boolean mHaveProgram = TcUtils.isBpfProgramUsable(PROG_PATH);
+
private Set<String> mAttachedIfaces;
private final BpfMap<Struct.S32, DscpPolicyValue> mBpfDscpIpv4Policies;
@@ -325,6 +327,7 @@
* Attach BPF program
*/
private boolean attachProgram(@NonNull String iface) {
+ if (!mHaveProgram) return false;
try {
NetworkInterface netIface = NetworkInterface.getByName(iface);
TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL,
diff --git a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
index 4d5001b..af4aee5 100644
--- a/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
+++ b/service/src/com/android/server/connectivity/MulticastRoutingCoordinatorService.java
@@ -27,6 +27,8 @@
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;
+import static com.android.net.module.util.CollectionUtils.getIndexForValue;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.MulticastRoutingConfig;
@@ -150,7 +152,7 @@
}
private Integer getInterfaceIndex(String ifName) {
- int mapIndex = mInterfaces.indexOfValue(ifName);
+ int mapIndex = getIndexForValue(mInterfaces, ifName);
if (mapIndex < 0) return null;
return mInterfaces.keyAt(mapIndex);
}
@@ -166,14 +168,18 @@
public void applyMulticastRoutingConfig(
final String iifName, final String oifName, final MulticastRoutingConfig newConfig) {
checkOnHandlerThread();
+ Objects.requireNonNull(iifName, "IifName can't be null");
+ Objects.requireNonNull(oifName, "OifName can't be null");
if (newConfig.getForwardingMode() != FORWARD_NONE) {
// Make sure iif and oif are added as multicast forwarding interfaces
- try {
- maybeAddAndTrackInterface(iifName);
- maybeAddAndTrackInterface(oifName);
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to apply multicast routing config, ", e);
+ if (!maybeAddAndTrackInterface(iifName) || !maybeAddAndTrackInterface(oifName)) {
+ Log.e(
+ TAG,
+ "Failed to apply multicast routing config from "
+ + iifName
+ + " to "
+ + oifName);
return;
}
}
@@ -246,7 +252,7 @@
if (virtualIndex == null) return;
updateMfcs();
- mInterfaces.removeAt(mInterfaces.indexOfValue(ifName));
+ mInterfaces.removeAt(getIndexForValue(mInterfaces, ifName));
mVirtualInterfaces.remove(virtualIndex);
try {
mDependencies.setsockoptMrt6DelMif(mMulticastRoutingFd, virtualIndex);
@@ -256,9 +262,14 @@
}
}
+ /**
+ * Returns the next available virtual index for multicast routing, or -1 if the number of
+ * virtual interfaces has reached max value.
+ */
private int getNextAvailableVirtualIndex() {
if (mVirtualInterfaces.size() >= MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES) {
- throw new IllegalStateException("Can't allocate new multicast virtual interface");
+ Log.e(TAG, "Can't allocate new multicast virtual interface");
+ return -1;
}
for (int i = 0; i < mVirtualInterfaces.size(); i++) {
if (!mVirtualInterfaces.contains(i)) {
@@ -270,7 +281,7 @@
@VisibleForTesting
public Integer getVirtualInterfaceIndex(String ifName) {
- int mapIndex = mVirtualInterfaces.indexOfValue(ifName);
+ int mapIndex = getIndexForValue(mVirtualInterfaces, ifName);
if (mapIndex < 0) return null;
return mVirtualInterfaces.keyAt(mapIndex);
}
@@ -289,12 +300,23 @@
return mVirtualInterfaces.get(virtualIndex);
}
- private void maybeAddAndTrackInterface(String ifName) {
+ /**
+ * Returns {@code true} if the interfaces is added and tracked, or {@code false} when failed
+ * to add the interface.
+ */
+ private boolean maybeAddAndTrackInterface(String ifName) {
checkOnHandlerThread();
- if (mVirtualInterfaces.indexOfValue(ifName) >= 0) return;
+ if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return true;
int nextVirtualIndex = getNextAvailableVirtualIndex();
+ if (nextVirtualIndex < 0) {
+ return false;
+ }
int ifIndex = mDependencies.getInterfaceIndex(ifName);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Can't get interface index for " + ifName);
+ return false;
+ }
final StructMif6ctl mif6ctl =
new StructMif6ctl(
nextVirtualIndex,
@@ -307,10 +329,11 @@
Log.d(TAG, "Added mifi " + nextVirtualIndex + " to MIF");
} catch (ErrnoException e) {
Log.e(TAG, "failed to add multicast virtual interface", e);
- return;
+ return false;
}
mVirtualInterfaces.put(nextVirtualIndex, ifName);
mInterfaces.put(ifIndex, ifName);
+ return true;
}
@VisibleForTesting
@@ -796,13 +819,12 @@
NetworkUtils.setsockoptBytes(fd, IPPROTO_IPV6, MRT6_DEL_MFC, bytes);
}
- public Integer getInterfaceIndex(String ifName) {
- try {
- NetworkInterface ni = NetworkInterface.getByName(ifName);
- return ni.getIndex();
- } catch (NullPointerException | SocketException e) {
- return null;
- }
+ /**
+ * Returns the interface index for an interface name, or 0 if the interface index could
+ * not be found.
+ */
+ public int getInterfaceIndex(String ifName) {
+ return Os.if_nametoindex(ifName);
}
public NetworkInterface getNetworkInterface(int physicalIndex) {
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 065922d..a979681 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -202,13 +202,13 @@
try {
addrStr = mClatCoordinator.clatStart(baseIface, getNetId(), mNat64PrefixInUse);
} catch (IOException e) {
- Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+ Log.e(TAG, "Error starting clatd on " + baseIface, e);
}
} else {
try {
addrStr = mNetd.clatdStart(baseIface, mNat64PrefixInUse.toString());
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
+ Log.e(TAG, "Error starting clatd on " + baseIface, e);
}
}
mIface = CLAT_PREFIX + baseIface;
@@ -217,7 +217,7 @@
try {
mIPv6Address = (Inet6Address) InetAddresses.parseNumericAddress(addrStr);
} catch (ClassCastException | IllegalArgumentException | NullPointerException e) {
- Log.e(TAG, "Invalid IPv6 address " + addrStr);
+ Log.e(TAG, "Invalid IPv6 address " + addrStr , e);
}
if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) {
stopPrefixDiscovery();
@@ -622,6 +622,18 @@
}
}
+ /**
+ * Dump the raw BPF maps in 464XLAT
+ *
+ * @param pw print writer.
+ * @param isEgress4Map whether to dump the egress4 map (true) or the ingress6 map (false).
+ */
+ public void dumpRawBpfMap(IndentingPrintWriter pw, boolean isEgress4Map) {
+ if (SdkLevel.isAtLeastT()) {
+ mClatCoordinator.dumpRawMap(pw, isEgress4Map);
+ }
+ }
+
@Override
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ede6d3f..e2834b0 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -135,6 +135,7 @@
"device/com/android/net/module/util/BpfUtils.java",
"device/com/android/net/module/util/IBpfMap.java",
"device/com/android/net/module/util/JniUtil.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
"device/com/android/net/module/util/TcUtils.java",
],
sdk_version: "module_current",
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
index 7549e71..4227194 100644
--- a/staticlibs/device/com/android/net/module/util/BpfDump.java
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -81,6 +81,26 @@
}
/**
+ * Dump the BpfMap entries with base64 format encoding.
+ */
+ public static <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
+ PrintWriter pw) {
+ try {
+ if (map == null) {
+ pw.println("BPF map is null");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No entries");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(toBase64EncodedString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Map dump end with error: " + Os.strerror(e.errno));
+ }
+ }
+
+ /**
* Dump the BpfMap name and entries
*/
public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
new file mode 100644
index 0000000..3eb59d8
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.net.module.util;
+
+import android.os.Build;
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+
+/**
+ * Subclass of BpfMap for maps that are only ever written by one userspace writer.
+ *
+ * This class stores all map data in a userspace HashMap in addition to in the BPF map. This makes
+ * reads (but not iterations) much faster because they do not require a system call or converting
+ * the raw map read to the Value struct. See, e.g., b/343166906 .
+ *
+ * Users of this class must ensure that no BPF program ever writes to the map, and that all
+ * userspace writes to the map occur through this object. Other userspace code may still read from
+ * the map; only writes are required to go through this object.
+ *
+ * Reads and writes to this object are thread-safe and internally synchronized. The read and write
+ * methods are synchronized to ensure that current writers always result in a consistent internal
+ * state (without synchronization, two concurrent writes might update the underlying map and the
+ * cache in the opposite order, resulting in the cache being out of sync with the map).
+ *
+ * getNextKey and iteration over the map are not synchronized or cached and always access the
+ * isunderlying map. The values returned by these calls may be temporarily out of sync with the
+ * values read and written through this object.
+ *
+ * TODO: consider caching reads on iterations as well. This is not trivial because the semantics for
+ * iterating BPF maps require passing in the previously-returned key, and Java iterators only
+ * support iterating from the beginning. It could be done by implementing forEach and possibly by
+ * making getFirstKey and getNextKey private (if no callers are using them). Because HashMap is not
+ * thread-safe, implementing forEach would require either making that method synchronized (and
+ * block reads and updates from other threads until iteration is complete) or switching the
+ * internal HashMap to ConcurrentHashMap.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
+ // HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
+ // our code can contain hundreds of items.
+ private final HashMap<K, V> mCache = new HashMap<>();
+
+ protected SingleWriterBpfMap(@NonNull final String path, final int flag, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ super(path, flag, key, value);
+
+ if (flag != BPF_F_RDWR) {
+ throw new IllegalArgumentException(
+ "Using " + getClass().getName() + " for read-only maps does not make sense");
+ }
+
+ // Populate cache with the current map contents.
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
+ }
+
+ public SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ this(path, BPF_F_RDWR, key, value);
+ }
+
+ @Override
+ public synchronized void updateEntry(K key, V value) throws ErrnoException {
+ super.updateEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void insertEntry(K key, V value)
+ throws ErrnoException, IllegalStateException {
+ super.insertEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void replaceEntry(K key, V value)
+ throws ErrnoException, NoSuchElementException {
+ super.replaceEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
+ final boolean ret = super.insertOrReplaceEntry(key, value);
+ mCache.put(key, value);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean deleteEntry(K key) throws ErrnoException {
+ final boolean ret = super.deleteEntry(key);
+ mCache.remove(key);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean containsKey(@NonNull K key) throws ErrnoException {
+ return mCache.containsKey(key);
+ }
+
+ @Override
+ public synchronized V getValue(@NonNull K key) throws ErrnoException {
+ return mCache.get(key);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
index 9d2fb7f..a6b222f 100644
--- a/staticlibs/device/com/android/net/module/util/TcUtils.java
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -101,4 +101,12 @@
* @throws IOException
*/
public static native void tcQdiscAddDevClsact(int ifIndex) throws IOException;
+
+ /**
+ * Attempt to fetch a bpf program from a path.
+ * Return true on success, false on non-existence or any other failure.
+ *
+ * @param bpfProgPath
+ */
+ public static native boolean isBpfProgramUsable(String bpfProgPath);
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
index 2e9a99b..586f19d 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java
@@ -156,8 +156,9 @@
@Override
public String toString() {
- return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)",
+ return String.format("Nduseroptmsg(family:%d, opts_len:%d, ifindex:%d, icmp_type:%d, "
+ + "icmp_code:%d, srcaddr: %s, %s)",
family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type),
- Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress());
+ Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress(), option);
}
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
index ad7a4d7..1896de6 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -123,6 +123,7 @@
public static final short RTM_NEWRULE = 32;
public static final short RTM_DELRULE = 33;
public static final short RTM_GETRULE = 34;
+ public static final short RTM_NEWPREFIX = 52;
public static final short RTM_NEWNDUSEROPT = 68;
// Netfilter netlink message types are presented by two bytes: high byte subsystem and
@@ -148,6 +149,8 @@
public static final int RTMGRP_IPV4_IFADDR = 0x10;
public static final int RTMGRP_IPV6_IFADDR = 0x100;
public static final int RTMGRP_IPV6_ROUTE = 0x400;
+ public static final int RTNLGRP_IPV6_PREFIX = 18;
+ public static final int RTMGRP_IPV6_PREFIX = 1 << (RTNLGRP_IPV6_PREFIX - 1);
public static final int RTNLGRP_ND_USEROPT = 20;
public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
@@ -207,6 +210,7 @@
case RTM_NEWRULE: return "RTM_NEWRULE";
case RTM_DELRULE: return "RTM_DELRULE";
case RTM_GETRULE: return "RTM_GETRULE";
+ case RTM_NEWPREFIX: return "RTM_NEWPREFIX";
case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT";
default: return "unknown RTM type: " + String.valueOf(nlmType);
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index 92ec0c4..df7010e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -41,6 +41,10 @@
public static final short IFLA_ADDRESS = 1;
public static final short IFLA_IFNAME = 3;
public static final short IFLA_MTU = 4;
+ public static final short IFLA_INET6_ADDR_GEN_MODE = 8;
+ public static final short IFLA_AF_SPEC = 26;
+
+ public static final short IN6_ADDR_GEN_MODE_NONE = 1;
private int mMtu;
@NonNull
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
index 02d1574..28eeaea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfinfoMsg.java
@@ -49,7 +49,7 @@
@Field(order = 4, type = Type.U32)
public final long change;
- StructIfinfoMsg(short family, int type, int index, long flags, long change) {
+ public StructIfinfoMsg(short family, int type, int index, long flags, long change) {
this.family = family;
this.type = type;
this.index = index;
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructPrefixCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixCacheInfo.java
new file mode 100644
index 0000000..cfaa6e1
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixCacheInfo.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 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.netlink;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct prefix_cacheinfo {
+ * __u32 preferred_time;
+ * __u32 valid_time;
+ * }
+ *
+ * see also:
+ *
+ * include/uapi/linux/if_addr.h
+ *
+ * @hide
+ */
+public class StructPrefixCacheInfo extends Struct {
+ public static final int STRUCT_SIZE = 8;
+
+ @Field(order = 0, type = Type.U32)
+ public final long preferred_time;
+ @Field(order = 1, type = Type.U32)
+ public final long valid_time;
+
+ StructPrefixCacheInfo(long preferred, long valid) {
+ this.preferred_time = preferred;
+ this.valid_time = valid;
+ }
+
+ /**
+ * Parse a prefix_cacheinfo struct from a {@link ByteBuffer}.
+ *
+ * @param byteBuffer The buffer from which to parse the prefix_cacheinfo.
+ * @return the parsed prefix_cacheinfo struct, or throw IllegalArgumentException if the
+ * prefix_cacheinfo struct could not be parsed successfully(for example, if it was
+ * truncated).
+ */
+ public static StructPrefixCacheInfo parse(@NonNull final ByteBuffer byteBuffer) {
+ if (byteBuffer.remaining() < STRUCT_SIZE) {
+ throw new IllegalArgumentException("Invalid bytebuffer remaining size "
+ + byteBuffer.remaining() + " for prefix_cacheinfo attribute");
+ }
+
+ // The ByteOrder must already have been set to native order.
+ return Struct.parse(StructPrefixCacheInfo.class, byteBuffer);
+ }
+
+ /**
+ * Write a prefix_cacheinfo struct to {@link ByteBuffer}.
+ */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ // The ByteOrder must already have been set to native order.
+ writeToByteBuffer(byteBuffer);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructPrefixMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixMsg.java
new file mode 100644
index 0000000..504d6c7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructPrefixMsg.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct prefixmsg {
+ * unsigned char prefix_family;
+ * unsigned char prefix_pad1;
+ * unsigned short prefix_pad2;
+ * int prefix_ifindex;
+ * unsigned char prefix_type;
+ * unsigned char prefix_len;
+ * unsigned char prefix_flags;
+ * unsigned char prefix_pad3;
+ * }
+ *
+ * see also:
+ *
+ * include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructPrefixMsg extends Struct {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 12;
+
+ @Field(order = 0, type = Type.U8, padding = 3)
+ public final short prefix_family;
+ @Field(order = 1, type = Type.S32)
+ public final int prefix_ifindex;
+ @Field(order = 2, type = Type.U8)
+ public final short prefix_type;
+ @Field(order = 3, type = Type.U8)
+ public final short prefix_len;
+ @Field(order = 4, type = Type.U8, padding = 1)
+ public final short prefix_flags;
+
+ @VisibleForTesting
+ public StructPrefixMsg(short family, int ifindex, short type, short len, short flags) {
+ this.prefix_family = family;
+ this.prefix_ifindex = ifindex;
+ this.prefix_type = type;
+ this.prefix_len = len;
+ this.prefix_flags = flags;
+ }
+
+ /**
+ * Parse a prefixmsg struct from a {@link ByteBuffer}.
+ *
+ * @param byteBuffer The buffer from which to parse the prefixmsg.
+ * @return the parsed prefixmsg struct, or throw IllegalArgumentException if the prefixmsg
+ * struct could not be parsed successfully (for example, if it was truncated).
+ */
+ public static StructPrefixMsg parse(@NonNull final ByteBuffer byteBuffer) {
+ if (byteBuffer.remaining() < STRUCT_SIZE) {
+ throw new IllegalArgumentException("Invalid bytebuffer remaining size "
+ + byteBuffer.remaining() + "for prefix_msg struct.");
+ }
+
+ // The ByteOrder must already have been set to native order.
+ return Struct.parse(StructPrefixMsg.class, byteBuffer);
+ }
+
+ /**
+ * Write a prefixmsg struct to {@link ByteBuffer}.
+ */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ // The ByteOrder must already have been set to native order.
+ writeToByteBuffer(byteBuffer);
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index 39e7ce9..f3d8c4a 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -389,4 +389,28 @@
}
return dest;
}
+
+ /**
+ * Returns an index of the given SparseArray that contains the given value, or -1
+ * number if no keys map to the given value.
+ *
+ * <p>Note this is a linear search, and if multiple keys can map to the same value
+ * then the smallest index is returned.
+ *
+ * <p>This function compares values with {@code equals} while the
+ * {@link SparseArray#indexOfValue} compares values using {@code ==}.
+ */
+ public static <T> int getIndexForValue(SparseArray<T> sparseArray, T value) {
+ for(int i = 0, nsize = sparseArray.size(); i < nsize; i++) {
+ T valueAt = sparseArray.valueAt(i);
+ if (valueAt == null) {
+ if (value == null) {
+ return i;
+ };
+ } else if (valueAt.equals(value)) {
+ return i;
+ }
+ }
+ return -1;
+ }
}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index fa07885..7066131 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -45,7 +45,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
-import static com.android.net.module.util.BitUtils.packBitList;
+import static com.android.net.module.util.BitUtils.packBits;
import static com.android.net.module.util.BitUtils.unpackBits;
import android.annotation.NonNull;
@@ -89,41 +89,41 @@
* and {@code FORCE_RESTRICTED_CAPABILITIES}.
*/
@VisibleForTesting
- public static final long RESTRICTED_CAPABILITIES = packBitList(
- NET_CAPABILITY_BIP,
- NET_CAPABILITY_CBS,
- NET_CAPABILITY_DUN,
- NET_CAPABILITY_EIMS,
- NET_CAPABILITY_ENTERPRISE,
- NET_CAPABILITY_FOTA,
- NET_CAPABILITY_IA,
- NET_CAPABILITY_IMS,
- NET_CAPABILITY_MCX,
- NET_CAPABILITY_RCS,
- NET_CAPABILITY_VEHICLE_INTERNAL,
- NET_CAPABILITY_VSIM,
- NET_CAPABILITY_XCAP,
- NET_CAPABILITY_MMTEL);
+ public static final long RESTRICTED_CAPABILITIES =
+ (1L << NET_CAPABILITY_BIP) |
+ (1L << NET_CAPABILITY_CBS) |
+ (1L << NET_CAPABILITY_DUN) |
+ (1L << NET_CAPABILITY_EIMS) |
+ (1L << NET_CAPABILITY_ENTERPRISE) |
+ (1L << NET_CAPABILITY_FOTA) |
+ (1L << NET_CAPABILITY_IA) |
+ (1L << NET_CAPABILITY_IMS) |
+ (1L << NET_CAPABILITY_MCX) |
+ (1L << NET_CAPABILITY_RCS) |
+ (1L << NET_CAPABILITY_VEHICLE_INTERNAL) |
+ (1L << NET_CAPABILITY_VSIM) |
+ (1L << NET_CAPABILITY_XCAP) |
+ (1L << NET_CAPABILITY_MMTEL);
/**
* Capabilities that force network to be restricted.
* See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
*/
- private static final long FORCE_RESTRICTED_CAPABILITIES = packBitList(
- NET_CAPABILITY_ENTERPRISE,
- NET_CAPABILITY_OEM_PAID,
- NET_CAPABILITY_OEM_PRIVATE);
+ private static final long FORCE_RESTRICTED_CAPABILITIES =
+ (1L << NET_CAPABILITY_ENTERPRISE) |
+ (1L << NET_CAPABILITY_OEM_PAID) |
+ (1L << NET_CAPABILITY_OEM_PRIVATE);
/**
* Capabilities that suggest that a network is unrestricted.
* See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
*/
@VisibleForTesting
- public static final long UNRESTRICTED_CAPABILITIES = packBitList(
- NET_CAPABILITY_INTERNET,
- NET_CAPABILITY_MMS,
- NET_CAPABILITY_SUPL,
- NET_CAPABILITY_WIFI_P2P);
+ public static final long UNRESTRICTED_CAPABILITIES =
+ (1L << NET_CAPABILITY_INTERNET) |
+ (1L << NET_CAPABILITY_MMS) |
+ (1L << NET_CAPABILITY_SUPL) |
+ (1L << NET_CAPABILITY_WIFI_P2P);
/**
* Get a transport that can be used to classify a network when displaying its info to users.
@@ -159,28 +159,33 @@
*
* @return {@code true} if the network should be restricted.
*/
- // TODO: Use packBits(nc.getCapabilities()) to check more easily using bit masks.
public static boolean inferRestrictedCapability(NetworkCapabilities nc) {
+ return inferRestrictedCapability(packBits(nc.getCapabilities()));
+ }
+
+ /**
+ * Infers that all the capabilities it provides are typically provided by restricted networks
+ * or not.
+ *
+ * @param capabilities see {@link NetworkCapabilities#getCapabilities()}
+ *
+ * @return {@code true} if the network should be restricted.
+ */
+ public static boolean inferRestrictedCapability(long capabilities) {
// Check if we have any capability that forces the network to be restricted.
- for (int capability : unpackBits(FORCE_RESTRICTED_CAPABILITIES)) {
- if (nc.hasCapability(capability)) {
- return true;
- }
+ if ((capabilities & FORCE_RESTRICTED_CAPABILITIES) != 0) {
+ return true;
}
// Verify there aren't any unrestricted capabilities. If there are we say
// the whole thing is unrestricted unless it is forced to be restricted.
- for (int capability : unpackBits(UNRESTRICTED_CAPABILITIES)) {
- if (nc.hasCapability(capability)) {
- return false;
- }
+ if ((capabilities & UNRESTRICTED_CAPABILITIES) != 0) {
+ return false;
}
// Must have at least some restricted capabilities.
- for (int capability : unpackBits(RESTRICTED_CAPABILITIES)) {
- if (nc.hasCapability(capability)) {
- return true;
- }
+ if ((capabilities & RESTRICTED_CAPABILITIES) != 0) {
+ return true;
}
return false;
}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 7c4abe0..319d51a 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -179,6 +179,8 @@
public static final int ICMPV6_RA_HEADER_LEN = 16;
public static final int ICMPV6_NS_HEADER_LEN = 24;
public static final int ICMPV6_NA_HEADER_LEN = 24;
+ public static final int ICMPV6_ND_OPTION_TLLA_LEN = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA_LEN = 8;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER = 1 << 31;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
index 59257b8..417a5c4 100644
--- a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
+++ b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
@@ -42,10 +42,26 @@
return kver;
}
-static inline __unused bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
return kernelVersion() >= KVER(major, minor, sub);
}
+static inline bool isKernelVersion(unsigned major, unsigned minor) {
+ return isAtLeastKernelVersion(major, minor, 0) && !isAtLeastKernelVersion(major, minor + 1, 0);
+}
+
+static inline bool __unused isLtsKernel() {
+ return isKernelVersion(4, 4) || // minimum for Android R
+ isKernelVersion(4, 9) || // minimum for Android S & T
+ isKernelVersion(4, 14) || // minimum for Android U
+ isKernelVersion(4, 19) || // minimum for Android V
+ isKernelVersion(5, 4) || // first supported in Android R
+ isKernelVersion(5, 10) || // first supported in Android S
+ isKernelVersion(5, 15) || // first supported in Android T
+ isKernelVersion(6, 1) || // first supported in Android U
+ isKernelVersion(6, 6); // first supported in Android V
+}
+
// Figure out the bitness of userspace.
// Trivial and known at compile time.
static constexpr bool isUserspace32bit() {
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index 9995cb9..2a0e8e0 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -128,26 +128,29 @@
});
}
-inline int mapRetrieve(const char* pathname, uint32_t flag) {
- return bpfFdGet(pathname, flag);
-}
-
inline int mapRetrieveRW(const char* pathname) {
- return mapRetrieve(pathname, 0);
+ return bpfFdGet(pathname, 0);
}
inline int mapRetrieveRO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_RDONLY);
+ return bpfFdGet(pathname, BPF_F_RDONLY);
}
inline int mapRetrieveWO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_WRONLY);
+ return bpfFdGet(pathname, BPF_F_WRONLY);
}
inline int retrieveProgram(const char* pathname) {
return bpfFdGet(pathname, BPF_F_RDONLY);
}
+inline bool usableProgram(const char* pathname) {
+ int fd = retrieveProgram(pathname);
+ bool ok = (fd >= 0);
+ if (ok) close(fd);
+ return ok;
+}
+
inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
const BPF_FD_TYPE cg_fd, uint32_t flags = 0) {
return bpf(BPF_PROG_ATTACH, {
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index 7e6b4ec..969ebd4 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -39,7 +39,7 @@
"-Werror",
"-Wno-unused-parameter",
],
- sdk_version: "30",
+ sdk_version: "current",
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index ab83da6..2a587b6 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -19,6 +19,9 @@
#include <nativehelper/scoped_utf_chars.h>
#include <tcutils/tcutils.h>
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+
namespace android {
static void throwIOException(JNIEnv *env, const char *msg, int error) {
@@ -96,6 +99,14 @@
}
}
+static jboolean com_android_net_module_util_TcUtils_isBpfProgramUsable(JNIEnv *env,
+ jclass clazz,
+ jstring bpfProgPath) {
+ ScopedUtfChars pathname(env, bpfProgPath);
+ return bpf::usableProgram(pathname.c_str());
+}
+
+
/*
* JNI registration.
*/
@@ -111,6 +122,8 @@
(void *)com_android_net_module_util_TcUtils_tcFilterDelDev},
{"tcQdiscAddDevClsact", "(I)V",
(void *)com_android_net_module_util_TcUtils_tcQdiscAddDevClsact},
+ {"isBpfProgramUsable", "(Ljava/lang/String;)Z",
+ (void *)com_android_net_module_util_TcUtils_isBpfProgramUsable},
};
int register_com_android_net_module_util_TcUtils(JNIEnv *env,
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index 926590d..e4742ce 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -21,7 +21,10 @@
name: "libtcutils",
srcs: ["tcutils.cpp"],
export_include_dirs: ["include"],
- header_libs: ["bpf_headers"],
+ header_libs: [
+ "bpf_headers",
+ "libbase_headers",
+ ],
shared_libs: [
"liblog",
],
@@ -31,7 +34,7 @@
"-Werror",
"-Wno-unused-parameter",
],
- sdk_version: "30",
+ sdk_version: "current",
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
diff --git a/staticlibs/native/tcutils/scopeguard.h b/staticlibs/native/tcutils/scopeguard.h
deleted file mode 100644
index 76bbb93..0000000
--- a/staticlibs/native/tcutils/scopeguard.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.
- */
-
-// -----------------------------------------------------------------------------
-// TODO: figure out a way to use libbase_ndk. This is currently not working
-// because of missing apex availability. For now, we can use a copy of
-// ScopeGuard which is very lean compared to unique_fd. This code has been
-// copied verbatim from:
-// https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/scopeguard.h
-
-#pragma once
-
-#include <utility> // for std::move, std::forward
-
-namespace android {
-namespace base {
-
-// ScopeGuard ensures that the specified functor is executed no matter how the
-// current scope exits.
-template <typename F> class ScopeGuard {
-public:
- ScopeGuard(F &&f) : f_(std::forward<F>(f)), active_(true) {}
-
- ScopeGuard(ScopeGuard &&that) noexcept
- : f_(std::move(that.f_)), active_(that.active_) {
- that.active_ = false;
- }
-
- template <typename Functor>
- ScopeGuard(ScopeGuard<Functor> &&that)
- : f_(std::move(that.f_)), active_(that.active_) {
- that.active_ = false;
- }
-
- ~ScopeGuard() {
- if (active_)
- f_();
- }
-
- ScopeGuard() = delete;
- ScopeGuard(const ScopeGuard &) = delete;
- void operator=(const ScopeGuard &) = delete;
- void operator=(ScopeGuard &&that) = delete;
-
- void Disable() { active_ = false; }
-
- bool active() const { return active_; }
-
-private:
- template <typename Functor> friend class ScopeGuard;
-
- F f_;
- bool active_;
-};
-
-template <typename F> ScopeGuard<F> make_scope_guard(F &&f) {
- return ScopeGuard<F>(std::forward<F>(f));
-}
-
-} // namespace base
-} // namespace android
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index c82390f..21e781c 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -20,8 +20,10 @@
#include "logging.h"
#include "bpf/KernelUtils.h"
-#include "scopeguard.h"
+#include <BpfSyscallWrappers.h>
+#include <android-base/scopeguard.h>
+#include <android-base/unique_fd.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
@@ -39,10 +41,6 @@
#include <unistd.h>
#include <utility>
-#define BPF_FD_JUST_USE_INT
-#include <BpfSyscallWrappers.h>
-#undef BPF_FD_JUST_USE_INT
-
// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
#define CLS_BPF_NAME_LEN 256
@@ -52,6 +50,9 @@
namespace android {
namespace {
+using base::make_scope_guard;
+using base::unique_fd;
+
/**
* IngressPoliceFilterBuilder builds a nlmsg request equivalent to the following
* tc command:
@@ -130,7 +131,7 @@
// class members
const unsigned mBurstInBytes;
const char *mBpfProgPath;
- int mBpfFd;
+ unique_fd mBpfFd;
Request mRequest;
static double getTickInUsec() {
@@ -139,7 +140,7 @@
ALOGE("fopen(\"/proc/net/psched\"): %s", strerror(errno));
return 0.0;
}
- auto scopeGuard = base::make_scope_guard([fp] { fclose(fp); });
+ auto scopeGuard = make_scope_guard([fp] { fclose(fp); });
uint32_t t2us;
uint32_t us2t;
@@ -166,7 +167,6 @@
unsigned burstInBytes, const char* bpfProgPath)
: mBurstInBytes(burstInBytes),
mBpfProgPath(bpfProgPath),
- mBpfFd(-1),
mRequest{
.n = {
.nlmsg_len = sizeof(mRequest),
@@ -298,13 +298,6 @@
}
// clang-format on
- ~IngressPoliceFilterBuilder() {
- // TODO: use unique_fd
- if (mBpfFd != -1) {
- close(mBpfFd);
- }
- }
-
constexpr unsigned getRequestSize() const { return sizeof(Request); }
private:
@@ -332,14 +325,14 @@
}
int initBpfFd() {
- mBpfFd = bpf::retrieveProgram(mBpfProgPath);
- if (mBpfFd == -1) {
+ mBpfFd.reset(bpf::retrieveProgram(mBpfProgPath));
+ if (!mBpfFd.ok()) {
int error = errno;
ALOGE("retrieveProgram failed: %d", error);
return -error;
}
- mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd);
+ mRequest.opt.acts.act2.opt.fd.u32 = static_cast<uint32_t>(mBpfFd.get());
snprintf(mRequest.opt.acts.act2.opt.name.str,
sizeof(mRequest.opt.acts.act2.opt.name.str), "%s:[*fsobj]",
basename(mBpfProgPath));
@@ -370,14 +363,13 @@
int sendAndProcessNetlinkResponse(const void *req, int len) {
// TODO: use unique_fd instead of ScopeGuard
- int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
- if (fd == -1) {
+ unique_fd fd(socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE));
+ if (!fd.ok()) {
int error = errno;
ALOGE("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
error);
return -error;
}
- auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
static constexpr int on = 1;
if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
@@ -460,10 +452,9 @@
}
int hardwareAddressType(const char *interface) {
- int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
- if (fd < 0)
+ unique_fd fd(socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+ if (!fd.ok())
return -errno;
- auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
struct ifreq ifr = {};
// We use strncpy() instead of strlcpy() since kernel has to be able
@@ -576,12 +567,11 @@
// /sys/fs/bpf/... direct-action
int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
const char *bpfProgPath) {
- const int bpfFd = bpf::retrieveProgram(bpfProgPath);
- if (bpfFd == -1) {
+ unique_fd bpfFd(bpf::retrieveProgram(bpfProgPath));
+ if (!bpfFd.ok()) {
ALOGE("retrieveProgram failed: %d", errno);
return -errno;
}
- auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); });
struct {
nlmsghdr n;
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 59ef20d..44abba2 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -22,7 +22,7 @@
sdk_version: "system_current",
min_sdk_version: "30",
static_libs: [
- "netd_aidl_interface-V14-java",
+ "netd_aidl_interface-V15-java",
],
apex_available: [
"//apex_available:platform", // used from services.net
@@ -45,7 +45,7 @@
cc_library_static {
name: "netd_aidl_interface-lateststable-ndk",
whole_static_libs: [
- "netd_aidl_interface-V14-ndk",
+ "netd_aidl_interface-V15-ndk",
],
apex_available: [
"com.android.resolv",
@@ -56,12 +56,12 @@
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_static",
- static_libs: ["netd_aidl_interface-V14-cpp"],
+ static_libs: ["netd_aidl_interface-V15-cpp"],
}
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_shared",
- shared_libs: ["netd_aidl_interface-V14-cpp"],
+ shared_libs: ["netd_aidl_interface-V15-cpp"],
}
aidl_interface {
@@ -167,6 +167,10 @@
version: "14",
imports: [],
},
+ {
+ version: "15",
+ imports: [],
+ },
],
frozen: true,
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash
new file mode 100644
index 0000000..afdadcc
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/.hash
@@ -0,0 +1 @@
+2be6ff6fb01645cdddb3bb60f6de5727e5733267
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl
new file mode 100644
index 0000000..80b3b62
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetd.aidl
@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+ boolean isAlive();
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+ boolean bandwidthEnableDataSaver(boolean enable);
+ /**
+ * @deprecated use networkCreate() instead.
+ */
+ void networkCreatePhysical(int netId, int permission);
+ /**
+ * @deprecated use networkCreate() instead.
+ */
+ void networkCreateVpn(int netId, boolean secure);
+ void networkDestroy(int netId);
+ void networkAddInterface(int netId, in @utf8InCpp String iface);
+ void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+ void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+ void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+ void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+ void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+ boolean tetherApplyDnsInterfaces();
+ android.net.TetherStatsParcel[] tetherGetStats();
+ void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+ void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+ @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+ void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+ void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+ int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+ void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+ void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+ void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+ void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+ void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+ void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+ void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+ void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+ void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+ void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+ void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+ void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+ void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+ void strictUidCleartextPenalty(int uid, int policyPenalty);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+ */
+ @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The clatd control plane moved to the mainline module starting in T. See ClatCoordinator.
+ */
+ void clatdStop(in @utf8InCpp String ifName);
+ boolean ipfwdEnabled();
+ @utf8InCpp String[] ipfwdGetRequesterList();
+ void ipfwdEnableForwarding(in @utf8InCpp String requester);
+ void ipfwdDisableForwarding(in @utf8InCpp String requester);
+ void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+ void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+ void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+ void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+ void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+ void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+ void bandwidthSetGlobalAlert(long bytes);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthAddNaughtyApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthRemoveNaughtyApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthAddNiceApp(int uid);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void bandwidthRemoveNiceApp(int uid);
+ void tetherStart(in @utf8InCpp String[] dhcpRanges);
+ void tetherStop();
+ boolean tetherIsEnabled();
+ void tetherInterfaceAdd(in @utf8InCpp String ifName);
+ void tetherInterfaceRemove(in @utf8InCpp String ifName);
+ @utf8InCpp String[] tetherInterfaceList();
+ void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+ @utf8InCpp String[] tetherDnsList();
+ void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+ void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+ void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+ void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+ int networkGetDefault();
+ void networkSetDefault(int netId);
+ void networkClearDefault();
+ void networkSetPermissionForNetwork(int netId, int permission);
+ void networkSetPermissionForUser(int permission, in int[] uids);
+ void networkClearPermissionForUser(in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void trafficSetNetPermForUids(int permission, in int[] uids);
+ void networkSetProtectAllow(int uid);
+ void networkSetProtectDeny(int uid);
+ boolean networkCanProtect(int uid);
+ void firewallSetFirewallType(int firewalltype);
+ void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallSetUidRule(int childChain, int uid, int firewallRule);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallEnableChildChain(int childChain, boolean enable);
+ @utf8InCpp String[] interfaceGetList();
+ android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+ void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+ void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+ void interfaceClearAddrs(in @utf8InCpp String ifName);
+ void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+ void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+ void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+ void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+ void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+ void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void firewallRemoveUidInterfaceRules(in int[] uids);
+ /**
+ * @deprecated unimplemented on T+.
+ */
+ void trafficSwapActiveStatsMap();
+ IBinder getOemNetd();
+ void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+ android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+ void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ android.net.TetherStatsParcel[] tetherOffloadGetStats();
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+ /**
+ * @deprecated This method has no effect and throws UnsupportedOperationException. The mainline module accesses the BPF map directly starting in S. See BpfCoordinator.
+ */
+ android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+ void networkCreate(in android.net.NativeNetworkConfig config);
+ void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+ void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+ void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
+ void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
+ const int IPV4 = 4;
+ const int IPV6 = 6;
+ const int CONF = 1;
+ const int NEIGH = 2;
+ const String IPSEC_INTERFACE_PREFIX = "ipsec";
+ const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+ const int IPV6_ADDR_GEN_MODE_NONE = 1;
+ const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+ const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+ const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+ const int PENALTY_POLICY_ACCEPT = 1;
+ const int PENALTY_POLICY_LOG = 2;
+ const int PENALTY_POLICY_REJECT = 3;
+ const int CLAT_MARK = 0xdeadc1a7;
+ const int LOCAL_NET_ID = 99;
+ const int DUMMY_NET_ID = 51;
+ const int UNREACHABLE_NET_ID = 52;
+ const String NEXTHOP_NONE = "";
+ const String NEXTHOP_UNREACHABLE = "unreachable";
+ const String NEXTHOP_THROW = "throw";
+ const int PERMISSION_NONE = 0;
+ const int PERMISSION_NETWORK = 1;
+ const int PERMISSION_SYSTEM = 2;
+ const int NO_PERMISSIONS = 0;
+ const int PERMISSION_INTERNET = 4;
+ const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+ const int PERMISSION_UNINSTALLED = (-1) /* -1 */;
+ /**
+ * @deprecated use FIREWALL_ALLOWLIST.
+ */
+ const int FIREWALL_WHITELIST = 0;
+ const int FIREWALL_ALLOWLIST = 0;
+ /**
+ * @deprecated use FIREWALL_DENYLIST.
+ */
+ const int FIREWALL_BLACKLIST = 1;
+ const int FIREWALL_DENYLIST = 1;
+ const int FIREWALL_RULE_ALLOW = 1;
+ const int FIREWALL_RULE_DENY = 2;
+ const int FIREWALL_CHAIN_NONE = 0;
+ const int FIREWALL_CHAIN_DOZABLE = 1;
+ const int FIREWALL_CHAIN_STANDBY = 2;
+ const int FIREWALL_CHAIN_POWERSAVE = 3;
+ const int FIREWALL_CHAIN_RESTRICTED = 4;
+ const String IF_STATE_UP = "up";
+ const String IF_STATE_DOWN = "down";
+ const String IF_FLAG_BROADCAST = "broadcast";
+ const String IF_FLAG_LOOPBACK = "loopback";
+ const String IF_FLAG_POINTOPOINT = "point-to-point";
+ const String IF_FLAG_RUNNING = "running";
+ const String IF_FLAG_MULTICAST = "multicast";
+ const int IPSEC_DIRECTION_IN = 0;
+ const int IPSEC_DIRECTION_OUT = 1;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+ oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+ oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+ oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+ oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+ oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+ oneway void onInterfaceAdded(@utf8InCpp String ifName);
+ oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+ oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+ oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+ oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+ oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+ @utf8InCpp String ifName;
+ @utf8InCpp String hwAddr;
+ @utf8InCpp String ipv4Addr;
+ int prefixLength;
+ @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl
new file mode 100644
index 0000000..975a261
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/IpSecMigrateInfoParcel.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@JavaOnlyImmutable
+parcelable IpSecMigrateInfoParcel {
+ int requestId;
+ int selAddrFamily;
+ int direction;
+ @utf8InCpp String oldSourceAddress;
+ @utf8InCpp String oldDestinationAddress;
+ @utf8InCpp String newSourceAddress;
+ @utf8InCpp String newDestinationAddress;
+ int interfaceId;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+ int mark;
+ int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+ int netId;
+ android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+ int permission;
+ boolean secure;
+ android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+ boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..e77a143
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeNetworkType.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+ PHYSICAL = 0,
+ VIRTUAL = 1,
+ PHYSICAL_LOCAL = 2,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+ SERVICE = 1,
+ PLATFORM = 2,
+ LEGACY = 3,
+ OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+ @utf8InCpp String destination;
+ @utf8InCpp String ifName;
+ @utf8InCpp String nextHop;
+ int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+ boolean usingLegacyDnsProxy;
+ @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+ int inputInterfaceIndex;
+ int outputInterfaceIndex;
+ byte[] destination;
+ int prefixLength;
+ byte[] srcL2Address;
+ byte[] dstL2Address;
+ int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+ @utf8InCpp String iface;
+ long rxBytes;
+ long rxPackets;
+ long txBytes;
+ long txPackets;
+ int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+ int start;
+ int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/15/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+ int netId;
+ android.net.UidRangeParcel[] uidRanges;
+ int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
index 8ccefb2..80b3b62 100644
--- a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/INetd.aidl
@@ -203,6 +203,7 @@
void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
void ipSecMigrate(in android.net.IpSecMigrateInfoParcel migrateInfo);
void setNetworkAllowlist(in android.net.netd.aidl.NativeUidRangeConfig[] allowedNetworks);
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
const int IPV4 = 4;
const int IPV6 = 6;
const int CONF = 1;
diff --git a/staticlibs/netd/binder/android/net/INetd.aidl b/staticlibs/netd/binder/android/net/INetd.aidl
index ee27e84..e4c63b9 100644
--- a/staticlibs/netd/binder/android/net/INetd.aidl
+++ b/staticlibs/netd/binder/android/net/INetd.aidl
@@ -1446,4 +1446,27 @@
* - subPriority: unused
*/
void setNetworkAllowlist(in NativeUidRangeConfig[] allowedNetworks);
+
+ /**
+ * Allow the UID to explicitly select the given network even if it is subject to a VPN.
+ *
+ * Throws ServiceSpecificException with error code EEXISTS when trying to add a bypass rule that
+ * already exists, and ENOENT when trying to remove a bypass rule that does not exist.
+ *
+ * netId specific bypass rules can be combined and are allowed to overlap with global VPN
+ * exclusions (by calling networkSetProtectAllow / networkSetProtectDeny, or by setting netId to
+ * 0). Adding or removing global VPN bypass rules does not affect the netId specific rules and
+ * vice versa.
+ *
+ * Note that if netId is set to 0 (NETID_UNSET) this API is equivalent to
+ * networkSetProtectAllow} / #networkSetProtectDeny.
+ *
+ * @param allow whether to allow or disallow the operation.
+ * @param uid the UID
+ * @param netId the netId that the UID is allowed to select.
+ *
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ void networkAllowBypassVpnOnNetwork(boolean allow, int uid, int netId);
}
diff --git a/staticlibs/netd/libnetdutils/InternetAddresses.cpp b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
index 322f1b1..6d98608 100644
--- a/staticlibs/netd/libnetdutils/InternetAddresses.cpp
+++ b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
@@ -16,6 +16,7 @@
#include "netdutils/InternetAddresses.h"
+#include <stdlib.h>
#include <string>
#include <android-base/stringprintf.h>
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index e23f999..4ed3afd 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -16,6 +16,7 @@
package com.android.net.module.util
+import android.util.SparseArray
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertThrows
@@ -179,4 +180,20 @@
CollectionUtils.assoc(listOf(1, 2), list15)
}
}
+
+ @Test
+ fun testGetIndexForValue() {
+ val sparseArray = SparseArray<String>();
+ sparseArray.put(5, "hello");
+ sparseArray.put(10, "abcd");
+ sparseArray.put(20, null);
+
+ val value1 = "abcd";
+ val value1Copy = String(value1.toCharArray())
+ val value2 = null;
+
+ assertEquals(1, CollectionUtils.getIndexForValue(sparseArray, value1));
+ assertEquals(1, CollectionUtils.getIndexForValue(sparseArray, value1Copy));
+ assertEquals(2, CollectionUtils.getIndexForValue(sparseArray, value2));
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
index 4fc5ec2..d1df3a6 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java
@@ -252,8 +252,10 @@
public void testToString() {
NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
assertNotNull(msg);
- assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)",
- msg.toString());
+ final String expected = "Nduseroptmsg(family:10, opts_len:16, ifindex:1431655765, "
+ + "icmp_type:134, icmp_code:0, srcaddr: fe80:2:3:4:5:6:7:8%1431655765, "
+ + "NdOptPref64(2001:db8:3:4:5:6::/96, 10064))";
+ assertEquals(expected, msg.toString());
}
// Convenience method to parse a NduseroptMessage that's not part of a netlink message.
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
index 143e4d4..e42c552 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java
@@ -46,6 +46,7 @@
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNDUSEROPT;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWPREFIX;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWROUTE;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWRULE;
@@ -89,6 +90,7 @@
assertEquals("RTM_NEWRULE", stringForNlMsgType(RTM_NEWRULE, NETLINK_ROUTE));
assertEquals("RTM_DELRULE", stringForNlMsgType(RTM_DELRULE, NETLINK_ROUTE));
assertEquals("RTM_GETRULE", stringForNlMsgType(RTM_GETRULE, NETLINK_ROUTE));
+ assertEquals("RTM_NEWPREFIX", stringForNlMsgType(RTM_NEWPREFIX, NETLINK_ROUTE));
assertEquals("RTM_NEWNDUSEROPT", stringForNlMsgType(RTM_NEWNDUSEROPT, NETLINK_ROUTE));
assertEquals("SOCK_DIAG_BY_FAMILY",
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 9124ac0..3843b90 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -99,6 +99,8 @@
"mcts-networking",
"mts-tethering",
"mcts-tethering",
+ "mcts-wifi",
+ "mcts-dnsresolver",
],
data: [":ConnectivityTestPreparer"],
}
diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp
index 5af8c14..394c6be 100644
--- a/staticlibs/testutils/app/connectivitychecker/Android.bp
+++ b/staticlibs/testutils/app/connectivitychecker/Android.bp
@@ -30,7 +30,6 @@
"modules-utils-build_system",
"net-tests-utils",
],
- host_required: ["net-tests-utils-host-common"],
lint: {
strict_updatability_linting: true,
},
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt
new file mode 100644
index 0000000..93422ad
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/AutoReleaseNetworkCallbackRule.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 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.testutils
+
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.os.Handler
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.RecorderCallback.CallbackEntry
+import java.util.Collections
+import kotlin.test.fail
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A rule to file [NetworkCallback]s to request or watch networks.
+ *
+ * The callbacks filed in test methods are automatically unregistered when the method completes.
+ */
+class AutoReleaseNetworkCallbackRule : NetworkCallbackHelper(), TestRule {
+ override fun apply(base: Statement, description: Description): Statement {
+ return RequestCellNetworkStatement(base, description)
+ }
+
+ private inner class RequestCellNetworkStatement(
+ private val base: Statement,
+ private val description: Description
+ ) : Statement() {
+ override fun evaluate() {
+ tryTest {
+ base.evaluate()
+ } cleanup {
+ unregisterAll()
+ }
+ }
+ }
+}
+
+/**
+ * Helps file [NetworkCallback]s to request or watch networks, keeping track of them for cleanup.
+ */
+open class NetworkCallbackHelper {
+ private val cm by lazy {
+ InstrumentationRegistry.getInstrumentation().context
+ .getSystemService(ConnectivityManager::class.java)
+ ?: fail("ConnectivityManager not found")
+ }
+ private val cbToCleanup = Collections.synchronizedSet(mutableSetOf<NetworkCallback>())
+ private var cellRequestCb: TestableNetworkCallback? = null
+
+ /**
+ * Convenience method to request a cell network, similarly to [requestNetwork].
+ *
+ * The rule will keep tract of a single cell network request, which can be unrequested manually
+ * using [unrequestCell].
+ */
+ fun requestCell(): Network {
+ if (cellRequestCb != null) {
+ fail("Cell network was already requested")
+ }
+ val cb = requestNetwork(
+ NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build()
+ )
+ cellRequestCb = cb
+ return cb.expect<CallbackEntry.Available>(
+ errorMsg = "Cell network not available. " +
+ "Please ensure the device has working mobile data."
+ ).network
+ }
+
+ /**
+ * Unrequest a cell network requested through [requestCell].
+ */
+ fun unrequestCell() {
+ val cb = cellRequestCb ?: fail("Cell network was not requested")
+ unregisterNetworkCallback(cb)
+ cellRequestCb = null
+ }
+
+ private fun addCallback(
+ cb: TestableNetworkCallback,
+ registrar: (TestableNetworkCallback) -> Unit
+ ): TestableNetworkCallback {
+ registrar(cb)
+ cbToCleanup.add(cb)
+ return cb
+ }
+
+ /**
+ * File a request for a Network.
+ *
+ * This will fail tests (throw) if the cell network cannot be obtained, or if it was already
+ * requested.
+ *
+ * Tests may call [unregisterNetworkCallback] once they are done using the returned [Network],
+ * otherwise it will be automatically unrequested after the test.
+ */
+ @JvmOverloads
+ fun requestNetwork(
+ request: NetworkRequest,
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ handler: Handler? = null
+ ) = addCallback(cb) {
+ if (handler == null) {
+ cm.requestNetwork(request, it)
+ } else {
+ cm.requestNetwork(request, it, handler)
+ }
+ }
+
+ /**
+ * Overload of [requestNetwork] that allows specifying a timeout.
+ */
+ @JvmOverloads
+ fun requestNetwork(
+ request: NetworkRequest,
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ timeoutMs: Int,
+ ) = addCallback(cb) { cm.requestNetwork(request, it, timeoutMs) }
+
+ /**
+ * File a callback for a NetworkRequest.
+ *
+ * This will fail tests (throw) if the cell network cannot be obtained, or if it was already
+ * requested.
+ *
+ * Tests may call [unregisterNetworkCallback] once they are done using the returned [Network],
+ * otherwise it will be automatically unrequested after the test.
+ */
+ @JvmOverloads
+ fun registerNetworkCallback(
+ request: NetworkRequest,
+ cb: TestableNetworkCallback = TestableNetworkCallback()
+ ) = addCallback(cb) { cm.registerNetworkCallback(request, it) }
+
+ /**
+ * @see ConnectivityManager.registerDefaultNetworkCallback
+ */
+ @JvmOverloads
+ fun registerDefaultNetworkCallback(
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ handler: Handler? = null
+ ) = addCallback(cb) {
+ if (handler == null) {
+ cm.registerDefaultNetworkCallback(it)
+ } else {
+ cm.registerDefaultNetworkCallback(it, handler)
+ }
+ }
+
+ /**
+ * @see ConnectivityManager.registerSystemDefaultNetworkCallback
+ */
+ @JvmOverloads
+ fun registerSystemDefaultNetworkCallback(
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ handler: Handler
+ ) = addCallback(cb) { cm.registerSystemDefaultNetworkCallback(it, handler) }
+
+ /**
+ * @see ConnectivityManager.registerDefaultNetworkCallbackForUid
+ */
+ @JvmOverloads
+ fun registerDefaultNetworkCallbackForUid(
+ uid: Int,
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ handler: Handler
+ ) = addCallback(cb) { cm.registerDefaultNetworkCallbackForUid(uid, it, handler) }
+
+ /**
+ * @see ConnectivityManager.registerBestMatchingNetworkCallback
+ */
+ @JvmOverloads
+ fun registerBestMatchingNetworkCallback(
+ request: NetworkRequest,
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ handler: Handler
+ ) = addCallback(cb) { cm.registerBestMatchingNetworkCallback(request, it, handler) }
+
+ /**
+ * @see ConnectivityManager.requestBackgroundNetwork
+ */
+ @JvmOverloads
+ fun requestBackgroundNetwork(
+ request: NetworkRequest,
+ cb: TestableNetworkCallback = TestableNetworkCallback(),
+ handler: Handler
+ ) = addCallback(cb) { cm.requestBackgroundNetwork(request, it, handler) }
+
+ /**
+ * Unregister a callback filed using registration methods in this class.
+ */
+ fun unregisterNetworkCallback(cb: NetworkCallback) {
+ cm.unregisterNetworkCallback(cb)
+ cbToCleanup.remove(cb)
+ }
+
+ /**
+ * Unregister all callbacks that were filed using registration methods in this class.
+ */
+ fun unregisterAll() {
+ cbToCleanup.forEach { cm.unregisterNetworkCallback(it) }
+ cbToCleanup.clear()
+ cellRequestCb = null
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
index 8090d5b..3857810 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ConnectUtil.kt
@@ -86,6 +86,7 @@
val callback = TestableNetworkCallback(timeoutMs = WIFI_CONNECT_TIMEOUT_MS)
cm.registerNetworkCallback(NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
.build(), callback)
return tryTest {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
new file mode 100644
index 0000000..d5e91c2
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/SetFeatureFlagsRule.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.testutils.com.android.testutils
+
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * A JUnit Rule that sets feature flags based on `@FeatureFlag` annotations.
+ *
+ * This rule enables dynamic control of feature flag states during testing.
+ * And restores the original values after performing tests.
+ *
+ * **Usage:**
+ * ```kotlin
+ * class MyTestClass {
+ * @get:Rule
+ * val setFeatureFlagsRule = SetFeatureFlagsRule(setFlagsMethod = (name, enabled) -> {
+ * // Custom handling code.
+ * }, (name) -> {
+ * // Custom getter code to retrieve the original values.
+ * })
+ *
+ * // ... test methods with @FeatureFlag annotations
+ * @FeatureFlag("FooBar1", true)
+ * @FeatureFlag("FooBar2", false)
+ * @Test
+ * fun testFooBar() {}
+ * }
+ * ```
+ */
+class SetFeatureFlagsRule(
+ val setFlagsMethod: (name: String, enabled: Boolean?) -> Unit,
+ val getFlagsMethod: (name: String) -> Boolean?
+) : TestRule {
+ /**
+ * This annotation marks a test method as requiring a specific feature flag to be configured.
+ *
+ * Use this on test methods to dynamically control feature flag states during testing.
+ *
+ * @param name The name of the feature flag.
+ * @param enabled The desired state (true for enabled, false for disabled) of the feature flag.
+ */
+ @Target(AnnotationTarget.FUNCTION)
+ @Retention(AnnotationRetention.RUNTIME)
+ annotation class FeatureFlag(val name: String, val enabled: Boolean = true)
+
+ /**
+ * This method is the core of the rule, executed by the JUnit framework before each test method.
+ *
+ * It retrieves the test method's metadata.
+ * If any `@FeatureFlag` annotation is found, it passes every feature flag's name
+ * and enabled state into the user-specified lambda to apply custom actions.
+ */
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ val testMethod = description.testClass.getMethod(description.methodName)
+ val featureFlagAnnotations = testMethod.getAnnotationsByType(
+ FeatureFlag::class.java
+ )
+
+ val valuesToBeRestored = mutableMapOf<String, Boolean?>()
+ for (featureFlagAnnotation in featureFlagAnnotations) {
+ valuesToBeRestored[featureFlagAnnotation.name] =
+ getFlagsMethod(featureFlagAnnotation.name)
+ setFlagsMethod(featureFlagAnnotation.name, featureFlagAnnotation.enabled)
+ }
+
+ // Execute the test method, which includes methods annotated with
+ // @Before, @Test and @After.
+ base.evaluate()
+
+ valuesToBeRestored.forEach {
+ setFlagsMethod(it.key, it.value)
+ }
+ }
+ }
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 05c0444..66362d4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -91,6 +91,7 @@
override val network: Network,
val reason: Int
) : CallbackEntry()
+
// Convenience constants for expecting a type
companion object {
@JvmField
@@ -216,7 +217,11 @@
) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
fun createLinkedCopy() = TestableNetworkCallback(
- this, defaultTimeoutMs, defaultNoCallbackTimeoutMs, waiterFunc)
+ this,
+ defaultTimeoutMs,
+ defaultNoCallbackTimeoutMs,
+ waiterFunc
+ )
// The last available network, or null if any network was lost since the last call to
// onAvailable. TODO : fix this by fixing the tests that rely on this behavior
@@ -355,17 +360,29 @@
timeoutMs: Long = defaultTimeoutMs,
errorMsg: String? = null,
test: (T) -> Boolean = { true }
- ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
+ ) = (poll(timeoutMs) ?: failWithErrorReason(errorMsg,
+ "Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
.also {
- if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
+ if (it !is T) {
+ failWithErrorReason(
+ errorMsg,
+ "Expected callback ${T::class.simpleName}, got $it"
+ )
+ }
if (ANY_NETWORK !== network && it.network != network) {
- fail("Expected network $network for callback : $it")
+ failWithErrorReason(errorMsg, "Expected network $network for callback : $it")
}
if (!test(it)) {
- fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
+ failWithErrorReason(errorMsg, "Callback doesn't match predicate : $it")
}
} as T
+ // "Nothing" is the return type to declare a function never returns a value.
+ fun failWithErrorReason(errorMsg: String?, errorReason: String): Nothing {
+ val message = if (errorMsg != null) "$errorMsg : $errorReason" else errorReason
+ fail(message)
+ }
+
inline fun <reified T : CallbackEntry> expect(
network: HasNetwork,
timeoutMs: Long = defaultTimeoutMs,
@@ -402,8 +419,11 @@
from: Int = mark,
crossinline predicate: (T) -> Boolean = { true }
): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also {
- assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " +
- "Got ${history.backtrace()}")
+ assertNotNull(
+ it,
+ "Callback ${T::class} not received within ${timeoutMs}ms. " +
+ "Got ${history.backtrace()}"
+ )
} as T
@JvmOverloads
@@ -412,8 +432,11 @@
timeoutMs: Long = defaultTimeoutMs,
predicate: (cb: T) -> Boolean = { true }
) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also {
- assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
- "Got ${history.backtrace()}")
+ assertNotNull(
+ it,
+ "Callback ${type.java} not received within ${timeoutMs}ms. " +
+ "Got ${history.backtrace()}"
+ )
} as T
fun <T : CallbackEntry> eventuallyExpect(
@@ -422,8 +445,11 @@
from: Int = mark,
predicate: (cb: T) -> Boolean = { true }
) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also {
- assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
- "Got ${history.backtrace()}")
+ assertNotNull(
+ it,
+ "Callback ${type.java} not received within ${timeoutMs}ms. " +
+ "Got ${history.backtrace()}"
+ )
} as T
// Expects onAvailable and the callbacks that follow it. These are:
@@ -534,8 +560,13 @@
blockedReason: Int,
tmt: Long = defaultTimeoutMs
) {
- expectAvailableCallbacks(net, validated = false, suspended = false,
- blockedReason = blockedReason, tmt = tmt)
+ expectAvailableCallbacks(
+ net,
+ validated = false,
+ suspended = false,
+ blockedReason = blockedReason,
+ tmt = tmt
+ )
expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
}
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
index 600a623..6d03042 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -61,6 +61,7 @@
setUpdaterNetworkingEnabled(testInfo, enableChain = true,
enablePkgs = UPDATER_PKGS.associateWith { false })
runPreparerApk(testInfo)
+ refreshTime(testInfo)
}
private fun runPreparerApk(testInfo: TestInformation) {
@@ -141,6 +142,14 @@
.contains(":deny")
}
+ private fun refreshTime(testInfo: TestInformation,) {
+ // Forces a synchronous time refresh using the network. Time is fetched synchronously but
+ // this does not guarantee that system time is updated when it returns.
+ // This avoids flakes where the system clock rolls back, for example when using test
+ // settings like test_url_expiration_time in NetworkMonitor.
+ testInfo.exec("cmd network_time_update_service force_refresh")
+ }
+
override fun tearDown(testInfo: TestInformation, e: Throwable?) {
if (isTearDownDisabled) return
installer.tearDown(testInfo, e)
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 6e9d614..e95a81a 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -201,3 +201,8 @@
name: "connectivity-mainline-presubmit-java-defaults",
test_mainline_modules: mainline_presubmit_modules,
}
+
+filegroup {
+ name: "connectivity_mainline_test_map",
+ srcs: ["connectivity_mainline_test.map"],
+}
diff --git a/tests/common/connectivity_mainline_test.map b/tests/common/connectivity_mainline_test.map
new file mode 100644
index 0000000..043312e
--- /dev/null
+++ b/tests/common/connectivity_mainline_test.map
@@ -0,0 +1,27 @@
+#
+# Copyright (C) 2024 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.
+#
+
+# Some connectivity tests run on older OS versions, and for those tests, many
+# library dependencies (such as libbase and libc++) need to be linked
+# statically. The tests also need to be linked with a version script to ensure
+# that the statically-linked library isn't exported from the executable, where
+# it would override the shared libraries that the OS itself uses. See
+# b/333438055 for an example of what goes wrong when libc++ is partially
+# exported from an executable.
+{
+ local:
+ *;
+};
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 6eb56c7b..0f0e2f1 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -61,6 +61,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.os.Process.INVALID_UID;
+
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
@@ -68,6 +69,7 @@
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -83,21 +85,25 @@
import android.os.Build;
import android.util.ArraySet;
import android.util.Range;
+
import androidx.test.filters.SmallTest;
+
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
// NetworkCapabilities is only updatable on S+, and this test covers behavior which implementation
@@ -1465,4 +1471,26 @@
assertEquals("-SUPL-VALIDATED-CAPTIVE_PORTAL+MMS+OEM_PAID",
nc1.describeCapsDifferencesFrom(nc2));
}
+
+ @Test
+ public void testInvalidCapability() {
+ final int invalidCapability = Integer.MAX_VALUE;
+ // Passing invalid capability does not throw
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .removeCapability(invalidCapability)
+ .removeForbiddenCapability(invalidCapability)
+ .addCapability(invalidCapability)
+ .addForbiddenCapability(invalidCapability)
+ .build();
+
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .build();
+
+ // nc1 and nc2 are the same since invalid capability is ignored
+ assertEquals(nc1, nc2);
+ }
}
diff --git a/tests/common/java/android/net/nsd/NsdServiceInfoTest.java b/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
index 8e89037..21e34ab 100644
--- a/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/common/java/android/net/nsd/NsdServiceInfoTest.java
@@ -16,6 +16,7 @@
package android.net.nsd;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -51,6 +52,23 @@
private static final InetAddress IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1");
private static final InetAddress IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8::");
+ private static final byte[] PUBLIC_KEY_RDATA = new byte[] {
+ (byte) 0x02, (byte)0x01, // flag
+ (byte) 0x03, // protocol
+ (byte) 0x0d, // algorithm
+ // 64-byte public key below
+ (byte) 0xC1, (byte) 0x41, (byte) 0xD0, (byte) 0x63, (byte) 0x79, (byte) 0x60,
+ (byte) 0xB9, (byte) 0x8C, (byte) 0xBC, (byte) 0x12, (byte) 0xCF, (byte) 0xCA,
+ (byte) 0x22, (byte) 0x1D, (byte) 0x28, (byte) 0x79, (byte) 0xDA, (byte) 0xC2,
+ (byte) 0x6E, (byte) 0xE5, (byte) 0xB4, (byte) 0x60, (byte) 0xE9, (byte) 0x00,
+ (byte) 0x7C, (byte) 0x99, (byte) 0x2E, (byte) 0x19, (byte) 0x02, (byte) 0xD8,
+ (byte) 0x97, (byte) 0xC3, (byte) 0x91, (byte) 0xB0, (byte) 0x37, (byte) 0x64,
+ (byte) 0xD4, (byte) 0x48, (byte) 0xF7, (byte) 0xD0, (byte) 0xC7, (byte) 0x72,
+ (byte) 0xFD, (byte) 0xB0, (byte) 0x3B, (byte) 0x1D, (byte) 0x9D, (byte) 0x6D,
+ (byte) 0x52, (byte) 0xFF, (byte) 0x88, (byte) 0x86, (byte) 0x76, (byte) 0x9E,
+ (byte) 0x8E, (byte) 0x23, (byte) 0x62, (byte) 0x51, (byte) 0x35, (byte) 0x65,
+ (byte) 0x27, (byte) 0x09, (byte) 0x62, (byte) 0xD3
+ };
@Test
public void testLimits() throws Exception {
@@ -120,6 +138,7 @@
fullInfo.setPort(4242);
fullInfo.setHostAddresses(List.of(IPV4_ADDRESS));
fullInfo.setHostname("home");
+ fullInfo.setPublicKey(PUBLIC_KEY_RDATA);
fullInfo.setNetwork(new Network(123));
fullInfo.setInterfaceIndex(456);
checkParcelable(fullInfo);
@@ -136,6 +155,7 @@
attributedInfo.setPort(4242);
attributedInfo.setHostAddresses(List.of(IPV6_ADDRESS, IPV4_ADDRESS));
attributedInfo.setHostname("home");
+ attributedInfo.setPublicKey(PUBLIC_KEY_RDATA);
attributedInfo.setAttribute("color", "pink");
attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
attributedInfo.setAttribute("adorable", (String) null);
@@ -172,6 +192,7 @@
assertEquals(original.getServiceType(), result.getServiceType());
assertEquals(original.getHost(), result.getHost());
assertEquals(original.getHostname(), result.getHostname());
+ assertArrayEquals(original.getPublicKey(), result.getPublicKey());
assertTrue(original.getPort() == result.getPort());
assertEquals(original.getNetwork(), result.getNetwork());
assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex());
diff --git a/tests/cts/hostside-network-policy/Android.bp b/tests/cts/hostside-network-policy/Android.bp
new file mode 100644
index 0000000..c3ce0b9
--- /dev/null
+++ b/tests/cts/hostside-network-policy/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_framework_backstage_power",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsHostsideNetworkPolicyTests",
+ defaults: ["cts_defaults"],
+ // Only compile source java files in this apk.
+ srcs: [
+ "src/**/*.java",
+ ":ArgumentConstants",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ ],
+ static_libs: [
+ "modules-utils-build-testing",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ ],
+ data: [
+ ":CtsHostsideNetworkPolicyTestsApp",
+ ":CtsHostsideNetworkPolicyTestsApp2",
+ ],
+ per_testcase_directory: true,
+}
diff --git a/tests/cts/hostside-network-policy/AndroidTest.xml b/tests/cts/hostside-network-policy/AndroidTest.xml
new file mode 100644
index 0000000..44f77f8
--- /dev/null
+++ b/tests/cts/hostside-network-policy/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Config for CTS network policy host test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+ <target_preparer class="com.android.cts.netpolicy.NetworkPolicyTestsPreparer" />
+
+ <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
+ <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
+ <option name="teardown-command" value="cmd power set-mode 0" />
+ <option name="teardown-command" value="cmd battery reset" />
+ <option name="teardown-command" value="cmd netpolicy stop-watching" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="force-skip-system-props" value="true" />
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="set-global-setting" key="low_power_standby_enabled" value="0" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsHostsideNetworkPolicyTests.jar" />
+ <option name="runtime-hint" value="3m56s" />
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/sdcard/CtsHostsideNetworkPolicyTests" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+</configuration>
diff --git a/tests/cts/hostside-network-policy/OWNERS b/tests/cts/hostside-network-policy/OWNERS
new file mode 100644
index 0000000..ea83e61
--- /dev/null
+++ b/tests/cts/hostside-network-policy/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 61373
+# Inherits parent owners
+include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
diff --git a/tests/cts/hostside-network-policy/TEST_MAPPING b/tests/cts/hostside-network-policy/TEST_MAPPING
new file mode 100644
index 0000000..57ac4f7
--- /dev/null
+++ b/tests/cts/hostside-network-policy/TEST_MAPPING
@@ -0,0 +1,27 @@
+{
+ "presubmit-large": [
+ {
+ "name": "CtsHostsideNetworkPolicyTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ // Postsubmit on virtual devices to monitor flakiness of all tests that don't require a
+ // physical device
+ "name": "CtsHostsideNetworkPolicyTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/cts/hostside-network-policy/aidl/Android.bp b/tests/cts/hostside-network-policy/aidl/Android.bp
new file mode 100644
index 0000000..b182090
--- /dev/null
+++ b/tests/cts/hostside-network-policy/aidl/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_framework_backstage_power",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+ name: "CtsHostsideNetworkPolicyTestsAidl",
+ sdk_version: "current",
+ srcs: [
+ "com/android/cts/netpolicy/hostside/*.aidl",
+ ],
+}
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
similarity index 82%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
index e7b2815..068d9d8 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import android.app.job.JobInfo;
-import com.android.cts.net.hostside.INetworkCallback;
+import com.android.cts.netpolicy.hostside.INetworkCallback;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
interface IMyService {
void registerBroadcastReceiver();
int getCounters(String receiverName, String action);
- String checkNetworkStatus();
+ NetworkCheckResult checkNetworkStatus(String customUrl);
String getRestrictBackgroundStatus();
void sendNotification(int notificationId, String notificationType);
void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb);
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
similarity index 95%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
index 2048bab..38efc7b 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import android.net.Network;
import android.net.NetworkCapabilities;
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
similarity index 78%
rename from tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
rename to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
index 19198c5..c6b7a1c 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkStateObserver.aidl
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
+
+import android.net.NetworkInfo;
+
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
interface INetworkStateObserver {
- void onNetworkStateChecked(int resultCode, String resultData);
+ void onNetworkStateChecked(int resultCode, in NetworkCheckResult networkCheckResult);
const int RESULT_SUCCESS_NETWORK_STATE_CHECKED = 0;
const int RESULT_ERROR_UNEXPECTED_PROC_STATE = 1;
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
similarity index 74%
copy from tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
copy to tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
index 472e347..7aac2ab 100644
--- a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
+++ b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-package com.android.cts.net.arguments;
+package com.android.cts.netpolicy.hostside;
-public interface InstrumentationArguments {
- String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
-}
+import android.net.NetworkInfo;
+
+@JavaDerive(toString=true)
+parcelable NetworkCheckResult {
+ boolean connected;
+ String details;
+ NetworkInfo networkInfo;
+}
\ No newline at end of file
diff --git a/tests/cts/hostside-network-policy/app/Android.bp b/tests/cts/hostside-network-policy/app/Android.bp
new file mode 100644
index 0000000..a31c843
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_framework_backstage_power",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "CtsHostsideNetworkPolicyTestsAppDefaults",
+ platform_apis: true,
+ static_libs: [
+ "CtsHostsideNetworkPolicyTestsAidl",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
+ "compatibility-device-util-axt",
+ "cts-net-utils",
+ "ctstestrunner-axt",
+ "modules-utils-build",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ":ArgumentConstants",
+ ],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "general-tests",
+ "sts",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkPolicyTestsApp",
+ defaults: [
+ "cts_support_defaults",
+ "framework-connectivity-test-defaults",
+ "CtsHostsideNetworkPolicyTestsAppDefaults",
+ ],
+}
diff --git a/tests/cts/hostside-network-policy/app/AndroidManifest.xml b/tests/cts/hostside-network-policy/app/AndroidManifest.xml
new file mode 100644
index 0000000..f19e35f
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.netpolicy.hostside">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+ <application android:requestLegacyExternalStorage="true">
+ <uses-library android:name="android.test.runner"/>
+ <service android:name=".MyNotificationListenerService"
+ android:label="MyNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.netpolicy.hostside"/>
+
+</manifest>
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
index d9ff539..19e4364 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
import static org.junit.Assert.assertEquals;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
index 0d7365f..ae226e2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
index 8a3e790..da633c0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDefaultRestrictionsTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
index b037953..0c8cb70 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.NOT_LOW_RAM_DEVICE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.NOT_LOW_RAM_DEVICE;
import android.os.SystemClock;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
similarity index 88%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
index 7cac2af..5435920 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractExpeditedJobTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
similarity index 90%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 2ca8832..d0203c5 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
@@ -23,17 +23,18 @@
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.executeShellCommand;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.forceRunJob;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getConnectivityManager;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getContext;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getInstrumentation;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -51,6 +52,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
@@ -78,10 +80,10 @@
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
/**
@@ -91,8 +93,8 @@
public abstract class AbstractRestrictBackgroundNetworkTestCase {
public static final String TAG = "RestrictBackgroundNetworkTests";
- protected static final String TEST_PKG = "com.android.cts.net.hostside";
- protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
+ protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
+ protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
// TODO(b/321797685): Configure it via device-config once it is available.
protected static final long PROCESS_STATE_TRANSITION_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
@@ -110,17 +112,17 @@
private static final String MANIFEST_RECEIVER = "ManifestReceiver";
private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
private static final String ACTION_FINISH_ACTIVITY =
- "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
+ "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
private static final String ACTION_FINISH_JOB =
- "com.android.cts.net.hostside.app2.action.FINISH_JOB";
+ "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
// Copied from com.android.server.net.NetworkPolicyManagerService class
private static final String ACTION_SNOOZE_WARNING =
"com.android.server.net.action.SNOOZE_WARNING";
private static final String ACTION_RECEIVER_READY =
- "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
+ "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
static final String ACTION_SHOW_TOAST =
- "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
+ "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
@@ -136,6 +138,7 @@
private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
private static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
+ private static final String KEY_CUSTOM_URL = TEST_PKG + ".custom_url";
private static final String EMPTY_STRING = "";
@@ -165,6 +168,7 @@
protected ConnectivityManager mCm;
protected int mUid;
private int mMyUid;
+ private @Nullable String mCustomUrl;
private MyServiceClient mServiceClient;
private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
private PowerManager mPowerManager;
@@ -185,6 +189,11 @@
mServiceClient = new MyServiceClient(mContext);
final Bundle args = InstrumentationRegistry.getArguments();
+ mCustomUrl = args.getString(ARG_CONNECTION_CHECK_CUSTOM_URL);
+ if (mCustomUrl != null) {
+ Log.d(TAG, "Using custom URL " + mCustomUrl + " for network checks");
+ }
+
final int bindPriorityFlags;
if (Boolean.valueOf(args.getString(ARG_WAIVE_BIND_PRIORITY, "false"))) {
bindPriorityFlags = Context.BIND_WAIVE_PRIORITY;
@@ -502,25 +511,23 @@
*/
private String checkNetworkAccess(boolean expectAvailable,
@Nullable final String expectedUnavailableError) throws Exception {
- final String resultData = mServiceClient.checkNetworkStatus();
- return checkForAvailabilityInResultData(resultData, expectAvailable,
+ final NetworkCheckResult checkResult = mServiceClient.checkNetworkStatus(mCustomUrl);
+ return checkForAvailabilityInNetworkCheckResult(checkResult, expectAvailable,
expectedUnavailableError);
}
- private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable,
- @Nullable final String expectedUnavailableError) {
- if (resultData == null) {
- assertNotNull("Network status from app2 is null", resultData);
- }
- // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
- final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
- assertEquals("Wrong network status: " + resultData, 5, parts.length);
- final State state = parts[0].equals("null") ? null : State.valueOf(parts[0]);
- final DetailedState detailedState = parts[1].equals("null")
- ? null : DetailedState.valueOf(parts[1]);
- final boolean connected = Boolean.valueOf(parts[2]);
- final String connectionCheckDetails = parts[3];
- final String networkInfo = parts[4];
+ private String checkForAvailabilityInNetworkCheckResult(NetworkCheckResult networkCheckResult,
+ boolean expectAvailable, @Nullable final String expectedUnavailableError) {
+ assertNotNull("NetworkCheckResult from app2 is null", networkCheckResult);
+
+ final NetworkInfo networkInfo = networkCheckResult.networkInfo;
+ assertNotNull("NetworkInfo from app2 is null", networkInfo);
+
+ final State state = networkInfo.getState();
+ final DetailedState detailedState = networkInfo.getDetailedState();
+
+ final boolean connected = networkCheckResult.connected;
+ final String connectionCheckDetails = networkCheckResult.details;
final StringBuilder errors = new StringBuilder();
final State expectedState;
@@ -926,33 +933,36 @@
if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
startForegroundService();
assertForegroundServiceNetworkAccess();
- return;
} else if (type == TYPE_COMPONENT_ACTIVTIY) {
turnScreenOn();
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = getIntentForComponent(type);
final Bundle extras = new Bundle();
- final ArrayList<Pair<Integer, String>> result = new ArrayList<>(1);
+ final AtomicReference<Pair<Integer, NetworkCheckResult>> result =
+ new AtomicReference<>();
extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
+ extras.putString(KEY_CUSTOM_URL, mCustomUrl);
launchIntent.putExtras(extras);
mContext.startActivity(launchIntent);
if (latch.await(ACTIVITY_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final int resultCode = result.get(0).first;
- final String resultData = result.get(0).second;
+ final int resultCode = result.get().first;
+ final NetworkCheckResult networkCheckResult = result.get().second;
if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
- final String error = checkForAvailabilityInResultData(
- resultData, expectAvailable, null /* expectedUnavailableError */);
+ final String error = checkForAvailabilityInNetworkCheckResult(
+ networkCheckResult, expectAvailable,
+ null /* expectedUnavailableError */);
if (error != null) {
fail("Network is not available for activity in app2 (" + mUid + "): "
+ error);
}
} else if (resultCode == INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE) {
- Log.d(TAG, resultData);
+ Log.d(TAG, networkCheckResult.details);
// App didn't come to foreground when the activity is started, so try again.
assertTopNetworkAccess(true);
} else {
- fail("Unexpected resultCode=" + resultCode + "; received=[" + resultData + "]");
+ fail("Unexpected resultCode=" + resultCode
+ + "; networkCheckResult=[" + networkCheckResult + "]");
}
} else {
fail("Timed out waiting for network availability status from app2's activity ("
@@ -960,10 +970,12 @@
}
} else if (type == TYPE_EXPEDITED_JOB) {
final Bundle extras = new Bundle();
- final ArrayList<Pair<Integer, String>> result = new ArrayList<>(1);
+ final AtomicReference<Pair<Integer, NetworkCheckResult>> result =
+ new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
+ extras.putString(KEY_CUSTOM_URL, mCustomUrl);
final JobInfo jobInfo = new JobInfo.Builder(TEST_JOB_ID, TEST_JOB_COMPONENT)
.setExpedited(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
@@ -973,11 +985,12 @@
RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final int resultCode = result.get(0).first;
- final String resultData = result.get(0).second;
+ final int resultCode = result.get().first;
+ final NetworkCheckResult networkCheckResult = result.get().second;
if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
- final String error = checkForAvailabilityInResultData(
- resultData, expectAvailable, null /* expectedUnavailableError */);
+ final String error = checkForAvailabilityInNetworkCheckResult(
+ networkCheckResult, expectAvailable,
+ null /* expectedUnavailableError */);
if (error != null) {
Log.d(TAG, "Network state is unexpected, checking again. " + error);
// Right now we could end up in an unexpected state if expedited job
@@ -985,7 +998,8 @@
assertNetworkAccess(expectAvailable, false /* needScreenOn */);
}
} else {
- fail("Unexpected resultCode=" + resultCode + "; received=[" + resultData + "]");
+ fail("Unexpected resultCode=" + resultCode
+ + "; networkCheckResult=[" + networkCheckResult + "]");
}
} else {
fail("Timed out waiting for network availability status from app2's expedited job ("
@@ -1028,11 +1042,12 @@
}
private Binder getNewNetworkStateObserver(final CountDownLatch latch,
- final ArrayList<Pair<Integer, String>> result) {
+ final AtomicReference<Pair<Integer, NetworkCheckResult>> result) {
return new INetworkStateObserver.Stub() {
@Override
- public void onNetworkStateChecked(int resultCode, String resultData) {
- result.add(Pair.create(resultCode, resultData));
+ public void onNetworkStateChecked(int resultCode,
+ NetworkCheckResult networkCheckResult) {
+ result.set(Pair.create(resultCode, networkCheckResult));
latch.countDown();
}
};
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
index f1858d6..6b802f6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
index e737a6d..2e725ae 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
index c78ca2e..2e421f6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
index fb52a54..0be5644 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
similarity index 80%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
index c1d576d..811190f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getUiDevice;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
import static org.junit.Assume.assumeTrue;
@@ -53,6 +53,7 @@
@After
public final void tearDown() throws Exception {
super.tearDown();
+ finishActivity();
resetDeviceState();
}
@@ -69,6 +70,7 @@
@RequiredProperties({BATTERY_SAVER_MODE})
public void testStartActivity_batterySaver() throws Exception {
setBatterySaverMode(true);
+ assertNetworkAccess(false, null);
assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver", null);
}
@@ -76,6 +78,7 @@
@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
public void testStartActivity_dataSaver() throws Exception {
setRestrictBackground(true);
+ assertNetworkAccess(false, null);
assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver", null);
}
@@ -83,6 +86,7 @@
@RequiredProperties({DOZE_MODE})
public void testStartActivity_doze() throws Exception {
setDozeMode(true);
+ assertNetworkAccess(false, null);
// TODO (235284115): We need to turn on Doze every time before starting
// the activity.
assertLaunchedActivityHasNetworkAccess("testStartActivity_doze", null);
@@ -93,6 +97,7 @@
public void testStartActivity_appStandby() throws Exception {
turnBatteryOn();
setAppIdle(true);
+ assertNetworkAccess(false, null);
// TODO (235284115): We need to put the app into app standby mode every
// time before starting the activity.
assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby", null);
@@ -104,6 +109,7 @@
assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
SystemClock.sleep(PROCESS_STATE_TRANSITION_DELAY_MS);
+ assertNetworkAccess(false, null);
});
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
index 790e031..66e0d00 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static com.android.compatibility.common.util.FeatureUtil.isTV;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NO_DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NO_DATA_SAVER_MODE;
import static org.junit.Assert.fail;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
index 13bbab6..69ca206 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
import android.content.pm.PackageManager;
import android.telephony.SubscriptionManager;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
similarity index 86%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
index f3a1026..810fd19 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class DefaultRestrictionsMeteredTest extends AbstractDefaultRestrictionsTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
index 5651dd0..fef546c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DefaultRestrictionsNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class DefaultRestrictionsNonMeteredTest extends AbstractDefaultRestrictionsTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
index 4306c99..741dd7e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
index 1e89f15..f343df5 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
similarity index 92%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
index 07434b1..2dc6cc4 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
import android.os.Environment;
import android.os.FileUtils;
@@ -42,7 +42,7 @@
public class DumpOnFailureRule extends OnFailureRule {
private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
- "CtsHostsideNetworkTests");
+ "CtsHostsideNetworkPolicyTests");
@Override
public void onTestFailure(Statement base, Description description, Throwable throwable) {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
index 3809534..d56a50b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
@RequiredProperties({METERED_NETWORK})
public class ExpeditedJobMeteredTest extends AbstractExpeditedJobTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
similarity index 85%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
index 6596269..0a776ee 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ExpeditedJobNonMeteredTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
@RequiredProperties({NON_METERED_NETWORK})
public class ExpeditedJobNonMeteredTest extends AbstractExpeditedJobTest {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
similarity index 86%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
index 5c99c67..4f4e68e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
import android.util.ArraySet;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
index c9edda6..b0fa106 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
+import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
+import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
import android.os.SystemClock;
import android.util.Log;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
index 0132536..6dc9921 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import android.app.Notification;
import android.app.PendingIntent;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
similarity index 94%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
index 980ecd5..71b28f6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import android.app.job.JobInfo;
import android.content.ComponentName;
@@ -98,9 +98,10 @@
return mService.getCounters(receiverName, action);
}
- public String checkNetworkStatus() throws RemoteException {
+ /** Retrieves the network state as observed from the bound test app */
+ public NetworkCheckResult checkNetworkStatus(String address) throws RemoteException {
ensureServiceConnection();
- return mService.checkNetworkStatus();
+ return mService.checkNetworkStatus(address);
}
public String getRestrictBackgroundStatus() throws RemoteException {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
index 5552b8f..7038d02 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
index 968e270..9b3fe9f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.os.Process.SYSTEM_UID;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
similarity index 96%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
index f340907..0207b00 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestRunner.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
similarity index 98%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
index 5331601..26a88f2 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
@@ -26,7 +26,7 @@
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/Property.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
similarity index 72%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/Property.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
index 18805f9..a03833f 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/Property.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isLowRamDevice;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isLowRamDevice;
public enum Property {
BATTERY_SAVER_MODE(1 << 0) {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredProperties.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredProperties.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
index 96838bb..799a513 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredProperties.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
similarity index 95%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
index 01f9f3e..5dea67c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
-import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
import android.text.TextUtils;
import android.util.ArraySet;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
similarity index 97%
rename from tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
rename to tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
index 4777bf4..f183f4e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
+++ b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.cts.net.hostside;
+package com.android.cts.netpolicy.hostside;
import org.junit.After;
import org.junit.Before;
diff --git a/tests/cts/hostside-network-policy/app2/Android.bp b/tests/cts/hostside-network-policy/app2/Android.bp
new file mode 100644
index 0000000..6ef0b06
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app2/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_framework_backstage_power",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsHostsideNetworkPolicyTestsApp2",
+ defaults: ["cts_support_defaults"],
+ platform_apis: true,
+ static_libs: [
+ "androidx.annotation_annotation",
+ "CtsHostsideNetworkPolicyTestsAidl",
+ "modules-utils-build",
+ ],
+ srcs: ["src/**/*.java"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ ],
+ certificate: ":cts-netpolicy-app",
+}
diff --git a/tests/cts/hostside-network-policy/app2/AndroidManifest.xml b/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
new file mode 100644
index 0000000..668f2da
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.netpolicy.hostside.app2">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
+ <!--
+ This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
+ them in a shared preferences which is then read by the test app. These broadcasts are
+ handled by 2 listeners, one defined the manifest and another dynamically registered by
+ a service.
+
+ The manifest-defined listener also handles ordered broadcasts used to share data with the
+ test app.
+
+ This application also provides a service, RemoteSocketFactoryService, that the test app can
+ use to open sockets to remote hosts as a different user ID.
+ -->
+ <application android:usesCleartextTraffic="true"
+ android:testOnly="true"
+ android:debuggable="true">
+
+ <activity android:name=".MyActivity"
+ android:exported="true"/>
+ <service android:name=".MyService"
+ android:exported="true"/>
+ <service android:name=".MyForegroundService"
+ android:foregroundServiceType="specialUse"
+ android:exported="true">
+ <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
+ android:value="Connectivity" />
+ </service>
+ <receiver android:name=".MyBroadcastReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
+ <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_COUNTERS"/>
+ <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
+ <action android:name="com.android.cts.netpolicy.hostside.app2.action.CHECK_NETWORK"/>
+ <action android:name="com.android.cts.netpolicy.hostside.app2.action.SEND_NOTIFICATION"/>
+ <action android:name="com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST"/>
+ </intent-filter>
+ </receiver>
+ <service android:name=".MyJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" />
+ </application>
+
+ <!--
+ Adding this to make sure that receiving the broadcast is not restricted by
+ package visibility restrictions.
+ -->
+ <queries>
+ <package android:name="android" />
+ </queries>
+
+</manifest>
diff --git a/tests/cts/hostside/app2/res/drawable/ic_notification.png b/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
similarity index 100%
rename from tests/cts/hostside/app2/res/drawable/ic_notification.png
rename to tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
Binary files differ
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
new file mode 100644
index 0000000..1719f9b
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
+
+import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_OTHER;
+import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES;
+import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.cts.netpolicy.hostside.INetworkStateObserver;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
+
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.URL;
+import java.util.concurrent.TimeUnit;
+
+public final class Common {
+
+ static final String TAG = "CtsNetApp2";
+
+ // Constants below must match values defined on app's
+ // AbstractRestrictBackgroundNetworkTestCase.java
+ static final String MANIFEST_RECEIVER = "ManifestReceiver";
+ static final String DYNAMIC_RECEIVER = "DynamicReceiver";
+
+ static final String ACTION_RECEIVER_READY =
+ "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
+ static final String ACTION_FINISH_ACTIVITY =
+ "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
+ static final String ACTION_FINISH_JOB =
+ "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
+ static final String ACTION_SHOW_TOAST =
+ "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
+ // Copied from com.android.server.net.NetworkPolicyManagerService class
+ static final String ACTION_SNOOZE_WARNING =
+ "com.android.server.net.action.SNOOZE_WARNING";
+
+ private static final String DEFAULT_TEST_URL =
+ "https://connectivitycheck.android.com/generate_204";
+
+ static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+ static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+ static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+ static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+ static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+ static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+ static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
+
+ static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
+ static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+ static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
+ static final String KEY_CUSTOM_URL = TEST_PKG + ".custom_url";
+
+ static final int TYPE_COMPONENT_ACTIVTY = 0;
+ static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
+ static final int TYPE_COMPONENT_EXPEDITED_JOB = 2;
+ private static final int NETWORK_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);
+
+ static int getUid(Context context) {
+ final String packageName = context.getPackageName();
+ try {
+ return context.getPackageManager().getPackageUid(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("Could not get UID for " + packageName, e);
+ }
+ }
+
+ private static NetworkCheckResult createNetworkCheckResult(boolean connected, String details,
+ NetworkInfo networkInfo) {
+ final NetworkCheckResult checkResult = new NetworkCheckResult();
+ checkResult.connected = connected;
+ checkResult.details = details;
+ checkResult.networkInfo = networkInfo;
+ return checkResult;
+ }
+
+ private static boolean validateComponentState(Context context, int componentType,
+ INetworkStateObserver observer) throws RemoteException {
+ final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+ switch (componentType) {
+ case TYPE_COMPONENT_ACTIVTY: {
+ final int procState = activityManager.getUidProcessState(Process.myUid());
+ if (procState != ActivityManager.PROCESS_STATE_TOP) {
+ observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_PROC_STATE,
+ createNetworkCheckResult(false, "Unexpected procstate: " + procState,
+ null));
+ return false;
+ }
+ return true;
+ }
+ case TYPE_COMPONENT_FOREGROUND_SERVICE: {
+ final int procState = activityManager.getUidProcessState(Process.myUid());
+ if (procState != ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_PROC_STATE,
+ createNetworkCheckResult(false, "Unexpected procstate: " + procState,
+ null));
+ return false;
+ }
+ return true;
+ }
+ case TYPE_COMPONENT_EXPEDITED_JOB: {
+ final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
+ if ((capabilities
+ & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) == 0) {
+ observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_CAPABILITIES,
+ createNetworkCheckResult(false,
+ "Unexpected capabilities: " + capabilities, null));
+ return false;
+ }
+ return true;
+ }
+ default: {
+ observer.onNetworkStateChecked(RESULT_ERROR_OTHER,
+ createNetworkCheckResult(false, "Unknown component type: " + componentType,
+ null));
+ return false;
+ }
+ }
+ }
+
+ static void notifyNetworkStateObserver(Context context, Intent intent, int componentType) {
+ if (intent == null) {
+ return;
+ }
+ final Bundle extras = intent.getExtras();
+ notifyNetworkStateObserver(context, extras, componentType);
+ }
+
+ static void notifyNetworkStateObserver(Context context, Bundle extras, int componentType) {
+ if (extras == null) {
+ return;
+ }
+ final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
+ extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
+ if (observer != null) {
+ final String customUrl = extras.getString(KEY_CUSTOM_URL);
+ try {
+ final boolean skipValidation = extras.getBoolean(KEY_SKIP_VALIDATION_CHECKS);
+ if (!skipValidation && !validateComponentState(context, componentType, observer)) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error occurred while informing the validation result: " + e);
+ }
+ AsyncTask.execute(() -> {
+ try {
+ observer.onNetworkStateChecked(
+ INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED,
+ checkNetworkStatus(context, customUrl));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error occurred while notifying the observer: " + e);
+ }
+ });
+ }
+ }
+
+ /**
+ * Checks whether the network is available by attempting a connection to the given address
+ * and returns a {@link NetworkCheckResult} object containing all the relevant details for
+ * debugging. Uses a default address if the given address is {@code null}.
+ *
+ * <p>
+ * The returned object has the following fields:
+ *
+ * <ul>
+ * <li>{@code connected}: whether or not the connection was successful.
+ * <li>{@code networkInfo}: the {@link NetworkInfo} describing the current active network as
+ * visible to this app.
+ * <li>{@code details}: A human readable string giving useful information about the success or
+ * failure.
+ * </ul>
+ */
+ static NetworkCheckResult checkNetworkStatus(Context context, String customUrl) {
+ final String address = (customUrl == null) ? DEFAULT_TEST_URL : customUrl;
+
+ // The current Android DNS resolver returns an UnknownHostException whenever network access
+ // is blocked. This can get cached in the current process-local InetAddress cache. Clearing
+ // the cache before attempting a connection ensures we never report a failure due to a
+ // negative cache entry.
+ InetAddress.clearDnsCache();
+
+ final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+
+ final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ Log.d(TAG, "Running checkNetworkStatus() on thread "
+ + Thread.currentThread().getName() + " for UID " + getUid(context)
+ + "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
+ boolean checkStatus = false;
+ String checkDetails = "N/A";
+ try {
+ final URL url = new URL(address);
+ final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setReadTimeout(NETWORK_TIMEOUT_MS);
+ conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
+ conn.setRequestMethod("GET");
+ conn.connect();
+ final int response = conn.getResponseCode();
+ checkStatus = true;
+ checkDetails = "HTTP response for " + address + ": " + response;
+ } catch (Exception e) {
+ checkStatus = false;
+ checkDetails = "Exception getting " + address + ": " + e;
+ }
+ final NetworkCheckResult result = createNetworkCheckResult(checkStatus, checkDetails,
+ networkInfo);
+ Log.d(TAG, "Offering: " + result);
+ return result;
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
similarity index 90%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
index aa58ff9..d274c50 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
-import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
import android.app.Activity;
import android.content.BroadcastReceiver;
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
new file mode 100644
index 0000000..27aec8c
--- /dev/null
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
+
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SHOW_TOAST;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
+import static com.android.cts.netpolicy.hostside.app2.Common.MANIFEST_RECEIVER;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
+import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+/**
+ * Receiver used to:
+ * <ol>
+ * <li>Count number of {@code RESTRICT_BACKGROUND_CHANGED} broadcasts received.
+ * <li>Show a toast.
+ * </ol>
+ */
+public class MyBroadcastReceiver extends BroadcastReceiver {
+
+ private final String mName;
+
+ public MyBroadcastReceiver() {
+ this(MANIFEST_RECEIVER);
+ }
+
+ MyBroadcastReceiver(String name) {
+ Log.d(TAG, "Constructing MyBroadcastReceiver named " + name);
+ mName = name;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive() for " + mName + ": " + intent);
+ final String action = intent.getAction();
+ switch (action) {
+ case ACTION_SNOOZE_WARNING:
+ increaseCounter(context, action);
+ break;
+ case ACTION_RESTRICT_BACKGROUND_CHANGED:
+ increaseCounter(context, action);
+ break;
+ case ACTION_RECEIVER_READY:
+ final String message = mName + " is ready to rumble";
+ Log.d(TAG, message);
+ setResultData(message);
+ break;
+ case ACTION_SHOW_TOAST:
+ showToast(context);
+ break;
+ default:
+ Log.e(TAG, "received unexpected action: " + action);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[MyBroadcastReceiver: mName=" + mName + "]";
+ }
+
+ private void increaseCounter(Context context, String action) {
+ final SharedPreferences prefs = context.getApplicationContext()
+ .getSharedPreferences(mName, Context.MODE_PRIVATE);
+ final int value = prefs.getInt(action, 0) + 1;
+ Log.d(TAG, "increaseCounter('" + action + "'): setting '" + mName + "' to " + value);
+ prefs.edit().putInt(action, value).apply();
+ }
+
+ static int getCounter(Context context, String action, String receiverName) {
+ final SharedPreferences prefs = context.getSharedPreferences(receiverName,
+ Context.MODE_PRIVATE);
+ final int value = prefs.getInt(action, 0);
+ Log.d(TAG, "getCounter('" + action + "', '" + receiverName + "'): " + value);
+ return value;
+ }
+
+ static String getRestrictBackgroundStatus(Context context) {
+ final ConnectivityManager cm = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ final int apiStatus = cm.getRestrictBackgroundStatus();
+ Log.d(TAG, "getRestrictBackgroundStatus: returning " + apiStatus);
+ return String.valueOf(apiStatus);
+ }
+
+ /**
+ * Sends a system notification containing actions with pending intents to launch the app's
+ * main activitiy or service.
+ */
+ static void sendNotification(Context context, String channelId, int notificationId,
+ String notificationType ) {
+ Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
+ final Intent serviceIntent = new Intent(context, MyService.class);
+ final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
+ PendingIntent.FLAG_MUTABLE);
+ final Bundle bundle = new Bundle();
+ bundle.putCharSequence("parcelable", "I am not");
+
+ final Notification.Builder builder = new Notification.Builder(context, channelId)
+ .setSmallIcon(R.drawable.ic_notification);
+
+ Action action = null;
+ switch (notificationType) {
+ case NOTIFICATION_TYPE_CONTENT:
+ builder
+ .setContentTitle("Light, Cameras...")
+ .setContentIntent(pendingIntent);
+ break;
+ case NOTIFICATION_TYPE_DELETE:
+ builder.setDeleteIntent(pendingIntent);
+ break;
+ case NOTIFICATION_TYPE_FULL_SCREEN:
+ builder.setFullScreenIntent(pendingIntent, true);
+ break;
+ case NOTIFICATION_TYPE_BUNDLE:
+ bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
+ builder.setExtras(bundle);
+ break;
+ case NOTIFICATION_TYPE_ACTION:
+ action = new Action.Builder(
+ R.drawable.ic_notification, "ACTION", pendingIntent)
+ .build();
+ builder.addAction(action);
+ break;
+ case NOTIFICATION_TYPE_ACTION_BUNDLE:
+ bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
+ action = new Action.Builder(
+ R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
+ .addExtras(bundle)
+ .build();
+ builder.addAction(action);
+ break;
+ case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
+ bundle.putParcelable("Magnum R.I. (Remote Input)", null);
+ final RemoteInput remoteInput = new RemoteInput.Builder("RI")
+ .addExtras(bundle)
+ .build();
+ action = new Action.Builder(
+ R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
+ .addRemoteInput(remoteInput)
+ .build();
+ builder.addAction(action);
+ break;
+ default:
+ Log.e(TAG, "Unknown notification type: " + notificationType);
+ return;
+ }
+
+ final Notification notification = builder.build();
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+ .notify(notificationId, notification);
+ }
+
+ private void showToast(Context context) {
+ Toast.makeText(context, "Toast from CTS test", Toast.LENGTH_SHORT).show();
+ setResultData("Shown");
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
similarity index 87%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
index b55761c..54cee3c 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
-import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TEST_PKG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
import android.R;
import android.app.Notification;
@@ -31,7 +31,7 @@
import android.os.RemoteException;
import android.util.Log;
-import com.android.cts.net.hostside.INetworkStateObserver;
+import com.android.cts.netpolicy.hostside.INetworkStateObserver;
/**
* Service used to change app state to FOREGROUND_SERVICE.
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
similarity index 90%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
index 8c112b6..eba55ed 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyJobService.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
-import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_JOB;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_JOB;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
+import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
import android.app.job.JobParameters;
import android.app.job.JobService;
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
similarity index 88%
rename from tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
rename to tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
index 3ed5391..71bcead 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
@@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net.hostside.app2;
+package com.android.cts.netpolicy.hostside.app2;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.net.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
+import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
+import static com.android.cts.netpolicy.hostside.app2.Common.DYNAMIC_RECEIVER;
+import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -39,8 +38,9 @@
import android.os.RemoteException;
import android.util.Log;
-import com.android.cts.net.hostside.IMyService;
-import com.android.cts.net.hostside.INetworkCallback;
+import com.android.cts.netpolicy.hostside.IMyService;
+import com.android.cts.netpolicy.hostside.INetworkCallback;
+import com.android.cts.netpolicy.hostside.NetworkCheckResult;
import com.android.modules.utils.build.SdkLevel;
/**
@@ -56,9 +56,7 @@
// TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
- private IMyService.Stub mBinder =
- new IMyService.Stub() {
-
+ private IMyService.Stub mBinder = new IMyService.Stub() {
@Override
public void registerBroadcastReceiver() {
if (mReceiver != null) {
@@ -83,8 +81,8 @@
}
@Override
- public String checkNetworkStatus() {
- return MyBroadcastReceiver.checkNetworkStatus(getApplicationContext());
+ public NetworkCheckResult checkNetworkStatus(String customUrl) {
+ return Common.checkNetworkStatus(getApplicationContext(), customUrl);
}
@Override
@@ -94,7 +92,7 @@
@Override
public void sendNotification(int notificationId, String notificationType) {
- MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
+ MyBroadcastReceiver.sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
notificationId, notificationType);
}
@@ -170,7 +168,7 @@
.getSystemService(JobScheduler.class);
return jobScheduler.schedule(jobInfo);
}
- };
+ };
@Override
public IBinder onBind(Intent intent) {
diff --git a/tests/cts/hostside-network-policy/certs/Android.bp b/tests/cts/hostside-network-policy/certs/Android.bp
new file mode 100644
index 0000000..bfbc341
--- /dev/null
+++ b/tests/cts/hostside-network-policy/certs/Android.bp
@@ -0,0 +1,9 @@
+package {
+ default_team: "trendy_team_framework_backstage_power",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app_certificate {
+ name: "cts-netpolicy-app",
+ certificate: "cts-net-app",
+}
diff --git a/tests/cts/hostside/certs/README b/tests/cts/hostside-network-policy/certs/README
similarity index 100%
rename from tests/cts/hostside/certs/README
rename to tests/cts/hostside-network-policy/certs/README
diff --git a/tests/cts/hostside/certs/cts-net-app.pk8 b/tests/cts/hostside-network-policy/certs/cts-net-app.pk8
similarity index 100%
rename from tests/cts/hostside/certs/cts-net-app.pk8
rename to tests/cts/hostside-network-policy/certs/cts-net-app.pk8
Binary files differ
diff --git a/tests/cts/hostside/certs/cts-net-app.x509.pem b/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
similarity index 100%
rename from tests/cts/hostside/certs/cts-net-app.x509.pem
rename to tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
diff --git a/tests/cts/hostside/instrumentation_arguments/Android.bp b/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
similarity index 100%
rename from tests/cts/hostside/instrumentation_arguments/Android.bp
rename to tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
diff --git a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java b/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
similarity index 85%
rename from tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
rename to tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
index 472e347..0fe98e9 100644
--- a/tests/cts/hostside/instrumentation_arguments/src/com/android/cts/net/arguments/InstrumentationArguments.java
+++ b/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net.arguments;
+package com.android.cts.netpolicy.arguments;
public interface InstrumentationArguments {
String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
+ String ARG_CONNECTION_CHECK_CUSTOM_URL = "connection_check_custom_url";
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
similarity index 76%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
index 880e826..422231d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.cts.net;
+package com.android.cts.netpolicy;
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
import android.platform.test.annotations.FlakyTest;
-import com.android.testutils.SkipPresubmit;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
@@ -30,8 +29,8 @@
import java.util.Map;
-@SkipPresubmit(reason = "Out of SLO flakiness")
-public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideConnOnActivityStartTest extends HostsideNetworkPolicyTestCase {
private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
@BeforeClassWithInfo
@@ -47,29 +46,29 @@
@Test
public void testStartActivity_batterySaver() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
}
@Test
public void testStartActivity_dataSaver() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
}
@FlakyTest(bugId = 231440256)
@Test
public void testStartActivity_doze() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
}
@Test
public void testStartActivity_appStandby() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
}
// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
@Test
public void testStartActivity_default() throws Exception {
- runDeviceTestsWithArgs(TEST_PKG, TEST_CLASS, "testStartActivity_default",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_default",
Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
}
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
similarity index 88%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
index 0d01fc1..62952bb 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideDefaultNetworkRestrictionsTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.cts.net;
+package com.android.cts.netpolicy;
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-import com.android.testutils.SkipPresubmit;
+import android.platform.test.annotations.FlakyTest;
+
import com.android.tradefed.device.DeviceNotAvailableException;
import org.junit.After;
@@ -28,8 +29,8 @@
import java.util.Map;
// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side tests.
-@SkipPresubmit(reason = "Monitoring for flakiness")
-public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkPolicyTestCase {
private static final String METERED_TEST_CLASS = TEST_PKG + ".DefaultRestrictionsMeteredTest";
private static final String NON_METERED_TEST_CLASS =
TEST_PKG + ".DefaultRestrictionsNonMeteredTest";
@@ -46,12 +47,12 @@
}
private void runMeteredTest(String methodName) throws DeviceNotAvailableException {
- runDeviceTestsWithArgs(TEST_PKG, METERED_TEST_CLASS, methodName,
+ runDeviceTestsWithCustomOptions(TEST_PKG, METERED_TEST_CLASS, methodName,
Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
}
private void runNonMeteredTest(String methodName) throws DeviceNotAvailableException {
- runDeviceTestsWithArgs(TEST_PKG, NON_METERED_TEST_CLASS, methodName,
+ runDeviceTestsWithCustomOptions(TEST_PKG, NON_METERED_TEST_CLASS, methodName,
Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
similarity index 79%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
index 361f7c7..2c2b118 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net;
+package com.android.cts.netpolicy;
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-import com.android.testutils.SkipPresubmit;
+import android.platform.test.annotations.FlakyTest;
import org.junit.After;
import org.junit.Before;
@@ -25,8 +25,8 @@
import java.util.Map;
-@SkipPresubmit(reason = "Out of SLO flakiness")
-public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideNetworkCallbackTests extends HostsideNetworkPolicyTestCase {
@Before
public void setUp() throws Exception {
@@ -41,20 +41,20 @@
@Test
public void testOnBlockedStatusChanged_dataSaver() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
}
@Test
public void testOnBlockedStatusChanged_powerSaver() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
}
// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
@Test
public void testOnBlockedStatusChanged_default() throws Exception {
- runDeviceTestsWithArgs(TEST_PKG, TEST_PKG + ".NetworkCallbackTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".NetworkCallbackTest",
"testOnBlockedStatusChanged_default", Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
}
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
similarity index 82%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
index e97db58..8ffe360 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.cts.net;
+package com.android.cts.netpolicy;
-import static com.android.cts.net.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
import org.junit.After;
import org.junit.Before;
@@ -24,7 +24,7 @@
import java.util.Map;
-public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
+public class HostsideNetworkPolicyManagerTests extends HostsideNetworkPolicyTestCase {
@Before
public void setUp() throws Exception {
uninstallPackage(TEST_APP2_PKG, false);
@@ -38,48 +38,48 @@
@Test
public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest",
"testIsUidNetworkingBlocked_withUidNotBlocked");
}
@Test
public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidNetworkingBlocked_withSystemUid");
}
@Test
public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest",
"testIsUidNetworkingBlocked_withDataSaverMode");
}
@Test
public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest",
"testIsUidNetworkingBlocked_withRestrictedNetworkingMode");
}
@Test
public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest",
"testIsUidNetworkingBlocked_withPowerSaverMode");
}
@Test
public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
- runDeviceTests(TEST_PKG,
+ runDeviceTestsWithCustomOptions(TEST_PKG,
TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
}
// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
@Test
public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
- runDeviceTestsWithArgs(TEST_PKG, TEST_PKG + ".NetworkPolicyManagerTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".NetworkPolicyManagerTest",
"testIsUidNetworkingBlocked_whenInBackground",
Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
new file mode 100644
index 0000000..6de6b17
--- /dev/null
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 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.cts.netpolicy;
+
+import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+abstract class HostsideNetworkPolicyTestCase extends BaseHostJUnit4Test {
+ protected static final boolean DEBUG = false;
+ protected static final String TAG = "HostsideNetworkPolicyTests";
+ protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
+ protected static final String TEST_APK = "CtsHostsideNetworkPolicyTestsApp.apk";
+ protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
+ protected static final String TEST_APP2_APK = "CtsHostsideNetworkPolicyTestsApp2.apk";
+
+ @Option(name = "custom-url", importance = Option.Importance.IF_UNSET,
+ description = "A custom url to use for testing network connections")
+ protected String mCustomUrl;
+
+ @BeforeClassWithInfo
+ public static void setUpOnceBase(TestInformation testInfo) throws Exception {
+ uninstallPackage(testInfo, TEST_PKG, false);
+ installPackage(testInfo, TEST_APK);
+ }
+
+ @AfterClassWithInfo
+ public static void tearDownOnceBase(TestInformation testInfo)
+ throws DeviceNotAvailableException {
+ uninstallPackage(testInfo, TEST_PKG, true);
+ }
+
+ // Custom static method to install the specified package, this is used to bypass auto-cleanup
+ // per test in BaseHostJUnit4.
+ protected static void installPackage(TestInformation testInfo, String apk)
+ throws DeviceNotAvailableException, TargetSetupError {
+ assertNotNull(testInfo);
+ final int userId = testInfo.getDevice().getCurrentUser();
+ final SuiteApkInstaller installer = new SuiteApkInstaller();
+ // Force the apk clean up
+ installer.setCleanApk(true);
+ installer.addTestFileName(apk);
+ installer.setUserId(userId);
+ installer.setShouldGrantPermission(true);
+ installer.addInstallArg("-t");
+ try {
+ installer.setUp(testInfo);
+ } catch (BuildError e) {
+ throw new TargetSetupError(
+ e.getMessage(), e, testInfo.getDevice().getDeviceDescriptor(), e.getErrorId());
+ }
+ }
+
+ protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
+ installPackage(getTestInformation(), apk);
+ }
+
+ protected static void uninstallPackage(TestInformation testInfo, String packageName,
+ boolean shouldSucceed)
+ throws DeviceNotAvailableException {
+ assertNotNull(testInfo);
+ final String result = testInfo.getDevice().uninstallPackage(packageName);
+ if (shouldSucceed) {
+ assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
+ }
+ }
+
+ protected void uninstallPackage(String packageName,
+ boolean shouldSucceed)
+ throws DeviceNotAvailableException {
+ uninstallPackage(getTestInformation(), packageName, shouldSucceed);
+ }
+
+ protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException {
+ final String command = "cmd package list packages " + packageName;
+ final int max_tries = 5;
+ for (int i = 1; i <= max_tries; i++) {
+ final String result = runCommand(command);
+ if (result.trim().isEmpty()) {
+ return;
+ }
+ // 'list packages' filters by substring, so we need to iterate with the results
+ // and check one by one, otherwise 'com.android.cts.netpolicy.hostside' could return
+ // 'com.android.cts.netpolicy.hostside.app2'
+ boolean found = false;
+ for (String line : result.split("[\\r\\n]+")) {
+ if (line.endsWith(packageName)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+ Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
+ + "); sleeping 1s before polling again");
+ RunUtil.getDefault().sleep(1000);
+ }
+ fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
+ }
+
+ protected int getUid(String packageName) throws DeviceNotAvailableException {
+ final int currentUser = getDevice().getCurrentUser();
+ final String uidLines = runCommand(
+ "cmd package list packages -U --user " + currentUser + " " + packageName);
+ for (String uidLine : uidLines.split("\n")) {
+ if (uidLine.startsWith("package:" + packageName + " uid:")) {
+ final String[] uidLineParts = uidLine.split(":");
+ // 3rd entry is package uid
+ return Integer.parseInt(uidLineParts[2].trim());
+ }
+ }
+ throw new IllegalStateException("Failed to find the test app on the device; pkg="
+ + packageName + ", u=" + currentUser);
+ }
+
+ protected boolean runDeviceTestsWithCustomOptions(String packageName, String className)
+ throws DeviceNotAvailableException {
+ return runDeviceTestsWithCustomOptions(packageName, className, null);
+ }
+
+ protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
+ String methodName) throws DeviceNotAvailableException {
+ return runDeviceTestsWithCustomOptions(packageName, className, methodName, null);
+ }
+
+ protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
+ String methodName, Map<String, String> testArgs) throws DeviceNotAvailableException {
+ final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
+ .setTestClassName(className)
+ .setTestMethodName(methodName);
+
+ // Currently there is only one custom option that the test exposes.
+ if (mCustomUrl != null) {
+ deviceTestRunOptions.addInstrumentationArg(ARG_CONNECTION_CHECK_CUSTOM_URL, mCustomUrl);
+ }
+ // Pass over any test specific arguments.
+ if (testArgs != null) {
+ for (Map.Entry<String, String> arg : testArgs.entrySet()) {
+ deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
+ }
+ }
+ return runDeviceTests(deviceTestRunOptions);
+ }
+
+ protected String runCommand(String command) throws DeviceNotAvailableException {
+ Log.d(TAG, "Command: '" + command + "'");
+ final String output = getDevice().executeShellCommand(command);
+ if (DEBUG) Log.v(TAG, "Output: " + output.trim());
+ return output;
+ }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
similarity index 76%
rename from tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
index 9c3751d..0261c7d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.cts.net;
+package com.android.cts.netpolicy;
import static org.junit.Assert.fail;
+import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.SecurityTest;
import com.android.ddmlib.Log;
-import com.android.testutils.SkipPresubmit;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.util.RunUtil;
@@ -29,8 +29,8 @@
import org.junit.Before;
import org.junit.Test;
-@SkipPresubmit(reason = "Out of SLO flakiness")
-public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
+@FlakyTest(bugId = 288324467)
+public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkPolicyTestCase {
@Before
public void setUp() throws Exception {
@@ -46,7 +46,7 @@
@SecurityTest
@Test
public void testDataWarningReceiver() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataWarningReceiverTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataWarningReceiverTest",
"testSnoozeWarningNotReceived");
}
@@ -56,25 +56,25 @@
@Test
public void testDataSaverMode_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_disabled");
}
@Test
public void testDataSaverMode_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_whitelisted");
}
@Test
public void testDataSaverMode_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_enabled");
}
@Test
public void testDataSaverMode_blacklisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_blacklisted");
}
@@ -97,13 +97,13 @@
@Test
public void testDataSaverMode_requiredWhitelistedPackages() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
}
@Test
public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
"testBroadcastNotSentOnUnsupportedDevices");
}
@@ -113,19 +113,19 @@
@Test
public void testBatterySaverModeMetered_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
@Test
public void testBatterySaverModeMetered_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
@Test
public void testBatterySaverModeMetered_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
@@ -149,19 +149,19 @@
@Test
public void testBatterySaverModeNonMetered_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
@Test
public void testBatterySaverModeNonMetered_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
@Test
public void testBatterySaverModeNonMetered_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
@@ -171,31 +171,31 @@
@Test
public void testAppIdleMetered_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
@Test
public void testAppIdleMetered_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
@Test
public void testAppIdleMetered_tempWhitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_tempWhitelisted");
}
@Test
public void testAppIdleMetered_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
@Test
public void testAppIdleMetered_idleWhitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testAppIdleNetworkAccess_idleWhitelisted");
}
@@ -206,51 +206,51 @@
@Test
public void testAppIdleNonMetered_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
@Test
public void testAppIdleNonMetered_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
@Test
public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_tempWhitelisted");
}
@Test
public void testAppIdleNonMetered_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
@Test
public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdleNetworkAccess_idleWhitelisted");
}
@Test
public void testAppIdleNonMetered_whenCharging() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdleNetworkAccess_whenCharging");
}
@Test
public void testAppIdleMetered_whenCharging() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testAppIdleNetworkAccess_whenCharging");
}
@Test
public void testAppIdle_toast() throws Exception {
// Check that showing a toast doesn't bring an app out of standby
- runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdle_toast");
}
@@ -260,25 +260,25 @@
@Test
public void testDozeModeMetered_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
@Test
public void testDozeModeMetered_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
@Test
public void testDozeModeMetered_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
@Test
public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
}
@@ -289,26 +289,26 @@
@Test
public void testDozeModeNonMetered_disabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_disabled");
}
@Test
public void testDozeModeNonMetered_whitelisted() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
}
@Test
public void testDozeModeNonMetered_enabled() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_enabled");
}
@Test
public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
}
@@ -318,55 +318,55 @@
@Test
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDataAndBatterySaverModes_meteredNetwork");
}
@Test
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDataAndBatterySaverModes_nonMeteredNetwork");
}
@Test
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDozeAndBatterySaverMode_powerSaveWhitelists");
}
@Test
public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDozeAndAppIdle_powerSaveWhitelists");
}
@Test
public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndDoze_tempPowerSaveWhitelists");
}
@Test
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
}
@Test
public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testDozeAndAppIdle_appIdleWhitelist");
}
@Test
public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
}
@Test
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
"testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
}
@@ -376,13 +376,13 @@
@Test
public void testNetworkAccess_restrictedMode() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
"testNetworkAccess");
}
@Test
public void testNetworkAccess_restrictedMode_withBatterySaver() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
"testNetworkAccess_withBatterySaver");
}
@@ -392,12 +392,12 @@
@Test
public void testMeteredNetworkAccess_expeditedJob() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".ExpeditedJobMeteredTest");
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".ExpeditedJobMeteredTest");
}
@Test
public void testNonMeteredNetworkAccess_expeditedJob() throws Exception {
- runDeviceTests(TEST_PKG, TEST_PKG + ".ExpeditedJobNonMeteredTest");
+ runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".ExpeditedJobNonMeteredTest");
}
/*******************
diff --git a/tests/cts/hostside/src/com/android/cts/net/NetworkPolicyTestsPreparer.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
similarity index 98%
rename from tests/cts/hostside/src/com/android/cts/net/NetworkPolicyTestsPreparer.java
rename to tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
index 23aca24..cbf2f4d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/NetworkPolicyTestsPreparer.java
+++ b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.cts.net;
+package com.android.cts.netpolicy;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index f6c0430..14d5d54 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -29,7 +29,6 @@
// Only compile source java files in this apk.
srcs: [
"src/**/*.java",
- ":ArgumentConstants",
],
libs: [
"net-tests-utils-host-device-common",
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 0ffe81e..ea6b078 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -22,7 +22,6 @@
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
- <target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
<!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
@@ -30,7 +29,6 @@
<option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.net.hostside.app2" />
<option name="teardown-command" value="cmd power set-mode 0" />
<option name="teardown-command" value="cmd battery reset" />
- <option name="teardown-command" value="cmd netpolicy stop-watching" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
diff --git a/tests/cts/hostside/OWNERS b/tests/cts/hostside/OWNERS
deleted file mode 100644
index 20bc55e..0000000
--- a/tests/cts/hostside/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 61373
-# Inherits parent owners
-sudheersai@google.com
-jchalard@google.com
diff --git a/tests/cts/hostside/aidl/Android.bp b/tests/cts/hostside/aidl/Android.bp
index 18a5897..33761dc 100644
--- a/tests/cts/hostside/aidl/Android.bp
+++ b/tests/cts/hostside/aidl/Android.bp
@@ -21,9 +21,7 @@
name: "CtsHostsideNetworkTestsAidl",
sdk_version: "current",
srcs: [
- "com/android/cts/net/hostside/IMyService.aidl",
- "com/android/cts/net/hostside/INetworkCallback.aidl",
- "com/android/cts/net/hostside/INetworkStateObserver.aidl",
- "com/android/cts/net/hostside/IRemoteSocketFactory.aidl",
+ "com/android/cts/net/hostside/*.aidl",
+ "com/android/cts/net/hostside/*.java",
],
}
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index cf4afa9..919e025 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -38,7 +38,6 @@
],
srcs: [
"src/**/*.java",
- ":ArgumentConstants",
],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index ca3397b..e0f4cdc 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -43,14 +43,6 @@
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
- <service android:name=".MyNotificationListenerService"
- android:label="MyNotificationListenerService"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
- android:exported="true">
- <intent-filter>
- <action android:name="android.service.notification.NotificationListenerService"/>
- </intent-filter>
- </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index f81a03d..e88c105 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -22,9 +22,10 @@
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.TYPE_VPN;
-import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
@@ -51,6 +52,7 @@
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -123,6 +125,8 @@
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.PacketBuilder;
+import com.android.testutils.AutoReleaseNetworkCallbackRule;
+import com.android.testutils.ConnectUtil;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.RecorderCallback;
@@ -220,6 +224,7 @@
private WifiManager mWifiManager;
private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
private CtsNetUtils mCtsNetUtils;
+ private ConnectUtil mConnectUtil;
private PackageManager mPackageManager;
private Context mTestContext;
private Context mTargetContext;
@@ -233,9 +238,13 @@
// The registered callbacks.
private List<NetworkCallback> mRegisteredCallbacks = new ArrayList<>();
- @Rule
+ @Rule(order = 1)
public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+ @Rule(order = 2)
+ public final AutoReleaseNetworkCallbackRule
+ mNetworkCallbackRule = new AutoReleaseNetworkCallbackRule();
+
private boolean supportedHardware() {
final PackageManager pm = getInstrumentation().getContext().getPackageManager();
return !pm.hasSystemFeature("android.hardware.type.watch");
@@ -252,7 +261,6 @@
@Before
public void setUp() throws Exception {
- assumeTrue(supportedHardware());
mNetwork = null;
mTestContext = getInstrumentation().getContext();
mTargetContext = getInstrumentation().getTargetContext();
@@ -266,14 +274,15 @@
mRemoteSocketFactoryClient.bind();
mDevice.waitForIdle();
mCtsNetUtils = new CtsNetUtils(mTestContext);
+ mConnectUtil = new ConnectUtil(mTestContext);
mPackageManager = mTestContext.getPackageManager();
+ assumeTrue(supportedHardware());
}
@After
public void tearDown() throws Exception {
restorePrivateDnsSetting();
mRemoteSocketFactoryClient.unbind();
- mCtsNetUtils.tearDown();
Log.i(TAG, "Stopping VPN");
stopVpn();
unregisterRegisteredCallbacks();
@@ -889,10 +898,8 @@
final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
testAndCleanup(() -> {
// Ensure both of wifi and mobile data are connected.
- final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
- assertTrue("Wifi is not connected", (wifiNetwork != null));
- final Network cellNetwork = mCtsNetUtils.connectToCell();
- assertTrue("Mobile data is not connected", (cellNetwork != null));
+ final Network wifiNetwork = mConnectUtil.ensureWifiValidated();
+ final Network cellNetwork = mNetworkCallbackRule.requestCell();
// Store current default network.
final Network defaultNetwork = mCM.getActiveNetwork();
// Start VPN and set empty array as its underlying networks.
@@ -972,11 +979,18 @@
registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
}, NETWORK_SETTINGS);
- for (TestableNetworkCallback callback :
- List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
+ for (TestableNetworkCallback callback : List.of(systemDefaultCallback, myUidCallback)) {
callback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
true /* validated */, false /* blocked */, TIMEOUT_MS);
}
+ // On V+, ConnectivityService generates blockedReasons based on bpf map contents even if
+ // the otherUid does not exist on device. So if the background chain is enabled,
+ // otherUid is blocked.
+ final boolean isOtherUidBlocked = SdkLevel.isAtLeastV()
+ && runAsShell(NETWORK_SETTINGS, () -> mCM.getFirewallChainEnabled(
+ FIREWALL_CHAIN_BACKGROUND));
+ otherUidCallback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
+ true /* validated */, isOtherUidBlocked, TIMEOUT_MS);
}
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
@@ -1938,7 +1952,7 @@
.build();
final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
mCM.requestNetwork(request, callback);
- final FileDescriptor srcTunFd = runWithShellPermissionIdentity(() -> {
+ final ParcelFileDescriptor srcTunFd = runWithShellPermissionIdentity(() -> {
final TestNetworkManager tnm = mTestContext.getSystemService(TestNetworkManager.class);
List<LinkAddress> linkAddresses = duplicatedAddress
? List.of(new LinkAddress("192.0.2.2/24"),
@@ -1947,7 +1961,7 @@
new LinkAddress("2001:db8:3:4::ffe/64"));
final TestNetworkInterface iface = tnm.createTunInterface(linkAddresses);
tnm.setupTestNetwork(iface.getInterfaceName(), new Binder());
- return iface.getFileDescriptor().getFileDescriptor();
+ return iface.getFileDescriptor();
}, MANAGE_TEST_NETWORKS);
final Network testNetwork = callback.waitForAvailable();
assertNotNull(testNetwork);
@@ -1961,11 +1975,11 @@
false /* isAlwaysMetered */);
final FileDescriptor dstUdpFd = dstSock.getFileDescriptor$();
- checkBlockUdp(srcTunFd, dstUdpFd,
+ checkBlockUdp(srcTunFd.getFileDescriptor(), dstUdpFd,
InetAddresses.parseNumericAddress("192.0.2.2") /* dstAddress */,
InetAddresses.parseNumericAddress("192.0.2.1") /* srcAddress */,
duplicatedAddress ? EXPECT_PASS : EXPECT_BLOCK);
- checkBlockUdp(srcTunFd, dstUdpFd,
+ checkBlockUdp(srcTunFd.getFileDescriptor(), dstUdpFd,
InetAddresses.parseNumericAddress("2001:db8:1:2::ffe") /* dstAddress */,
InetAddresses.parseNumericAddress("2001:db8:1:2::ffa") /* srcAddress */,
duplicatedAddress ? EXPECT_PASS : EXPECT_BLOCK);
@@ -1973,7 +1987,7 @@
// Traffic on VPN should not be affected
checkTrafficOnVpn();
}, /* cleanup */ () -> {
- Os.close(srcTunFd);
+ srcTunFd.close();
dstSock.close();
}, /* cleanup */ () -> {
runWithShellPermissionIdentity(() -> {
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index c526172..ad25562 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -35,5 +35,4 @@
"general-tests",
"sts",
],
- certificate: ":cts-net-app",
}
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index 2c2d957..412b307 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -40,33 +40,8 @@
<application android:usesCleartextTraffic="true"
android:testOnly="true"
android:debuggable="true">
-
- <activity android:name=".MyActivity"
- android:exported="true"/>
- <service android:name=".MyService"
- android:exported="true"/>
- <service android:name=".MyForegroundService"
- android:foregroundServiceType="specialUse"
- android:exported="true">
- <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
- android:value="Connectivity" />
- </service>
<service android:name=".RemoteSocketFactoryService"
android:exported="true"/>
-
- <receiver android:name=".MyBroadcastReceiver"
- android:exported="true">
- <intent-filter>
- <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
- <action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS"/>
- <action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
- <action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK"/>
- <action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION"/>
- <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST"/>
- </intent-filter>
- </receiver>
- <service android:name=".MyJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
</application>
<!--
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
deleted file mode 100644
index 37dc7a0..0000000
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.net.hostside.app2;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.net.hostside.INetworkStateObserver;
-
-public final class Common {
-
- static final String TAG = "CtsNetApp2";
-
- // Constants below must match values defined on app's
- // AbstractRestrictBackgroundNetworkTestCase.java
- static final String MANIFEST_RECEIVER = "ManifestReceiver";
- static final String DYNAMIC_RECEIVER = "DynamicReceiver";
-
- static final String ACTION_RECEIVER_READY =
- "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
- static final String ACTION_FINISH_ACTIVITY =
- "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
- static final String ACTION_FINISH_JOB =
- "com.android.cts.net.hostside.app2.action.FINISH_JOB";
- static final String ACTION_SHOW_TOAST =
- "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
- // Copied from com.android.server.net.NetworkPolicyManagerService class
- static final String ACTION_SNOOZE_WARNING =
- "com.android.server.net.action.SNOOZE_WARNING";
-
- static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
- static final String NOTIFICATION_TYPE_DELETE = "DELETE";
- static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
- static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
- static final String NOTIFICATION_TYPE_ACTION = "ACTION";
- static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
- static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
-
- static final String TEST_PKG = "com.android.cts.net.hostside";
- static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
- static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
-
- static final int TYPE_COMPONENT_ACTIVTY = 0;
- static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
- static final int TYPE_COMPONENT_EXPEDITED_JOB = 2;
-
- static int getUid(Context context) {
- final String packageName = context.getPackageName();
- try {
- return context.getPackageManager().getPackageUid(packageName, 0);
- } catch (NameNotFoundException e) {
- throw new IllegalStateException("Could not get UID for " + packageName, e);
- }
- }
-
- private static boolean validateComponentState(Context context, int componentType,
- INetworkStateObserver observer) throws RemoteException {
- final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- switch (componentType) {
- case TYPE_COMPONENT_ACTIVTY: {
- final int procState = activityManager.getUidProcessState(Process.myUid());
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- observer.onNetworkStateChecked(
- INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE,
- "Unexpected procstate: " + procState);
- return false;
- }
- return true;
- }
- case TYPE_COMPONENT_FOREGROUND_SERVICE: {
- final int procState = activityManager.getUidProcessState(Process.myUid());
- if (procState != ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- observer.onNetworkStateChecked(
- INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE,
- "Unexpected procstate: " + procState);
- return false;
- }
- return true;
- }
- case TYPE_COMPONENT_EXPEDITED_JOB: {
- final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
- if ((capabilities
- & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) == 0) {
- observer.onNetworkStateChecked(
- INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES,
- "Unexpected capabilities: " + capabilities);
- return false;
- }
- return true;
- }
- default: {
- observer.onNetworkStateChecked(INetworkStateObserver.RESULT_ERROR_OTHER,
- "Unknown component type: " + componentType);
- return false;
- }
- }
- }
-
- static void notifyNetworkStateObserver(Context context, Intent intent, int componentType) {
- if (intent == null) {
- return;
- }
- final Bundle extras = intent.getExtras();
- notifyNetworkStateObserver(context, extras, componentType);
- }
-
- static void notifyNetworkStateObserver(Context context, Bundle extras, int componentType) {
- if (extras == null) {
- return;
- }
- final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
- extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
- if (observer != null) {
- try {
- final boolean skipValidation = extras.getBoolean(KEY_SKIP_VALIDATION_CHECKS);
- if (!skipValidation && !validateComponentState(context, componentType, observer)) {
- return;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error occurred while informing the validation result: " + e);
- }
- AsyncTask.execute(() -> {
- try {
- observer.onNetworkStateChecked(
- INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED,
- MyBroadcastReceiver.checkNetworkStatus(context));
- } catch (RemoteException e) {
- Log.e(TAG, "Error occurred while notifying the observer: " + e);
- }
- });
- }
- }
-}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
deleted file mode 100644
index 825f2c9..0000000
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.net.hostside.app2;
-
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-
-import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.net.hostside.app2.Common.ACTION_SHOW_TOAST;
-import static com.android.cts.net.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
-import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
-import static com.android.cts.net.hostside.app2.Common.TAG;
-import static com.android.cts.net.hostside.app2.Common.getUid;
-
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.URL;
-
-/**
- * Receiver used to:
- * <ol>
- * <li>Count number of {@code RESTRICT_BACKGROUND_CHANGED} broadcasts received.
- * <li>Show a toast.
- * </ol>
- */
-public class MyBroadcastReceiver extends BroadcastReceiver {
-
- private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
-
- private final String mName;
-
- public MyBroadcastReceiver() {
- this(MANIFEST_RECEIVER);
- }
-
- MyBroadcastReceiver(String name) {
- Log.d(TAG, "Constructing MyBroadcastReceiver named " + name);
- mName = name;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive() for " + mName + ": " + intent);
- final String action = intent.getAction();
- switch (action) {
- case ACTION_SNOOZE_WARNING:
- increaseCounter(context, action);
- break;
- case ACTION_RESTRICT_BACKGROUND_CHANGED:
- increaseCounter(context, action);
- break;
- case ACTION_RECEIVER_READY:
- final String message = mName + " is ready to rumble";
- Log.d(TAG, message);
- setResultData(message);
- break;
- case ACTION_SHOW_TOAST:
- showToast(context);
- break;
- default:
- Log.e(TAG, "received unexpected action: " + action);
- }
- }
-
- @Override
- public String toString() {
- return "[MyBroadcastReceiver: mName=" + mName + "]";
- }
-
- private void increaseCounter(Context context, String action) {
- final SharedPreferences prefs = context.getApplicationContext()
- .getSharedPreferences(mName, Context.MODE_PRIVATE);
- final int value = prefs.getInt(action, 0) + 1;
- Log.d(TAG, "increaseCounter('" + action + "'): setting '" + mName + "' to " + value);
- prefs.edit().putInt(action, value).apply();
- }
-
- static int getCounter(Context context, String action, String receiverName) {
- final SharedPreferences prefs = context.getSharedPreferences(receiverName,
- Context.MODE_PRIVATE);
- final int value = prefs.getInt(action, 0);
- Log.d(TAG, "getCounter('" + action + "', '" + receiverName + "'): " + value);
- return value;
- }
-
- static String getRestrictBackgroundStatus(Context context) {
- final ConnectivityManager cm = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- final int apiStatus = cm.getRestrictBackgroundStatus();
- Log.d(TAG, "getRestrictBackgroundStatus: returning " + apiStatus);
- return String.valueOf(apiStatus);
- }
-
- private static final String NETWORK_STATUS_TEMPLATE = "%s|%s|%s|%s|%s";
- /**
- * Checks whether the network is available and return a string which can then be send as a
- * result data for the ordered broadcast.
- *
- * <p>
- * The string has the following format:
- *
- * <p><pre><code>
- * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo
- * </code></pre>
- *
- * <p>Where:
- *
- * <ul>
- * <li>{@code NetinfoState}: enum value of {@link NetworkInfo.State}.
- * <li>{@code NetinfoDetailedState}: enum value of {@link NetworkInfo.DetailedState}.
- * <li>{@code RealConnectionCheck}: boolean value of a real connection check (i.e., an attempt
- * to access an external website.
- * <li>{@code RealConnectionCheckDetails}: if HTTP output core or exception string of the real
- * connection attempt
- * <li>{@code Netinfo}: string representation of the {@link NetworkInfo}.
- * </ul>
- *
- * For example, if the connection was established fine, the result would be something like:
- * <p><pre><code>
- * CONNECTED|CONNECTED|true|200|[type: WIFI[], state: CONNECTED/CONNECTED, reason: ...]
- * </code></pre>
- *
- */
- // TODO: now that it uses Binder, it counl return a Bundle with the data parts instead...
- static String checkNetworkStatus(Context context) {
- final ConnectivityManager cm =
- (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- // TODO: connect to a hostside server instead
- final String address = "http://example.com";
- final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
- Log.d(TAG, "Running checkNetworkStatus() on thread "
- + Thread.currentThread().getName() + " for UID " + getUid(context)
- + "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
- boolean checkStatus = false;
- String checkDetails = "N/A";
- try {
- final URL url = new URL(address);
- final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setReadTimeout(NETWORK_TIMEOUT_MS);
- conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
- conn.setRequestMethod("GET");
- conn.setDoInput(true);
- conn.connect();
- final int response = conn.getResponseCode();
- checkStatus = true;
- checkDetails = "HTTP response for " + address + ": " + response;
- } catch (Exception e) {
- checkStatus = false;
- checkDetails = "Exception getting " + address + ": " + e;
- }
- // If the app tries to make a network connection in the foreground immediately after
- // trying to do the same when it's network access was blocked, it could receive a
- // UnknownHostException due to the cached DNS entry. So, clear the dns cache after
- // every network access for now until we have a fix on the platform side.
- InetAddress.clearDnsCache();
- Log.d(TAG, checkDetails);
- final String state, detailedState;
- if (networkInfo != null) {
- state = networkInfo.getState().name();
- detailedState = networkInfo.getDetailedState().name();
- } else {
- state = detailedState = "null";
- }
- final String status = String.format(NETWORK_STATUS_TEMPLATE, state, detailedState,
- Boolean.valueOf(checkStatus), checkDetails, networkInfo);
- Log.d(TAG, "Offering " + status);
- return status;
- }
-
- /**
- * Sends a system notification containing actions with pending intents to launch the app's
- * main activitiy or service.
- */
- static void sendNotification(Context context, String channelId, int notificationId,
- String notificationType ) {
- Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
- final Intent serviceIntent = new Intent(context, MyService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
- PendingIntent.FLAG_MUTABLE);
- final Bundle bundle = new Bundle();
- bundle.putCharSequence("parcelable", "I am not");
-
- final Notification.Builder builder = new Notification.Builder(context, channelId)
- .setSmallIcon(R.drawable.ic_notification);
-
- Action action = null;
- switch (notificationType) {
- case NOTIFICATION_TYPE_CONTENT:
- builder
- .setContentTitle("Light, Cameras...")
- .setContentIntent(pendingIntent);
- break;
- case NOTIFICATION_TYPE_DELETE:
- builder.setDeleteIntent(pendingIntent);
- break;
- case NOTIFICATION_TYPE_FULL_SCREEN:
- builder.setFullScreenIntent(pendingIntent, true);
- break;
- case NOTIFICATION_TYPE_BUNDLE:
- bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
- builder.setExtras(bundle);
- break;
- case NOTIFICATION_TYPE_ACTION:
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION", pendingIntent)
- .build();
- builder.addAction(action);
- break;
- case NOTIFICATION_TYPE_ACTION_BUNDLE:
- bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
- .addExtras(bundle)
- .build();
- builder.addAction(action);
- break;
- case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
- bundle.putParcelable("Magnum R.I. (Remote Input)", null);
- final RemoteInput remoteInput = new RemoteInput.Builder("RI")
- .addExtras(bundle)
- .build();
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
- .addRemoteInput(remoteInput)
- .build();
- builder.addAction(action);
- break;
- default:
- Log.e(TAG, "Unknown notification type: " + notificationType);
- return;
- }
-
- final Notification notification = builder.build();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .notify(notificationId, notification);
- }
-
- private void showToast(Context context) {
- Toast.makeText(context, "Toast from CTS test", Toast.LENGTH_SHORT).show();
- setResultData("Shown");
- }
-}
diff --git a/tests/cts/hostside/certs/Android.bp b/tests/cts/hostside/certs/Android.bp
deleted file mode 100644
index 301973e..0000000
--- a/tests/cts/hostside/certs/Android.bp
+++ /dev/null
@@ -1,9 +0,0 @@
-package {
- default_team: "trendy_team_fwk_core_networking",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app_certificate {
- name: "cts-net-app",
- certificate: "cts-net-app",
-}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index ca95ed6..69d61b3 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -18,9 +18,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import com.android.ddmlib.Log;
import com.android.modules.utils.build.testing.DeviceSdkLevel;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.TestInformation;
@@ -31,17 +29,11 @@
import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
-import com.android.tradefed.util.RunUtil;
import org.junit.runner.RunWith;
-import java.util.Map;
-
@RunWith(DeviceJUnit4ClassRunner.class)
abstract class HostsideNetworkTestCase extends BaseHostJUnit4Test {
- protected static final boolean DEBUG = false;
- protected static final String TAG = "HostsideNetworkTests";
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
protected static final String TEST_APK_NEXT = "CtsHostsideNetworkTestsAppNext.apk";
@@ -103,67 +95,4 @@
throws DeviceNotAvailableException {
uninstallPackage(getTestInformation(), packageName, shouldSucceed);
}
-
- protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException,
- InterruptedException {
- final String command = "cmd package list packages " + packageName;
- final int max_tries = 5;
- for (int i = 1; i <= max_tries; i++) {
- final String result = runCommand(command);
- if (result.trim().isEmpty()) {
- return;
- }
- // 'list packages' filters by substring, so we need to iterate with the results
- // and check one by one, otherwise 'com.android.cts.net.hostside' could return
- // 'com.android.cts.net.hostside.app2'
- boolean found = false;
- for (String line : result.split("[\\r\\n]+")) {
- if (line.endsWith(packageName)) {
- found = true;
- break;
- }
- }
- if (!found) {
- return;
- }
- i++;
- Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
- + "); sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
- }
-
- protected int getUid(String packageName) throws DeviceNotAvailableException {
- final int currentUser = getDevice().getCurrentUser();
- final String uidLines = runCommand(
- "cmd package list packages -U --user " + currentUser + " " + packageName);
- for (String uidLine : uidLines.split("\n")) {
- if (uidLine.startsWith("package:" + packageName + " uid:")) {
- final String[] uidLineParts = uidLine.split(":");
- // 3rd entry is package uid
- return Integer.parseInt(uidLineParts[2].trim());
- }
- }
- throw new IllegalStateException("Failed to find the test app on the device; pkg="
- + packageName + ", u=" + currentUser);
- }
-
- protected boolean runDeviceTestsWithArgs(String packageName, String className,
- String methodName, Map<String, String> args) throws DeviceNotAvailableException {
- final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
- .setTestClassName(className)
- .setTestMethodName(methodName);
- for (Map.Entry<String, String> arg : args.entrySet()) {
- deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
- }
- return runDeviceTests(deviceTestRunOptions);
- }
-
- protected String runCommand(String command) throws DeviceNotAvailableException {
- Log.d(TAG, "Command: '" + command + "'");
- final String output = getDevice().executeShellCommand(command);
- if (DEBUG) Log.v(TAG, "Output: " + output.trim());
- return output;
- }
}
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 5ac4229..5f9b3ef 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -14,12 +14,16 @@
package {
default_applicable_licenses: ["Android-Apache-2.0"],
+ default_team: "trendy_team_fwk_core_networking",
}
python_test_host {
name: "CtsConnectivityMultiDevicesTestCases",
main: "connectivity_multi_devices_test.py",
- srcs: ["connectivity_multi_devices_test.py"],
+ srcs: [
+ "connectivity_multi_devices_test.py",
+ "tether_utils.py",
+ ],
libs: [
"mobly",
],
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index ab88504..417db99 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,23 +1,16 @@
# Lint as: python3
"""Connectivity multi devices tests."""
-import base64
import sys
-import uuid
-
-from mobly import asserts
from mobly import base_test
from mobly import test_runner
from mobly import utils
from mobly.controllers import android_device
+import tether_utils
+from tether_utils import UpstreamType
CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
-class UpstreamType:
- CELLULAR = 1
- WIFI = 2
-
-
class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
def setup_class(self):
@@ -40,66 +33,27 @@
raise_on_exception=True,
)
- @staticmethod
- def generate_uuid32_base64():
- """Generates a UUID32 and encodes it in Base64.
-
- Returns:
- str: The Base64-encoded UUID32 string. Which is 22 characters.
- """
- return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
-
- def _do_test_hotspot_for_upstream_type(self, upstream_type):
- """Test hotspot with the specified upstream type.
-
- This test create a hotspot, make the client connect
- to it, and verify the packet is forwarded by the hotspot.
- """
- server = self.serverDevice.connectivity_multi_devices_snippet
- client = self.clientDevice.connectivity_multi_devices_snippet
-
- # Assert pre-conditions specific to each upstream type.
- asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
- asserts.skip_if(
- not server.hasHotspotFeature(), "Server requires hotspot feature"
- )
- if upstream_type == UpstreamType.CELLULAR:
- asserts.skip_if(
- not server.hasTelephonyFeature(), "Server requires Telephony feature"
- )
- server.requestCellularAndEnsureDefault()
- elif upstream_type == UpstreamType.WIFI:
- asserts.skip_if(
- not server.isStaApConcurrencySupported(),
- "Server requires Wifi AP + STA concurrency",
- )
- server.ensureWifiIsDefault()
- else:
- raise ValueError(f"Invalid upstream type: {upstream_type}")
-
- # Generate ssid/passphrase with random characters to make sure nearby devices won't
- # connect unexpectedly. Note that total length of ssid cannot go over 32.
- testSsid = "HOTSPOT-" + self.generate_uuid32_base64()
- testPassphrase = self.generate_uuid32_base64()
-
- try:
- # Create a hotspot with fixed SSID and password.
- server.startHotspot(testSsid, testPassphrase)
-
- # Make the client connects to the hotspot.
- client.connectToWifi(testSsid, testPassphrase, True)
-
- finally:
- if upstream_type == UpstreamType.CELLULAR:
- server.unrequestCellular()
- # Teardown the hotspot.
- server.stopAllTethering()
-
def test_hotspot_upstream_wifi(self):
- self._do_test_hotspot_for_upstream_type(UpstreamType.WIFI)
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.WIFI
+ )
+ finally:
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.WIFI
+ )
def test_hotspot_upstream_cellular(self):
- self._do_test_hotspot_for_upstream_type(UpstreamType.CELLULAR)
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.CELLULAR
+ )
+ finally:
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.CELLULAR
+ )
if __name__ == "__main__":
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 115210b..8805edd 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -16,11 +16,11 @@
package com.google.snippet.connectivity
+import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.OVERRIDE_WIFI_CONFIG
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
-import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
@@ -30,27 +30,37 @@
import android.net.wifi.SoftApConfiguration
import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiSsid
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.ConnectUtil
-import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.NetworkCallbackHelper
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
import com.google.android.mobly.snippet.Snippet
import com.google.android.mobly.snippet.rpc.Rpc
+import org.junit.Rule
class ConnectivityMultiDevicesSnippet : Snippet {
+ @get:Rule
+ val networkCallbackRule = AutoReleaseNetworkCallbackRule()
private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
private val wifiManager = context.getSystemService(WifiManager::class.java)!!
private val cm = context.getSystemService(ConnectivityManager::class.java)!!
private val pm = context.packageManager
private val ctsNetUtils = CtsNetUtils(context)
+ private val cbHelper = NetworkCallbackHelper()
private val ctsTetheringUtils = CtsTetheringUtils(context)
private var oldSoftApConfig: SoftApConfiguration? = null
+ override fun shutdown() {
+ cbHelper.unregisterAll()
+ }
+
@Rpc(description = "Check whether the device has wifi feature.")
fun hasWifiFeature() = pm.hasSystemFeature(FEATURE_WIFI)
@@ -58,20 +68,18 @@
fun hasTelephonyFeature() = pm.hasSystemFeature(FEATURE_TELEPHONY)
@Rpc(description = "Check whether the device supporters AP + STA concurrency.")
- fun isStaApConcurrencySupported() {
- wifiManager.isStaApConcurrencySupported()
- }
+ fun isStaApConcurrencySupported() = wifiManager.isStaApConcurrencySupported()
@Rpc(description = "Request cellular connection and ensure it is the default network.")
fun requestCellularAndEnsureDefault() {
ctsNetUtils.disableWifi()
- val network = ctsNetUtils.connectToCell()
+ val network = cbHelper.requestCell()
ctsNetUtils.expectNetworkIsSystemDefault(network)
}
@Rpc(description = "Unrequest cellular connection.")
fun unrequestCellular() {
- ctsNetUtils.disconnectFromCell()
+ cbHelper.unrequestCell()
}
@Rpc(description = "Ensure any wifi is connected and is the default network.")
@@ -84,10 +92,8 @@
// Suppress warning because WifiManager methods to connect to a config are
// documented not to be deprecated for privileged users.
@Suppress("DEPRECATION")
- fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Network {
+ fun connectToWifi(ssid: String, passphrase: String): Long {
val specifier = WifiNetworkSpecifier.Builder()
- .setSsid(ssid)
- .setWpa2Passphrase(passphrase)
.setBand(ScanResult.WIFI_BAND_24_GHZ)
.build()
val wifiConfig = WifiConfiguration()
@@ -98,27 +104,28 @@
wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
- // Register network callback for the specific wifi.
+ // Add the test configuration and connect to it.
+ val connectUtil = ConnectUtil(context)
+ connectUtil.connectToWifiConfig(wifiConfig)
+
+ // Implement manual SSID matching. Specifying the SSID in
+ // NetworkSpecifier is ineffective
+ // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details).
+ // Note that holding permission is necessary when waiting for
+ // the callbacks. The handler thread checks permission; if
+ // it's not present, the SSID will be redacted.
val networkCallback = TestableNetworkCallback()
- val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI)
- .setNetworkSpecifier(specifier)
- .build()
- cm.registerNetworkCallback(wifiRequest, networkCallback)
-
- try {
- // Add the test configuration and connect to it.
- val connectUtil = ConnectUtil(context)
- connectUtil.connectToWifiConfig(wifiConfig)
-
- val event = networkCallback.expect<Available>()
- if (requireValidation) {
- networkCallback.eventuallyExpect<CapabilitiesChanged> {
- it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
- }
- }
- return event.network
- } finally {
- cm.unregisterNetworkCallback(networkCallback)
+ val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build()
+ return runAsShell(NETWORK_SETTINGS) {
+ // Register the network callback is needed here.
+ // This is to avoid the race condition where callback is fired before
+ // acquiring permission.
+ networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback)
+ return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> {
+ // Remove double quotes.
+ val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid)
+ ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+ }.network.networkHandle
}
}
diff --git a/tests/cts/multidevices/tether_utils.py b/tests/cts/multidevices/tether_utils.py
new file mode 100644
index 0000000..a2d703c
--- /dev/null
+++ b/tests/cts/multidevices/tether_utils.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2024 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.
+#
+# 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.
+
+import base64
+import uuid
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+class UpstreamType:
+ CELLULAR = 1
+ WIFI = 2
+
+
+def generate_uuid32_base64() -> str:
+ """Generates a UUID32 and encodes it in Base64.
+
+ Returns:
+ str: The Base64-encoded UUID32 string. Which is 22 characters.
+ """
+ # Strip padding characters to make it safer for hotspot name length limit.
+ return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
+
+
+def setup_hotspot_and_client_for_upstream_type(
+ server_device: android_device,
+ client_device: android_device,
+ upstream_type: UpstreamType,
+) -> (str, int):
+ """Setup the hotspot with a connected client with the specified upstream type.
+
+ This creates a hotspot, make the client connect
+ to it, and verify the packet is forwarded by the hotspot.
+ And returns interface name of both if successful.
+ """
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
+ # Assert pre-conditions specific to each upstream type.
+ asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+ asserts.skip_if(
+ not server.hasHotspotFeature(), "Server requires hotspot feature"
+ )
+ if upstream_type == UpstreamType.CELLULAR:
+ asserts.skip_if(
+ not server.hasTelephonyFeature(), "Server requires Telephony feature"
+ )
+ server.requestCellularAndEnsureDefault()
+ elif upstream_type == UpstreamType.WIFI:
+ asserts.skip_if(
+ not server.isStaApConcurrencySupported(),
+ "Server requires Wifi AP + STA concurrency",
+ )
+ server.ensureWifiIsDefault()
+ else:
+ raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+ # Generate ssid/passphrase with random characters to make sure nearby devices won't
+ # connect unexpectedly. Note that total length of ssid cannot go over 32.
+ test_ssid = "HOTSPOT-" + generate_uuid32_base64()
+ test_passphrase = generate_uuid32_base64()
+
+ # Create a hotspot with fixed SSID and password.
+ hotspot_interface = server.startHotspot(test_ssid, test_passphrase)
+
+ # Make the client connects to the hotspot.
+ client_network = client.connectToWifi(test_ssid, test_passphrase)
+
+ return hotspot_interface, client_network
+
+
+def cleanup_tethering_for_upstream_type(
+ server_device: android_device, upstream_type: UpstreamType
+) -> None:
+ server = server_device.connectivity_multi_devices_snippet
+ if upstream_type == UpstreamType.CELLULAR:
+ server.unrequestCellular()
+ # Teardown the hotspot.
+ server.stopAllTethering()
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 074c587..768ba12 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -46,6 +46,7 @@
],
jarjar_rules: "jarjar-rules-shared.txt",
static_libs: [
+ "ApfGeneratorLib",
"bouncycastle-unbundled",
"FrameworksNetCommonTests",
"core-tests-support",
diff --git a/tests/cts/net/native/src/BpfCompatTest.cpp b/tests/cts/net/native/src/BpfCompatTest.cpp
index 5c02b0d..e2fdd3d 100644
--- a/tests/cts/net/native/src/BpfCompatTest.cpp
+++ b/tests/cts/net/native/src/BpfCompatTest.cpp
@@ -27,35 +27,32 @@
using namespace android::bpf;
-void doBpfStructSizeTest(const char *elfPath) {
+void doBpfStructSizeTest(const char *elfPath, unsigned mapSz, unsigned progSz) {
std::ifstream elfFile(elfPath, std::ios::in | std::ios::binary);
ASSERT_TRUE(elfFile.is_open());
- if (android::modules::sdklevel::IsAtLeastU()) {
- EXPECT_EQ(120, readSectionUint("size_of_bpf_map_def", elfFile, 0));
- EXPECT_EQ(92, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
- } else if (android::modules::sdklevel::IsAtLeastT()) {
- EXPECT_EQ(116, readSectionUint("size_of_bpf_map_def", elfFile, 0));
- EXPECT_EQ(92, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
- } else {
- EXPECT_EQ(48, readSectionUint("size_of_bpf_map_def", elfFile, 0));
- EXPECT_EQ(28, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
- }
-}
-
-TEST(BpfTest, bpfStructSizeTestPreT) {
- if (android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << "T+ device.";
- doBpfStructSizeTest("/system/etc/bpf/netd.o");
- doBpfStructSizeTest("/system/etc/bpf/clatd.o");
+ EXPECT_EQ(mapSz, readSectionUint("size_of_bpf_map_def", elfFile, 0));
+ EXPECT_EQ(progSz, readSectionUint("size_of_bpf_prog_def", elfFile, 0));
}
TEST(BpfTest, bpfStructSizeTest) {
- if (android::modules::sdklevel::IsAtLeastU()) {
- doBpfStructSizeTest("/system/etc/bpf/gpuMem.o");
- doBpfStructSizeTest("/system/etc/bpf/timeInState.o");
+ if (android::modules::sdklevel::IsAtLeastV()) {
+ // Due to V+ using mainline netbpfload, there is no longer a need to
+ // enforce consistency between platform and mainline bpf .o files.
+ GTEST_SKIP() << "V+ device.";
+ } else if (android::modules::sdklevel::IsAtLeastU()) {
+ doBpfStructSizeTest("/system/etc/bpf/gpuMem.o", 120, 92);
+ doBpfStructSizeTest("/system/etc/bpf/timeInState.o", 120, 92);
+ } else if (android::modules::sdklevel::IsAtLeastT()) {
+ doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o", 116, 92);
+ doBpfStructSizeTest("/system/etc/bpf/time_in_state.o", 116, 92);
+ } else if (android::modules::sdklevel::IsAtLeastS()) {
+ // These files were moved to mainline in Android T
+ doBpfStructSizeTest("/system/etc/bpf/netd.o", 48, 28);
+ doBpfStructSizeTest("/system/etc/bpf/clatd.o", 48, 28);
} else {
- doBpfStructSizeTest("/system/etc/bpf/gpu_mem.o");
- doBpfStructSizeTest("/system/etc/bpf/time_in_state.o");
+ // There is no mainline bpf code before S.
+ GTEST_SKIP() << "R- device.";
}
}
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 0cdd5a8..0b6637d 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -13,49 +13,138 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+// ktlint does not allow annotating function argument literals inline. Disable the specific rule
+// since this negatively affects readability.
+@file:Suppress("ktlint:standard:comment-wrapping")
+
package android.net.cts
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
+import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.apf.ApfCapabilities
+import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
+import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
+import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
+import android.net.apf.ApfV4Generator
+import android.net.apf.BaseApfGenerator
+import android.net.apf.BaseApfGenerator.MemorySlot
+import android.net.apf.BaseApfGenerator.Register.R0
+import android.net.apf.BaseApfGenerator.Register.R1
import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.PowerManager
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.system.Os
import android.system.OsConstants
+import android.system.OsConstants.AF_INET6
+import android.system.OsConstants.ETH_P_IPV6
+import android.system.OsConstants.IPPROTO_ICMPV6
+import android.system.OsConstants.SOCK_DGRAM
+import android.system.OsConstants.SOCK_NONBLOCK
+import android.util.Log
+import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.compatibility.common.util.PropertyUtil.isVendorApiLevelNewerThan
+import com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel
+import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.VsrTest
+import com.android.internal.util.HexDump
+import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.NetworkStackModuleTest
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.SkipPresubmit
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
+import com.android.testutils.waitForIdle
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import com.google.common.truth.TruthJUnit.assume
+import java.io.FileDescriptor
+import java.lang.Thread
+import java.net.InetSocketAddress
+import java.nio.ByteBuffer
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import kotlin.random.Random
+import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import org.junit.After
-import org.junit.Assume.assumeTrue
+import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+private const val TAG = "ApfIntegrationTest"
private const val TIMEOUT_MS = 2000L
private const val APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"
+private const val POLLING_INTERVAL_MS: Int = 100
+private const val RCV_BUFFER_SIZE = 1480
+private const val PING_HEADER_LENGTH = 8
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
@RunWith(DevSdkIgnoreRunner::class)
+@RequiresDevice
@NetworkStackModuleTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+// ByteArray.toHexString is experimental API
+@kotlin.ExperimentalStdlibApi
class ApfIntegrationTest {
companion object {
+ private val PING_DESTINATION = InetSocketAddress("2001:4860:4860::8888", 0)
+
+ private val context = InstrumentationRegistry.getInstrumentation().context
+ private val powerManager = context.getSystemService(PowerManager::class.java)!!
+ private val wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG)
+
+ fun pollingCheck(condition: () -> Boolean, timeout_ms: Int): Boolean {
+ var polling_time = 0
+ do {
+ Thread.sleep(POLLING_INTERVAL_MS.toLong())
+ polling_time += POLLING_INTERVAL_MS
+ if (condition()) return true
+ } while (polling_time < timeout_ms)
+ return false
+ }
+
+ fun turnScreenOff() {
+ if (!wakeLock.isHeld()) wakeLock.acquire()
+ runShellCommandOrThrow("input keyevent KEYCODE_SLEEP")
+ val result = pollingCheck({ !powerManager.isInteractive() }, timeout_ms = 2000)
+ assertThat(result).isTrue()
+ }
+
+ fun turnScreenOn() {
+ if (wakeLock.isHeld()) wakeLock.release()
+ runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
+ val result = pollingCheck({ powerManager.isInteractive() }, timeout_ms = 2000)
+ assertThat(result).isTrue()
+ }
+
@BeforeClass
@JvmStatic
+ @Suppress("ktlint:standard:no-multi-spaces")
fun setupOnce() {
+ // TODO: assertions thrown in @BeforeClass / @AfterClass are not well supported in the
+ // test infrastructure. Consider saving excepion and throwing it in setUp().
+ // APF must run when the screen is off and the device is not interactive.
+ turnScreenOff()
+ // Wait for APF to become active.
+ Thread.sleep(1000)
// TODO: check that there is no active wifi network. Otherwise, ApfFilter has already been
// created.
// APF adb cmds are only implemented in ApfFilter.java. Enable experiment to prevent
@@ -69,18 +158,110 @@
)
}
}
+
+ @AfterClass
+ @JvmStatic
+ fun tearDownOnce() {
+ turnScreenOn()
+ }
}
- private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+ class Icmp6PacketReader(
+ handler: Handler,
+ private val network: Network
+ ) : PacketReader(handler, RCV_BUFFER_SIZE) {
+ private var sockFd: FileDescriptor? = null
+ private var futureReply: CompletableFuture<ByteArray>? = null
+
+ override fun createFd(): FileDescriptor {
+ // sockFd is closed by calling super.stop()
+ val sock = Os.socket(AF_INET6, SOCK_DGRAM or SOCK_NONBLOCK, IPPROTO_ICMPV6)
+ // APF runs only on WiFi, so make sure the socket is bound to the right network.
+ network.bindSocket(sock)
+ sockFd = sock
+ return sock
+ }
+
+ override fun handlePacket(recvbuf: ByteArray, length: Int) {
+ // If zero-length or Type is not echo reply: ignore.
+ if (length == 0 || recvbuf[0] != 0x81.toByte()) {
+ return
+ }
+ // Only copy the ping data and complete the future.
+ val result = recvbuf.sliceArray(8..<length)
+ Log.i(TAG, "Received ping reply: ${result.toHexString()}")
+ futureReply!!.complete(recvbuf.sliceArray(8..<length))
+ }
+
+ fun sendPing(data: ByteArray, payloadSize: Int) {
+ require(data.size == payloadSize)
+
+ // rfc4443#section-4.1: Echo Request Message
+ // 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 |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Identifier | Sequence Number |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | Data ...
+ // +-+-+-+-+-
+ val icmp6Header = byteArrayOf(0x80.toByte(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
+ val packet = icmp6Header + data
+ Log.i(TAG, "Sent ping: ${packet.toHexString()}")
+ futureReply = CompletableFuture<ByteArray>()
+ Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
+ }
+
+ fun expectPingReply(): ByteArray {
+ return futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ fun expectPingDropped() {
+ assertFailsWith(TimeoutException::class) {
+ futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+ }
+
+ override fun start(): Boolean {
+ // Ignore the fact start() could return false or throw an exception.
+ handler.post({ super.start() })
+ handler.waitForIdle(TIMEOUT_MS)
+ return true
+ }
+
+ override fun stop() {
+ handler.post({ super.stop() })
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
+ @get:Rule val ignoreRule = DevSdkIgnoreRule()
+ @get:Rule val expect = Expect.create()
+
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
private val pm by lazy { context.packageManager }
+ private lateinit var network: Network
private lateinit var ifname: String
private lateinit var networkCallback: TestableNetworkCallback
+ private lateinit var caps: ApfCapabilities
+ private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
+ private val handler = Handler(handlerThread.looper)
+ private lateinit var packetReader: Icmp6PacketReader
+
+ fun getApfCapabilities(): ApfCapabilities {
+ val caps = runShellCommand("cmd network_stack apf $ifname capabilities").trim()
+ if (caps.isEmpty()) {
+ return ApfCapabilities(0, 0, 0)
+ }
+ val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() }
+ return ApfCapabilities(version, maxLen, packetFormat)
+ }
@Before
fun setUp() {
- assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
- assumeTrue(isVendorApiLevelNewerThan(Build.VERSION_CODES.TIRAMISU))
+ assume().that(pm.hasSystemFeature(FEATURE_WIFI)).isTrue()
+
networkCallback = TestableNetworkCallback()
cm.requestNetwork(
NetworkRequest.Builder()
@@ -89,35 +270,289 @@
.build(),
networkCallback
)
+ network = networkCallback.expect<Available>().network
networkCallback.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
ifname = assertNotNull(it.lp.interfaceName)
true
}
- runShellCommandOrThrow("cmd network_stack apf $ifname pause")
+ // It's possible the device does not support APF, in which case this command will not be
+ // successful. Ignore the error as testApfCapabilities() already asserts APF support on the
+ // respective VSR releases and all other tests are based on the capabilities indicated.
+ runShellCommand("cmd network_stack apf $ifname pause")
+ caps = getApfCapabilities()
+
+ packetReader = Icmp6PacketReader(handler, network)
+ packetReader.start()
}
@After
fun tearDown() {
+ if (::packetReader.isInitialized) {
+ packetReader.stop()
+ }
+ handlerThread.quitSafely()
+ handlerThread.join()
+
+ if (::ifname.isInitialized) {
+ runShellCommand("cmd network_stack apf $ifname resume")
+ }
if (::networkCallback.isInitialized) {
cm.unregisterNetworkCallback(networkCallback)
}
- runShellCommandOrThrow("cmd network_stack apf $ifname resume")
}
- fun getApfCapabilities(): ApfCapabilities {
- val caps = runShellCommandOrThrow("cmd network_stack apf $ifname capabilities").trim()
- val (version, maxLen, packetFormat) = caps.split(",").map { it.toInt() }
- return ApfCapabilities(version, maxLen, packetFormat)
- }
-
+ @VsrTest(
+ requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009",
+ "VSR-5.3.12-012"]
+ )
@Test
- fun testGetApfCapabilities() {
- val caps = getApfCapabilities()
- assertThat(caps.apfVersionSupported).isEqualTo(4)
- assertThat(caps.maximumApfProgramSize).isAtLeast(1024)
- if (isVendorApiLevelNewerThan(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
- assertThat(caps.maximumApfProgramSize).isAtLeast(2000)
- }
+ fun testApfCapabilities() {
+ // APF became mandatory in Android 14 VSR.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
+
+ // ApfFilter does not support anything but ARPHRD_ETHER.
assertThat(caps.apfPacketFormat).isEqualTo(OsConstants.ARPHRD_ETHER)
+
+ // DEVICEs launching with Android 14 with CHIPSETs that set ro.board.first_api_level to 34:
+ // - [GMS-VSR-5.3.12-003] MUST return 4 or higher as the APF version number from calls to
+ // the getApfPacketFilterCapabilities HAL method.
+ // - [GMS-VSR-5.3.12-004] MUST indicate at least 1024 bytes of usable memory from calls to
+ // the getApfPacketFilterCapabilities HAL method.
+ // TODO: check whether above text should be changed "34 or higher"
+ assertThat(caps.apfVersionSupported).isAtLeast(4)
+ assertThat(caps.maximumApfProgramSize).isAtLeast(1024)
+
+ if (caps.apfVersionSupported > 4) {
+ assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
+ assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
+ }
+
+ // DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
+ // ro.board.first_api_level or ro.board.api_level to 202404 or higher:
+ // - [GMS-VSR-5.3.12-009] MUST indicate at least 2048 bytes of usable memory from calls to
+ // the getApfPacketFilterCapabilities HAL method.
+ if (getVsrApiLevel() >= 202404) {
+ assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
+ }
+ }
+
+ // APF is backwards compatible, i.e. a v6 interpreter supports both v2 and v4 functionality.
+ fun assumeApfVersionSupportAtLeast(version: Int) {
+ assume().that(caps.apfVersionSupported).isAtLeast(version)
+ }
+
+ fun installProgram(bytes: ByteArray) {
+ val prog = bytes.toHexString()
+ val result = runShellCommandOrThrow("cmd network_stack apf $ifname install $prog").trim()
+ // runShellCommandOrThrow only throws on S+.
+ assertThat(result).isEqualTo("success")
+ }
+
+ fun readProgram(): ByteArray {
+ val progHexString = runShellCommandOrThrow("cmd network_stack apf $ifname read").trim()
+ // runShellCommandOrThrow only throws on S+.
+ assertThat(progHexString).isNotEmpty()
+ return HexDump.hexStringToByteArray(progHexString)
+ }
+
+ @VsrTest(
+ requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"]
+ )
+ @SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
+ // APF integration is mostly broken before V, only run the full read / write test on V+.
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testReadWriteProgram() {
+ assumeApfVersionSupportAtLeast(4)
+
+ val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 2
+ } else {
+ 8
+ }
+
+ // The minReadWriteSize is 2 bytes. The first byte always stays PASS.
+ val program = ByteArray(caps.maximumApfProgramSize)
+ for (i in caps.maximumApfProgramSize downTo minReadWriteSize) {
+ // Randomize bytes in range [1, i). And install first [0, i) bytes of program.
+ // Note that only the very first instruction (PASS) is valid APF bytecode.
+ Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */)
+ installProgram(program.sliceArray(0..<i))
+
+ // Compare entire memory region.
+ val readResult = readProgram()
+ val errMsg = """
+ read/write $i byte prog failed.
+ In APFv4, the APF memory region MUST NOT be modified or cleared except by APF
+ instructions executed by the interpreter or by Android OS calls to the HAL. If this
+ requirement cannot be met, the firmware cannot declare that it supports APFv4 and
+ it should declare that it only supports APFv3(if counter is partially supported) or
+ APFv2.
+ """.trimIndent()
+ assertWithMessage(errMsg).that(readResult).isEqualTo(program)
+ }
+ }
+
+ fun ApfV4Generator.addPassIfNotIcmpv6EchoReply() {
+ // If not IPv6 -> PASS
+ addLoad16(R0, ETH_ETHERTYPE_OFFSET)
+ addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), BaseApfGenerator.PASS_LABEL)
+
+ // If not ICMPv6 -> PASS
+ addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), BaseApfGenerator.PASS_LABEL)
+
+ // If not echo reply -> PASS
+ addLoad8(R0, ICMP6_TYPE_OFFSET)
+ addJumpIfR0NotEquals(0x81, BaseApfGenerator.PASS_LABEL)
+ }
+
+ // APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testDropPingReply() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
+ assumeApfVersionSupportAtLeast(4)
+
+ // clear any active APF filter
+ var gen = ApfV4Generator(4).addPass()
+ installProgram(gen.generate())
+ readProgram() // wait for install completion
+
+ // Assert that initial ping does not get filtered.
+ val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 68
+ } else {
+ 4
+ }
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ assertThat(packetReader.expectPingReply()).isEqualTo(data)
+
+ // Generate an APF program that drops the next ping
+ gen = ApfV4Generator(4)
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // if not data matches -> PASS
+ gen.addLoadImmediate(R0, ICMP6_TYPE_OFFSET + PING_HEADER_LENGTH)
+ gen.addJumpIfBytesAtR0NotEqual(data, BaseApfGenerator.PASS_LABEL)
+
+ // else DROP
+ gen.addJump(BaseApfGenerator.DROP_LABEL)
+
+ val program = gen.generate()
+ installProgram(program)
+ readProgram() // wait for install completion
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingDropped()
+ }
+
+ fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
+
+ // APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testPrefilledMemorySlotsV4() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
+ // Test v4 memory slots on both v4 and v6 interpreters.
+ assumeApfVersionSupportAtLeast(4)
+ clearApfMemory()
+ val gen = ApfV4Generator(4)
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ val counterRegion = 500
+ gen.addLoadImmediate(R1, counterRegion)
+ gen.addLoadFromMemory(R0, MemorySlot.PROGRAM_SIZE)
+ gen.addStoreData(R0, 0)
+ gen.addLoadFromMemory(R0, MemorySlot.RAM_LEN)
+ gen.addStoreData(R0, 4)
+ gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE)
+ gen.addStoreData(R0, 8)
+ gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ gen.addStoreData(R0, 12)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
+ gen.addStoreData(R0, 16)
+
+ val program = gen.generate()
+ assertThat(program.size).isLessThan(counterRegion)
+ installProgram(program)
+ readProgram() // wait for install completion
+
+ // Trigger the program by sending a ping and waiting on the reply.
+ val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 68
+ } else {
+ 4
+ }
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ val readResult = readProgram()
+ val buffer = ByteBuffer.wrap(readResult, counterRegion, 20 /* length */)
+ expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size)
+ expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize)
+ expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0)
+ // Ping packet payload + ICMPv6 header (8) + IPv6 header (40) + ethernet header (14)
+ expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14)
+ expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5)
+ }
+
+ // APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testFilterAgeIncreasesBetweenPackets() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
+ assumeApfVersionSupportAtLeast(4)
+ clearApfMemory()
+ val gen = ApfV4Generator(4)
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ val counterRegion = 500
+ gen.addLoadImmediate(R1, counterRegion)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
+ gen.addStoreData(R0, 0)
+
+ installProgram(gen.generate())
+ readProgram() // wait for install completion
+
+ val payloadSize = 56
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
+ val filterAgeSecondsOrig = buffer.getInt()
+
+ Thread.sleep(5100)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
+ val filterAgeSeconds = buffer.getInt()
+ // Assert that filter age has increased, but not too much.
+ assertThat(filterAgeSeconds - filterAgeSecondsOrig).isEqualTo(5)
}
}
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
index 3e5d0ba..16a7b73 100644
--- a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -48,6 +48,7 @@
import androidx.test.filters.SdkSuppress;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.DevSdkIgnoreRule;
import org.junit.Before;
@@ -67,7 +68,10 @@
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // BatteryStatsManager did not exist on Q
public class BatteryStatsManagerTest{
- @Rule
+ @Rule(order = 1)
+ public final AutoReleaseNetworkCallbackRule
+ networkCallbackRule = new AutoReleaseNetworkCallbackRule();
+ @Rule(order = 2)
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
private static final String TAG = BatteryStatsManagerTest.class.getSimpleName();
private static final String TEST_URL = "https://connectivitycheck.gstatic.com/generate_204";
@@ -145,7 +149,7 @@
return;
}
- final Network cellNetwork = mCtsNetUtils.connectToCell();
+ final Network cellNetwork = networkCallbackRule.requestCell();
final URL url = new URL(TEST_URL);
// Get cellular battery stats
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index 99222dd..07e2024 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -47,6 +47,7 @@
import com.android.modules.utils.build.SdkLevel.isAtLeastR
import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL
import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
+import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.DeviceConfigRule
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.SkipMainlinePresubmit
@@ -101,9 +102,12 @@
private val server = TestHttpServer("localhost")
- @get:Rule
+ @get:Rule(order = 1)
val deviceConfigRule = DeviceConfigRule(retryCountBeforeSIfConfigChanged = 5)
+ @get:Rule(order = 2)
+ val networkCallbackRule = AutoReleaseNetworkCallbackRule()
+
companion object {
@JvmStatic @BeforeClass
fun setUpClass() {
@@ -144,15 +148,15 @@
assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
assumeFalse(pm.hasSystemFeature(FEATURE_WATCH))
utils.ensureWifiConnected()
- val cellNetwork = utils.connectToCell()
+ val cellNetwork = networkCallbackRule.requestCell()
// Verify cell network is validated
val cellReq = NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET)
.build()
- val cellCb = TestableNetworkCallback(timeoutMs = TEST_TIMEOUT_MS)
- cm.registerNetworkCallback(cellReq, cellCb)
+ val cellCb = networkCallbackRule.registerNetworkCallback(cellReq,
+ TestableNetworkCallback(timeoutMs = TEST_TIMEOUT_MS))
val cb = cellCb.poll { it.network == cellNetwork &&
it is CapabilitiesChanged && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
}
@@ -213,8 +217,6 @@
} finally {
cm.unregisterNetworkCallback(wifiCb)
server.stop()
- // disconnectFromCell should be called after connectToCell
- utils.disconnectFromCell()
}
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index cdf8340..633f2b6 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -36,11 +36,22 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.EXTRA_NETWORK;
import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -71,7 +82,9 @@
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.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -81,7 +94,6 @@
import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
import static android.net.cts.util.CtsNetUtils.TEST_HOST;
-import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.os.Process.INVALID_UID;
@@ -191,9 +203,11 @@
import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.NetworkInformationShimImpl;
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
+import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceConfigRule;
import com.android.testutils.DeviceInfoUtils;
@@ -211,6 +225,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -259,10 +274,14 @@
@RunWith(AndroidJUnit4.class)
public class ConnectivityManagerTest {
- @Rule
+ @Rule(order = 1)
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
- @Rule
+ @Rule(order = 2)
+ public final AutoReleaseNetworkCallbackRule
+ networkCallbackRule = new AutoReleaseNetworkCallbackRule();
+
+ @Rule(order = 3)
public final DeviceConfigRule mTestValidationConfigRule = new DeviceConfigRule(
5 /* retryCountBeforeSIfConfigChanged */);
@@ -290,6 +309,7 @@
// Airplane Mode BroadcastReceiver Timeout
private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
+ private static final long CELL_DATA_AVAILABLE_TIMEOUT_MS = 120_000L;
// Timeout for applying uids allowed on restricted networks
private static final long APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS = 3_000L;
@@ -328,8 +348,6 @@
private final ArraySet<Integer> mNetworkTypes = new ArraySet<>();
private UiAutomation mUiAutomation;
private CtsNetUtils mCtsNetUtils;
- // The registered callbacks.
- private List<NetworkCallback> mRegisteredCallbacks = new ArrayList<>();
// Used for cleanup purposes.
private final List<Range<Integer>> mVpnRequiredUidRanges = new ArrayList<>();
@@ -411,11 +429,6 @@
@After
public void tearDown() throws Exception {
- // Release any NetworkRequests filed to connect mobile data.
- if (mCtsNetUtils.cellConnectAttempted()) {
- mCtsNetUtils.disconnectFromCell();
- }
-
if (TestUtils.shouldTestSApis()) {
runWithShellPermissionIdentity(
() -> mCmShim.setRequireVpnForUids(false, mVpnRequiredUidRanges),
@@ -425,15 +438,12 @@
// All tests in this class require a working Internet connection as they start. Make
// sure there is still one as they end that's ready to use for the next test to use.
mTestValidationConfigRule.runAfterNextCleanup(() -> {
- final TestNetworkCallback callback = new TestNetworkCallback();
- registerDefaultNetworkCallback(callback);
- try {
- assertNotNull("Couldn't restore Internet connectivity",
- callback.waitForAvailable());
- } finally {
- // Unregister all registered callbacks.
- unregisterRegisteredCallbacks();
- }
+ // mTestValidationConfigRule has higher order than networkCallbackRule, so
+ // networkCallbackRule is the outer rule and will be cleaned up after this method.
+ final TestableNetworkCallback callback =
+ networkCallbackRule.registerDefaultNetworkCallback();
+ assertNotNull("Couldn't restore Internet connectivity",
+ callback.eventuallyExpect(CallbackEntry.AVAILABLE));
});
}
@@ -555,7 +565,7 @@
throws InterruptedException {
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
// Make sure cell is active to retrieve IMSI for verification in later step.
- final Network cellNetwork = mCtsNetUtils.connectToCell();
+ final Network cellNetwork = networkCallbackRule.requestCell();
final String subscriberId = getSubscriberIdForCellNetwork(cellNetwork);
assertFalse(TextUtils.isEmpty(subscriberId));
@@ -853,7 +863,7 @@
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
- Network cellNetwork = mCtsNetUtils.connectToCell();
+ Network cellNetwork = networkCallbackRule.requestCell();
// This server returns the requestor's IP address as the response body.
URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
String wifiAddressString = httpGet(wifiNetwork, url);
@@ -993,10 +1003,10 @@
// default network.
return new NetworkRequest.Builder()
.clearCapabilities()
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NET_CAPABILITY_TRUSTED)
+ .addCapability(NET_CAPABILITY_NOT_VPN)
+ .addCapability(NET_CAPABILITY_INTERNET)
.build();
}
@@ -1027,10 +1037,10 @@
final String invalidPrivateDnsServer = "invalidhostname.example.com";
final String goodPrivateDnsServer = "dns.google";
mCtsNetUtils.storePrivateDnsSetting();
- final TestableNetworkCallback cb = new TestableNetworkCallback();
final NetworkRequest networkRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET).build();
- registerNetworkCallback(networkRequest, cb);
+ final TestableNetworkCallback cb =
+ networkCallbackRule.registerNetworkCallback(networkRequest);
final Network networkForPrivateDns = mCm.getActiveNetwork();
try {
// Verifying the good private DNS sever
@@ -1068,24 +1078,27 @@
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
// We will register for a WIFI network being available or lost.
- final TestNetworkCallback callback = new TestNetworkCallback();
- registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ final TestableNetworkCallback callback = networkCallbackRule.registerNetworkCallback(
+ makeWifiNetworkRequest());
- final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback();
- registerDefaultNetworkCallback(defaultTrackingCallback);
+ final TestableNetworkCallback defaultTrackingCallback =
+ networkCallbackRule.registerDefaultNetworkCallback();
- final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback();
- final TestNetworkCallback perUidCallback = new TestNetworkCallback();
- final TestNetworkCallback bestMatchingCallback = new TestNetworkCallback();
+ final TestableNetworkCallback systemDefaultCallback = new TestableNetworkCallback();
+ final TestableNetworkCallback perUidCallback = new TestableNetworkCallback();
+ final TestableNetworkCallback bestMatchingCallback = new TestableNetworkCallback();
final Handler h = new Handler(Looper.getMainLooper());
if (TestUtils.shouldTestSApis()) {
assertThrows(SecurityException.class, () ->
- registerSystemDefaultNetworkCallback(systemDefaultCallback, h));
+ networkCallbackRule.registerSystemDefaultNetworkCallback(
+ systemDefaultCallback, h));
runWithShellPermissionIdentity(() -> {
- registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
- registerDefaultNetworkCallbackForUid(Process.myUid(), perUidCallback, h);
+ networkCallbackRule.registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
+ networkCallbackRule.registerDefaultNetworkCallbackForUid(Process.myUid(),
+ perUidCallback, h);
}, NETWORK_SETTINGS);
- registerBestMatchingNetworkCallback(makeDefaultRequest(), bestMatchingCallback, h);
+ networkCallbackRule.registerBestMatchingNetworkCallback(
+ makeDefaultRequest(), bestMatchingCallback, h);
}
Network wifiNetwork = null;
@@ -1094,24 +1107,22 @@
// Now we should expect to get a network callback about availability of the wifi
// network even if it was already connected as a state-based action when the callback
// is registered.
- wifiNetwork = callback.waitForAvailable();
+ wifiNetwork = callback.eventuallyExpect(CallbackEntry.AVAILABLE).getNetwork();
assertNotNull("Did not receive onAvailable for TRANSPORT_WIFI request",
wifiNetwork);
- final Network defaultNetwork = defaultTrackingCallback.waitForAvailable();
+ final Network defaultNetwork = defaultTrackingCallback.eventuallyExpect(
+ CallbackEntry.AVAILABLE).getNetwork();
assertNotNull("Did not receive onAvailable on default network callback",
defaultNetwork);
if (TestUtils.shouldTestSApis()) {
- assertNotNull("Did not receive onAvailable on system default network callback",
- systemDefaultCallback.waitForAvailable());
- final Network perUidNetwork = perUidCallback.waitForAvailable();
- assertNotNull("Did not receive onAvailable on per-UID default network callback",
- perUidNetwork);
+ systemDefaultCallback.eventuallyExpect(CallbackEntry.AVAILABLE);
+ final Network perUidNetwork = perUidCallback.eventuallyExpect(CallbackEntry.AVAILABLE)
+ .getNetwork();
assertEquals(defaultNetwork, perUidNetwork);
- final Network bestMatchingNetwork = bestMatchingCallback.waitForAvailable();
- assertNotNull("Did not receive onAvailable on best matching network callback",
- bestMatchingNetwork);
+ final Network bestMatchingNetwork = bestMatchingCallback.eventuallyExpect(
+ CallbackEntry.AVAILABLE).getNetwork();
assertEquals(defaultNetwork, bestMatchingNetwork);
}
}
@@ -1123,8 +1134,8 @@
final Handler h = new Handler(Looper.getMainLooper());
// Verify registerSystemDefaultNetworkCallback can be accessed via
// CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
- runWithShellPermissionIdentity(() ->
- registerSystemDefaultNetworkCallback(new TestNetworkCallback(), h),
+ runWithShellPermissionIdentity(
+ () -> networkCallbackRule.registerSystemDefaultNetworkCallback(h),
CONNECTIVITY_USE_RESTRICTED_NETWORKS);
}
@@ -1294,15 +1305,14 @@
*/
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
@Test
- public void testRequestNetworkCallback() throws Exception {
- final TestNetworkCallback callback = new TestNetworkCallback();
- requestNetwork(new NetworkRequest.Builder()
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .build(), callback);
+ public void testRequestNetworkCallback() {
+ final TestableNetworkCallback callback = networkCallbackRule.requestNetwork(
+ new NetworkRequest.Builder().addCapability(
+ NET_CAPABILITY_INTERNET)
+ .build());
// Wait to get callback for availability of internet
- Network internetNetwork = callback.waitForAvailable();
- assertNotNull("Did not receive NetworkCallback#onAvailable for INTERNET", internetNetwork);
+ callback.eventuallyExpect(CallbackEntry.AVAILABLE).getNetwork();
}
/**
@@ -1320,16 +1330,13 @@
}
}
- final TestNetworkCallback callback = new TestNetworkCallback();
- requestNetwork(new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(),
- callback, 100);
+ final TestableNetworkCallback callback = networkCallbackRule.requestNetwork(
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(),
+ 100 /* timeoutMs */);
try {
// Wait to get callback for unavailability of requested network
- assertTrue("Did not receive NetworkCallback#onUnavailable",
- callback.waitForUnavailable());
- } catch (InterruptedException e) {
- fail("NetworkCallback wait was interrupted.");
+ callback.eventuallyExpect(CallbackEntry.UNAVAILABLE, 2_000 /* timeoutMs */);
} finally {
if (previousWifiEnabledState) {
mCtsNetUtils.connectToWifi();
@@ -1416,40 +1423,48 @@
final boolean useSystemDefault)
throws Exception {
final CompletableFuture<Network> networkFuture = new CompletableFuture<>();
- final NetworkCallback networkCallback = new NetworkCallback() {
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
- if (!nc.hasTransport(targetTransportType)) return;
- final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
- final boolean validated = nc.hasCapability(NET_CAPABILITY_VALIDATED);
- if (metered == requestedMeteredness && (!waitForValidation || validated)) {
- networkFuture.complete(network);
+ // Registering a callback here guarantees onCapabilitiesChanged is called immediately
+ // with the current setting. Therefore, if the setting has already been changed,
+ // this method will return right away, and if not, it'll wait for the setting to change.
+ final TestableNetworkCallback networkCallback;
+ if (useSystemDefault) {
+ networkCallback = runWithShellPermissionIdentity(() -> {
+ if (isAtLeastS()) {
+ return networkCallbackRule.registerSystemDefaultNetworkCallback(
+ new Handler(Looper.getMainLooper()));
+ } else {
+ // registerSystemDefaultNetworkCallback is only supported on S+.
+ return networkCallbackRule.requestNetwork(
+ new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NET_CAPABILITY_TRUSTED)
+ .addCapability(NET_CAPABILITY_NOT_VPN)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build(),
+ new TestableNetworkCallback(),
+ new Handler(Looper.getMainLooper()));
}
- }
- };
-
- try {
- // Registering a callback here guarantees onCapabilitiesChanged is called immediately
- // with the current setting. Therefore, if the setting has already been changed,
- // this method will return right away, and if not, it'll wait for the setting to change.
- if (useSystemDefault) {
- runWithShellPermissionIdentity(() ->
- registerSystemDefaultNetworkCallback(networkCallback,
- new Handler(Looper.getMainLooper())),
- NETWORK_SETTINGS);
- } else {
- registerDefaultNetworkCallback(networkCallback);
- }
-
- // Changing meteredness on wifi involves reconnecting, which can take several seconds
- // (involves re-associating, DHCP...).
- return networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
- throw new AssertionError("Timed out waiting for active network metered status to "
- + "change to " + requestedMeteredness + " ; network = "
- + mCm.getActiveNetwork(), e);
+ },
+ NETWORK_SETTINGS);
+ } else {
+ networkCallback = networkCallbackRule.registerDefaultNetworkCallback();
}
+
+ return networkCallback.eventuallyExpect(
+ CallbackEntry.NETWORK_CAPS_UPDATED,
+ // Changing meteredness on wifi involves reconnecting, which can take several
+ // seconds (involves re-associating, DHCP...).
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ cb -> {
+ final NetworkCapabilities nc = cb.getCaps();
+ if (!nc.hasTransport(targetTransportType)) return false;
+
+ final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
+ final boolean validated = nc.hasCapability(NET_CAPABILITY_VALIDATED);
+ return metered == requestedMeteredness && (!waitForValidation || validated);
+ }).getNetwork();
}
private Network setWifiMeteredStatusAndWait(String ssid, boolean isMetered,
@@ -2025,7 +2040,7 @@
return;
}
- final Network network = mCtsNetUtils.connectToCell();
+ final Network network = networkCallbackRule.requestCell();
final int supported = getSupportedKeepalivesForNet(network);
final InetAddress srcAddr = getFirstV4Address(network);
assumeTrue("This test requires native IPv4", srcAddr != null);
@@ -2091,15 +2106,15 @@
}
private void verifyBindSocketToRestrictedNetworkDisallowed() throws Exception {
- final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
final NetworkRequest testRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
TEST_RESTRICTED_NW_IFACE_NAME))
.build();
- runWithShellPermissionIdentity(() -> requestNetwork(testRequest, testNetworkCb),
+ final TestableNetworkCallback testNetworkCb = runWithShellPermissionIdentity(
+ () -> networkCallbackRule.requestNetwork(testRequest),
CONNECTIVITY_USE_RESTRICTED_NETWORKS,
// CONNECTIVITY_INTERNAL is for requesting restricted network because shell does not
// have CONNECTIVITY_USE_RESTRICTED_NETWORKS on R.
@@ -2115,7 +2130,7 @@
NETWORK_CALLBACK_TIMEOUT_MS,
entry -> network.equals(entry.getNetwork())
&& (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)));
// CtsNetTestCases package doesn't hold CONNECTIVITY_USE_RESTRICTED_NETWORKS, so it
// does not allow to bind socket to restricted network.
assertThrows(IOException.class, () -> network.bindSocket(socket));
@@ -2200,8 +2215,7 @@
registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
}
if (supportTelephony) {
- // connectToCell needs to be followed by disconnectFromCell, which is called in tearDown
- mCtsNetUtils.connectToCell();
+ networkCallbackRule.requestCell();
registerCallbackAndWaitForAvailable(makeCellNetworkRequest(), telephonyCb);
}
@@ -2229,7 +2243,10 @@
// connectToCell only registers a request, it cannot / does not need to be called twice
mCtsNetUtils.ensureWifiConnected();
if (verifyWifi) waitForAvailable(wifiCb);
- if (supportTelephony) waitForAvailable(telephonyCb);
+ if (supportTelephony) {
+ telephonyCb.eventuallyExpect(
+ CallbackEntry.AVAILABLE, CELL_DATA_AVAILABLE_TIMEOUT_MS);
+ }
} finally {
// Restore the previous state of airplane mode and permissions:
runShellCommand("cmd connectivity airplane-mode "
@@ -2239,7 +2256,7 @@
private void registerCallbackAndWaitForAvailable(@NonNull final NetworkRequest request,
@NonNull final TestableNetworkCallback cb) {
- registerNetworkCallback(request, cb);
+ networkCallbackRule.registerNetworkCallback(request, cb);
waitForAvailable(cb);
}
@@ -2347,21 +2364,13 @@
private void verifySsidFromCallbackNetworkCapabilities(@NonNull String ssid, boolean hasSsid)
throws Exception {
- final CompletableFuture<NetworkCapabilities> foundNc = new CompletableFuture();
- final NetworkCallback callback = new NetworkCallback() {
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
- foundNc.complete(nc);
- }
- };
-
- registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ final TestableNetworkCallback callback =
+ networkCallbackRule.registerNetworkCallback(makeWifiNetworkRequest());
// Registering a callback here guarantees onCapabilitiesChanged is called immediately
// because WiFi network should be connected.
- final NetworkCapabilities nc =
- foundNc.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ final NetworkCapabilities nc = callback.eventuallyExpect(
+ CallbackEntry.NETWORK_CAPS_UPDATED, NETWORK_CALLBACK_TIMEOUT_MS).getCaps();
// Verify if ssid is contained in the NetworkCapabilities received from callback.
- assertNotNull("NetworkCapabilities of the network is null", nc);
assertEquals(hasSsid, Pattern.compile(ssid).matcher(nc.toString()).find());
}
@@ -2390,8 +2399,8 @@
final NetworkRequest testRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_TEST)
// Test networks do not have NOT_VPN or TRUSTED capabilities by default
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
.setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
testNetworkInterface.getInterfaceName()))
.build();
@@ -2400,14 +2409,15 @@
final TestableNetworkCallback callback = new TestableNetworkCallback();
final Handler handler = new Handler(Looper.getMainLooper());
assertThrows(SecurityException.class,
- () -> requestBackgroundNetwork(testRequest, callback, handler));
+ () -> networkCallbackRule.requestBackgroundNetwork(testRequest, callback, handler));
Network testNetwork = null;
try {
// Request background test network via Shell identity which has NETWORK_SETTINGS
// permission granted.
runWithShellPermissionIdentity(
- () -> requestBackgroundNetwork(testRequest, callback, handler),
+ () -> networkCallbackRule.requestBackgroundNetwork(
+ testRequest, callback, handler),
new String[] { android.Manifest.permission.NETWORK_SETTINGS });
// Register the test network agent which has no foreground request associated to it.
@@ -2478,8 +2488,11 @@
}
}
+ // On V+, ConnectivityService generates blockedReasons based on bpf map contents even if the
+ // otherUid does not exist on device. So if allowlist chain (e.g. background chain) is enabled,
+ // blockedReasons for otherUid will not be BLOCKED_REASON_NONE.
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
- @Test
+ @Test @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testBlockedStatusCallback() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
@@ -2500,9 +2513,10 @@
final int otherUid = UserHandle.getUid(5, Process.FIRST_APPLICATION_UID);
final Handler handler = new Handler(Looper.getMainLooper());
- registerDefaultNetworkCallback(myUidCallback, handler);
- runWithShellPermissionIdentity(() -> registerDefaultNetworkCallbackForUid(
- otherUid, otherUidCallback, handler), NETWORK_SETTINGS);
+ networkCallbackRule.registerDefaultNetworkCallback(myUidCallback, handler);
+ runWithShellPermissionIdentity(
+ () -> networkCallbackRule.registerDefaultNetworkCallbackForUid(
+ otherUid, otherUidCallback, handler), NETWORK_SETTINGS);
final Network defaultNetwork = myUidCallback.expect(CallbackEntry.AVAILABLE).getNetwork();
final List<DetailedBlockedStatusCallback> allCallbacks =
@@ -2558,14 +2572,14 @@
assertNotNull(info);
assertEquals(DetailedState.CONNECTED, info.getDetailedState());
- final TestableNetworkCallback callback = new TestableNetworkCallback();
+ final TestableNetworkCallback callback;
try {
mCmShim.setLegacyLockdownVpnEnabled(true);
// setLegacyLockdownVpnEnabled is asynchronous and only takes effect when the
// ConnectivityService handler thread processes it. Ensure it has taken effect by doing
// something that blocks until the handler thread is idle.
- registerDefaultNetworkCallback(callback);
+ callback = networkCallbackRule.registerDefaultNetworkCallback();
waitForAvailable(callback);
// Test one of the effects of setLegacyLockdownVpnEnabled: the fact that any NetworkInfo
@@ -2832,9 +2846,9 @@
private void registerTestOemNetworkPreferenceCallbacks(
@NonNull final TestableNetworkCallback defaultCallback,
@NonNull final TestableNetworkCallback systemDefaultCallback) {
- registerDefaultNetworkCallback(defaultCallback);
+ networkCallbackRule.registerDefaultNetworkCallback(defaultCallback);
runWithShellPermissionIdentity(() ->
- registerSystemDefaultNetworkCallback(systemDefaultCallback,
+ networkCallbackRule.registerSystemDefaultNetworkCallback(systemDefaultCallback,
new Handler(Looper.getMainLooper())), NETWORK_SETTINGS);
}
@@ -2950,18 +2964,18 @@
+ " unless device supports WiFi",
mPackageManager.hasSystemFeature(FEATURE_WIFI));
- final TestNetworkCallback cb = new TestNetworkCallback();
try {
// Wait for partial connectivity to be detected on the network
final Network network = preparePartialConnectivity();
- requestNetwork(makeWifiNetworkRequest(), cb);
+ final TestableNetworkCallback cb = networkCallbackRule.requestNetwork(
+ makeWifiNetworkRequest());
runAsShell(NETWORK_SETTINGS, () -> {
// The always bit is verified in NetworkAgentTest
mCm.setAcceptPartialConnectivity(network, false /* accept */, false /* always */);
});
// Reject partial connectivity network should cause the network being torn down
- assertEquals(network, cb.waitForLost());
+ assertEquals(network, cb.eventuallyExpect(CallbackEntry.LOST).getNetwork());
} finally {
mHttpServer.stop();
// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
@@ -2989,17 +3003,17 @@
assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+ " unless device supports WiFi and telephony", canRunTest);
- final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
try {
// Ensure at least one default network candidate connected.
- mCtsNetUtils.connectToCell();
+ networkCallbackRule.requestCell();
final Network wifiNetwork = prepareUnvalidatedNetwork();
// Default network should not be wifi ,but checking that wifi is not the default doesn't
// guarantee that it won't become the default in the future.
assertNotEquals(wifiNetwork, mCm.getActiveNetwork());
- registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+ final TestableNetworkCallback wifiCb = networkCallbackRule.registerNetworkCallback(
+ makeWifiNetworkRequest());
runAsShell(NETWORK_SETTINGS, () -> {
mCm.setAcceptUnvalidated(wifiNetwork, false /* accept */, false /* always */);
});
@@ -3026,19 +3040,19 @@
assumeTrue("testSetAvoidUnvalidated cannot execute"
+ " unless device supports WiFi and telephony", canRunTest);
- final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
- final TestableNetworkCallback defaultCb = new TestableNetworkCallback();
final int previousAvoidBadWifi =
ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
allowBadWifi();
try {
- final Network cellNetwork = mCtsNetUtils.connectToCell();
+ final Network cellNetwork = networkCallbackRule.requestCell();
final Network wifiNetwork = prepareValidatedNetwork();
- registerDefaultNetworkCallback(defaultCb);
- registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+ final TestableNetworkCallback defaultCb =
+ networkCallbackRule.registerDefaultNetworkCallback();
+ final TestableNetworkCallback wifiCb = networkCallbackRule.registerNetworkCallback(
+ makeWifiNetworkRequest());
// Verify wifi is the default network.
defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
@@ -3101,20 +3115,11 @@
});
}
- private Network expectNetworkHasCapability(Network network, int expectedNetCap, long timeout)
- throws Exception {
- final CompletableFuture<Network> future = new CompletableFuture();
- final NetworkCallback cb = new NetworkCallback() {
- @Override
- public void onCapabilitiesChanged(Network n, NetworkCapabilities nc) {
- if (n.equals(network) && nc.hasCapability(expectedNetCap)) {
- future.complete(network);
- }
- }
- };
-
- registerNetworkCallback(new NetworkRequest.Builder().build(), cb);
- return future.get(timeout, TimeUnit.MILLISECONDS);
+ private Network expectNetworkHasCapability(Network network, int expectedNetCap, long timeout) {
+ return networkCallbackRule.registerNetworkCallback(new NetworkRequest.Builder().build())
+ .eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, timeout,
+ cb -> cb.getNetwork().equals(network)
+ && cb.getCaps().hasCapability(expectedNetCap)).getNetwork();
}
private void prepareHttpServer() throws Exception {
@@ -3214,8 +3219,6 @@
if (supportWifi) {
mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
- } else {
- mCtsNetUtils.disconnectFromCell();
}
final CompletableFuture<Boolean> future = new CompletableFuture<>();
@@ -3226,7 +3229,7 @@
if (supportWifi) {
mCtsNetUtils.ensureWifiConnected();
} else {
- mCtsNetUtils.connectToCell();
+ networkCallbackRule.requestCell();
}
assertTrue(future.get(LISTEN_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}, () -> {
@@ -3267,13 +3270,14 @@
// For testing mobile data preferred uids feature, it needs both wifi and cell network.
final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
- final Network cellNetwork = mCtsNetUtils.connectToCell();
- final TestableNetworkCallback defaultTrackingCb = new TestableNetworkCallback();
- final TestableNetworkCallback systemDefaultCb = new TestableNetworkCallback();
+ final Network cellNetwork = networkCallbackRule.requestCell();
final Handler h = new Handler(Looper.getMainLooper());
- runWithShellPermissionIdentity(() -> registerSystemDefaultNetworkCallback(
- systemDefaultCb, h), NETWORK_SETTINGS);
- registerDefaultNetworkCallback(defaultTrackingCb);
+ final TestableNetworkCallback systemDefaultCb = runWithShellPermissionIdentity(
+ () -> networkCallbackRule.registerSystemDefaultNetworkCallback(h),
+ NETWORK_SETTINGS);
+
+ final TestableNetworkCallback defaultTrackingCb =
+ networkCallbackRule.registerDefaultNetworkCallback();
try {
// CtsNetTestCases uid is not listed in MOBILE_DATA_PREFERRED_UIDS setting, so the
@@ -3342,7 +3346,7 @@
// Create test network agent with restricted network.
final NetworkCapabilities nc = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
TEST_RESTRICTED_NW_IFACE_NAME))
.build();
@@ -3376,23 +3380,23 @@
mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
// File a restricted network request with permission first to hold the connection.
- final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
final NetworkRequest testRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
TEST_RESTRICTED_NW_IFACE_NAME))
.build();
- runWithShellPermissionIdentity(() -> requestNetwork(testRequest, testNetworkCb),
+ final TestableNetworkCallback testNetworkCb = runWithShellPermissionIdentity(
+ () -> networkCallbackRule.requestNetwork(testRequest),
CONNECTIVITY_USE_RESTRICTED_NETWORKS);
// File another restricted network request without permission.
final TestableNetworkCallback restrictedNetworkCb = new TestableNetworkCallback();
final NetworkRequest restrictedRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
TEST_RESTRICTED_NW_IFACE_NAME))
.build();
@@ -3409,7 +3413,7 @@
NETWORK_CALLBACK_TIMEOUT_MS,
entry -> network.equals(entry.getNetwork())
&& (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)));
// CtsNetTestCases package doesn't hold CONNECTIVITY_USE_RESTRICTED_NETWORKS, so it
// does not allow to bind socket to restricted network.
assertThrows(IOException.class, () -> network.bindSocket(socket));
@@ -3427,13 +3431,13 @@
if (TestUtils.shouldTestTApis()) {
// Uid is in allowed list. Try file network request again.
- requestNetwork(restrictedRequest, restrictedNetworkCb);
+ networkCallbackRule.requestNetwork(restrictedRequest, restrictedNetworkCb);
// Verify that the network is restricted.
restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
NETWORK_CALLBACK_TIMEOUT_MS,
entry -> network.equals(entry.getNetwork())
&& (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+ .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)));
}
} finally {
agent.unregister();
@@ -3572,6 +3576,8 @@
doTestFirewallBlocking(FIREWALL_CHAIN_DOZABLE, ALLOWLIST);
}
+ // Disable test - needs to be fixed
+ @Ignore
@Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testFirewallBlockingBackground() {
@@ -3726,63 +3732,263 @@
Process.myUid() + 1, EXPECT_OPEN);
}
+ private int getBlockedReason(final int chain) {
+ switch(chain) {
+ case FIREWALL_CHAIN_DOZABLE:
+ return BLOCKED_REASON_DOZE;
+ case FIREWALL_CHAIN_POWERSAVE:
+ return BLOCKED_REASON_BATTERY_SAVER;
+ case FIREWALL_CHAIN_RESTRICTED:
+ return BLOCKED_REASON_RESTRICTED_MODE;
+ case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+ return BLOCKED_REASON_LOW_POWER_STANDBY;
+ case FIREWALL_CHAIN_BACKGROUND:
+ return BLOCKED_REASON_APP_BACKGROUND;
+ case FIREWALL_CHAIN_STANDBY:
+ return BLOCKED_REASON_APP_STANDBY;
+ case FIREWALL_CHAIN_METERED_DENY_USER:
+ return BLOCKED_METERED_REASON_USER_RESTRICTED;
+ case FIREWALL_CHAIN_METERED_DENY_ADMIN:
+ return BLOCKED_METERED_REASON_ADMIN_DISABLED;
+ case FIREWALL_CHAIN_OEM_DENY_1:
+ case FIREWALL_CHAIN_OEM_DENY_2:
+ case FIREWALL_CHAIN_OEM_DENY_3:
+ return BLOCKED_REASON_OEM_DENY;
+ default:
+ throw new IllegalArgumentException(
+ "Failed to find blockedReasons for chain: " + chain);
+ }
+ }
+
+ private void doTestBlockedReasons_setUidFirewallRule(final int chain, final boolean metered)
+ throws Exception {
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Store current Wi-Fi metered value and update metered value
+ final Network currentWifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ final NetworkCapabilities wifiNetworkCapabilities = callWithShellPermissionIdentity(
+ () -> mCm.getNetworkCapabilities(currentWifiNetwork));
+ final String ssid = unquoteSSID(wifiNetworkCapabilities.getSsid());
+ final boolean oldMeteredValue = wifiNetworkCapabilities.isMetered();
+ final Network wifiNetwork =
+ setWifiMeteredStatusAndWait(ssid, metered, true /* waitForValidation */);
+
+ // Store current firewall chains status. This test operates on the chain that is passed in,
+ // but also always operates on FIREWALL_CHAIN_METERED_DENY_USER to ensure that metered
+ // chains are tested as well.
+ final int myUid = Process.myUid();
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid));
+ final int previousMeteredDenyFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid));
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.requestNetwork(makeWifiNetworkRequest(), cb);
+ testAndCleanup(() -> {
+ int blockedReasonsWithoutChain = BLOCKED_REASON_NONE;
+ int blockedReasonsWithChain = getBlockedReason(chain);
+ int blockedReasonsWithChainAndLockDown =
+ getBlockedReason(chain) | BLOCKED_REASON_LOCKDOWN_VPN;
+ if (metered) {
+ blockedReasonsWithoutChain |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ blockedReasonsWithChain |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ blockedReasonsWithChainAndLockDown |= BLOCKED_METERED_REASON_USER_RESTRICTED;
+ }
+
+ // Set RULE_DENY on target chain and metered deny chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_DENY);
+ mCm.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid,
+ FIREWALL_RULE_DENY);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithChain);
+
+ // Set VPN lockdown
+ final Range<Integer> myUidRange = new Range<>(myUid, myUid);
+ runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+ true /* requireVpn */, List.of(myUidRange)), NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork,
+ blockedReasonsWithChainAndLockDown);
+
+ // Unset VPN lockdown
+ runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+ false /* requireVpn */, List.of(myUidRange)), NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithChain);
+
+ // Set RULE_ALLOW on target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_ALLOW),
+ NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, blockedReasonsWithoutChain);
+
+ // Set RULE_ALLOW on metered deny chain
+ runWithShellPermissionIdentity(() -> mCm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER, myUid, FIREWALL_RULE_ALLOW),
+ NETWORK_SETTINGS);
+ if (metered) {
+ cb.eventuallyExpectBlockedStatusCallback(wifiNetwork, BLOCKED_REASON_NONE);
+ }
+ }, /* cleanup */ () -> {
+ setWifiMeteredStatusAndWait(ssid, oldMeteredValue, false /* waitForValidation */);
+ }, /* cleanup */ () -> {
+ mCm.unregisterNetworkCallback(cb);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ try {
+ mCm.setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, myUid,
+ previousMeteredDenyFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_setUidFirewallRule() throws Exception {
+ doTestBlockedReasons_setUidFirewallRule(FIREWALL_CHAIN_DOZABLE, true /* metered */);
+ doTestBlockedReasons_setUidFirewallRule(FIREWALL_CHAIN_STANDBY, false /* metered */);
+ }
+
+ private void doTestBlockedReasons_setFirewallChainEnabled(final int chain) {
+ // Store current firewall chains status.
+ final int myUid = Process.myUid();
+ // TODO(b/342508466): Use runAsShell
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid), NETWORK_SETTINGS);
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.registerDefaultNetworkCallback(cb);
+ final Network network = cb.expect(CallbackEntry.AVAILABLE).getNetwork();
+ testAndCleanup(() -> {
+ // Disable chain and set RULE_DENY on target chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ mCm.setUidFirewallRule(chain, myUid, FIREWALL_RULE_DENY);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+
+ // Enable chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+
+ // Disable chain
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, false /* enable */);
+ }, NETWORK_SETTINGS);
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_setFirewallChainEnabled() {
+ doTestBlockedReasons_setFirewallChainEnabled(FIREWALL_CHAIN_POWERSAVE);
+ doTestBlockedReasons_setFirewallChainEnabled(FIREWALL_CHAIN_OEM_DENY_1);
+ }
+
+ private void doTestBlockedReasons_replaceFirewallChain(
+ final int chain, final boolean isAllowList) {
+ // Store current firewall chains status.
+ final int myUid = Process.myUid();
+ final boolean wasChainEnabled = runWithShellPermissionIdentity(
+ () -> mCm.getFirewallChainEnabled(chain), NETWORK_SETTINGS);
+ final int previousFirewallRule = runWithShellPermissionIdentity(
+ () -> mCm.getUidFirewallRule(chain, myUid), NETWORK_SETTINGS);
+
+ final DetailedBlockedStatusCallback cb = new DetailedBlockedStatusCallback();
+ networkCallbackRule.registerDefaultNetworkCallback(cb);
+ final Network network = cb.expect(CallbackEntry.AVAILABLE).getNetwork();
+ testAndCleanup(() -> {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+
+ // Remove uid from the target chain and enable chain
+ runWithShellPermissionIdentity(() -> {
+ // Note that this removes *all* UIDs from the chain, not just the UID that is
+ // being tested. This is probably OK since FIREWALL_CHAIN_OEM_DENY_2 is unused
+ // in AOSP and FIREWALL_CHAIN_BACKGROUND is probably empty when running this
+ // test (since nothing is in the foreground).
+ //
+ // TODO(b/342508466): add a getFirewallUidChainContents or similar method to fetch
+ // chain contents, and update this test to use it.
+ mCm.replaceFirewallChain(chain, new int[0]);
+ mCm.setFirewallChainEnabled(chain, true /* enable */);
+ }, NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ } else {
+ cb.assertNoBlockedStatusCallback();
+ }
+
+ // Put uid on the target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.replaceFirewallChain(chain, new int[]{myUid}), NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ } else {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ }
+
+ // Remove uid from the target chain
+ runWithShellPermissionIdentity(
+ () -> mCm.replaceFirewallChain(chain, new int[0]), NETWORK_SETTINGS);
+
+ if (isAllowList) {
+ cb.eventuallyExpectBlockedStatusCallback(network, getBlockedReason(chain));
+ } else {
+ cb.eventuallyExpectBlockedStatusCallback(network, BLOCKED_REASON_NONE);
+ }
+ }, /* cleanup */ () -> {
+ runWithShellPermissionIdentity(() -> {
+ mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+ try {
+ mCm.setUidFirewallRule(chain, myUid, previousFirewallRule);
+ } catch (IllegalStateException ignored) {
+ // Removing match causes an exception when the rule entry for the uid does
+ // not exist. But this is fine and can be ignored.
+ }
+ }, NETWORK_SETTINGS);
+ });
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @ConnectivityModuleTest
+ public void testBlockedReasons_replaceFirewallChain() {
+ doTestBlockedReasons_replaceFirewallChain(
+ FIREWALL_CHAIN_BACKGROUND, true /* isAllowChain */);
+ doTestBlockedReasons_replaceFirewallChain(
+ FIREWALL_CHAIN_OEM_DENY_2, false /* isAllowChain */);
+ }
+
private void assumeTestSApis() {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
// shims, and @IgnoreUpTo does not check that.
assumeTrue(TestUtils.shouldTestSApis());
}
-
- private void unregisterRegisteredCallbacks() {
- for (NetworkCallback callback: mRegisteredCallbacks) {
- mCm.unregisterNetworkCallback(callback);
- }
- }
-
- private void registerDefaultNetworkCallback(NetworkCallback callback) {
- mCm.registerDefaultNetworkCallback(callback);
- mRegisteredCallbacks.add(callback);
- }
-
- private void registerDefaultNetworkCallback(NetworkCallback callback, Handler handler) {
- mCm.registerDefaultNetworkCallback(callback, handler);
- mRegisteredCallbacks.add(callback);
- }
-
- private void registerNetworkCallback(NetworkRequest request, NetworkCallback callback) {
- mCm.registerNetworkCallback(request, callback);
- mRegisteredCallbacks.add(callback);
- }
-
- private void registerSystemDefaultNetworkCallback(NetworkCallback callback, Handler handler) {
- mCmShim.registerSystemDefaultNetworkCallback(callback, handler);
- mRegisteredCallbacks.add(callback);
- }
-
- private void registerDefaultNetworkCallbackForUid(int uid, NetworkCallback callback,
- Handler handler) throws Exception {
- mCmShim.registerDefaultNetworkCallbackForUid(uid, callback, handler);
- mRegisteredCallbacks.add(callback);
- }
-
- private void requestNetwork(NetworkRequest request, NetworkCallback callback) {
- mCm.requestNetwork(request, callback);
- mRegisteredCallbacks.add(callback);
- }
-
- private void requestNetwork(NetworkRequest request, NetworkCallback callback, int timeoutSec) {
- mCm.requestNetwork(request, callback, timeoutSec);
- mRegisteredCallbacks.add(callback);
- }
-
- private void registerBestMatchingNetworkCallback(NetworkRequest request,
- NetworkCallback callback, Handler handler) {
- mCm.registerBestMatchingNetworkCallback(request, callback, handler);
- mRegisteredCallbacks.add(callback);
- }
-
- private void requestBackgroundNetwork(NetworkRequest request, NetworkCallback callback,
- Handler handler) throws Exception {
- mCmShim.requestBackgroundNetwork(request, callback, handler);
- mRegisteredCallbacks.add(callback);
- }
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index d052551..6fa2812 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -905,13 +905,17 @@
val iface = createInterface()
val listener = EthernetStateListener()
addInterfaceStateListener(listener)
+ // Uses eventuallyExpect to account for interfaces that could already exist on device
+ listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ disableInterface(iface).expectResult(iface.name)
+ listener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+ enableInterface(iface).expectResult(iface.name)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
disableInterface(iface).expectResult(iface.name)
listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
-
- enableInterface(iface).expectResult(iface.name)
- listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index a40ed0f..b5f43d3 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -69,9 +69,11 @@
import android.net.IpSecManager.SecurityParameterIndex;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.IpSecTransform;
+import android.net.IpSecTransformState;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.os.Build;
+import android.os.OutcomeReceiver;
import android.platform.test.annotations.AppModeFull;
import android.system.ErrnoException;
import android.system.Os;
@@ -81,6 +83,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.SkipMainlinePresubmit;
@@ -100,7 +103,11 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+@ConnectivityModuleTest
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "Socket cannot bind in instant app mode")
public class IpSecManagerTest extends IpSecBaseTest {
@@ -444,6 +451,11 @@
long uidTxDelta = 0;
long uidRxDelta = 0;
for (int i = 0; i < 100; i++) {
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
+ }
uidTxDelta = TrafficStats.getUidTxPackets(Os.getuid()) - uidTxPackets;
uidRxDelta = TrafficStats.getUidRxPackets(Os.getuid()) - uidRxPackets;
@@ -518,6 +530,11 @@
}
private static void initStatsChecker() throws Exception {
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
+ }
uidTxBytes = TrafficStats.getUidTxBytes(Os.getuid());
uidRxBytes = TrafficStats.getUidRxBytes(Os.getuid());
uidTxPackets = TrafficStats.getUidTxPackets(Os.getuid());
@@ -1642,4 +1659,37 @@
newReplayBitmap(expectedPacketCount));
}
}
+
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ public void testRequestIpSecTransformStateOnClosedTransform() throws Exception {
+ assumeRequestIpSecTransformStateSupported();
+
+ final InetAddress localAddr = InetAddresses.parseNumericAddress(IPV6_LOOPBACK);
+ final CompletableFuture<RuntimeException> futureError = new CompletableFuture<>();
+
+ try (SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(localAddr);
+ IpSecTransform transform =
+ buildTransportModeTransform(spi, localAddr, null /* encapSocket*/)) {
+ transform.close();
+
+ transform.requestIpSecTransformState(
+ Executors.newSingleThreadExecutor(),
+ new OutcomeReceiver<IpSecTransformState, RuntimeException>() {
+ @Override
+ public void onResult(IpSecTransformState state) {
+ fail("Expect to fail but received a state");
+ }
+
+ @Override
+ public void onError(RuntimeException error) {
+ futureError.complete(error);
+ }
+ });
+
+ assertTrue(
+ futureError.get(SOCK_TIMEOUT, TimeUnit.MILLISECONDS)
+ instanceof IllegalStateException);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt b/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
index 5ba6c4c..93cec9c 100644
--- a/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
+++ b/tests/cts/net/src/android/net/cts/MdnsTestUtils.kt
@@ -287,6 +287,12 @@
): TestDnsPacket? = pollForMdnsPacket(timeoutMs) { it.isQueryFor(recordName, *requiredTypes) }
fun TapPacketReader.pollForReply(
+ recordName: String,
+ type: Int,
+ timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
+): TestDnsPacket? = pollForMdnsPacket(timeoutMs) { it.isReplyFor(recordName, type) }
+
+fun TapPacketReader.pollForReply(
serviceName: String,
serviceType: String,
timeoutMs: Long = MDNS_REGISTRATION_TIMEOUT_MS
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 7ab73c2..06a827b 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -43,9 +43,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.DeviceConfigRule;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -55,9 +55,13 @@
@RunWith(AndroidJUnit4.class)
public class MultinetworkApiTest {
- @Rule
+ @Rule(order = 1)
public final DeviceConfigRule mDeviceConfigRule = new DeviceConfigRule();
+ @Rule(order = 2)
+ public final AutoReleaseNetworkCallbackRule
+ mNetworkCallbackRule = new AutoReleaseNetworkCallbackRule();
+
static {
System.loadLibrary("nativemultinetwork_jni");
}
@@ -93,13 +97,6 @@
mCtsNetUtils = new CtsNetUtils(mContext);
}
- @After
- public void tearDown() {
- if (mCtsNetUtils.cellConnectAttempted()) {
- mCtsNetUtils.disconnectFromCell();
- }
- }
-
@Test
public void testGetaddrinfo() throws Exception {
for (Network network : getTestableNetworks()) {
@@ -268,14 +265,22 @@
* Get all testable Networks with internet capability.
*/
private Set<Network> getTestableNetworks() throws InterruptedException {
+ // Calling requestNetwork() to request a cell or Wi-Fi network via CtsNetUtils or
+ // NetworkCallbackRule requires the CHANGE_NETWORK_STATE permission. This permission cannot
+ // be granted to instant apps. Therefore, return currently available testable networks
+ // directly in instant mode.
+ if (mContext.getApplicationInfo().isInstantApp()) {
+ return new ArraySet<>(mCtsNetUtils.getTestableNetworks());
+ }
+
// Obtain cell and Wi-Fi through CtsNetUtils (which uses NetworkCallbacks), as they may have
// just been reconnected by the test using NetworkCallbacks, so synchronous calls may not
// yet return them (synchronous calls and callbacks should not be mixed for a given
// Network).
final Set<Network> testableNetworks = new ArraySet<>();
if (mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
- if (!mCtsNetUtils.cellConnectAttempted()) {
- mRequestedCellNetwork = mCtsNetUtils.connectToCell();
+ if (mRequestedCellNetwork == null) {
+ mRequestedCellNetwork = mNetworkCallbackRule.requestCell();
}
assertNotNull("Cell network requested but not obtained", mRequestedCellNetwork);
testableNetworks.add(mRequestedCellNetwork);
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 6ec4e62..5b53839 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -68,6 +68,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -511,7 +512,7 @@
assertArrayEquals(netCapabilities, nr.getCapabilities());
}
- @Test @IgnoreUpTo(VANILLA_ICE_CREAM)
+ @Test @IgnoreUpTo(VANILLA_ICE_CREAM) @Ignore("b/338200742")
public void testDefaultCapabilities() {
final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 6dd4857..6394599 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -81,7 +81,9 @@
import com.android.compatibility.common.util.SystemUtil
import com.android.modules.utils.build.SdkLevel.isAtLeastU
import com.android.net.module.util.DnsPacket
+import com.android.net.module.util.DnsPacket.ANSECTION
import com.android.net.module.util.HexDump
+import com.android.net.module.util.HexDump.hexStringToByteArray
import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
import com.android.net.module.util.PacketBuilder
import com.android.testutils.ConnectivityModuleTest
@@ -96,6 +98,7 @@
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.assertContainsExactly
import com.android.testutils.assertEmpty
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
@@ -127,6 +130,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.test.assertNotEquals
private const val TAG = "NsdManagerTest"
private const val TIMEOUT_MS = 2000L
@@ -138,6 +142,9 @@
private const val DBG = false
private const val TEST_PORT = 12345
private const val MDNS_PORT = 5353.toShort()
+private const val TYPE_KEY = 25
+private const val QCLASS_INTERNET = 0x0001
+private const val NAME_RECORDS_TTL_MILLIS: Long = 120
private val multicastIpv6Addr = parseNumericAddress("ff02::fb") as Inet6Address
private val testSrcAddr = parseNumericAddress("2001:db8::123") as Inet6Address
@@ -167,6 +174,12 @@
private val serviceType2 = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
private val customHostname = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
private val customHostname2 = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
+ private val publicKey = hexStringToByteArray(
+ "0201030dc141d0637960b98cbc12cfca"
+ + "221d2879dac26ee5b460e9007c992e19"
+ + "02d897c391b03764d448f7d0c772fdb0"
+ + "3b1d9d6d52ff8886769e8e2362513565"
+ + "270962d3")
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -1451,10 +1464,8 @@
handlerThread.waitForIdle(TIMEOUT_MS)
tryTest {
- repeat(3) {
- assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
- "Expect 3 announcements sent after initial probing")
- }
+ assertNotNull(packetReader.pollForAdvertisement(serviceName, serviceType),
+ "No announcements sent after initial probing")
assertEquals(si.serviceName, registeredService.serviceName)
assertEquals(si.hostname, registeredService.hostname)
@@ -2027,7 +2038,7 @@
}
@Test
- fun testAdvertisingAndDiscovery_multipleRegistrationsForSameCustomHost_unionOfAddressesFound() {
+ fun testAdvertisingAndDiscovery_multipleRegistrationsForSameCustomHost_hostRenamed() {
val hostAddresses1 = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
@@ -2035,9 +2046,6 @@
val hostAddresses2 = listOf(
parseNumericAddress("192.0.2.24"),
parseNumericAddress("2001:db8::3"))
- val hostAddresses3 = listOf(
- parseNumericAddress("2001:db8::3"),
- parseNumericAddress("2001:db8::5"))
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.hostname = customHostname
@@ -2051,18 +2059,9 @@
it.hostname = customHostname
it.hostAddresses = hostAddresses2
}
- val si3 = NsdServiceInfo().also {
- it.network = testNetwork1.network
- it.serviceName = serviceName3
- it.serviceType = serviceType
- it.port = TEST_PORT + 1
- it.hostname = customHostname
- it.hostAddresses = hostAddresses3
- }
val registrationRecord1 = NsdRegistrationRecord()
val registrationRecord2 = NsdRegistrationRecord()
- val registrationRecord3 = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
tryTest {
@@ -2072,27 +2071,13 @@
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, Executor { it.run() }, discoveryRecord)
- val discoveredInfo1 = discoveryRecord.waitForServiceDiscovered(
+ val discoveredInfo = discoveryRecord.waitForServiceDiscovered(
serviceName, serviceType, testNetwork1.network)
- val resolvedInfo1 = resolveService(discoveredInfo1)
+ val resolvedInfo = resolveService(discoveredInfo)
- assertEquals(TEST_PORT, resolvedInfo1.port)
- assertEquals(si1.hostname, resolvedInfo1.hostname)
- assertAddressEquals(
- hostAddresses1 + hostAddresses2,
- resolvedInfo1.hostAddresses)
-
- registerService(registrationRecord3, si3)
-
- val discoveredInfo2 = discoveryRecord.waitForServiceDiscovered(
- serviceName3, serviceType, testNetwork1.network)
- val resolvedInfo2 = resolveService(discoveredInfo2)
-
- assertEquals(TEST_PORT + 1, resolvedInfo2.port)
- assertEquals(si2.hostname, resolvedInfo2.hostname)
- assertAddressEquals(
- hostAddresses1 + hostAddresses2 + hostAddresses3,
- resolvedInfo2.hostAddresses)
+ assertEquals(TEST_PORT, resolvedInfo.port)
+ assertNotEquals(si1.hostname, resolvedInfo.hostname)
+ assertAddressEquals(hostAddresses2, resolvedInfo.hostAddresses)
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
@@ -2100,7 +2085,6 @@
} cleanup {
nsdManager.unregisterService(registrationRecord1)
nsdManager.unregisterService(registrationRecord2)
- nsdManager.unregisterService(registrationRecord3)
}
}
@@ -2266,6 +2250,165 @@
}
@Test
+ fun testAdvertising_registerServiceAndPublicKey_keyAnnounced() {
+ val si = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceType = serviceType
+ it.serviceName = serviceName
+ it.port = TEST_PORT
+ it.publicKey = publicKey
+ }
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ val registrationRecord = NsdRegistrationRecord()
+ val discoveryRecord = NsdDiscoveryRecord()
+ tryTest {
+ registerService(registrationRecord, si)
+
+ val announcement = packetReader.pollForReply(
+ "$serviceName.$serviceType.local",
+ TYPE_KEY
+ )
+ assertNotNull(announcement)
+ val keyRecords = announcement.records[ANSECTION].filter { it.nsType == TYPE_KEY }
+ assertEquals(1, keyRecords.size)
+ val actualRecord = keyRecords.get(0)
+ assertEquals(TYPE_KEY, actualRecord.nsType)
+ assertEquals("$serviceName.$serviceType.local", actualRecord.dName)
+ assertEquals(NAME_RECORDS_TTL_MILLIS, actualRecord.ttl)
+ assertArrayEquals(publicKey, actualRecord.rr)
+
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network, Executor { it.run() }, discoveryRecord)
+
+ val discoveredInfo1 = discoveryRecord.waitForServiceDiscovered(
+ serviceName, serviceType, testNetwork1.network)
+ val resolvedInfo1 = resolveService(discoveredInfo1)
+
+ assertEquals(serviceName, discoveredInfo1.serviceName)
+ assertEquals(TEST_PORT, resolvedInfo1.port)
+ } cleanupStep {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
+ fun testAdvertising_registerCustomHostAndPublicKey_keyAnnounced() {
+ val si = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.hostname = customHostname
+ it.hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ it.publicKey = publicKey
+ }
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ val registrationRecord = NsdRegistrationRecord()
+ tryTest {
+ registerService(registrationRecord, si)
+
+ val announcement = packetReader.pollForReply("$customHostname.local", TYPE_KEY)
+ assertNotNull(announcement)
+ val keyRecords = announcement.records[ANSECTION].filter { it.nsType == TYPE_KEY }
+ assertEquals(1, keyRecords.size)
+ val actualRecord = keyRecords.get(0)
+ assertEquals(TYPE_KEY, actualRecord.nsType)
+ assertEquals("$customHostname.local", actualRecord.dName)
+ assertEquals(NAME_RECORDS_TTL_MILLIS, actualRecord.ttl)
+ assertArrayEquals(publicKey, actualRecord.rr)
+
+ // This test case focuses on key announcement so we don't check the details of the
+ // announcement of the custom host addresses.
+ val addressRecords = announcement.records[ANSECTION].filter {
+ it.nsType == DnsResolver.TYPE_AAAA ||
+ it.nsType == DnsResolver.TYPE_A
+ }
+ assertEquals(3, addressRecords.size)
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ }
+ }
+
+ @Test
+ fun testAdvertising_registerTwoServicesWithSameCustomHostAndPublicKey_keyAnnounced() {
+ val si1 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceType = serviceType
+ it.serviceName = serviceName
+ it.port = TEST_PORT
+ it.hostname = customHostname
+ it.hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ it.publicKey = publicKey
+ }
+ val si2 = NsdServiceInfo().also {
+ it.network = testNetwork1.network
+ it.serviceType = serviceType2
+ it.serviceName = serviceName2
+ it.port = TEST_PORT + 1
+ it.hostname = customHostname
+ it.hostAddresses = listOf()
+ it.publicKey = publicKey
+ }
+ val packetReader = TapPacketReader(Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ val registrationRecord1 = NsdRegistrationRecord()
+ val registrationRecord2 = NsdRegistrationRecord()
+ tryTest {
+ registerService(registrationRecord1, si1)
+
+ var announcement =
+ packetReader.pollForReply("$serviceName.$serviceType.local", TYPE_KEY)
+ assertNotNull(announcement)
+ var keyRecords = announcement.records[ANSECTION].filter { it.nsType == TYPE_KEY }
+ assertEquals(2, keyRecords.size)
+ assertTrue(keyRecords.any { it.dName == "$serviceName.$serviceType.local" })
+ assertTrue(keyRecords.any { it.dName == "$customHostname.local" })
+ assertTrue(keyRecords.all { it.ttl == NAME_RECORDS_TTL_MILLIS })
+ assertTrue(keyRecords.all { it.rr.contentEquals(publicKey) })
+
+ // This test case focuses on key announcement so we don't check the details of the
+ // announcement of the custom host addresses.
+ val addressRecords = announcement.records[ANSECTION].filter {
+ it.nsType == DnsResolver.TYPE_AAAA ||
+ it.nsType == DnsResolver.TYPE_A
+ }
+ assertEquals(3, addressRecords.size)
+
+ registerService(registrationRecord2, si2)
+
+ announcement = packetReader.pollForReply("$serviceName2.$serviceType2.local", TYPE_KEY)
+ assertNotNull(announcement)
+ keyRecords = announcement.records[ANSECTION].filter { it.nsType == TYPE_KEY }
+ assertEquals(2, keyRecords.size)
+ assertTrue(keyRecords.any { it.dName == "$serviceName2.$serviceType2.local" })
+ assertTrue(keyRecords.any { it.dName == "$customHostname.local" })
+ assertTrue(keyRecords.all { it.ttl == NAME_RECORDS_TTL_MILLIS })
+ assertTrue(keyRecords.all { it.rr.contentEquals(publicKey) })
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord1)
+ nsdManager.unregisterService(registrationRecord2)
+ }
+ }
+
+ @Test
fun testServiceTypeClientRemovedAfterSocketDestroyed() {
val si = makeTestServiceInfo(testNetwork1.network)
// Register service on testNetwork1
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 3d828a4..0dd2a23 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
@@ -93,7 +93,7 @@
private static final int SOCKET_TIMEOUT_MS = 10_000;
private static final int PRIVATE_DNS_PROBE_MS = 1_000;
- private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 10_000;
+ private static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 30_000;
private static final int CONNECTIVITY_CHANGE_TIMEOUT_SECS = 30;
private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
@@ -408,40 +408,10 @@
return network;
}
- public Network connectToCell() throws InterruptedException {
- if (cellConnectAttempted()) {
- mCm.unregisterNetworkCallback(mCellNetworkCallback);
- }
- NetworkRequest cellRequest = new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .build();
- mCellNetworkCallback = new TestNetworkCallback();
- mCm.requestNetwork(cellRequest, mCellNetworkCallback);
- final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
- assertNotNull("Cell network not available. " +
- "Please ensure the device has working mobile data.", cellNetwork);
- return cellNetwork;
- }
-
- public void disconnectFromCell() {
- if (!cellConnectAttempted()) {
- throw new IllegalStateException("Cell connection not attempted");
- }
- mCm.unregisterNetworkCallback(mCellNetworkCallback);
- mCellNetworkCallback = null;
- }
-
public boolean cellConnectAttempted() {
return mCellNetworkCallback != null;
}
- public void tearDown() {
- if (cellConnectAttempted()) {
- disconnectFromCell();
- }
- }
-
private NetworkRequest makeWifiNetworkRequest() {
return new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index 2fde1ce..689ce74 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -19,11 +19,17 @@
android_test {
name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
+ platform_apis: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "net-tests-utils",
+ ],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
index bea843c..56bad31 100644
--- a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
+++ b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
@@ -16,6 +16,10 @@
package android.net.cts.networkpermission.updatestatspermission;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -25,6 +29,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +73,11 @@
out.write(buf);
out.close();
socket.close();
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
+ }
long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index d2e46af..06bdca6 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -27,6 +27,7 @@
import android.net.ConnectivityManager
import android.net.IDnsResolver
import android.net.INetd
+import android.net.INetd.PERMISSION_INTERNET
import android.net.LinkProperties
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
@@ -225,6 +226,9 @@
override fun getSystemProperties() = mock(MockableSystemProperties::class.java)
override fun makeNetIdManager() = TestNetIdManager()
override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java)
+ .also {
+ doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
+ }
override fun isChangeEnabled(changeId: Long, uid: Int) = true
override fun makeMultinetworkPolicyTracker(
diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt
index 52e502d..4780c5d 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStatsIntegrationTest.kt
@@ -38,6 +38,7 @@
import android.os.Build
import android.os.Process
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
import com.android.server.net.integrationtests.NetworkStatsIntegrationTest.Direction.DOWNLOAD
import com.android.server.net.integrationtests.NetworkStatsIntegrationTest.Direction.UPLOAD
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -214,6 +215,11 @@
// In practice, for one way 10k download payload, the download usage is about
// 11222~12880 bytes, with 14~17 packets. And the upload usage is about 1279~1626 bytes
// with 14~17 packets, which is majorly contributed by TCP ACK packets.
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ TrafficStats.clearRateLimitCaches()
+ }
val snapshotAfterDownload = StatsSnapshot(context, internalInterfaceName)
val (expectedDownloadLower, expectedDownloadUpper) = getExpectedStatsBounds(
TEST_DOWNLOAD_SIZE,
@@ -236,6 +242,9 @@
)
// Verify upload data usage accounting.
+ if (SdkLevel.isAtLeastV()) {
+ TrafficStats.clearRateLimitCaches()
+ }
val snapshotAfterUpload = StatsSnapshot(context, internalInterfaceName)
val (expectedUploadLower, expectedUploadUpper) = getExpectedStatsBounds(
TEST_UPLOAD_SIZE,
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index 336be2e..c118d0a 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -31,6 +31,8 @@
header_libs: [
"bpf_headers",
],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
static_libs: [
"libbase",
"libmodules-utils-build",
diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index 2f66d17..c5088c6 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -17,8 +17,9 @@
"connectivity_native_test.cpp",
],
header_libs: ["bpf_connectivity_headers"],
+ version_script: ":connectivity_mainline_test_map",
+ stl: "libc++_static",
shared_libs: [
- "libbase",
"libbinder_ndk",
"liblog",
"libnetutils",
@@ -26,6 +27,7 @@
],
static_libs: [
"connectivity_native_aidl_interface-lateststable-ndk",
+ "libbase",
"libcutils",
"libmodules-utils-build",
"libutils",
diff --git a/tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt b/tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt
index a9ccbdd..b5d78f3 100644
--- a/tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt
+++ b/tests/unit/java/android/net/NetworkStackBpfNetMapsTest.kt
@@ -21,7 +21,8 @@
import android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY
import android.net.BpfNetMapsConstants.DOZABLE_MATCH
import android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH
-import android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH
+import android.net.BpfNetMapsConstants.PENALTY_BOX_ADMIN_MATCH
+import android.net.BpfNetMapsConstants.PENALTY_BOX_USER_MATCH
import android.net.BpfNetMapsConstants.STANDBY_MATCH
import android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY
import android.net.BpfNetMapsUtils.getMatchByFirewallChain
@@ -48,9 +49,9 @@
private const val TEST_UID3 = TEST_UID2 + 1
private const val NO_IIF = 0
-// pre-T devices does not support Bpf.
+// NetworkStack can not use this before U due to b/326143935
@RunWith(DevSdkIgnoreRunner::class)
-@IgnoreUpTo(VERSION_CODES.S_V2)
+@IgnoreUpTo(VERSION_CODES.TIRAMISU)
class NetworkStackBpfNetMapsTest {
@Rule
@JvmField
@@ -102,14 +103,18 @@
}
// Verify the size matches, this also verifies no common item in allow and deny chains.
assertEquals(
- BpfNetMapsConstants.ALLOW_CHAINS.size +
- BpfNetMapsConstants.DENY_CHAINS.size,
+ BpfNetMapsConstants.ALLOW_CHAINS.size +
+ BpfNetMapsConstants.DENY_CHAINS.size +
+ BpfNetMapsConstants.METERED_ALLOW_CHAINS.size +
+ BpfNetMapsConstants.METERED_DENY_CHAINS.size,
declaredChains.size
)
declaredChains.forEach {
assertTrue(
- BpfNetMapsConstants.ALLOW_CHAINS.contains(it.get(null)) ||
- BpfNetMapsConstants.DENY_CHAINS.contains(it.get(null))
+ BpfNetMapsConstants.ALLOW_CHAINS.contains(it.get(null)) ||
+ BpfNetMapsConstants.METERED_ALLOW_CHAINS.contains(it.get(null)) ||
+ BpfNetMapsConstants.DENY_CHAINS.contains(it.get(null)) ||
+ BpfNetMapsConstants.METERED_DENY_CHAINS.contains(it.get(null))
)
}
}
@@ -190,7 +195,16 @@
// Add uid1 to penalty box, verify the network is blocked for uid1, while uid2 is not
// affected.
- testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH))
+ testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ testUidOwnerMap.updateEntry(S32(TEST_UID1), UidOwnerValue(NO_IIF, PENALTY_BOX_ADMIN_MATCH))
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ testUidOwnerMap.updateEntry(
+ S32(TEST_UID1),
+ UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH or PENALTY_BOX_ADMIN_MATCH)
+ )
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
assertFalse(isUidNetworkingBlocked(TEST_UID2, metered = true))
@@ -206,7 +220,14 @@
// priority.
testUidOwnerMap.updateEntry(
S32(TEST_UID1),
- UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH or HAPPY_BOX_MATCH)
+ UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH or HAPPY_BOX_MATCH)
+ )
+ assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
+ assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true))
+ assertFalse(isUidNetworkingBlocked(TEST_UID3, metered = true))
+ testUidOwnerMap.updateEntry(
+ S32(TEST_UID1),
+ UidOwnerValue(NO_IIF, PENALTY_BOX_ADMIN_MATCH or HAPPY_BOX_MATCH)
)
assertTrue(isUidNetworkingBlocked(TEST_UID1, metered = true))
assertTrue(isUidNetworkingBlocked(TEST_UID2, metered = true))
@@ -240,7 +261,7 @@
for (uid in FIRST_APPLICATION_UID - 5..FIRST_APPLICATION_UID + 5) {
// system uid is not blocked regardless of firewall chains
val expectBlocked = uid >= FIRST_APPLICATION_UID
- testUidOwnerMap.updateEntry(S32(uid), UidOwnerValue(NO_IIF, PENALTY_BOX_MATCH))
+ testUidOwnerMap.updateEntry(S32(uid), UidOwnerValue(NO_IIF, PENALTY_BOX_USER_MATCH))
assertEquals(
expectBlocked,
isUidNetworkingBlocked(uid, metered = true),
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 27c4561..9c812a1 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -16,6 +16,11 @@
package android.net.nsd;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.nsd.NsdManager.checkServiceInfoForRegistration;
+
+import static com.android.net.module.util.HexDump.hexStringToByteArray;
+
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -54,6 +59,7 @@
import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
+import java.util.Collections;
import java.util.List;
import java.time.Duration;
@@ -395,6 +401,7 @@
NsdManager.RegistrationListener listener4 = mock(NsdManager.RegistrationListener.class);
NsdManager.RegistrationListener listener5 = mock(NsdManager.RegistrationListener.class);
NsdManager.RegistrationListener listener6 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener7 = mock(NsdManager.RegistrationListener.class);
NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
NsdServiceInfo validService = new NsdServiceInfo("a_name", "_a_type._tcp");
@@ -439,6 +446,19 @@
validServiceWithCustomHostNoAddresses.setPort(2222);
validServiceWithCustomHostNoAddresses.setHostname("a_host");
+ NsdServiceInfo validServiceWithPublicKey = new NsdServiceInfo("a_name", "_a_type._tcp");
+ validServiceWithPublicKey.setPublicKey(
+ hexStringToByteArray(
+ "0201030dc141d0637960b98cbc12cfca"
+ + "221d2879dac26ee5b460e9007c992e19"
+ + "02d897c391b03764d448f7d0c772fdb0"
+ + "3b1d9d6d52ff8886769e8e2362513565"
+ + "270962d3"));
+
+ NsdServiceInfo invalidServiceWithTooShortPublicKey =
+ new NsdServiceInfo("a_name", "_a_type._tcp");
+ invalidServiceWithTooShortPublicKey.setPublicKey(hexStringToByteArray("0201"));
+
// Service registration
// - invalid arguments
mustFail(() -> { manager.unregisterService(null); });
@@ -449,6 +469,8 @@
mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
mustFail(() -> {
manager.registerService(invalidMissingHostnameWithAddresses, PROTOCOL, listener1); });
+ mustFail(() -> {
+ manager.registerService(invalidServiceWithTooShortPublicKey, PROTOCOL, listener1); });
manager.registerService(validService, PROTOCOL, listener1);
// - update without subtype is not allowed
mustFail(() -> { manager.registerService(validServiceDuplicate, PROTOCOL, listener1); });
@@ -479,6 +501,9 @@
// - registering a service with a custom host with no addresses is valid
manager.registerService(validServiceWithCustomHostNoAddresses, PROTOCOL, listener6);
manager.unregisterService(listener6);
+ // - registering a service with a public key is valid
+ manager.registerService(validServiceWithPublicKey, PROTOCOL, listener7);
+ manager.unregisterService(listener7);
// Discover service
// - invalid arguments
@@ -506,6 +531,229 @@
mustFail(() -> { manager.resolveService(validService, listener3); });
}
+ private static final class NsdServiceInfoBuilder {
+ private static final String SERVICE_NAME = "TestService";
+ private static final String SERVICE_TYPE = "_testservice._tcp";
+ private static final int SERVICE_PORT = 12345;
+ private static final String HOSTNAME = "TestHost";
+ private static final List<InetAddress> HOST_ADDRESSES =
+ List.of(parseNumericAddress("192.168.2.23"), parseNumericAddress("2001:db8::3"));
+ private static final byte[] PUBLIC_KEY =
+ hexStringToByteArray(
+ "0201030dc141d0637960b98cbc12cfca"
+ + "221d2879dac26ee5b460e9007c992e19"
+ + "02d897c391b03764d448f7d0c772fdb0"
+ + "3b1d9d6d52ff8886769e8e2362513565"
+ + "270962d3");
+
+ private final NsdServiceInfo mNsdServiceInfo = new NsdServiceInfo();
+
+ NsdServiceInfo build() {
+ return mNsdServiceInfo;
+ }
+
+ NsdServiceInfoBuilder setNoService() {
+ mNsdServiceInfo.setServiceName(null);
+ mNsdServiceInfo.setServiceType(null);
+ mNsdServiceInfo.setPort(0);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setService() {
+ mNsdServiceInfo.setServiceName(SERVICE_NAME);
+ mNsdServiceInfo.setServiceType(SERVICE_TYPE);
+ mNsdServiceInfo.setPort(SERVICE_PORT);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setZeroPortService() {
+ mNsdServiceInfo.setServiceName(SERVICE_NAME);
+ mNsdServiceInfo.setServiceType(SERVICE_TYPE);
+ mNsdServiceInfo.setPort(0);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setInvalidService() {
+ mNsdServiceInfo.setServiceName(SERVICE_NAME);
+ mNsdServiceInfo.setServiceType(null);
+ mNsdServiceInfo.setPort(SERVICE_PORT);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setDefaultHost() {
+ mNsdServiceInfo.setHostname(null);
+ mNsdServiceInfo.setHostAddresses(Collections.emptyList());
+ return this;
+ }
+
+ NsdServiceInfoBuilder setCustomHost() {
+ mNsdServiceInfo.setHostname(HOSTNAME);
+ mNsdServiceInfo.setHostAddresses(HOST_ADDRESSES);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setCustomHostNoAddress() {
+ mNsdServiceInfo.setHostname(HOSTNAME);
+ mNsdServiceInfo.setHostAddresses(Collections.emptyList());
+ return this;
+ }
+
+ NsdServiceInfoBuilder setHostAddressesNoHostname() {
+ mNsdServiceInfo.setHostname(null);
+ mNsdServiceInfo.setHostAddresses(HOST_ADDRESSES);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setNoPublicKey() {
+ mNsdServiceInfo.setPublicKey(null);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setPublicKey() {
+ mNsdServiceInfo.setPublicKey(PUBLIC_KEY);
+ return this;
+ }
+
+ NsdServiceInfoBuilder setInvalidPublicKey() {
+ mNsdServiceInfo.setPublicKey(new byte[3]);
+ return this;
+ }
+ }
+
+ @Test
+ public void testCheckServiceInfoForRegistration() {
+ // The service is invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setInvalidService()
+ .setCustomHost()
+ .setPublicKey().build()));
+ // Keep compatible with the legacy behavior: It's allowed to set host
+ // addresses for a service registration although the host addresses
+ // won't be registered. To register the addresses for a host, the
+ // hostname must be specified.
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setHostAddressesNoHostname()
+ .setPublicKey().build());
+ // The public key is invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setCustomHost()
+ .setInvalidPublicKey().build()));
+ // Invalid combinations
+ // 1. (service, custom host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setCustomHost()
+ .setPublicKey().build());
+ // 2. (service, custom host, no key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setCustomHost()
+ .setNoPublicKey().build());
+ // 3. (service, no-address custom host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setCustomHostNoAddress()
+ .setPublicKey().build());
+ // 4. (service, no-address custom host, no key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setCustomHostNoAddress()
+ .setNoPublicKey().build());
+ // 5. (service, default host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setDefaultHost()
+ .setPublicKey().build());
+ // 6. (service, default host, no key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setService()
+ .setDefaultHost()
+ .setNoPublicKey().build());
+ // 7. (0-port service, custom host, valid key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setZeroPortService()
+ .setCustomHost()
+ .setPublicKey().build());
+ // 8. (0-port service, custom host, no key): invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setZeroPortService()
+ .setCustomHost()
+ .setNoPublicKey().build()));
+ // 9. (0-port service, no-address custom host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setZeroPortService()
+ .setCustomHostNoAddress()
+ .setPublicKey().build());
+ // 10. (0-port service, no-address custom host, no key): invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setZeroPortService()
+ .setCustomHostNoAddress()
+ .setNoPublicKey().build()));
+ // 11. (0-port service, default host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setZeroPortService()
+ .setDefaultHost()
+ .setPublicKey().build());
+ // 12. (0-port service, default host, no key): invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setZeroPortService()
+ .setDefaultHost()
+ .setNoPublicKey().build()));
+ // 13. (no service, custom host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setNoService()
+ .setCustomHost()
+ .setPublicKey().build());
+ // 14. (no service, custom host, no key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setNoService()
+ .setCustomHost()
+ .setNoPublicKey().build());
+ // 15. (no service, no-address custom host, key): valid
+ checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setNoService()
+ .setCustomHostNoAddress()
+ .setPublicKey().build());
+ // 16. (no service, no-address custom host, no key): invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setNoService()
+ .setCustomHostNoAddress()
+ .setNoPublicKey().build()));
+ // 17. (no service, default host, key): invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setNoService()
+ .setDefaultHost()
+ .setPublicKey().build()));
+ // 18. (no service, default host, no key): invalid
+ mustFail(() -> checkServiceInfoForRegistration(
+ new NsdServiceInfoBuilder()
+ .setNoService()
+ .setDefaultHost()
+ .setNoPublicKey().build()));
+ }
+
public void mustFail(Runnable fn) {
try {
fn.run();
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index 3f6e88d..10ba6a4 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -163,7 +163,8 @@
val sentQueryCount = 150
val metrics = NetworkNsdReportedMetrics(clientId, deps)
metrics.reportServiceDiscoveryStop(true /* isLegacy */, transactionId, durationMs,
- foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount)
+ foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount,
+ true /* isServiceFromCache */)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -179,6 +180,7 @@
assertEquals(servicesCount, it.foundServiceCount)
assertEquals(durationMs, it.eventDurationMillisec)
assertEquals(sentQueryCount, it.sentQueryCount)
+ assertTrue(it.isKnownService)
}
}
@@ -231,8 +233,10 @@
val clientId = 99
val transactionId = 100
val durationMs = 10L
+ val sentQueryCount = 10
val metrics = NetworkNsdReportedMetrics(clientId, deps)
- metrics.reportServiceResolutionStop(true /* isLegacy */, transactionId, durationMs)
+ metrics.reportServiceResolutionStop(
+ true /* isLegacy */, transactionId, durationMs, sentQueryCount)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -243,6 +247,7 @@
assertEquals(NsdEventType.NET_RESOLVE, it.type)
assertEquals(MdnsQueryResult.MQR_SERVICE_RESOLUTION_STOP, it.queryResult)
assertEquals(durationMs, it.eventDurationMillisec)
+ assertEquals(sentQueryCount, it.sentQueryCount)
}
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index ea905d5..cbc060a 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -17,6 +17,7 @@
package com.android.server;
import static android.net.BpfNetMapsConstants.ALLOW_CHAINS;
+import static android.net.BpfNetMapsConstants.BACKGROUND_MATCH;
import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY;
import static android.net.BpfNetMapsConstants.DATA_SAVER_DISABLED;
@@ -31,13 +32,25 @@
import static android.net.BpfNetMapsConstants.OEM_DENY_1_MATCH;
import static android.net.BpfNetMapsConstants.OEM_DENY_2_MATCH;
import static android.net.BpfNetMapsConstants.OEM_DENY_3_MATCH;
-import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_ADMIN_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_USER_MATCH;
import static android.net.BpfNetMapsConstants.POWERSAVE_MATCH;
import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_OEM_DENY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -334,146 +347,6 @@
}
}
- private void doTestRemoveNaughtyApp(final int iif, final long match) throws Exception {
- mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
-
- mBpfNetMaps.removeNaughtyApp(TEST_UID);
-
- checkUidOwnerValue(TEST_UID, iif, match & ~PENALTY_BOX_MATCH);
- }
-
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testRemoveNaughtyApp() throws Exception {
- doTestRemoveNaughtyApp(NO_IIF, PENALTY_BOX_MATCH);
-
- // PENALTY_BOX_MATCH with other matches
- doTestRemoveNaughtyApp(NO_IIF, PENALTY_BOX_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH);
-
- // PENALTY_BOX_MATCH with IIF_MATCH
- doTestRemoveNaughtyApp(TEST_IF_INDEX, PENALTY_BOX_MATCH | IIF_MATCH);
-
- // PENALTY_BOX_MATCH is not enabled
- doTestRemoveNaughtyApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
- }
-
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testRemoveNaughtyAppMissingUid() {
- // UidOwnerMap does not have entry for TEST_UID
- assertThrows(ServiceSpecificException.class,
- () -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
- }
-
- @Test
- @IgnoreAfter(Build.VERSION_CODES.S_V2)
- public void testRemoveNaughtyAppBeforeT() {
- assertThrows(UnsupportedOperationException.class,
- () -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
- }
-
- private void doTestAddNaughtyApp(final int iif, final long match) throws Exception {
- if (match != NO_MATCH) {
- mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
- }
-
- mBpfNetMaps.addNaughtyApp(TEST_UID);
-
- checkUidOwnerValue(TEST_UID, iif, match | PENALTY_BOX_MATCH);
- }
-
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testAddNaughtyApp() throws Exception {
- doTestAddNaughtyApp(NO_IIF, NO_MATCH);
-
- // Other matches are enabled
- doTestAddNaughtyApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
-
- // IIF_MATCH is enabled
- doTestAddNaughtyApp(TEST_IF_INDEX, IIF_MATCH);
-
- // PENALTY_BOX_MATCH is already enabled
- doTestAddNaughtyApp(NO_IIF, PENALTY_BOX_MATCH | DOZABLE_MATCH);
- }
-
- @Test
- @IgnoreAfter(Build.VERSION_CODES.S_V2)
- public void testAddNaughtyAppBeforeT() {
- assertThrows(UnsupportedOperationException.class,
- () -> mBpfNetMaps.addNaughtyApp(TEST_UID));
- }
-
- private void doTestRemoveNiceApp(final int iif, final long match) throws Exception {
- mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
-
- mBpfNetMaps.removeNiceApp(TEST_UID);
-
- checkUidOwnerValue(TEST_UID, iif, match & ~HAPPY_BOX_MATCH);
- }
-
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testRemoveNiceApp() throws Exception {
- doTestRemoveNiceApp(NO_IIF, HAPPY_BOX_MATCH);
-
- // HAPPY_BOX_MATCH with other matches
- doTestRemoveNiceApp(NO_IIF, HAPPY_BOX_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH);
-
- // HAPPY_BOX_MATCH with IIF_MATCH
- doTestRemoveNiceApp(TEST_IF_INDEX, HAPPY_BOX_MATCH | IIF_MATCH);
-
- // HAPPY_BOX_MATCH is not enabled
- doTestRemoveNiceApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
- }
-
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testRemoveNiceAppMissingUid() {
- // UidOwnerMap does not have entry for TEST_UID
- assertThrows(ServiceSpecificException.class,
- () -> mBpfNetMaps.removeNiceApp(TEST_UID));
- }
-
- @Test
- @IgnoreAfter(Build.VERSION_CODES.S_V2)
- public void testRemoveNiceAppBeforeT() {
- assertThrows(UnsupportedOperationException.class,
- () -> mBpfNetMaps.removeNiceApp(TEST_UID));
- }
-
- private void doTestAddNiceApp(final int iif, final long match) throws Exception {
- if (match != NO_MATCH) {
- mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
- }
-
- mBpfNetMaps.addNiceApp(TEST_UID);
-
- checkUidOwnerValue(TEST_UID, iif, match | HAPPY_BOX_MATCH);
- }
-
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.S_V2)
- public void testAddNiceApp() throws Exception {
- doTestAddNiceApp(NO_IIF, NO_MATCH);
-
- // Other matches are enabled
- doTestAddNiceApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
-
- // IIF_MATCH is enabled
- doTestAddNiceApp(TEST_IF_INDEX, IIF_MATCH);
-
- // HAPPY_BOX_MATCH is already enabled
- doTestAddNiceApp(NO_IIF, HAPPY_BOX_MATCH | DOZABLE_MATCH);
- }
-
- @Test
- @IgnoreAfter(Build.VERSION_CODES.S_V2)
- public void testAddNiceAppBeforeT() {
- assertThrows(UnsupportedOperationException.class,
- () -> mBpfNetMaps.addNiceApp(TEST_UID));
- }
-
private void doTestUpdateUidLockdownRule(final int iif, final long match, final boolean add)
throws Exception {
if (match != NO_MATCH) {
@@ -658,6 +531,9 @@
doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_1);
doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_2);
doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_3);
+ doTestSetUidRule(FIREWALL_CHAIN_METERED_ALLOW);
+ doTestSetUidRule(FIREWALL_CHAIN_METERED_DENY_USER);
+ doTestSetUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN);
}
@Test
@@ -972,6 +848,21 @@
}
@Test
+ @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testGetNetPermFoUid() throws Exception {
+ mUidPermissionMap.deleteEntry(new S32(TEST_UID));
+ assertEquals(PERMISSION_INTERNET, mBpfNetMaps.getNetPermForUid(TEST_UID));
+
+ mUidPermissionMap.updateEntry(new S32(TEST_UID), new U8((short) PERMISSION_NONE));
+ assertEquals(PERMISSION_NONE, mBpfNetMaps.getNetPermForUid(TEST_UID));
+
+ mUidPermissionMap.updateEntry(new S32(TEST_UID),
+ new U8((short) (PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS)));
+ assertEquals(PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
+ mBpfNetMaps.getNetPermForUid(TEST_UID));
+ }
+
+ @Test
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testSwapActiveStatsMap() throws Exception {
mConfigurationMap.updateEntry(
@@ -1079,7 +970,7 @@
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testDumpUidOwnerMap() throws Exception {
doTestDumpUidOwnerMap(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH");
- doTestDumpUidOwnerMap(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH");
+ doTestDumpUidOwnerMap(PENALTY_BOX_USER_MATCH, "PENALTY_BOX_USER_MATCH");
doTestDumpUidOwnerMap(DOZABLE_MATCH, "DOZABLE_MATCH");
doTestDumpUidOwnerMap(STANDBY_MATCH, "STANDBY_MATCH");
doTestDumpUidOwnerMap(POWERSAVE_MATCH, "POWERSAVE_MATCH");
@@ -1089,6 +980,7 @@
doTestDumpUidOwnerMap(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH");
doTestDumpUidOwnerMap(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH");
doTestDumpUidOwnerMap(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH");
+ doTestDumpUidOwnerMap(PENALTY_BOX_ADMIN_MATCH, "PENALTY_BOX_ADMIN_MATCH");
doTestDumpUidOwnerMap(HAPPY_BOX_MATCH | POWERSAVE_MATCH,
"HAPPY_BOX_MATCH POWERSAVE_MATCH");
@@ -1137,7 +1029,6 @@
@IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testDumpUidOwnerMapConfig() throws Exception {
doTestDumpOwnerMatchConfig(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH");
- doTestDumpOwnerMatchConfig(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH");
doTestDumpOwnerMatchConfig(DOZABLE_MATCH, "DOZABLE_MATCH");
doTestDumpOwnerMatchConfig(STANDBY_MATCH, "STANDBY_MATCH");
doTestDumpOwnerMatchConfig(POWERSAVE_MATCH, "POWERSAVE_MATCH");
@@ -1283,4 +1174,138 @@
assertDumpContains(dump, TEST_V6_ADDRESS.getHostAddress());
assertDumpContains(dump, TEST_IF_INDEX + "(" + TEST_IF_NAME + ")");
}
+
+ private void doTestGetUidNetworkingBlockedReasons(
+ final long configurationMatches,
+ final long uidRules,
+ final short dataSaverStatus,
+ final int expectedBlockedReasons
+ ) throws Exception {
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(configurationMatches));
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(NULL_IIF, uidRules));
+ mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(dataSaverStatus));
+
+ assertEquals(expectedBlockedReasons, mBpfNetMaps.getUidNetworkingBlockedReasons(TEST_UID));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testGetUidNetworkingBlockedReasons() throws Exception {
+ doTestGetUidNetworkingBlockedReasons(
+ NO_MATCH,
+ NO_MATCH,
+ DATA_SAVER_DISABLED,
+ BLOCKED_REASON_NONE
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ DOZABLE_MATCH,
+ NO_MATCH,
+ DATA_SAVER_DISABLED,
+ BLOCKED_REASON_DOZE
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ DOZABLE_MATCH | POWERSAVE_MATCH | STANDBY_MATCH,
+ DOZABLE_MATCH | STANDBY_MATCH,
+ DATA_SAVER_DISABLED,
+ BLOCKED_REASON_BATTERY_SAVER | BLOCKED_REASON_APP_STANDBY
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH,
+ OEM_DENY_1_MATCH | OEM_DENY_3_MATCH,
+ DATA_SAVER_DISABLED,
+ BLOCKED_REASON_OEM_DENY
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ DOZABLE_MATCH,
+ DOZABLE_MATCH | BACKGROUND_MATCH | STANDBY_MATCH,
+ DATA_SAVER_DISABLED,
+ BLOCKED_REASON_NONE
+ );
+
+ // Note that HAPPY_BOX and PENALTY_BOX are not disabled by configuration map
+ doTestGetUidNetworkingBlockedReasons(
+ NO_MATCH,
+ PENALTY_BOX_USER_MATCH,
+ DATA_SAVER_DISABLED,
+ BLOCKED_METERED_REASON_USER_RESTRICTED
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ NO_MATCH,
+ PENALTY_BOX_ADMIN_MATCH,
+ DATA_SAVER_ENABLED,
+ BLOCKED_METERED_REASON_ADMIN_DISABLED | BLOCKED_METERED_REASON_DATA_SAVER
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ NO_MATCH,
+ PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH | HAPPY_BOX_MATCH,
+ DATA_SAVER_ENABLED,
+ BLOCKED_METERED_REASON_USER_RESTRICTED | BLOCKED_METERED_REASON_ADMIN_DISABLED
+ );
+ doTestGetUidNetworkingBlockedReasons(
+ STANDBY_MATCH,
+ STANDBY_MATCH | PENALTY_BOX_USER_MATCH | HAPPY_BOX_MATCH,
+ DATA_SAVER_ENABLED,
+ BLOCKED_REASON_APP_STANDBY | BLOCKED_METERED_REASON_USER_RESTRICTED
+ );
+ }
+
+ private void doTestIsUidRestrictedOnMeteredNetworks(
+ final long enabledMatches,
+ final long uidRules,
+ final short dataSaver,
+ final boolean expectedRestricted
+ ) throws Exception {
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(enabledMatches));
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(NULL_IIF, uidRules));
+ mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(dataSaver));
+
+ assertEquals(expectedRestricted, mBpfNetMaps.isUidRestrictedOnMeteredNetworks(TEST_UID));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
+ doTestIsUidRestrictedOnMeteredNetworks(
+ NO_MATCH,
+ NO_MATCH,
+ DATA_SAVER_DISABLED,
+ false /* expectRestricted */
+ );
+ doTestIsUidRestrictedOnMeteredNetworks(
+ DOZABLE_MATCH | POWERSAVE_MATCH | STANDBY_MATCH,
+ DOZABLE_MATCH | STANDBY_MATCH ,
+ DATA_SAVER_DISABLED,
+ false /* expectRestricted */
+ );
+ doTestIsUidRestrictedOnMeteredNetworks(
+ NO_MATCH,
+ PENALTY_BOX_USER_MATCH,
+ DATA_SAVER_DISABLED,
+ true /* expectRestricted */
+ );
+ doTestIsUidRestrictedOnMeteredNetworks(
+ NO_MATCH,
+ PENALTY_BOX_ADMIN_MATCH,
+ DATA_SAVER_DISABLED,
+ true /* expectRestricted */
+ );
+ doTestIsUidRestrictedOnMeteredNetworks(
+ NO_MATCH,
+ PENALTY_BOX_USER_MATCH | PENALTY_BOX_ADMIN_MATCH | HAPPY_BOX_MATCH,
+ DATA_SAVER_DISABLED,
+ true /* expectRestricted */
+ );
+ doTestIsUidRestrictedOnMeteredNetworks(
+ NO_MATCH,
+ NO_MATCH,
+ DATA_SAVER_ENABLED,
+ true /* expectRestricted */
+ );
+ doTestIsUidRestrictedOnMeteredNetworks(
+ NO_MATCH,
+ HAPPY_BOX_MATCH,
+ DATA_SAVER_ENABLED,
+ false /* expectRestricted */
+ );
+ }
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 7822fe0..8526a9a 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -53,6 +53,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_DEVICE_TYPE;
@@ -63,6 +64,9 @@
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
@@ -84,6 +88,7 @@
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
@@ -148,6 +153,7 @@
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
import static android.net.Proxy.PROXY_CHANGE_ACTION;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
@@ -158,7 +164,6 @@
import static android.telephony.DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
import static com.android.server.ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK;
-import static com.android.server.ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION;
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
@@ -173,6 +178,7 @@
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN;
+import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS;
import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.ConcurrentUtils.await;
@@ -457,6 +463,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -799,8 +806,10 @@
// This relies on all contexts for a given user returning the same UM mock
final DevicePolicyManager dpmMock = createContextAsUser(userHandle, 0 /* flags */)
.getSystemService(DevicePolicyManager.class);
- doReturn(value).when(dpmMock).getDeviceOwner();
- doReturn(value).when(mDevicePolicyManager).getDeviceOwner();
+ ComponentName componentName = value == null
+ ? null : new ComponentName(value, "deviceOwnerClass");
+ doReturn(componentName).when(dpmMock).getDeviceOwnerComponentOnAnyUser();
+ doReturn(componentName).when(mDevicePolicyManager).getDeviceOwnerComponentOnAnyUser();
}
@Override
@@ -1738,7 +1747,15 @@
private void setBlockedReasonChanged(int blockedReasons) {
mBlockedReasons = blockedReasons;
- mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
+ if (mDeps.isAtLeastV()) {
+ visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(),
+ () -> mService.handleBlockedReasonsChanged(
+ List.of(new Pair<>(Process.myUid(), blockedReasons))
+
+ ));
+ } else {
+ mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
+ }
}
private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) {
@@ -1919,11 +1936,16 @@
mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS;
- final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
- ArgumentCaptor.forClass(NetworkPolicyCallback.class);
- verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
- policyCallbackCaptor.capture());
- mPolicyCallback = policyCallbackCaptor.getValue();
+ if (mDeps.isAtLeastV()) {
+ verify(mNetworkPolicyManager, never()).registerNetworkPolicyCallback(any(), any());
+ mPolicyCallback = null;
+ } else {
+ final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkPolicyCallback.class);
+ verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(),
+ policyCallbackCaptor.capture());
+ mPolicyCallback = policyCallbackCaptor.getValue();
+ }
// Create local CM before sending system ready so that we can answer
// getSystemService() correctly.
@@ -1939,6 +1961,10 @@
setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT);
setAlwaysOnNetworks(false);
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
+
+ mDeps.setChangeIdEnabled(
+ true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, Process.myUid());
+ doReturn(PERMISSION_INTERNET).when(mBpfNetMaps).getNetPermForUid(anyInt());
// Note : Please do not add any new instrumentation here. If you need new instrumentation,
// please add it in CSTest and use subclasses of CSTest instead of adding more
// tools in ConnectivityServiceTest.
@@ -2152,13 +2178,10 @@
switch (name) {
case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
- return true;
case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
- return true;
+ case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
- case DELAY_DESTROY_FROZEN_SOCKETS_VERSION:
- return true;
default:
return super.isFeatureEnabled(context, name);
}
@@ -2175,6 +2198,8 @@
return true;
case BACKGROUND_FIREWALL_CHAIN:
return true;
+ case DELAY_DESTROY_SOCKETS:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -7515,13 +7540,13 @@
@Test
public void testNetworkCallbackMaximum() throws Exception {
final int MAX_REQUESTS = 100;
- final int CALLBACKS = 87;
+ final int CALLBACKS = 88;
final int DIFF_INTENTS = 10;
final int SAME_INTENTS = 10;
final int SYSTEM_ONLY_MAX_REQUESTS = 250;
- // Assert 1 (Default request filed before testing) + CALLBACKS + DIFF_INTENTS +
- // 1 (same intent) = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
- assertEquals(MAX_REQUESTS - 1, 1 + CALLBACKS + DIFF_INTENTS + 1);
+ // CALLBACKS + DIFF_INTENTS + 1 (same intent)
+ // = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
+ assertEquals(MAX_REQUESTS - 1, CALLBACKS + DIFF_INTENTS + 1);
NetworkRequest networkRequest = new NetworkRequest.Builder().build();
ArrayList<Object> registered = new ArrayList<>();
@@ -9855,6 +9880,28 @@
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellAgent);
+ // Remove PERMISSION_INTERNET and disable NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+ doReturn(INetd.PERMISSION_NONE).when(mBpfNetMaps).getNetPermForUid(Process.myUid());
+ mDeps.setChangeIdEnabled(false,
+ NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION, Process.myUid());
+
+ setBlockedReasonChanged(BLOCKED_REASON_DOZE);
+ if (mDeps.isAtLeastV()) {
+ // On V+, network access from app that does not have INTERNET permission is considered
+ // not blocked if NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION is disabled.
+ // So blocked status does not change from BLOCKED_REASON_NONE
+ cellNetworkCallback.assertNoCallback();
+ detailedCallback.assertNoCallback();
+ } else {
+ // On U-, onBlockedStatusChanged callback is called with blocked reasons CS receives
+ // from NPMS callback regardless of permission app has.
+ // Note that this cannot actually happen because on U-, NPMS will never notify any
+ // blocked reasons for apps that don't have the INTERNET permission.
+ cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+ detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+ cb -> cb.getReason() == BLOCKED_REASON_DOZE);
+ }
+
mCm.unregisterNetworkCallback(cellNetworkCallback);
}
@@ -10499,6 +10546,9 @@
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_3, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, FIREWALL_RULE_DENY);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, FIREWALL_RULE_ALLOW);
+ doTestSetUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, FIREWALL_RULE_ALLOW);
}
@Test @IgnoreUpTo(SC_V2)
@@ -19190,6 +19240,25 @@
verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
}
- // Note : adding tests is ConnectivityServiceTest is deprecated, as it is too big for
+ private static final int EXPECTED_TEST_METHOD_COUNT = 332;
+
+ @Test
+ public void testTestMethodCount() {
+ final Class<?> testClass = this.getClass();
+
+ int actualTestMethodCount = 0;
+ for (final Method method : testClass.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(Test.class)) {
+ actualTestMethodCount++;
+ }
+ }
+
+ assertEquals("Adding tests in ConnectivityServiceTest is deprecated, "
+ + "as it is too big for maintenance. Please consider adding new tests "
+ + "in subclasses of CSTest instead.",
+ EXPECTED_TEST_METHOD_COUNT, actualTestMethodCount);
+ }
+
+ // Note : adding tests in ConnectivityServiceTest is deprecated, as it is too big for
// maintenance. Please consider adding new tests in subclasses of CSTest instead.
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 881de56..979e0a1 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -41,6 +41,7 @@
import static com.android.server.NsdService.DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF;
import static com.android.server.NsdService.MdnsListener;
import static com.android.server.NsdService.NO_TRANSACTION;
+import static com.android.server.NsdService.checkHostname;
import static com.android.server.NsdService.parseTypeAndSubtype;
import static com.android.testutils.ContextUtils.mockService;
@@ -53,6 +54,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -521,6 +523,56 @@
}
@Test
+ @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+ public void testDiscoverOnTetheringDownstream_DiscoveryManager() throws Exception {
+ final NsdManager client = connectClient(mService);
+ final DiscoveryListener discListener = mock(DiscoveryListener.class);
+ client.discoverServices(SERVICE_TYPE, PROTOCOL, discListener);
+ waitForIdle();
+
+ final ArgumentCaptor<MdnsServiceBrowserListener> discoverListenerCaptor =
+ ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+ final InOrder discManagerOrder = inOrder(mDiscoveryManager);
+ final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+ discManagerOrder.verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
+ discoverListenerCaptor.capture(), any());
+
+ final int interfaceIdx = 123;
+ final MdnsServiceInfo mockServiceInfo = new MdnsServiceInfo(
+ SERVICE_NAME, /* serviceInstanceName */
+ serviceTypeWithLocalDomain.split("\\."), /* serviceType */
+ List.of(), /* subtypes */
+ new String[] {"android", "local"}, /* hostName */
+ 12345, /* port */
+ List.of(IPV4_ADDRESS),
+ List.of(IPV6_ADDRESS),
+ List.of(), /* textStrings */
+ List.of(), /* textEntries */
+ interfaceIdx, /* interfaceIndex */
+ null /* network */,
+ Instant.MAX /* expirationTime */);
+
+ // Verify service is found with the interface index
+ discoverListenerCaptor.getValue().onServiceNameDiscovered(
+ mockServiceInfo, false /* isServiceFromCache */);
+ final ArgumentCaptor<NsdServiceInfo> foundInfoCaptor =
+ ArgumentCaptor.forClass(NsdServiceInfo.class);
+ verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(foundInfoCaptor.capture());
+ final NsdServiceInfo foundInfo = foundInfoCaptor.getValue();
+ assertNull(foundInfo.getNetwork());
+ assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
+
+ // Using the returned service info to resolve or register callback uses the interface index
+ client.resolveService(foundInfo, mock(ResolveListener.class));
+ client.registerServiceInfoCallback(foundInfo, Runnable::run,
+ mock(ServiceInfoCallback.class));
+ waitForIdle();
+
+ discManagerOrder.verify(mDiscoveryManager, times(2)).registerListener(any(), any(), argThat(
+ o -> o.getNetwork() == null && o.getInterfaceIndex() == interfaceIdx));
+ }
+
+ @Test
@DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
@DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testDiscoverOnBlackholeNetwork() throws Exception {
@@ -854,7 +906,7 @@
request.getServiceName().equals(ns.getServiceName())
&& request.getServiceType().equals(ns.getServiceType())));
verify(mMetrics).reportServiceResolutionStop(
- true /* isLegacy */, resolveId, 10L /* durationMs */);
+ true /* isLegacy */, resolveId, 10L /* durationMs */, 0 /* sentQueryCount */);
}
@Test
@@ -928,7 +980,7 @@
request.getServiceName().equals(ns.getServiceName())
&& request.getServiceType().equals(ns.getServiceType())));
verify(mMetrics).reportServiceResolutionStop(
- true /* isLegacy */, getAddrId, 10L /* durationMs */);
+ true /* isLegacy */, getAddrId, 10L /* durationMs */, 0 /* sentQueryCount */);
}
private void verifyUpdatedServiceInfo(NsdServiceInfo info, String serviceName,
@@ -1144,7 +1196,7 @@
Instant.MAX /* expirationTime */);
// Verify onServiceNameDiscovered callback
- listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */);
+ listener.onServiceNameDiscovered(foundInfo, true /* isServiceFromCache */);
verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(argThat(info ->
info.getServiceName().equals(SERVICE_NAME)
// Service type in discovery callbacks has a dot at the end
@@ -1180,7 +1232,7 @@
verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
verify(mMetrics).reportServiceDiscoveryStop(false /* isLegacy */, discId,
10L /* durationMs */, 1 /* foundCallbackCount */, 1 /* lostCallbackCount */,
- 1 /* servicesCount */, 3 /* sentQueryCount */);
+ 1 /* servicesCount */, 3 /* sentQueryCount */, true /* isServiceFromCache */);
}
@Test
@@ -1629,20 +1681,23 @@
// Subtypes are not used for resolution, only for discovery
assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
+ final MdnsListener listener = listenerCaptor.getValue();
+ // Callbacks for query sent.
+ listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
+
doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
client.stopServiceResolution(resolveListener);
waitForIdle();
// Verify the listener has been unregistered.
- final MdnsListener listener = listenerCaptor.getValue();
verify(mDiscoveryManager, timeout(TIMEOUT_MS))
.unregisterListener(eq(constructedServiceType), eq(listener));
verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
request.getServiceName().equals(ns.getServiceName())
&& request.getServiceType().equals(ns.getServiceType())));
verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
- verify(mMetrics).reportServiceResolutionStop(
- false /* isLegacy */, listener.mTransactionId, 10L /* durationMs */);
+ verify(mMetrics).reportServiceResolutionStop(false /* isLegacy */, listener.mTransactionId,
+ 10L /* durationMs */, 1 /* sentQueryCount */);
}
@Test
@@ -1673,6 +1728,36 @@
}
@Test
+ public void TestCheckHostname() {
+ // Valid cases
+ assertTrue(checkHostname(null));
+ assertTrue(checkHostname("a"));
+ assertTrue(checkHostname("1"));
+ assertTrue(checkHostname("a-1234-bbbb-cccc000"));
+ assertTrue(checkHostname("A-1234-BBbb-CCCC000"));
+ assertTrue(checkHostname("1234-bbbb-cccc000"));
+ assertTrue(checkHostname("0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcde" // 63 characters
+ ));
+
+ // Invalid cases
+ assertFalse(checkHostname("?"));
+ assertFalse(checkHostname("/"));
+ assertFalse(checkHostname("a-"));
+ assertFalse(checkHostname("B-"));
+ assertFalse(checkHostname("-A"));
+ assertFalse(checkHostname("-b"));
+ assertFalse(checkHostname("-1-"));
+ assertFalse(checkHostname("0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcdef"
+ + "0123456789abcdef" // 64 characters
+ ));
+ }
+
+ @Test
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
public void testEnablePlatformMdnsBackend() {
final NsdManager client = connectClient(mService);
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index da7fda3..72dde7f 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -418,21 +419,6 @@
inOrder.verify(mDeps).generateIpv6Address(eq(BASE_IFACE),
eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(NAT64_PREFIX_STRING), eq(MARK));
- // Open, configure and bring up the tun interface.
- inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
- inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
- inOrder.verify(mDeps).getInterfaceIndex(eq(STACKED_IFACE));
- inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
- inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
- inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
- eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
- inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
- STACKED_IFACE.equals(cfg.ifName)
- && XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
- && (32 == cfg.prefixLength)
- && "".equals(cfg.hwAddr)
- && assertContainsFlag(cfg.flags, IF_STATE_UP)));
-
// Open and configure 464xlat read/write sockets.
inOrder.verify(mDeps).openPacketSocket();
inOrder.verify(mDeps).adoptFd(eq(PACKET_SOCK_FD));
@@ -449,6 +435,21 @@
argThat(fd -> Objects.equals(PACKET_SOCK_PFD.getFileDescriptor(), fd)),
eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(BASE_IFINDEX));
+ // Open, configure and bring up the tun interface.
+ inOrder.verify(mDeps).createTunInterface(eq(STACKED_IFACE));
+ inOrder.verify(mDeps).adoptFd(eq(TUN_FD));
+ inOrder.verify(mDeps).getInterfaceIndex(eq(STACKED_IFACE));
+ inOrder.verify(mNetd).interfaceSetEnableIPv6(eq(STACKED_IFACE), eq(false /* enable */));
+ inOrder.verify(mDeps).detectMtu(eq(NAT64_PREFIX_STRING), eq(GOOGLE_DNS_4), eq(MARK));
+ inOrder.verify(mNetd).interfaceSetMtu(eq(STACKED_IFACE),
+ eq(1472 /* ETHER_MTU(1500) - MTU_DELTA(28) */));
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ STACKED_IFACE.equals(cfg.ifName)
+ && XLAT_LOCAL_IPV4ADDR_STRING.equals(cfg.ipv4Addr)
+ && (32 == cfg.prefixLength)
+ && "".equals(cfg.hwAddr)
+ && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+
// Start clatd.
inOrder.verify(mDeps).startClatd(
argThat(fd -> Objects.equals(TUN_PFD.getFileDescriptor(), fd)),
@@ -630,9 +631,13 @@
public int createTunInterface(@NonNull String tuniface) throws IOException {
throw new IOException();
}
+ @Override
+ public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
+ return mock(IBpfMap.class);
+ }
}
checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
- false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@Test
@@ -643,9 +648,14 @@
throws IOException {
throw new IOException();
}
+
+ @Override
+ public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() {
+ return mock(IBpfMap.class);
+ }
}
checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
- false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@Test
@@ -656,7 +666,7 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@@ -668,7 +678,7 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
true /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
}
@@ -681,7 +691,7 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@@ -694,7 +704,7 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@@ -721,7 +731,7 @@
throw new IOException();
}
}
- checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
@@ -733,7 +743,7 @@
return null;
}
}
- checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 44512bb..ea3d2dd 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -29,8 +29,6 @@
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
-import static com.android.testutils.MiscAsserts.assertContainsExactly;
-import static com.android.testutils.MiscAsserts.assertContainsStringsExactly;
import static com.android.testutils.MiscAsserts.assertFieldCountEquals;
import static org.junit.Assert.assertEquals;
@@ -38,12 +36,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivitySettingsManager;
@@ -74,7 +72,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -122,29 +119,6 @@
assertFieldCountEquals(3, ResolverOptionsParcel.class);
}
- private void assertResolverParamsEquals(@NonNull ResolverParamsParcel actual,
- @NonNull ResolverParamsParcel expected) {
- assertEquals(actual.netId, expected.netId);
- assertEquals(actual.sampleValiditySeconds, expected.sampleValiditySeconds);
- assertEquals(actual.successThreshold, expected.successThreshold);
- assertEquals(actual.minSamples, expected.minSamples);
- assertEquals(actual.maxSamples, expected.maxSamples);
- assertEquals(actual.baseTimeoutMsec, expected.baseTimeoutMsec);
- assertEquals(actual.retryCount, expected.retryCount);
- assertContainsStringsExactly(actual.servers, expected.servers);
- assertContainsStringsExactly(actual.domains, expected.domains);
- assertEquals(actual.tlsName, expected.tlsName);
- assertContainsStringsExactly(actual.tlsServers, expected.tlsServers);
- assertContainsStringsExactly(actual.tlsFingerprints, expected.tlsFingerprints);
- assertEquals(actual.caCertificate, expected.caCertificate);
- assertEquals(actual.tlsConnectTimeoutMs, expected.tlsConnectTimeoutMs);
- assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
- assertContainsExactly(actual.transportTypes, expected.transportTypes);
- assertEquals(actual.meteredNetwork, expected.meteredNetwork);
- assertEquals(actual.dohParams, expected.dohParams);
- assertFieldCountEquals(18, ResolverParamsParcel.class);
- }
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -365,11 +339,6 @@
mDnsManager.noteDnsServersForNetwork(TEST_NETID, lp);
mDnsManager.flushVmDnsCache();
- final ArgumentCaptor<ResolverParamsParcel> resolverParamsParcelCaptor =
- ArgumentCaptor.forClass(ResolverParamsParcel.class);
- verify(mMockDnsResolver, times(1)).setResolverConfiguration(
- resolverParamsParcelCaptor.capture());
- final ResolverParamsParcel actualParams = resolverParamsParcelCaptor.getValue();
final ResolverParamsParcel expectedParams = new ResolverParamsParcel();
expectedParams.netId = TEST_NETID;
expectedParams.sampleValiditySeconds = TEST_DEFAULT_SAMPLE_VALIDITY_SECONDS;
@@ -384,7 +353,8 @@
expectedParams.resolverOptions = null;
expectedParams.meteredNetwork = true;
expectedParams.dohParams = null;
- assertResolverParamsEquals(actualParams, expectedParams);
+ expectedParams.interfaceNames = new String[]{TEST_IFACENAME};
+ verify(mMockDnsResolver, times(1)).setResolverConfiguration(eq(expectedParams));
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt b/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt
index 6c2c256..5c994f5 100644
--- a/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MulticastRoutingCoordinatorServiceTest.kt
@@ -402,15 +402,18 @@
mService.getVirtualInterfaceIndex(mIfName1), oifsUpdate)
val mf6cctlDel = createStructMf6cctl(mSourceAddress, mGroupAddressScope5,
mService.getVirtualInterfaceIndex(mIfName1), mEmptyOifs)
+ val ifName1Copy = String(mIfName1.toCharArray())
+ val ifName2Copy = String(mIfName2.toCharArray())
+ val ifName3Copy = String(mIfName3.toCharArray())
verify(mDeps).setsockoptMrt6AddMfc(eq(mFd), eq(mf6cctlAdd))
- applyMulticastForwardNone(mIfName1, mIfName2)
+ applyMulticastForwardNone(ifName1Copy, ifName2Copy)
mLooper.dispatchAll()
verify(mDeps).setsockoptMrt6AddMfc(eq(mFd), eq(mf6cctlUpdate))
- applyMulticastForwardNone(mIfName1, mIfName3)
+ applyMulticastForwardNone(ifName1Copy, ifName3Copy)
mLooper.dispatchAll()
verify(mDeps, timeout(TIMEOUT_MS).times(1)).setsockoptMrt6DelMfc(eq(mFd), eq(mf6cctlDel))
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 5251e2a..b5c0132 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -18,6 +18,8 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -65,8 +67,9 @@
private static final String SERVICE_TYPE_2 = "_test._tcp.local";
private static final Network NETWORK_1 = Mockito.mock(Network.class);
private static final Network NETWORK_2 = Mockito.mock(Network.class);
+ private static final int INTERFACE_INDEX_NULL_NETWORK = 123;
private static final SocketKey SOCKET_KEY_NULL_NETWORK =
- new SocketKey(null /* network */, 999 /* interfaceIndex */);
+ new SocketKey(null /* network */, INTERFACE_INDEX_NULL_NETWORK);
private static final SocketKey SOCKET_KEY_NETWORK_1 =
new SocketKey(NETWORK_1, 998 /* interfaceIndex */);
private static final SocketKey SOCKET_KEY_NETWORK_2 =
@@ -97,6 +100,8 @@
private HandlerThread thread;
private Handler handler;
+ private int createdServiceTypeClientCount;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -106,11 +111,13 @@
handler = new Handler(thread.getLooper());
doReturn(thread.getLooper()).when(socketClient).getLooper();
doReturn(true).when(socketClient).supportsRequestingSpecificNetworks();
+ createdServiceTypeClientCount = 0;
discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient,
sharedLog, MdnsFeatureFlags.newBuilder().build()) {
@Override
MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
@NonNull SocketKey socketKey) {
+ createdServiceTypeClientCount++;
final Pair<String, SocketKey> perSocketServiceType =
Pair.create(serviceType, socketKey);
if (perSocketServiceType.equals(PER_SOCKET_SERVICE_TYPE_1_NULL_NETWORK)) {
@@ -128,6 +135,7 @@
PER_SOCKET_SERVICE_TYPE_2_NETWORK_2)) {
return mockServiceTypeClientType2Network2;
}
+ fail("Unexpected perSocketServiceType: " + perSocketServiceType);
return null;
}
};
@@ -324,7 +332,6 @@
// Receive a response, it should be processed on the client.
final MdnsPacket response = createMdnsPacket(SERVICE_TYPE_1);
- final int ifIndex = 1;
runOnHandler(() -> discoveryManager.onResponseReceived(response, SOCKET_KEY_NULL_NETWORK));
verify(mockServiceTypeClientType1NullNetwork).processResponse(
response, SOCKET_KEY_NULL_NETWORK);
@@ -350,6 +357,39 @@
verify(socketClient, never()).stopDiscovery();
}
+ @Test
+ public void testInterfaceIndexRequested_OnlyUsesSelectedInterface() throws IOException {
+ final MdnsSearchOptions searchOptions =
+ MdnsSearchOptions.newBuilder()
+ .setNetwork(null /* network */)
+ .setInterfaceIndex(INTERFACE_INDEX_NULL_NETWORK)
+ .build();
+
+ final SocketCreationCallback callback = expectSocketCreationCallback(
+ SERVICE_TYPE_1, mockListenerOne, searchOptions);
+ final SocketKey unusedIfaceKey = new SocketKey(null, INTERFACE_INDEX_NULL_NETWORK + 1);
+ final SocketKey matchingIfaceWithNetworkKey =
+ new SocketKey(Mockito.mock(Network.class), INTERFACE_INDEX_NULL_NETWORK);
+ runOnHandler(() -> {
+ callback.onSocketCreated(unusedIfaceKey);
+ callback.onSocketCreated(matchingIfaceWithNetworkKey);
+ callback.onSocketCreated(SOCKET_KEY_NULL_NETWORK);
+ callback.onSocketCreated(SOCKET_KEY_NETWORK_1);
+ });
+ // Only the client for INTERFACE_INDEX_NULL_NETWORK is created
+ verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(
+ mockListenerOne, searchOptions);
+ assertEquals(1, createdServiceTypeClientCount);
+
+ runOnHandler(() -> {
+ callback.onSocketDestroyed(SOCKET_KEY_NETWORK_1);
+ callback.onSocketDestroyed(SOCKET_KEY_NULL_NETWORK);
+ callback.onSocketDestroyed(matchingIfaceWithNetworkKey);
+ callback.onSocketDestroyed(unusedIfaceKey);
+ });
+ verify(mockServiceTypeClientType1NullNetwork).notifySocketDestroyed();
+ }
+
private MdnsPacket createMdnsPacket(String serviceType) {
final String[] type = TextUtils.split(serviceType, "\\.");
final ArrayList<String> name = new ArrayList<>(type.length + 1);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 271cc65..2cb97c9 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -21,12 +21,13 @@
import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.os.HandlerThread
+import com.android.net.module.util.HexDump.hexStringToByteArray
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST
import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE
-import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_A
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_AAAA
+import com.android.server.connectivity.mdns.MdnsRecord.TYPE_KEY
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_PTR
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_SRV
import com.android.server.connectivity.mdns.MdnsRecord.TYPE_TXT
@@ -52,10 +53,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
private const val TEST_SERVICE_ID_1 = 42
private const val TEST_SERVICE_ID_2 = 43
@@ -125,6 +122,20 @@
port = TEST_PORT
}
+private val TEST_PUBLIC_KEY = hexStringToByteArray(
+ "0201030dc141d0637960b98cbc12cfca"
+ + "221d2879dac26ee5b460e9007c992e19"
+ + "02d897c391b03764d448f7d0c772fdb0"
+ + "3b1d9d6d52ff8886769e8e2362513565"
+ + "270962d3")
+
+private val TEST_PUBLIC_KEY_2 = hexStringToByteArray(
+ "0201030dc141d0637960b98cbc12cfca"
+ + "221d2879dac26ee5b460e9007c992e19"
+ + "02d897c391b03764d448f7d0c772fdb0"
+ + "3b1d9d6d52ff8886769e8e2362513565"
+ + "270962d4")
+
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsRecordRepositoryTest {
@@ -132,10 +143,23 @@
private val deps = object : Dependencies() {
override fun getInterfaceInetAddresses(iface: NetworkInterface) =
Collections.enumeration(TEST_ADDRESSES.map { it.address })
+
+ override fun elapsedRealTime() = now
+
+ fun elapse(duration: Long) {
+ now += duration
+ }
+
+ fun resetElapsedRealTime() {
+ now = 100
+ }
+
+ var now: Long = 100
}
@Before
fun setUp() {
+ deps.resetElapsedRealTime();
thread.start()
}
@@ -573,6 +597,7 @@
TYPE_PTR -> return MdnsPointerRecord(name, false /* isUnicast */)
TYPE_SRV -> return MdnsServiceRecord(name, false /* isUnicast */)
TYPE_TXT -> return MdnsTextRecord(name, false /* isUnicast */)
+ TYPE_KEY -> return MdnsKeyRecord(name, false /* isUnicast */)
TYPE_A, TYPE_AAAA -> return MdnsInetAddressRecord(name, type, false /* isUnicast */)
else -> fail("Unexpected question type: $type")
}
@@ -900,6 +925,159 @@
}
@Test
+ fun testGetReply_keyQuestionForServiceName_returnsKeyRecord() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService1"
+ port = TEST_PORT
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService2"
+ port = 0 // No SRV RR
+ publicKey = TEST_PUBLIC_KEY
+ })
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+ val serviceName1 = arrayOf("MyTestService1", "_testservice", "_tcp", "local")
+ val serviceName2 = arrayOf("MyTestService2", "_testservice", "_tcp", "local")
+
+ val query1 = makeQuery(TYPE_KEY to serviceName1)
+ val reply1 = repository.getReply(query1, src)
+
+ assertNotNull(reply1)
+ assertEquals(listOf(MdnsKeyRecord(serviceName1,
+ 0, false, LONG_TTL, TEST_PUBLIC_KEY)),
+ reply1.answers)
+ assertEquals(listOf(),
+ reply1.additionalAnswers)
+
+ val query2 = makeQuery(TYPE_KEY to serviceName2)
+ val reply2 = repository.getReply(query2, src)
+
+ assertNotNull(reply2)
+ assertEquals(listOf(MdnsKeyRecord(serviceName2,
+ 0, false, LONG_TTL, TEST_PUBLIC_KEY)),
+ reply2.answers)
+ assertEquals(listOf(MdnsNsecRecord(serviceName2,
+ 0L, true, SHORT_TTL,
+ serviceName2 /* nextDomain */,
+ intArrayOf(TYPE_KEY))),
+ reply2.additionalAnswers)
+ }
+
+ @Test
+ fun testGetReply_keyQuestionForHostname_returnsKeyRecord() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ hostname = "MyHost1"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
+ hostname = "MyHost2"
+ hostAddresses = listOf() // No address records
+ publicKey = TEST_PUBLIC_KEY
+ })
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+ val hostname1 = arrayOf("MyHost1", "local")
+ val hostname2 = arrayOf("MyHost2", "local")
+
+ val query1 = makeQuery(TYPE_KEY to hostname1)
+ val reply1 = repository.getReply(query1, src)
+
+ assertNotNull(reply1)
+ assertEquals(listOf(MdnsKeyRecord(hostname1,
+ 0, false, LONG_TTL, TEST_PUBLIC_KEY)),
+ reply1.answers)
+ assertEquals(listOf(),
+ reply1.additionalAnswers)
+
+ val query2 = makeQuery(TYPE_KEY to hostname2)
+ val reply2 = repository.getReply(query2, src)
+
+ assertNotNull(reply2)
+ assertEquals(listOf(MdnsKeyRecord(hostname2,
+ 0, false, LONG_TTL, TEST_PUBLIC_KEY)),
+ reply2.answers)
+ assertEquals(listOf(MdnsNsecRecord(hostname2, 0L, true, SHORT_TTL,
+ hostname2 /* nextDomain */,
+ intArrayOf(TYPE_KEY))),
+ reply2.additionalAnswers)
+ }
+
+ @Test
+ fun testGetReply_keyRecordForHostRemoved_noAnswertoKeyQuestion() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ hostname = "MyHost1"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
+ hostname = "MyHost2"
+ hostAddresses = listOf() // No address records
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.removeService(TEST_SERVICE_ID_1)
+ repository.removeService(TEST_SERVICE_ID_2)
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+ val hostname1 = arrayOf("MyHost1", "local")
+ val hostname2 = arrayOf("MyHost2", "local")
+
+ val query1 = makeQuery(TYPE_KEY to hostname1)
+ val reply1 = repository.getReply(query1, src)
+
+ assertNull(reply1)
+
+ val query2 = makeQuery(TYPE_KEY to hostname2)
+ val reply2 = repository.getReply(query2, src)
+
+ assertNull(reply2)
+ }
+
+ @Test
+ fun testGetReply_keyRecordForServiceRemoved_noAnswertoKeyQuestion() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService1"
+ port = TEST_PORT
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService2"
+ port = 0 // No SRV RR
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.removeService(TEST_SERVICE_ID_1)
+ repository.removeService(TEST_SERVICE_ID_2)
+ val src = InetSocketAddress(parseNumericAddress("fe80::123"), 5353)
+ val serviceName1 = arrayOf("MyTestService1", "_testservice", "_tcp", "local")
+ val serviceName2 = arrayOf("MyTestService2", "_testservice", "_tcp", "local")
+
+ val query1 = makeQuery(TYPE_KEY to serviceName1)
+ val reply1 = repository.getReply(query1, src)
+
+ assertNull(reply1)
+
+ val query2 = makeQuery(TYPE_KEY to serviceName2)
+ val reply2 = repository.getReply(query2, src)
+
+ assertNull(reply2)
+ }
+
+ @Test
fun testGetReply_customHostRemoved_noAnswerToAAAAQuestion() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.initWithService(
@@ -1003,6 +1181,102 @@
}
@Test
+ fun testGetReply_ipv4AndIpv6Queries_ipv4AndIpv6Replies() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val replyIpv4 = repository.getReply(query, srcIpv4)
+ val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
+ val replyIpv6 = repository.getReply(query, srcIpv6)
+
+ assertNotNull(replyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), replyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, replyIpv4.destination.port)
+ assertNotNull(replyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), replyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, replyIpv6.destination.port)
+ }
+
+ @Test
+ fun testGetReply_twoIpv4QueriesInOneSecond_theSecondReplyIsThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val firstReplyIpv4 = repository.getReply(query, srcIpv4)
+ deps.elapse(500L)
+ val secondReply = repository.getReply(query, srcIpv4)
+
+ assertNotNull(firstReplyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), firstReplyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv4.destination.port)
+ assertNull(secondReply)
+ }
+
+
+ @Test
+ fun testGetReply_twoIpv6QueriesInOneSecond_theSecondReplyIsThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
+ val firstReplyIpv6 = repository.getReply(query, srcIpv6)
+ deps.elapse(500L)
+ val secondReply = repository.getReply(query, srcIpv6)
+
+ assertNotNull(firstReplyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), firstReplyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv6.destination.port)
+ assertNull(secondReply)
+ }
+
+ @Test
+ fun testGetReply_twoIpv4QueriesInMoreThanOneSecond_repliesAreNotThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv4 = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val firstReplyIpv4 = repository.getReply(query, srcIpv4)
+ // The longest possible interval that may make the reply throttled is
+ // 1000 (MIN_MULTICAST_REPLY_INTERVAL_MS) + 120 (delay for shared name) = 1120
+ deps.elapse(1121L)
+ val secondReplyIpv4 = repository.getReply(query, srcIpv4)
+
+ assertNotNull(firstReplyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), firstReplyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv4.destination.port)
+ assertNotNull(secondReplyIpv4)
+ assertEquals(MdnsConstants.getMdnsIPv4Address(), secondReplyIpv4.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, secondReplyIpv4.destination.port)
+ }
+
+ @Test
+ fun testGetReply_twoIpv6QueriesInMoreThanOneSecond_repliesAreNotThrottled() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, setOf(TEST_SUBTYPE))
+ val query = makeQuery(TYPE_PTR to arrayOf("_testservice", "_tcp", "local"))
+
+ val srcIpv6 = InetSocketAddress(parseNumericAddress("2001:db8::123"), 5353)
+ val firstReplyIpv6 = repository.getReply(query, srcIpv6)
+ // The longest possible interval that may make the reply throttled is
+ // 1000 (MIN_MULTICAST_REPLY_INTERVAL_MS) + 120 (delay for shared name) = 1120
+ deps.elapse(1121L)
+ val secondReplyIpv6 = repository.getReply(query, srcIpv6)
+
+ assertNotNull(firstReplyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), firstReplyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, firstReplyIpv6.destination.port)
+ assertNotNull(secondReplyIpv6)
+ assertEquals(MdnsConstants.getMdnsIPv6Address(), secondReplyIpv6.destination.address)
+ assertEquals(MdnsConstants.MDNS_PORT, secondReplyIpv6.destination.port)
+ }
+
+ @Test
fun testGetConflictingServices() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
@@ -1117,8 +1391,8 @@
@Test
fun testGetConflictingServices_customHostsReplyHasFewerAddressesThanUs_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
val packet = MdnsPacket(
0, /* flags */
@@ -1136,10 +1410,30 @@
}
@Test
- fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() {
+ fun testGetConflictingServices_customHostsReplyHasSameNameRecord_conflictDuringProbing() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
+
+ val packet = MdnsPacket(
+ 0, /* flags */
+ emptyList(), /* questions */
+ listOf(MdnsKeyRecord(arrayOf("TestHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ 0L /* ttlMillis */, TEST_PUBLIC_KEY),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(mapOf(TEST_CUSTOM_HOST_ID_1 to CONFLICT_HOST),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_customHostsReplyHasIdenticalHosts_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
val packet = MdnsPacket(
0, /* flags */
@@ -1163,8 +1457,8 @@
@Test
fun testGetConflictingServices_customHostsCaseInsensitiveReplyHasIdenticalHosts_noConflict() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
- repository.addService(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1, null /* ttl */)
- repository.addService(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2, null /* ttl */)
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_1, TEST_CUSTOM_HOST_1)
+ repository.addServiceAndFinishProbing(TEST_CUSTOM_HOST_ID_2, TEST_CUSTOM_HOST_2)
val packet = MdnsPacket(
0, /* flags */
@@ -1185,6 +1479,190 @@
}
@Test
+ fun testGetConflictingServices_identicalKeyRecordsForService_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addService(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService"
+ port = TEST_PORT
+ publicKey = TEST_PUBLIC_KEY
+ }, null /* ttl */)
+
+ val otherTtlMillis = 1234L
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsKeyRecord(
+ arrayOf("MyTestService", "_testservice", "_tcp", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ otherTtlMillis,
+ TEST_PUBLIC_KEY)
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(emptyMap(),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_differentKeyRecordsForService_conflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addService(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService"
+ port = TEST_PORT
+ publicKey = TEST_PUBLIC_KEY
+ }, null /* null */)
+
+ val otherTtlMillis = 1234L
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsKeyRecord(
+ arrayOf("MyTestService", "_testservice", "_tcp", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ otherTtlMillis,
+ TEST_PUBLIC_KEY_2)
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(mapOf(TEST_SERVICE_ID_1 to CONFLICT_SERVICE),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_identicalKeyRecordsForHost_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ hostname = "MyHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2")
+ )
+ publicKey = TEST_PUBLIC_KEY
+ })
+
+ val otherTtlMillis = 1234L
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsKeyRecord(
+ arrayOf("MyHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ otherTtlMillis,
+ TEST_PUBLIC_KEY)
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(emptyMap(),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_keyForCustomHostReplySameRecordName_conflictDuringProbing() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addService(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ hostname = "MyHost"
+ publicKey = TEST_PUBLIC_KEY
+ }, null /* ttl */)
+
+ val otherTtlMillis = 1234L
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(MdnsInetAddressRecord(arrayOf("MyHost", "local"),
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ otherTtlMillis,
+ parseNumericAddress("192.168.2.111"))
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */
+ )
+
+ assertEquals(mapOf(TEST_SERVICE_ID_1 to CONFLICT_HOST),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_differentKeyRecordsForHost_conflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addService(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ hostname = "MyHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ publicKey = TEST_PUBLIC_KEY
+ }, null /* ttl */)
+
+ val otherTtlMillis = 1234L
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsKeyRecord(
+ arrayOf("MyHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ otherTtlMillis,
+ TEST_PUBLIC_KEY_2)
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(mapOf(TEST_SERVICE_ID_1 to CONFLICT_HOST),
+ repository.getConflictingServices(packet))
+ }
+
+ @Test
+ fun testGetConflictingServices_multipleRegistrationsForHostKey_noConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, NsdServiceInfo().apply {
+ hostname = "MyHost"
+ hostAddresses = listOf(
+ parseNumericAddress("2001:db8::1"),
+ parseNumericAddress("2001:db8::2"))
+ publicKey = TEST_PUBLIC_KEY
+ })
+ repository.addService(TEST_SERVICE_ID_2, NsdServiceInfo().apply {
+ serviceType = "_testservice._tcp"
+ serviceName = "MyTestService"
+ port = TEST_PORT
+ hostname = "MyHost"
+ publicKey = TEST_PUBLIC_KEY
+ }, null /* ttl */)
+
+ // Although there's a KEY RR in the second registration being probed, it shouldn't conflict
+ // with an address record which is from a probed registration in the repository.
+ val otherTtlMillis = 1234L
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsInetAddressRecord(
+ arrayOf("MyHost", "local"),
+ 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ otherTtlMillis,
+ parseNumericAddress("2001:db8::1"))
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */)
+
+ assertEquals(mapOf(), repository.getConflictingServices(packet))
+ }
+
+ @Test
fun testGetConflictingServices_IdenticalService() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* ttl */)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
index 55c2846..63548c1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
@@ -16,11 +16,13 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsConstants.QCLASS_INTERNET;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -424,4 +426,92 @@
assertEquals(new TextEntry("xyz", HexDump.hexStringToByteArray("FFEFDFCF")),
entries.get(2));
}
+
+ @Test
+ public void testKeyRecord() throws IOException {
+ final byte[] dataIn =
+ HexDump.hexStringToByteArray(
+ "09746573742d686f7374056c6f63616c"
+ + "00001980010000000a00440201030dc1"
+ + "41d0637960b98cbc12cfca221d2879da"
+ + "c26ee5b460e9007c992e1902d897c391"
+ + "b03764d448f7d0c772fdb03b1d9d6d52"
+ + "ff8886769e8e2362513565270962d3");
+ final byte[] rData =
+ HexDump.hexStringToByteArray(
+ "0201030dc141d0637960b98cbc12cfca"
+ + "221d2879dac26ee5b460e9007c992e19"
+ + "02d897c391b03764d448f7d0c772fdb0"
+ + "3b1d9d6d52ff8886769e8e2362513565"
+ + "270962d3");
+ assertNotNull(dataIn);
+ String dataInText = HexDump.dumpHexString(dataIn, 0, dataIn.length);
+
+ // Decode
+ DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
+ MdnsPacketReader reader = new MdnsPacketReader(packet);
+
+ String[] name = reader.readLabels();
+ assertNotNull(name);
+ assertEquals(2, name.length);
+ String fqdn = MdnsRecord.labelsToString(name);
+ assertEquals("test-host.local", fqdn);
+
+ int type = reader.readUInt16();
+ assertEquals(MdnsRecord.TYPE_KEY, type);
+
+ MdnsKeyRecord keyRecord;
+
+ // MdnsKeyRecord(String[] name, MdnsPacketReader reader)
+ reader = new MdnsPacketReader(packet);
+ reader.readLabels(); // Skip labels
+ reader.readUInt16(); // Skip type
+ keyRecord = new MdnsKeyRecord(name, reader);
+ assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
+ assertTrue(keyRecord.getTtl() > 0); // Not a question so the TTL is greater than 0
+ assertTrue(keyRecord.getCacheFlush());
+ assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
+ assertArrayEquals(rData, keyRecord.getRData());
+ assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA
+ assertEquals(dataInText, toHex(keyRecord));
+
+ // MdnsKeyRecord(String[] name, MdnsPacketReader reader, boolean isQuestion)
+ reader = new MdnsPacketReader(packet);
+ reader.readLabels(); // Skip labels
+ reader.readUInt16(); // Skip type
+ keyRecord = new MdnsKeyRecord(name, reader, false /* isQuestion */);
+ assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
+ assertTrue(keyRecord.getTtl() > 0); // Not a question, so the TTL is greater than 0
+ assertTrue(keyRecord.getCacheFlush());
+ assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
+ assertArrayEquals(rData, keyRecord.getRData());
+ assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA
+
+ // MdnsKeyRecord(String[] name, boolean isUnicast)
+ keyRecord = new MdnsKeyRecord(name, false /* isUnicast */);
+ assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
+ assertEquals(0, keyRecord.getTtl());
+ assertEquals(QCLASS_INTERNET, keyRecord.getRecordClass());
+ assertFalse(keyRecord.getCacheFlush());
+ assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
+ assertArrayEquals(null, keyRecord.getRData());
+
+ // MdnsKeyRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis,
+ // byte[] rData)
+ keyRecord =
+ new MdnsKeyRecord(
+ name,
+ 10 /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ 20_000 /* ttlMillis */,
+ rData);
+ assertEquals(MdnsRecord.TYPE_KEY, keyRecord.getType());
+ assertEquals(10, keyRecord.getReceiptTime());
+ assertTrue(keyRecord.getCacheFlush());
+ assertEquals(20_000, keyRecord.getTtl());
+ assertEquals(QCLASS_INTERNET, keyRecord.getRecordClass());
+ assertArrayEquals(new String[] {"test-host", "local"}, keyRecord.getName());
+ assertArrayEquals(rData, keyRecord.getRData());
+ assertNotEquals(rData, keyRecord.getRData()); // Uses a copy of the original RDATA
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt
new file mode 100644
index 0000000..360a298
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSActiveNetworkInfoTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.INetd.PERMISSION_INTERNET
+import android.net.INetd.PERMISSION_NONE
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkInfo.DetailedState.BLOCKED
+import android.net.NetworkInfo.DetailedState.CONNECTED
+import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doReturn
+
+private fun nc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSActiveNetworkInfoTest : CSTest() {
+
+ fun doTestGetActiveNetworkInfo(
+ changeEnabled: Boolean,
+ permissions: Int,
+ expectBlocked: Boolean
+ ) {
+ deps.setChangeIdEnabled(changeEnabled, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doReturn(permissions).`when`(bpfNetMaps).getNetPermForUid(anyInt())
+
+ val agent = Agent(nc = nc())
+ agent.connect()
+
+ val networkInfo = cm.activeNetworkInfo
+ assertNotNull(networkInfo)
+ if (expectBlocked) {
+ assertEquals(BLOCKED, networkInfo.detailedState)
+ } else {
+ assertEquals(CONNECTED, networkInfo.detailedState)
+ }
+ assertEquals(expectBlocked, cm.activeNetwork == null)
+ agent.disconnect()
+ }
+
+ @Test
+ fun testGetActiveNetworkInfo() {
+ doReturn(true).`when`(bpfNetMaps).isUidNetworkingBlocked(anyInt(), anyBoolean())
+ doTestGetActiveNetworkInfo(
+ changeEnabled = true,
+ permissions = PERMISSION_NONE,
+ expectBlocked = true
+ )
+ doTestGetActiveNetworkInfo(
+ changeEnabled = false,
+ permissions = PERMISSION_INTERNET,
+ expectBlocked = true
+ )
+ // Network access is considered not blocked if the compat change is disabled and an app
+ // does not have PERMISSION_INTERNET
+ doTestGetActiveNetworkInfo(
+ changeEnabled = false,
+ permissions = PERMISSION_NONE,
+ expectBlocked = false
+ )
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
new file mode 100644
index 0000000..0590fbb
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED
+import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
+import android.net.ConnectivityManager.BLOCKED_REASON_DOZE
+import android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
+import android.net.ConnectivitySettingsManager
+import android.net.INetd.PERMISSION_NONE
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.connectivity.ConnectivityCompatChanges.NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION
+import android.os.Build
+import android.os.Process
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mockito.doReturn
+
+private fun cellNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+private fun cellRequest() = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build()
+private fun wifiNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .build()
+private fun wifiRequest() = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build()
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSBlockedReasonsTest : CSTest() {
+
+ inner class DetailedBlockedStatusCallback : TestableNetworkCallback() {
+ override fun onBlockedStatusChanged(network: Network, blockedReasons: Int) {
+ history.add(BlockedStatusInt(network, blockedReasons))
+ }
+
+ fun expectBlockedStatusChanged(network: Network, blockedReasons: Int) {
+ expect<BlockedStatusInt>(network) { it.reason == blockedReasons }
+ }
+ }
+
+ @Test
+ fun testBlockedReasons_onAvailable() {
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE
+ )
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_dataSaverChanged() {
+ doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ doReturn(true).`when`(netd).bandwidthEnableDataSaver(anyBoolean())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ // Disable data saver
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setDataSaverEnabled(false)
+ cellCb.expectBlockedStatusChanged(cellAgent.network, BLOCKED_REASON_APP_BACKGROUND)
+
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
+ // method on this callback was called, but it does not guarantee that ConnectivityService
+ // has finished processing all onBlockedStatusChanged callbacks for all requests.
+ waitForIdle()
+ // Enable data saver
+ doReturn(BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setDataSaverEnabled(true)
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_APP_BACKGROUND or BLOCKED_METERED_REASON_DATA_SAVER
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_setUidFirewallRule() {
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ cellCb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_DOZE
+ )
+
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // The expectBlockedStatusChanged just above guarantees that the onBlockedStatusChanged
+ // method on this callback was called, but it does not guarantee that ConnectivityService
+ // has finished processing all onBlockedStatusChanged callbacks for all requests.
+ waitForIdle()
+ // Set RULE_ALLOW on metered deny chain
+ doReturn(BLOCKED_REASON_DOZE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ Process.myUid(),
+ FIREWALL_RULE_ALLOW
+ )
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_DOZE
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ // Set RULE_DENY on metered deny chain
+ doReturn(BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_METERED_DENY_USER,
+ Process.myUid(),
+ FIREWALL_RULE_DENY
+ )
+ cellCb.expectBlockedStatusChanged(
+ cellAgent.network,
+ BLOCKED_REASON_DOZE or BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+ // BlockedStatus does not change for the non-metered network
+ wifiCb.assertNoCallback()
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_setFirewallChainEnabled() {
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ // Enable dozable firewall chain
+ doReturn(BLOCKED_REASON_DOZE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, true)
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_DOZE
+ )
+
+ // Disable dozable firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_DOZABLE, false)
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NONE
+ )
+
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_replaceFirewallChain() {
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ // Put uid on background firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(Process.myUid()))
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NONE
+ )
+
+ // Remove uid from background firewall chain
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf())
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_APP_BACKGROUND
+ )
+
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(wifiCb)
+ }
+
+ @Test
+ fun testBlockedReasons_perAppDefaultNetwork() {
+ doReturn(BLOCKED_METERED_REASON_USER_RESTRICTED)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+
+ val cellCb = DetailedBlockedStatusCallback()
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(cellRequest(), cellCb)
+ cm.requestNetwork(wifiRequest(), wifiCb)
+
+ val cellAgent = Agent(nc = cellNc())
+ cellAgent.connect()
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+
+ val cb = DetailedBlockedStatusCallback()
+ cm.registerDefaultNetworkCallback(cb)
+ cb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ // CS must send correct blocked reasons after per app default network change
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf(Process.myUid()))
+ service.updateMobileDataPreferredUids()
+ cb.expectAvailableCallbacks(
+ cellAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_METERED_REASON_USER_RESTRICTED
+ )
+
+ // Remove per app default network request
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, setOf())
+ service.updateMobileDataPreferredUids()
+ cb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = BLOCKED_REASON_NONE
+ )
+
+ cellAgent.disconnect()
+ wifiAgent.disconnect()
+ cm.unregisterNetworkCallback(cellCb)
+ cm.unregisterNetworkCallback(wifiCb)
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ private fun doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission: Boolean) {
+ doReturn(PERMISSION_NONE).`when`(bpfNetMaps).getNetPermForUid(Process.myUid())
+
+ val wifiCb = DetailedBlockedStatusCallback()
+ cm.requestNetwork(wifiRequest(), wifiCb)
+ val wifiAgent = Agent(nc = wifiNc())
+ wifiAgent.connect()
+ val expectedBlockedReason = if (blockedByNoInternetPermission) {
+ BLOCKED_REASON_NETWORK_RESTRICTED
+ } else {
+ BLOCKED_REASON_NONE
+ }
+ wifiCb.expectAvailableCallbacks(
+ wifiAgent.network,
+ validated = false,
+ blockedReason = expectedBlockedReason
+ )
+
+ // Enable background firewall chain
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
+ if (blockedByNoInternetPermission) {
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
+ )
+ }
+
+ // Disable background firewall chain
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(Process.myUid())
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ if (blockedByNoInternetPermission) {
+ wifiCb.expectBlockedStatusChanged(
+ wifiAgent.network,
+ BLOCKED_REASON_NETWORK_RESTRICTED
+ )
+ } else {
+ // No callback is expected since blocked reasons does not change from
+ // BLOCKED_REASON_NONE.
+ wifiCb.assertNoCallback()
+ }
+ }
+
+ @Test
+ fun testBlockedReasonsNoInternetPermission_changeDisabled() {
+ deps.setChangeIdEnabled(false, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = false)
+ }
+
+ @Test
+ fun testBlockedReasonsNoInternetPermission_changeEnabled() {
+ deps.setChangeIdEnabled(true, NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION)
+ doTestBlockedReasonsNoInternetPermission(blockedByNoInternetPermission = true)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt
new file mode 100644
index 0000000..bc5be78
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDestroySocketTest.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.app.ActivityManager.UidFrozenStateChangedCallback
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN
+import android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.os.Build
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener
+import com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val TIMESTAMP = 1234L
+private const val TEST_UID = 1234
+private const val TEST_UID2 = 5678
+private const val TEST_CELL_IFACE = "test_rmnet"
+
+private fun cellNc() = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun cellLp() = LinkProperties().also{
+ it.interfaceName = TEST_CELL_IFACE
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CSDestroySocketTest : CSTest() {
+ private fun getRegisteredNetdUnsolicitedEventListener(): BaseNetdUnsolicitedEventListener {
+ val captor = ArgumentCaptor.forClass(BaseNetdUnsolicitedEventListener::class.java)
+ verify(netd).registerUnsolicitedEventListener(captor.capture())
+ return captor.value
+ }
+
+ private fun getUidFrozenStateChangedCallback(): UidFrozenStateChangedCallback {
+ val captor = ArgumentCaptor.forClass(UidFrozenStateChangedCallback::class.java)
+ verify(activityManager).registerUidFrozenStateChangedCallback(any(), captor.capture())
+ return captor.value
+ }
+
+ private fun doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork: Boolean,
+ expectDelay: Boolean
+ ) {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val inOrder = inOrder(destroySocketsWrapper)
+
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ if (restrictionWithIdleNetwork) {
+ // Make cell default network idle
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ }
+
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_DENY
+ )
+ waitForIdle()
+ if (expectDelay) {
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+ if (expectDelay) {
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+
+ cellAgent.disconnect()
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)])
+ fun testBackgroundAppDestroySockets() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = true,
+ expectDelay = true
+ )
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, true)])
+ fun testBackgroundAppDestroySockets_activeNetwork() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = false,
+ expectDelay = false
+ )
+ }
+
+ @Test
+ @FeatureFlags(flags = [Flag(DELAY_DESTROY_SOCKETS, false)])
+ fun testBackgroundAppDestroySockets_featureIsDisabled() {
+ doTestBackgroundRestrictionDestroySockets(
+ restrictionWithIdleNetwork = true,
+ expectDelay = false
+ )
+ }
+
+ @Test
+ fun testReplaceFirewallChain() {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val inOrder = inOrder(destroySocketsWrapper)
+
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ // Make cell default network idle
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+
+ // Set allow rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_ALLOW
+ )
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID2,
+ FIREWALL_RULE_DENY
+ )
+
+ // Put only TEST_UID2 on background chain (deny TEST_UID and allow TEST_UID2)
+ doReturn(setOf(TEST_UID))
+ .`when`(bpfNetMaps).getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_BACKGROUND)
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID2)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, intArrayOf(TEST_UID2))
+ waitForIdle()
+ inOrder.verify(destroySocketsWrapper, never())
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+ inOrder.verify(destroySocketsWrapper)
+ .destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ cellAgent.disconnect()
+ }
+
+ private fun doTestDestroySockets(
+ isFrozen: Boolean,
+ denyOnBackgroundChain: Boolean,
+ enableBackgroundChain: Boolean,
+ expectDestroySockets: Boolean
+ ) {
+ val netdEventListener = getRegisteredNetdUnsolicitedEventListener()
+ val frozenStateCallback = getUidFrozenStateChangedCallback()
+
+ // Make cell default network idle
+ val cellAgent = Agent(nc = cellNc(), lp = cellLp())
+ cellAgent.connect()
+ netdEventListener.onInterfaceClassActivityChanged(
+ false, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+
+ // Set deny rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_APP_BACKGROUND)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_DENY
+ )
+
+ // Freeze TEST_UID
+ frozenStateCallback.onUidFrozenStateChanged(
+ intArrayOf(TEST_UID),
+ intArrayOf(UID_FROZEN_STATE_FROZEN)
+ )
+
+ if (!isFrozen) {
+ // Unfreeze TEST_UID
+ frozenStateCallback.onUidFrozenStateChanged(
+ intArrayOf(TEST_UID),
+ intArrayOf(UID_FROZEN_STATE_UNFROZEN)
+ )
+ }
+ if (!enableBackgroundChain) {
+ // Disable background chain
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ }
+ if (!denyOnBackgroundChain) {
+ // Set allow rule on background chain for TEST_UID
+ doReturn(BLOCKED_REASON_NONE)
+ .`when`(bpfNetMaps).getUidNetworkingBlockedReasons(TEST_UID)
+ cm.setUidFirewallRule(
+ FIREWALL_CHAIN_BACKGROUND,
+ TEST_UID,
+ FIREWALL_RULE_ALLOW
+ )
+ }
+ verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+
+ // Make cell network active
+ netdEventListener.onInterfaceClassActivityChanged(
+ true, // isActive
+ cellAgent.network.netId,
+ TIMESTAMP,
+ TEST_UID
+ )
+ waitForIdle()
+
+ if (expectDestroySockets) {
+ verify(destroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ } else {
+ verify(destroySocketsWrapper, never()).destroyLiveTcpSocketsByOwnerUids(setOf(TEST_UID))
+ }
+ }
+
+ @Test
+ fun testDestroySockets_backgroundDeny_frozen() {
+ doTestDestroySockets(
+ isFrozen = true,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundDeny_nonFrozen() {
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundAllow_frozen() {
+ doTestDestroySockets(
+ isFrozen = true,
+ denyOnBackgroundChain = false,
+ enableBackgroundChain = true,
+ expectDestroySockets = true
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundAllow_nonFrozen() {
+ // If the app is neither frozen nor under background restriction, sockets are not
+ // destroyed
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = false,
+ enableBackgroundChain = true,
+ expectDestroySockets = false
+ )
+ }
+
+ @Test
+ fun testDestroySockets_backgroundChainDisabled_nonFrozen() {
+ // If the app is neither frozen nor under background restriction, sockets are not
+ // destroyed
+ doTestDestroySockets(
+ isFrozen = false,
+ denyOnBackgroundChain = true,
+ enableBackgroundChain = false,
+ expectDestroySockets = false
+ )
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt
index 16de4da..83ccccd 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSFirewallChainTest.kt
@@ -16,7 +16,14 @@
package com.android.server
-import android.net.ConnectivityManager
+import android.net.BpfNetMapsConstants.METERED_ALLOW_CHAINS
+import android.net.BpfNetMapsConstants.METERED_DENY_CHAINS
+import android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND
+import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW
+import android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER
+import android.net.ConnectivityManager.FIREWALL_RULE_ALLOW
+import android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT
+import android.net.ConnectivityManager.FIREWALL_RULE_DENY
import android.os.Build
import androidx.test.filters.SmallTest
import com.android.server.connectivity.ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN
@@ -24,6 +31,7 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,13 +60,13 @@
@FeatureFlags(flags = [Flag(BACKGROUND_FIREWALL_CHAIN, true)])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun setFirewallChainEnabled_backgroundChainEnabled_afterU() {
- cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true)
- verify(bpfNetMaps).setChildChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true)
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
+ verify(bpfNetMaps).setChildChain(FIREWALL_CHAIN_BACKGROUND, true)
clearInvocations(bpfNetMaps)
- cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false)
- verify(bpfNetMaps).setChildChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false)
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
+ verify(bpfNetMaps).setChildChain(FIREWALL_CHAIN_BACKGROUND, false)
}
@Test
@@ -69,10 +77,10 @@
}
private fun verifySetFirewallChainEnabledOnBackgroundDoesNothing() {
- cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, true)
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true)
verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean())
- cm.setFirewallChainEnabled(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, false)
+ cm.setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, false)
verify(bpfNetMaps, never()).setChildChain(anyInt(), anyBoolean())
}
@@ -88,8 +96,8 @@
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun replaceFirewallChain_backgroundChainEnabled_afterU() {
val uids = intArrayOf(53, 42, 79)
- cm.replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids)
- verify(bpfNetMaps).replaceUidChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, uids)
+ verify(bpfNetMaps).replaceUidChain(FIREWALL_CHAIN_BACKGROUND, uids)
}
@Test
@@ -101,7 +109,7 @@
private fun verifyReplaceFirewallChainOnBackgroundDoesNothing() {
val uids = intArrayOf(53, 42, 79)
- cm.replaceFirewallChain(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uids)
+ cm.replaceFirewallChain(FIREWALL_CHAIN_BACKGROUND, uids)
verify(bpfNetMaps, never()).replaceUidChain(anyInt(), any(IntArray::class.java))
}
@@ -118,24 +126,18 @@
fun setUidFirewallRule_backgroundChainEnabled_afterU() {
val uid = 2345
- cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
- ConnectivityManager.FIREWALL_RULE_DEFAULT)
- verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
- ConnectivityManager.FIREWALL_RULE_DENY)
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DENY)
clearInvocations(bpfNetMaps)
- cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
- ConnectivityManager.FIREWALL_RULE_DENY)
- verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
- ConnectivityManager.FIREWALL_RULE_DENY)
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DENY)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DENY)
clearInvocations(bpfNetMaps)
- cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
- ConnectivityManager.FIREWALL_RULE_ALLOW)
- verify(bpfNetMaps).setUidRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid,
- ConnectivityManager.FIREWALL_RULE_ALLOW)
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_ALLOW)
}
@Test
@@ -148,10 +150,49 @@
private fun verifySetUidFirewallRuleOnBackgroundDoesNothing() {
val uid = 2345
- listOf(ConnectivityManager.FIREWALL_RULE_DEFAULT, ConnectivityManager.FIREWALL_RULE_ALLOW,
- ConnectivityManager.FIREWALL_RULE_DENY).forEach { rule ->
- cm.setUidFirewallRule(ConnectivityManager.FIREWALL_CHAIN_BACKGROUND, uid, rule)
+ listOf(FIREWALL_RULE_DEFAULT, FIREWALL_RULE_ALLOW, FIREWALL_RULE_DENY).forEach { rule ->
+ cm.setUidFirewallRule(FIREWALL_CHAIN_BACKGROUND, uid, rule)
verify(bpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt())
}
}
+
+ @Test
+ fun testSetFirewallChainEnabled_meteredChain() {
+ (METERED_ALLOW_CHAINS + METERED_DENY_CHAINS).forEach {
+ assertThrows(UnsupportedOperationException::class.java) {
+ cm.setFirewallChainEnabled(it, true)
+ }
+ assertThrows(UnsupportedOperationException::class.java) {
+ cm.setFirewallChainEnabled(it, false)
+ }
+ }
+ }
+
+ @Test
+ fun testAddUidToMeteredNetworkAllowList() {
+ val uid = 1001
+ cm.addUidToMeteredNetworkAllowList(uid)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW)
+ }
+
+ @Test
+ fun testRemoveUidFromMeteredNetworkAllowList() {
+ val uid = 1001
+ cm.removeUidFromMeteredNetworkAllowList(uid)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DENY)
+ }
+
+ @Test
+ fun testAddUidToMeteredNetworkDenyList() {
+ val uid = 1001
+ cm.addUidToMeteredNetworkDenyList(uid)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY)
+ }
+
+ @Test
+ fun testRemoveUidFromMeteredNetworkDenyList() {
+ val uid = 1001
+ cm.removeUidFromMeteredNetworkDenyList(uid)
+ verify(bpfNetMaps).setUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_ALLOW)
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
index bb7fb51..93f6e81 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -37,6 +37,7 @@
import com.android.testutils.TestableNetworkCallback
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.InOrder
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
@@ -96,6 +97,11 @@
private val LOCAL_IPV6_ADDRRESS = InetAddresses.parseNumericAddress("fe80::1234")
private val LOCAL_IPV6_LINK_ADDRRESS = LinkAddress(LOCAL_IPV6_ADDRRESS, 64)
+ fun verifyNoMoreIngressDiscardRuleChange(inorder: InOrder) {
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+ inorder.verify(bpfNetMaps, never()).removeIngressDiscardRule(any())
+ }
+
@Test
fun testVpnIngressDiscardRule_UpdateVpnAddress() {
// non-VPN network whose address will be not duplicated with VPN address
@@ -148,7 +154,7 @@
// IngressDiscardRule is added to the VPN address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
// The VPN interface name is changed
val newlp = lp(VPN_IFNAME2, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
@@ -157,7 +163,7 @@
// IngressDiscardRule is updated with the new interface name
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME2)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
agent.disconnect()
inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
@@ -206,10 +212,10 @@
// IngressDiscardRule for IPV6_ADDRESS2 is removed but IngressDiscardRule for
// IPV6_LINK_ADDRESS is not added since Wi-Fi also uses IPV6_LINK_ADDRESS
inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS2)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
vpnAgent.disconnect()
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
cm.unregisterNetworkCallback(cb)
}
@@ -225,7 +231,7 @@
// IngressDiscardRule is added to the VPN address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
val nr = nr(TRANSPORT_WIFI)
val cb = TestableNetworkCallback()
@@ -247,7 +253,7 @@
// IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
// with the Wi-Fi address
inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
- inorder.verifyNoMoreInteractions()
+ verifyNoMoreIngressDiscardRuleChange(inorder)
// The Wi-Fi address is changed back to the same address as the VPN interface
wifiAgent.sendLinkProperties(wifiLp)
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
similarity index 92%
rename from tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
rename to tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
index 9024641..45de4a7 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
@@ -61,7 +61,7 @@
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class CSSatelliteNetworkPreferredTest : CSTest() {
+class CSSatelliteNetworkTest : CSTest() {
/**
* Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
* NetworkRequestInfo.
@@ -92,7 +92,7 @@
val satelliteNetId = satelliteAgent.network.netId
netdInOrder.verify(netd).networkCreate(
- nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+ nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_SYSTEM))
val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
@@ -152,7 +152,7 @@
private fun createSatelliteAgent(name: String): CSAgentWrapper {
return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ nc = satelliteNc()
)
}
@@ -176,17 +176,16 @@
return uidRangesForUids(*CollectionUtils.toIntArray(uids))
}
- private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
- addTransportType(transport)
- caps.forEach {
- addCapability(it)
- }
- // Useful capabilities for everybody
- addCapability(NET_CAPABILITY_NOT_RESTRICTED)
- addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- addCapability(NET_CAPABILITY_NOT_ROAMING)
- addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- }.build()
+ private fun satelliteNc() =
+ NetworkCapabilities.Builder().apply {
+ addTransportType(TRANSPORT_SATELLITE)
+
+ addCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ }.build()
private fun lp(iface: String) = LinkProperties().apply {
interfaceName = iface
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index 3b06ad0..ed72fd2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -27,6 +27,7 @@
import android.content.res.Resources
import android.net.ConnectivityManager
import android.net.INetd
+import android.net.INetd.PERMISSION_INTERNET
import android.net.InetAddresses
import android.net.LinkProperties
import android.net.LocalNetworkConfig
@@ -89,6 +90,7 @@
import org.junit.Rule
import org.junit.rules.TestName
import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -157,11 +159,12 @@
it[ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER] = true
it[ConnectivityFlags.REQUEST_RESTRICTED_WIFI] = true
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
- it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
+ it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
+ it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
@@ -171,7 +174,11 @@
val contentResolver = makeMockContentResolver(context)
val PRIMARY_USER = 0
- val PRIMARY_USER_INFO = UserInfo(PRIMARY_USER, "" /* name */, UserInfo.FLAG_PRIMARY)
+ val PRIMARY_USER_INFO = UserInfo(
+ PRIMARY_USER,
+ "", // name
+ UserInfo.FLAG_PRIMARY
+ )
val PRIMARY_USER_HANDLE = UserHandle(PRIMARY_USER)
val userManager = makeMockUserManager(PRIMARY_USER_INFO, PRIMARY_USER_HANDLE)
val activityManager = makeActivityManager()
@@ -183,10 +190,16 @@
val connResources = makeMockConnResources(sysResources, packageManager)
val netd = mock<INetd>()
- val bpfNetMaps = mock<BpfNetMaps>()
+ val bpfNetMaps = mock<BpfNetMaps>().also {
+ doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt())
+ }
val clatCoordinator = mock<ClatCoordinator>()
val networkRequestStateStatsMetrics = mock<NetworkRequestStateStatsMetrics>()
- val proxyTracker = ProxyTracker(context, mock<Handler>(), 16 /* EVENT_PROXY_HAS_CHANGED */)
+ val proxyTracker = ProxyTracker(
+ context,
+ mock<Handler>(),
+ 16 // EVENT_PROXY_HAS_CHANGED
+ )
val systemConfigManager = makeMockSystemConfigManager()
val batteryStats = mock<IBatteryStats>()
val batteryManager = BatteryStatsManager(batteryStats)
@@ -198,6 +211,7 @@
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
+ val destroySocketsWrapper = mock<DestroySocketsWrapper>()
val deps = CSDeps()
@@ -251,6 +265,11 @@
alarmHandlerThread.join()
}
+ // Class to be mocked and used to verify destroy sockets methods call
+ open inner class DestroySocketsWrapper {
+ open fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {}
+ }
+
inner class CSDeps : ConnectivityService.Dependencies() {
override fun getResources(ctx: Context) = connResources
override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps
@@ -356,6 +375,11 @@
override fun getCallingUid() =
if (callingUid == CALLING_UID_UNMOCKED) super.getCallingUid() else callingUid
+
+ override fun destroyLiveTcpSocketsByOwnerUids(ownerUids: Set<Int>) {
+ // Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
+ destroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids)
+ }
}
inner class CSContext(base: Context) : BroadcastInterceptingContext(base) {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 949e0c2..70d4ad8 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -554,4 +554,22 @@
mNetworkOfferCallback.onNetworkNeeded(createDefaultRequest());
verify(mIpClient, never()).startProvisioning(any());
}
+
+ @Test
+ public void testGetMacAddressProvisionedInterface() throws Exception {
+ initEthernetNetworkFactory();
+ createAndVerifyProvisionedInterface(TEST_IFACE);
+
+ final String result = mNetFactory.getHwAddress(TEST_IFACE);
+ assertEquals(HW_ADDR, result);
+ }
+
+ @Test
+ public void testGetMacAddressForNonExistingInterface() {
+ initEthernetNetworkFactory();
+
+ final String result = mNetFactory.getHwAddress(TEST_IFACE);
+ // No interface exists due to not calling createAndVerifyProvisionedInterface(...).
+ assertNull(result);
+ }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index d4f5619..c997b01 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -56,6 +56,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
@@ -67,11 +68,14 @@
import static com.android.server.net.NetworkStatsEventLogger.POLL_REASON_RAT_CHANGED;
import static com.android.server.net.NetworkStatsEventLogger.PollEvent.pollReasonNameOf;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
+import static com.android.server.net.NetworkStatsService.DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_FALLBACKS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_FASTDATAINPUT_SUCCESSES_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_ATTEMPTS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_FALLBACKS_COUNTER_NAME;
import static com.android.server.net.NetworkStatsService.NETSTATS_IMPORT_SUCCESSES_COUNTER_NAME;
+import static com.android.server.net.NetworkStatsService.TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
@@ -116,6 +120,7 @@
import android.net.TestNetworkSpecifier;
import android.net.TetherStatsParcel;
import android.net.TetheringManager;
+import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.net.wifi.WifiInfo;
@@ -125,6 +130,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.Process;
import android.os.SimpleClock;
import android.provider.Settings;
import android.system.ErrnoException;
@@ -159,12 +165,15 @@
import com.android.testutils.HandlerUtils;
import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule;
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule.FeatureFlag;
import libcore.testing.io.TestIoUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -189,6 +198,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
/**
* Tests for {@link NetworkStatsService}.
@@ -202,6 +212,7 @@
// NetworkStatsService is not updatable before T, so tests do not need to be backwards compatible
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
+
private static final String TAG = "NetworkStatsServiceTest";
private static final long TEST_START = 1194220800000L;
@@ -295,6 +306,17 @@
private Boolean mIsDebuggable;
private HandlerThread mObserverHandlerThread;
final TestDependencies mDeps = new TestDependencies();
+ final HashMap<String, Boolean> mFeatureFlags = new HashMap<>();
+ final HashMap<Long, Boolean> mCompatChanges = new HashMap<>();
+
+ // This will set feature flags from @FeatureFlag annotations
+ // into the map before setUp() runs.
+ @Rule
+ public final SetFeatureFlagsRule mSetFeatureFlagsRule =
+ new SetFeatureFlagsRule((name, enabled) -> {
+ mFeatureFlags.put(name, enabled);
+ return null;
+ }, (name) -> mFeatureFlags.getOrDefault(name, false));
private class MockContext extends BroadcastInterceptingContext {
private final Context mBaseContext;
@@ -395,8 +417,6 @@
mElapsedRealtime = 0L;
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
// Verify that system ready fetches realtime stats
@@ -432,6 +452,7 @@
class TestDependencies extends NetworkStatsService.Dependencies {
private int mCompareStatsInvocation = 0;
+ private NetworkStats.Entry mMockedTrafficStatsNativeStat = null;
@Override
public File getLegacyStatsDir() {
@@ -573,6 +594,51 @@
public boolean supportEventLogger(@NonNull Context cts) {
return true;
}
+
+ @Override
+ public boolean alwaysUseTrafficStatsRateLimitCache(Context ctx) {
+ return mFeatureFlags.getOrDefault(TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, false);
+ }
+
+ @Override
+ public int getTrafficStatsRateLimitCacheExpiryDuration() {
+ return DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS;
+ }
+
+ @Override
+ public int getTrafficStatsRateLimitCacheMaxEntries() {
+ return DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
+ }
+
+ @Override
+ public boolean isChangeEnabled(long changeId, int uid) {
+ return mCompatChanges.getOrDefault(changeId, true);
+ }
+
+ public void setChangeEnabled(long changeId, boolean enabled) {
+ mCompatChanges.put(changeId, enabled);
+ }
+ @Nullable
+ @Override
+ public NetworkStats.Entry nativeGetTotalStat() {
+ return mMockedTrafficStatsNativeStat;
+ }
+
+ @Nullable
+ @Override
+ public NetworkStats.Entry nativeGetIfaceStat(String iface) {
+ return mMockedTrafficStatsNativeStat;
+ }
+
+ @Nullable
+ @Override
+ public NetworkStats.Entry nativeGetUidStat(int uid) {
+ return mMockedTrafficStatsNativeStat;
+ }
+
+ public void setNativeStat(NetworkStats.Entry entry) {
+ mMockedTrafficStatsNativeStat = entry;
+ }
}
@After
@@ -729,8 +795,6 @@
assertStatsFilesExist(true);
// boot through serviceReady() again
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
@@ -2111,8 +2175,6 @@
getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
// Mock zero usage and boot through serviceReady(), verify there is no imported data.
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
assertStatsFilesExist(false);
@@ -2124,8 +2186,6 @@
assertStatsFilesExist(false);
// Boot through systemReady() again.
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
@@ -2199,8 +2259,6 @@
getLegacyCollection(PREFIX_UID_TAG, true /* includeTags */));
// Mock zero usage and boot through serviceReady(), verify there is no imported data.
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
assertStatsFilesExist(false);
@@ -2212,8 +2270,6 @@
assertStatsFilesExist(false);
// Boot through systemReady() again.
- mockDefaultSettings();
- mockNetworkStatsUidDetail(buildEmptyStats());
prepareForSystemReady();
mService.systemReady();
@@ -2365,6 +2421,84 @@
assertUidTotal(sTemplateWifi, UID_GREEN, 64L, 3L, 1024L, 8L, 0);
}
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(false /* expectCached */);
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
+ }
+
+ private void doTestTrafficStatsRateLimitCache(boolean expectCached) throws Exception {
+ mockDefaultSettings();
+ // Calling uid is not injected into the service, use the real uid to pass the caller check.
+ final int myUid = Process.myUid();
+ mockTrafficStatsValues(64L, 3L, 1024L, 8L);
+ assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
+
+ // Verify the values are cached.
+ incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS / 2);
+ mockTrafficStatsValues(65L, 8L, 1055L, 9L);
+ if (expectCached) {
+ assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
+ } else {
+ assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
+ }
+
+ // Verify the values are updated after cache expiry.
+ incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS);
+ assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
+ }
+
+ private void mockTrafficStatsValues(long rxBytes, long rxPackets,
+ long txBytes, long txPackets) {
+ // In practice, keys and operations are not used and filled with default values when
+ // returned by JNI layer.
+ final NetworkStats.Entry entry = new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ rxBytes, rxPackets, txBytes, txPackets, 0L);
+ mDeps.setNativeStat(entry);
+ }
+
+ // Assert for 3 different API return values respectively.
+ private void assertTrafficStatsValues(String iface, int uid, long rxBytes, long rxPackets,
+ long txBytes, long txPackets) {
+ assertTrafficStatsValuesThat(rxBytes, rxPackets, txBytes, txPackets,
+ (type) -> mService.getTotalStats(type));
+ assertTrafficStatsValuesThat(rxBytes, rxPackets, txBytes, txPackets,
+ (type) -> mService.getIfaceStats(iface, type));
+ assertTrafficStatsValuesThat(rxBytes, rxPackets, txBytes, txPackets,
+ (type) -> mService.getUidStats(uid, type));
+ }
+
+ private void assertTrafficStatsValuesThat(long rxBytes, long rxPackets, long txBytes,
+ long txPackets, Function<Integer, Long> fetcher) {
+ assertEquals(rxBytes, (long) fetcher.apply(TrafficStats.TYPE_RX_BYTES));
+ assertEquals(rxPackets, (long) fetcher.apply(TrafficStats.TYPE_RX_PACKETS));
+ assertEquals(txBytes, (long) fetcher.apply(TrafficStats.TYPE_TX_BYTES));
+ assertEquals(txPackets, (long) fetcher.apply(TrafficStats.TYPE_TX_PACKETS));
+ }
+
private void assertShouldRunComparison(boolean expected, boolean isDebuggable) {
assertEquals("shouldRunComparison (debuggable=" + isDebuggable + "): ",
expected, mService.shouldRunComparison());
@@ -2429,6 +2563,8 @@
}
private void prepareForSystemReady() throws Exception {
+ mockDefaultSettings();
+ mockNetworkStatsUidDetail(buildEmptyStats());
mockNetworkStatsSummary(buildEmptyStats());
}
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index ebbb9af..34d67bb 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -11,5 +11,8 @@
}
],
"postsubmit": [
+ {
+ "name": "ThreadNetworkMultiDeviceTests"
+ }
]
}
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index 2c14f1d..d0cb9b8 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -19,11 +19,15 @@
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -34,6 +38,7 @@
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
@@ -41,6 +46,7 @@
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -56,24 +62,36 @@
* {@code mHandler} itself.
*/
public final class NsdPublisher extends INsdPublisher.Stub {
- // TODO: b/321883491 - specify network for mDNS operations
private static final String TAG = NsdPublisher.class.getSimpleName();
+
+ // TODO: b/321883491 - specify network for mDNS operations
+ @Nullable private Network mNetwork;
private final NsdManager mNsdManager;
+ private final DnsResolver mDnsResolver;
private final Handler mHandler;
private final Executor mExecutor;
private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
private final SparseArray<DiscoveryListener> mDiscoveryListeners = new SparseArray<>(0);
private final SparseArray<ServiceInfoListener> mServiceInfoListeners = new SparseArray<>(0);
+ private final SparseArray<HostInfoListener> mHostInfoListeners = new SparseArray<>(0);
@VisibleForTesting
- public NsdPublisher(NsdManager nsdManager, Handler handler) {
+ public NsdPublisher(NsdManager nsdManager, DnsResolver dnsResolver, Handler handler) {
+ mNetwork = null;
mNsdManager = nsdManager;
+ mDnsResolver = dnsResolver;
mHandler = handler;
mExecutor = runnable -> mHandler.post(runnable);
}
public static NsdPublisher newInstance(Context context, Handler handler) {
- return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
+ return new NsdPublisher(
+ context.getSystemService(NsdManager.class), DnsResolver.getInstance(), handler);
+ }
+
+ // TODO: b/321883491 - NsdPublisher should be disabled when mNetwork is null
+ public void setNetworkForHostResolution(@Nullable Network network) {
+ mNetwork = network;
}
@Override
@@ -291,6 +309,53 @@
}
}
+ @Override
+ public void resolveHost(String name, INsdResolveHostCallback callback, int listenerId) {
+ mHandler.post(() -> resolveHostInternal(name, callback, listenerId));
+ }
+
+ private void resolveHostInternal(
+ String name, INsdResolveHostCallback callback, int listenerId) {
+ checkOnHandlerThread();
+
+ String fullHostname = name + ".local";
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ HostInfoListener listener =
+ new HostInfoListener(name, callback, cancellationSignal, listenerId);
+ mDnsResolver.query(
+ mNetwork,
+ fullHostname,
+ DnsResolver.FLAG_NO_CACHE_LOOKUP,
+ mExecutor,
+ cancellationSignal,
+ listener);
+ mHostInfoListeners.append(listenerId, listener);
+
+ Log.i(TAG, "Resolving host." + " Listener ID: " + listenerId + ", hostname: " + name);
+ }
+
+ @Override
+ public void stopHostResolution(int listenerId) {
+ mHandler.post(() -> stopHostResolutionInternal(listenerId));
+ }
+
+ private void stopHostResolutionInternal(int listenerId) {
+ checkOnHandlerThread();
+
+ HostInfoListener listener = mHostInfoListeners.get(listenerId);
+ if (listener == null) {
+ Log.w(
+ TAG,
+ "Failed to stop host resolution. Listener ID: "
+ + listenerId
+ + ". The listener is null.");
+ return;
+ }
+ Log.i(TAG, "Stopping host resolution. Listener: " + listener);
+ listener.cancel();
+ mHostInfoListeners.remove(listenerId);
+ }
+
private void checkOnHandlerThread() {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
@@ -586,4 +651,78 @@
return "ID: " + mListenerId + ", service name: " + mName + ", service type: " + mType;
}
}
+
+ class HostInfoListener implements DnsResolver.Callback<List<InetAddress>> {
+ private final String mHostname;
+ private final INsdResolveHostCallback mResolveHostCallback;
+ private final CancellationSignal mCancellationSignal;
+ private final int mListenerId;
+
+ HostInfoListener(
+ @NonNull String hostname,
+ INsdResolveHostCallback resolveHostCallback,
+ CancellationSignal cancellationSignal,
+ int listenerId) {
+ this.mHostname = hostname;
+ this.mResolveHostCallback = resolveHostCallback;
+ this.mCancellationSignal = cancellationSignal;
+ this.mListenerId = listenerId;
+ }
+
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Host is resolved."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname
+ + ", addresses: "
+ + answerList
+ + ", return code: "
+ + rcode);
+ List<String> addressStrings = new ArrayList<>();
+ for (InetAddress address : answerList) {
+ addressStrings.add(address.getHostAddress());
+ }
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, addressStrings);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Failed to resolve host."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname,
+ error);
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, Collections.emptyList());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ public String toString() {
+ return "ID: " + mListenerId + ", hostname: " + mHostname;
+ }
+
+ void cancel() {
+ mCancellationSignal.cancel();
+ mHostInfoListeners.remove(mListenerId);
+ }
+ }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 63cd574..737ec41 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -73,7 +73,6 @@
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
-import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
import android.net.LocalNetworkInfo;
@@ -108,6 +107,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -123,16 +123,17 @@
import com.android.server.thread.openthread.IOtStatusReceiver;
import com.android.server.thread.openthread.Ipv6AddressInfo;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
+import com.android.server.thread.openthread.OnMeshPrefixConfig;
import com.android.server.thread.openthread.OtDaemonState;
import libcore.util.HexEncoding;
import java.io.IOException;
import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
+import java.time.Clock;
+import java.time.DateTimeException;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
@@ -165,6 +166,9 @@
// Note that this regex allows "XX:XX-XX" as well but we don't need to be a strict checker
private static final String OUI_REGEX = "^([0-9A-Fa-f]{2}[:-]?){2}([0-9A-Fa-f]{2})$";
+ // The channel mask that indicates all channels from channel 11 to channel 24
+ private static final int CHANNEL_MASK_11_TO_24 = 0x1FFF800;
+
// Below member fields can be accessed from both the binder and handler threads
private final Context mContext;
@@ -200,6 +204,7 @@
private final ThreadPersistentSettings mPersistentSettings;
private final UserManager mUserManager;
private boolean mUserRestricted;
+ private boolean mAirplaneModeOn;
private boolean mForceStopOtDaemonEnabled;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -260,38 +265,6 @@
countryCodeSupplier);
}
- private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
- try {
- return (Inet6Address) Inet6Address.getByAddress(addressBytes);
- } catch (UnknownHostException e) {
- // This is unlikely to happen unless the Thread daemon is critically broken
- return null;
- }
- }
-
- private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
- return bytesToInet6Address(addressInfo.address);
- }
-
- private static LinkAddress newLinkAddress(Ipv6AddressInfo addressInfo) {
- long deprecationTimeMillis =
- addressInfo.isPreferred
- ? LinkAddress.LIFETIME_PERMANENT
- : SystemClock.elapsedRealtime();
-
- InetAddress address = addressInfoToInetAddress(addressInfo);
-
- // flags and scope will be adjusted automatically depending on the address and
- // its lifetimes.
- return new LinkAddress(
- address,
- addressInfo.prefixLength,
- 0 /* flags */,
- 0 /* scope */,
- deprecationTimeMillis,
- LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
- }
-
private NetworkRequest newUpstreamNetworkRequest() {
NetworkRequest.Builder builder = new NetworkRequest.Builder().clearCapabilities();
@@ -315,7 +288,7 @@
}
private void maybeInitializeOtDaemon() {
- if (!isEnabled()) {
+ if (!shouldEnableThread()) {
return;
}
@@ -350,7 +323,7 @@
otDaemon.initialize(
mTunIfController.getTunFd(),
- isEnabled(),
+ shouldEnableThread(),
mNsdPublisher,
getMeshcopTxtAttributes(mResources.get()),
mOtDaemonCallbackProxy,
@@ -415,7 +388,7 @@
Log.d(
TAG,
"Initializing Thread system service: Thread is "
- + (isEnabled() ? "enabled" : "disabled"));
+ + (shouldEnableThread() ? "enabled" : "disabled"));
try {
mTunIfController.createTunInterface();
} catch (IOException e) {
@@ -427,6 +400,8 @@
requestThreadNetwork();
mUserRestricted = isThreadUserRestricted();
registerUserRestrictionsReceiver();
+ mAirplaneModeOn = isAirplaneModeOn();
+ registerAirplaneModeReceiver();
maybeInitializeOtDaemon();
});
}
@@ -493,6 +468,7 @@
private void setEnabledInternal(
boolean isEnabled, boolean persist, @NonNull OperationReceiverWrapper receiver) {
+ checkOnHandlerThread();
if (isEnabled && isThreadUserRestricted()) {
receiver.onError(
ERROR_FAILED_PRECONDITION,
@@ -507,6 +483,15 @@
// the otDaemon set enabled state operation succeeded or not, so that it can recover
// to the desired value after reboot.
mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
+
+ // Remember whether the user wanted to keep Thread enabled in airplane mode. If once
+ // the user disabled Thread again in airplane mode, the persistent settings state is
+ // reset (so that Thread will be auto-disabled again when airplane mode is turned on).
+ // This behavior is consistent with Wi-Fi and bluetooth.
+ if (mAirplaneModeOn) {
+ mPersistentSettings.put(
+ ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE.key, isEnabled);
+ }
}
try {
@@ -543,36 +528,30 @@
+ newUserRestrictedState);
mUserRestricted = newUserRestrictedState;
- final boolean isEnabled = isEnabled();
+ final boolean shouldEnableThread = shouldEnableThread();
final IOperationReceiver receiver =
new IOperationReceiver.Stub() {
@Override
public void onSuccess() {
Log.d(
TAG,
- (isEnabled ? "Enabled" : "Disabled")
+ (shouldEnableThread ? "Enabled" : "Disabled")
+ " Thread due to user restriction change");
}
@Override
- public void onError(int otError, String messages) {
+ public void onError(int errorCode, String errorMessage) {
Log.e(
TAG,
"Failed to "
- + (isEnabled ? "enable" : "disable")
+ + (shouldEnableThread ? "enable" : "disable")
+ " Thread for user restriction change");
}
};
// Do not save the user restriction state to persistent settings so that the user
// configuration won't be overwritten
- setEnabledInternal(isEnabled, false /* persist */, new OperationReceiverWrapper(receiver));
- }
-
- /** Returns {@code true} if Thread is set enabled. */
- private boolean isEnabled() {
- return !mForceStopOtDaemonEnabled
- && !mUserRestricted
- && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+ setEnabledInternal(
+ shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
}
/** Returns {@code true} if Thread has been restricted for the user. */
@@ -580,6 +559,77 @@
return mUserManager.hasUserRestriction(DISALLOW_THREAD_NETWORK);
}
+ private void registerAirplaneModeReceiver() {
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onAirplaneModeChanged(isAirplaneModeOn());
+ }
+ },
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
+ }
+
+ private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
+ checkOnHandlerThread();
+ if (mAirplaneModeOn == newAirplaneModeOn) {
+ return;
+ }
+ Log.i(TAG, "Airplane mode changed: " + mAirplaneModeOn + " -> " + newAirplaneModeOn);
+ mAirplaneModeOn = newAirplaneModeOn;
+
+ final boolean shouldEnableThread = shouldEnableThread();
+ final IOperationReceiver receiver =
+ new IOperationReceiver.Stub() {
+ @Override
+ public void onSuccess() {
+ Log.d(
+ TAG,
+ (shouldEnableThread ? "Enabled" : "Disabled")
+ + " Thread due to airplane mode change");
+ }
+
+ @Override
+ public void onError(int errorCode, String errorMessage) {
+ Log.e(
+ TAG,
+ "Failed to "
+ + (shouldEnableThread ? "enable" : "disable")
+ + " Thread for airplane mode change");
+ }
+ };
+ // Do not save the user restriction state to persistent settings so that the user
+ // configuration won't be overwritten
+ setEnabledInternal(
+ shouldEnableThread, false /* persist */, new OperationReceiverWrapper(receiver));
+ }
+
+ /** Returns {@code true} if Airplane mode has been turned on. */
+ private boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0)
+ == 1;
+ }
+
+ /**
+ * Returns {@code true} if Thread should be enabled based on current settings, runtime user
+ * restriction and airplane mode state.
+ */
+ private boolean shouldEnableThread() {
+ final boolean enabledInAirplaneMode =
+ mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE);
+
+ return !mForceStopOtDaemonEnabled
+ && !mUserRestricted
+ // FIXME(b/340744397): Note that here we need to call `isAirplaneModeOn()` to get
+ // the latest state of airplane mode but can't use `mIsAirplaneMode`. This is for
+ // avoiding the race conditions described in b/340744397
+ && (!isAirplaneModeOn() || enabledInAirplaneMode)
+ && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+ }
+
private void requestUpstreamNetwork() {
if (mUpstreamNetworkCallback != null) {
throw new AssertionError("The upstream network request is already there.");
@@ -667,6 +717,7 @@
if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
}
+ mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
}
}
}
@@ -777,7 +828,6 @@
networkName,
supportedChannelMask,
preferredChannelMask,
- Instant.now(),
new Random(),
new SecureRandom());
@@ -795,9 +845,18 @@
String networkName,
int supportedChannelMask,
int preferredChannelMask,
- Instant now,
Random random,
SecureRandom secureRandom) {
+ boolean authoritative = false;
+ Instant now = Instant.now();
+ try {
+ Clock clock = SystemClock.currentNetworkTimeClock();
+ now = clock.instant();
+ authoritative = true;
+ } catch (DateTimeException e) {
+ Log.w(TAG, "Failed to get authoritative time", e);
+ }
+
int panId = random.nextInt(/* bound= */ 0xffff);
final byte[] meshLocalPrefix = newRandomBytes(random, LENGTH_MESH_LOCAL_PREFIX_BITS / 8);
meshLocalPrefix[0] = MESH_LOCAL_PREFIX_FIRST_BYTE;
@@ -811,7 +870,7 @@
return new ActiveOperationalDataset.Builder()
.setActiveTimestamp(
new OperationalDatasetTimestamp(
- now.getEpochSecond() & 0xffffffffffffL, 0, false))
+ now.getEpochSecond() & 0xffffffffffffL, 0, authoritative))
.setExtendedPanId(newRandomBytes(random, LENGTH_EXTENDED_PAN_ID))
.setPanId(panId)
.setNetworkName(networkName)
@@ -826,6 +885,14 @@
private static int selectChannel(
int supportedChannelMask, int preferredChannelMask, Random random) {
+ // Due to radio hardware performance reasons, many Thread radio chips need to reduce their
+ // transmit power on edge channels to pass regulatory RF certification. Thread edge channel
+ // 25 and 26 are not preferred here.
+ //
+ // If users want to use channel 25 or 26, they can change the channel via the method
+ // ActiveOperationalDataset.Builder(activeOperationalDataset).setChannel(channel).build().
+ preferredChannelMask = preferredChannelMask & CHANNEL_MASK_11_TO_24;
+
// If the preferred channel mask is not empty, select a random channel from it, otherwise
// choose one from the supported channel mask.
preferredChannelMask = preferredChannelMask & supportedChannelMask;
@@ -909,7 +976,11 @@
private void checkOnHandlerThread() {
if (Looper.myLooper() != mHandler.getLooper()) {
- Log.wtf(TAG, "Must be on the handler thread!");
+ throw new IllegalStateException(
+ "Not running on ThreadNetworkControllerService thread ("
+ + mHandler.getLooper()
+ + ") : "
+ + Looper.myLooper());
}
}
@@ -1007,7 +1078,7 @@
}
@Override
- public void leave(@NonNull IOperationReceiver receiver) throws RemoteException {
+ public void leave(@NonNull IOperationReceiver receiver) {
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
mHandler.post(() -> leaveInternal(new OperationReceiverWrapper(receiver)));
@@ -1043,7 +1114,7 @@
checkOnHandlerThread();
// Fails early to avoid waking up ot-daemon by the ThreadNetworkCountryCode class
- if (!isEnabled()) {
+ if (!shouldEnableThread()) {
receiver.onError(
ERROR_THREAD_DISABLED, "Can't set country code when Thread is disabled");
return;
@@ -1172,20 +1243,22 @@
}
}
- private void handleAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
+ private void handleAddressChanged(List<Ipv6AddressInfo> addressInfoList) {
checkOnHandlerThread();
- InetAddress address = addressInfoToInetAddress(addressInfo);
- if (address.isMulticastAddress()) {
- Log.i(TAG, "Ignoring multicast address " + address.getHostAddress());
- return;
- }
- LinkAddress linkAddress = newLinkAddress(addressInfo);
- if (isAdded) {
- mTunIfController.addAddress(linkAddress);
- } else {
- mTunIfController.removeAddress(linkAddress);
+ mTunIfController.updateAddresses(addressInfoList);
+
+ // The OT daemon can send link property updates before the networkAgent is
+ // registered
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkProperties(mTunIfController.getLinkProperties());
}
+ }
+
+ private void handlePrefixChanged(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+ checkOnHandlerThread();
+
+ mTunIfController.updatePrefixes(onMeshPrefixConfigList);
// The OT daemon can send link property updates before the networkAgent is
// registered
@@ -1315,15 +1388,6 @@
}
}
- private void notifyThreadEnabledUpdated(IStateCallback callback, int enabledState) {
- try {
- callback.onThreadEnableStateChanged(enabledState);
- Log.i(TAG, "onThreadEnableStateChanged " + enabledState);
- } catch (RemoteException ignored) {
- // do nothing if the client is dead
- }
- }
-
public void unregisterStateCallback(IStateCallback callback) {
checkOnHandlerThread();
if (!mStateCallbacks.containsKey(callback)) {
@@ -1390,15 +1454,19 @@
}
}
- @Override
- public void onThreadEnabledChanged(int state) {
- mHandler.post(() -> onThreadEnabledChangedInternal(state));
- }
-
- private void onThreadEnabledChangedInternal(int state) {
+ private void onThreadEnabledChanged(int state, long listenerId) {
checkOnHandlerThread();
- for (IStateCallback callback : mStateCallbacks.keySet()) {
- notifyThreadEnabledUpdated(callback, otStateToAndroidState(state));
+ boolean stateChanged = (mState == null || mState.threadEnabled != state);
+
+ for (var callbackEntry : mStateCallbacks.entrySet()) {
+ if (!stateChanged && callbackEntry.getValue().id != listenerId) {
+ continue;
+ }
+ try {
+ callbackEntry.getKey().onThreadEnableStateChanged(otStateToAndroidState(state));
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
}
}
@@ -1425,6 +1493,7 @@
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
+ onThreadEnabledChanged(newState.threadEnabled, listenerId);
mState = newState;
ActiveOperationalDataset newActiveDataset;
@@ -1534,13 +1603,18 @@
}
@Override
- public void onAddressChanged(Ipv6AddressInfo addressInfo, boolean isAdded) {
- mHandler.post(() -> handleAddressChanged(addressInfo, isAdded));
+ public void onAddressChanged(List<Ipv6AddressInfo> addressInfoList) {
+ mHandler.post(() -> handleAddressChanged(addressInfoList));
}
@Override
public void onBackboneRouterStateChanged(BackboneRouterState state) {
mHandler.post(() -> handleMulticastForwardingChanged(state));
}
+
+ @Override
+ public void onPrefixChanged(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+ mHandler.post(() -> handlePrefixChanged(onMeshPrefixConfigList));
+ }
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
index 30c67ca..4c22278 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java
@@ -72,7 +72,10 @@
// PHASE_ACTIVITY_MANAGER_READY and PHASE_THIRD_PARTY_APPS_CAN_START
mCountryCode.initialize();
mShellCommand =
- new ThreadNetworkShellCommand(requireNonNull(mControllerService), mCountryCode);
+ new ThreadNetworkShellCommand(
+ mContext,
+ requireNonNull(mControllerService),
+ requireNonNull(mCountryCode));
}
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
index c6a1618..54155ee 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkShellCommand.java
@@ -16,50 +16,57 @@
package com.android.server.thread;
-import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IOperationReceiver;
+import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.PendingOperationalDataset;
import android.net.thread.ThreadNetworkException;
-import android.os.Binder;
-import android.os.Process;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.net.module.util.HexDump;
import java.io.PrintWriter;
import java.time.Duration;
-import java.util.List;
+import java.time.Instant;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
- * Interprets and executes 'adb shell cmd thread_network [args]'.
+ * Interprets and executes 'adb shell cmd thread_network <subcommand>'.
+ *
+ * <p>Subcommands which don't have an equivalent Java API now require the
+ * "android.permission.THREAD_NETWORK_TESTING" permission. For a specific subcommand, it also
+ * requires the same permissions of the equivalent Java / AIDL API.
*
* <p>To add new commands: - onCommand: Add a case "<command>" execute. Return a 0 if command
* executed successfully. - onHelp: add a description string.
- *
- * <p>Permissions: currently root permission is required for some commands. Others will enforce the
- * corresponding API permissions.
*/
-public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
+public final class ThreadNetworkShellCommand extends BasicShellCommandHandler {
private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
+ private static final Duration MIGRATE_TIMEOUT = Duration.ofSeconds(2);
private static final Duration FORCE_STOP_TIMEOUT = Duration.ofSeconds(1);
+ private static final String PERMISSION_THREAD_NETWORK_TESTING =
+ "android.permission.THREAD_NETWORK_TESTING";
- // These don't require root access.
- private static final List<String> NON_PRIVILEGED_COMMANDS =
- List.of("help", "get-country-code", "enable", "disable");
+ private final Context mContext;
+ private final ThreadNetworkControllerService mControllerService;
+ private final ThreadNetworkCountryCode mCountryCode;
- @NonNull private final ThreadNetworkControllerService mControllerService;
- @NonNull private final ThreadNetworkCountryCode mCountryCode;
@Nullable private PrintWriter mOutputWriter;
@Nullable private PrintWriter mErrorWriter;
- ThreadNetworkShellCommand(
- @NonNull ThreadNetworkControllerService controllerService,
- @NonNull ThreadNetworkCountryCode countryCode) {
+ public ThreadNetworkShellCommand(
+ Context context,
+ ThreadNetworkControllerService controllerService,
+ ThreadNetworkCountryCode countryCode) {
+ mContext = context;
mControllerService = controllerService;
mCountryCode = countryCode;
}
@@ -79,79 +86,120 @@
}
@Override
+ public void onHelp() {
+ final PrintWriter pw = getOutputWriter();
+ pw.println("Thread network commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ pw.println(" enable");
+ pw.println(" Enables Thread radio");
+ pw.println(" disable");
+ pw.println(" Disables Thread radio");
+ pw.println(" join <active-dataset-tlvs>");
+ pw.println(" Joins a network of the given dataset");
+ pw.println(" migrate <active-dataset-tlvs> <delay-seconds>");
+ pw.println(" Migrate to the given network by a specific delay");
+ pw.println(" leave");
+ pw.println(" Leave the current network and erase datasets");
+ pw.println(" force-stop-ot-daemon enabled | disabled ");
+ pw.println(" force stop ot-daemon service");
+ pw.println(" get-country-code");
+ pw.println(" Gets country code as a two-letter string");
+ pw.println(" force-country-code enabled <two-letter code> | disabled ");
+ pw.println(" Sets country code to <two-letter code> or left for normal value");
+ }
+
+ @Override
public int onCommand(String cmd) {
- // Treat no command as help command.
+ // Treat no command as the "help" command
if (TextUtils.isEmpty(cmd)) {
cmd = "help";
}
- final PrintWriter pw = getOutputWriter();
- final PrintWriter perr = getErrorWriter();
-
- // Explicit exclusion from root permission
- if (!NON_PRIVILEGED_COMMANDS.contains(cmd)) {
- final int uid = Binder.getCallingUid();
-
- if (uid != Process.ROOT_UID) {
- perr.println(
- "Uid "
- + uid
- + " does not have access to "
- + cmd
- + " thread command "
- + "(or such command doesn't exist)");
- return -1;
- }
- }
-
switch (cmd) {
case "enable":
return setThreadEnabled(true);
case "disable":
return setThreadEnabled(false);
+ case "join":
+ return join();
+ case "leave":
+ return leave();
+ case "migrate":
+ return migrate();
case "force-stop-ot-daemon":
return forceStopOtDaemon();
case "force-country-code":
- boolean enabled;
- try {
- enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
- } catch (IllegalArgumentException e) {
- perr.println("Invalid argument: " + e.getMessage());
- return -1;
- }
-
- if (enabled) {
- String countryCode = getNextArgRequired();
- if (!ThreadNetworkCountryCode.isValidCountryCode(countryCode)) {
- perr.println(
- "Invalid argument: Country code must be a 2-Character"
- + " string. But got country code "
- + countryCode
- + " instead");
- return -1;
- }
- mCountryCode.setOverrideCountryCode(countryCode);
- pw.println("Set Thread country code: " + countryCode);
-
- } else {
- mCountryCode.clearOverrideCountryCode();
- }
- return 0;
+ return forceCountryCode();
case "get-country-code":
- pw.println("Thread country code = " + mCountryCode.getCountryCode());
- return 0;
+ return getCountryCode();
default:
return handleDefaultCommands(cmd);
}
}
+ private void ensureTestingPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ PERMISSION_THREAD_NETWORK_TESTING,
+ "Permission " + PERMISSION_THREAD_NETWORK_TESTING + " is missing!");
+ }
+
private int setThreadEnabled(boolean enabled) {
CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
mControllerService.setEnabled(enabled, newOperationReceiver(setEnabledFuture));
- return waitForFuture(setEnabledFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
+ return waitForFuture(setEnabledFuture, SET_ENABLED_TIMEOUT, getErrorWriter());
+ }
+
+ private int join() {
+ byte[] datasetTlvs = HexDump.hexStringToByteArray(getNextArgRequired());
+ ActiveOperationalDataset dataset;
+ try {
+ dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs);
+ } catch (IllegalArgumentException e) {
+ getErrorWriter().println("Invalid dataset argument: " + e.getMessage());
+ return -1;
+ }
+ // Do not wait for join to complete because this can take 8 to 30 seconds
+ mControllerService.join(dataset, new IOperationReceiver.Default());
+ return 0;
+ }
+
+ private int leave() {
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+ mControllerService.leave(newOperationReceiver(leaveFuture));
+ return waitForFuture(leaveFuture, LEAVE_TIMEOUT, getErrorWriter());
+ }
+
+ private int migrate() {
+ byte[] datasetTlvs = HexDump.hexStringToByteArray(getNextArgRequired());
+ ActiveOperationalDataset dataset;
+ try {
+ dataset = ActiveOperationalDataset.fromThreadTlvs(datasetTlvs);
+ } catch (IllegalArgumentException e) {
+ getErrorWriter().println("Invalid dataset argument: " + e.getMessage());
+ return -1;
+ }
+
+ int delaySeconds;
+ try {
+ delaySeconds = Integer.parseInt(getNextArgRequired());
+ } catch (NumberFormatException e) {
+ getErrorWriter().println("Invalid delay argument: " + e.getMessage());
+ return -1;
+ }
+
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ dataset,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(delaySeconds));
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+ mControllerService.scheduleMigration(pendingDataset, newOperationReceiver(migrateFuture));
+ return waitForFuture(migrateFuture, MIGRATE_TIMEOUT, getErrorWriter());
}
private int forceStopOtDaemon() {
+ ensureTestingPermission();
final PrintWriter errorWriter = getErrorWriter();
boolean enabled;
try {
@@ -166,6 +214,40 @@
return waitForFuture(forceStopFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
}
+ private int forceCountryCode() {
+ ensureTestingPermission();
+ final PrintWriter perr = getErrorWriter();
+ boolean enabled;
+ try {
+ enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
+ } catch (IllegalArgumentException e) {
+ perr.println("Invalid argument: " + e.getMessage());
+ return -1;
+ }
+
+ if (enabled) {
+ String countryCode = getNextArgRequired();
+ if (!ThreadNetworkCountryCode.isValidCountryCode(countryCode)) {
+ perr.println(
+ "Invalid argument: Country code must be a 2-letter"
+ + " string. But got country code "
+ + countryCode
+ + " instead");
+ return -1;
+ }
+ mCountryCode.setOverrideCountryCode(countryCode);
+ } else {
+ mCountryCode.clearOverrideCountryCode();
+ }
+ return 0;
+ }
+
+ private int getCountryCode() {
+ ensureTestingPermission();
+ getOutputWriter().println("Thread country code = " + mCountryCode.getCountryCode());
+ return 0;
+ }
+
private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
return new IOperationReceiver.Stub() {
@Override
@@ -224,33 +306,4 @@
String nextArg = getNextArgRequired();
return argTrueOrFalse(nextArg, trueString, falseString);
}
-
- private void onHelpNonPrivileged(PrintWriter pw) {
- pw.println(" enable");
- pw.println(" Enables Thread radio");
- pw.println(" disable");
- pw.println(" Disables Thread radio");
- pw.println(" get-country-code");
- pw.println(" Gets country code as a two-letter string");
- }
-
- private void onHelpPrivileged(PrintWriter pw) {
- pw.println(" force-country-code enabled <two-letter code> | disabled ");
- pw.println(" Sets country code to <two-letter code> or left for normal value");
- pw.println(" force-stop-ot-daemon enabled | disabled ");
- pw.println(" force stop ot-daemon service");
- }
-
- @Override
- public void onHelp() {
- final PrintWriter pw = getOutputWriter();
- pw.println("Thread network commands:");
- pw.println(" help or -h");
- pw.println(" Print this help text.");
- onHelpNonPrivileged(pw);
- if (Binder.getCallingUid() == Process.ROOT_UID) {
- onHelpPrivileged(pw);
- }
- pw.println();
- }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index 8aaff60..f18aac9 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -63,6 +63,14 @@
/** Stores the Thread feature toggle state, true for enabled and false for disabled. */
public static final Key<Boolean> THREAD_ENABLED = new Key<>("thread_enabled", true);
+ /**
+ * Indicates that Thread was enabled (i.e. via the setEnabled() API) when the airplane mode is
+ * turned on in settings. When this value is {@code true}, the current airplane mode state will
+ * be ignored when evaluating the Thread enabled state.
+ */
+ public static final Key<Boolean> THREAD_ENABLED_IN_AIRPLANE_MODE =
+ new Key<>("thread_enabled_in_airplane_mode", false);
+
/** Stores the Thread country code, null if no country code is stored. */
public static final Key<String> THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null);
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index b29a54f..976f93d 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -16,29 +16,56 @@
package com.android.server.thread;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.EADDRINUSE;
+import static android.system.OsConstants.IFF_MULTICAST;
+import static android.system.OsConstants.IFF_NOARP;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_AF_SPEC;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_INET6_ADDR_GEN_MODE;
+import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IN6_ADDR_GEN_MODE_NONE;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
import android.annotation.Nullable;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
-import android.net.util.SocketUtils;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.OsConstants;
import android.util.Log;
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.netlink.NetlinkUtils;
-import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.net.module.util.netlink.StructNlAttr;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
+import com.android.server.thread.openthread.Ipv6AddressInfo;
+import com.android.server.thread.openthread.OnMeshPrefixConfig;
-import java.io.FileDescriptor;
import java.io.IOException;
-import java.io.InterruptedIOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
/** Controller for virtual/tunnel network interfaces. */
public class TunInterfaceController {
private static final String TAG = "TunIfController";
+ private static final boolean DBG = false;
private static final long INFINITE_LIFETIME = 0xffffffffL;
static final int MTU = 1280;
@@ -48,15 +75,19 @@
private final String mIfName;
private final LinkProperties mLinkProperties = new LinkProperties();
+ private final MulticastSocket mMulticastSocket; // For join group and leave group
+ private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
+ private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
+
private ParcelFileDescriptor mParcelTunFd;
- private FileDescriptor mNetlinkSocket;
- private static int sNetlinkSeqNo = 0;
+ private NetworkInterface mNetworkInterface;
/** Creates a new {@link TunInterfaceController} instance for given interface. */
public TunInterfaceController(String interfaceName) {
mIfName = interfaceName;
mLinkProperties.setInterfaceName(mIfName);
mLinkProperties.setMtu(MTU);
+ mMulticastSocket = createMulticastSocket();
}
/** Returns link properties of the Thread TUN interface. */
@@ -72,21 +103,22 @@
public void createTunInterface() throws IOException {
mParcelTunFd = ParcelFileDescriptor.adoptFd(nativeCreateTunInterface(mIfName, MTU));
try {
- mNetlinkSocket = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_ROUTE);
- } catch (ErrnoException e) {
- throw new IOException("Failed to create netlink socket", e);
+ mNetworkInterface = NetworkInterface.getByName(mIfName);
+ } catch (SocketException e) {
+ throw new IOException("Failed to get NetworkInterface", e);
}
+
+ setAddrGenModeToNone();
}
public void destroyTunInterface() {
try {
mParcelTunFd.close();
- SocketUtils.closeSocket(mNetlinkSocket);
} catch (IOException e) {
// Should never fail
}
mParcelTunFd = null;
- mNetlinkSocket = null;
+ mNetworkInterface = null;
}
/** Returns the FD of the tunnel interface. */
@@ -103,6 +135,10 @@
for (LinkAddress address : mLinkProperties.getAllLinkAddresses()) {
removeAddress(address);
}
+ for (RouteInfo route : mLinkProperties.getAllRoutes()) {
+ mLinkProperties.removeRoute(route);
+ }
+ mNetDataPrefixes.clear();
}
nativeSetInterfaceUp(mIfName, isUp);
}
@@ -113,14 +149,14 @@
public void addAddress(LinkAddress address) {
Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
- long validLifetimeSeconds;
long preferredLifetimeSeconds;
+ long validLifetimeSeconds;
if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- validLifetimeSeconds = INFINITE_LIFETIME;
+ preferredLifetimeSeconds = INFINITE_LIFETIME;
} else {
- validLifetimeSeconds =
+ preferredLifetimeSeconds =
Math.max(
(address.getDeprecationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
@@ -128,28 +164,23 @@
if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- preferredLifetimeSeconds = INFINITE_LIFETIME;
+ validLifetimeSeconds = INFINITE_LIFETIME;
} else {
- preferredLifetimeSeconds =
+ validLifetimeSeconds =
Math.max(
(address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
0L);
}
- byte[] message =
- RtNetlinkAddressMessage.newRtmNewAddressMessage(
- sNetlinkSeqNo++,
- address.getAddress(),
- (short) address.getPrefixLength(),
- address.getFlags(),
- (byte) address.getScope(),
- Os.if_nametoindex(mIfName),
- validLifetimeSeconds,
- preferredLifetimeSeconds);
- try {
- Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException | InterruptedIOException e) {
- Log.e(TAG, "Failed to add address " + address, e);
+ if (!NetlinkUtils.sendRtmNewAddressRequest(
+ Os.if_nametoindex(mIfName),
+ address.getAddress(),
+ (short) address.getPrefixLength(),
+ address.getFlags(),
+ (byte) address.getScope(),
+ preferredLifetimeSeconds,
+ validLifetimeSeconds)) {
+ Log.w(TAG, "Failed to add address " + address.getAddress().getHostAddress());
return;
}
mLinkProperties.addLinkAddress(address);
@@ -159,32 +190,96 @@
/** Removes an address from the interface. */
public void removeAddress(LinkAddress address) {
Log.d(TAG, "Removing address " + address);
- byte[] message =
- RtNetlinkAddressMessage.newRtmDelAddressMessage(
- sNetlinkSeqNo++,
- address.getAddress(),
- (short) address.getPrefixLength(),
- Os.if_nametoindex(mIfName));
// Intentionally update the mLinkProperties before send netlink message because the
// address is already removed from ot-daemon and apps can't reach to the address even
// when the netlink request below fails
mLinkProperties.removeLinkAddress(address);
mLinkProperties.removeRoute(getRouteForAddress(address));
- try {
- Os.write(mNetlinkSocket, message, 0, message.length);
- } catch (ErrnoException | InterruptedIOException e) {
- Log.e(TAG, "Failed to remove address " + address, e);
+ if (!NetlinkUtils.sendRtmDelAddressRequest(
+ Os.if_nametoindex(mIfName),
+ (Inet6Address) address.getAddress(),
+ (short) address.getPrefixLength())) {
+ Log.w(TAG, "Failed to remove address " + address.getAddress().getHostAddress());
}
}
+ public void updateAddresses(List<Ipv6AddressInfo> addressInfoList) {
+ final List<LinkAddress> newLinkAddresses = new ArrayList<>();
+ final List<InetAddress> newMulticastAddresses = new ArrayList<>();
+ boolean hasActiveOmrAddress = false;
+
+ for (Ipv6AddressInfo addressInfo : addressInfoList) {
+ if (addressInfo.isActiveOmr) {
+ hasActiveOmrAddress = true;
+ break;
+ }
+ }
+
+ for (Ipv6AddressInfo addressInfo : addressInfoList) {
+ InetAddress address = addressInfoToInetAddress(addressInfo);
+ if (address.isMulticastAddress()) {
+ newMulticastAddresses.add(address);
+ } else {
+ newLinkAddresses.add(newLinkAddress(addressInfo, hasActiveOmrAddress));
+ }
+ }
+
+ final CompareResult<LinkAddress> addressDiff =
+ new CompareResult<>(mLinkProperties.getAllLinkAddresses(), newLinkAddresses);
+ for (LinkAddress linkAddress : addressDiff.removed) {
+ removeAddress(linkAddress);
+ }
+ for (LinkAddress linkAddress : addressDiff.added) {
+ addAddress(linkAddress);
+ }
+
+ final CompareResult<InetAddress> multicastAddressDiff =
+ new CompareResult<>(mMulticastAddresses, newMulticastAddresses);
+ for (InetAddress address : multicastAddressDiff.removed) {
+ leaveGroup(address);
+ }
+ for (InetAddress address : multicastAddressDiff.added) {
+ joinGroup(address);
+ }
+ mMulticastAddresses.clear();
+ mMulticastAddresses.addAll(newMulticastAddresses);
+ }
+
+ public void updatePrefixes(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
+ final List<RouteInfo> newNetDataPrefixes = new ArrayList<>();
+
+ for (OnMeshPrefixConfig onMeshPrefixConfig : onMeshPrefixConfigList) {
+ newNetDataPrefixes.add(getRouteForOnMeshPrefix(onMeshPrefixConfig));
+ }
+
+ final CompareResult<RouteInfo> prefixDiff =
+ new CompareResult<>(mNetDataPrefixes, newNetDataPrefixes);
+ for (RouteInfo routeRemoved : prefixDiff.removed) {
+ mLinkProperties.removeRoute(routeRemoved);
+ }
+ for (RouteInfo routeAdded : prefixDiff.added) {
+ mLinkProperties.addRoute(routeAdded);
+ }
+
+ mNetDataPrefixes.clear();
+ mNetDataPrefixes.addAll(newNetDataPrefixes);
+ }
+
private RouteInfo getRouteForAddress(LinkAddress linkAddress) {
- return new RouteInfo(
- new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()),
- null,
- mIfName,
- RouteInfo.RTN_UNICAST,
- MTU);
+ return getRouteForIpPrefix(
+ new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()));
+ }
+
+ private RouteInfo getRouteForOnMeshPrefix(OnMeshPrefixConfig onMeshPrefixConfig) {
+ return getRouteForIpPrefix(
+ new IpPrefix(
+ bytesToInet6Address(onMeshPrefixConfig.prefix),
+ onMeshPrefixConfig.prefixLength));
+ }
+
+ private RouteInfo getRouteForIpPrefix(IpPrefix ipPrefix) {
+ return new RouteInfo(ipPrefix, null, mIfName, RouteInfo.RTN_UNICAST, MTU);
}
/** Called by {@link ThreadNetworkControllerService} to do clean up when ot-daemon is dead. */
@@ -195,4 +290,139 @@
Log.e(TAG, "Failed to set Thread TUN interface down");
}
}
+
+ private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
+ return bytesToInet6Address(addressInfo.address);
+ }
+
+ private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
+ try {
+ return (Inet6Address) Inet6Address.getByAddress(addressBytes);
+ } catch (UnknownHostException e) {
+ // This is unlikely to happen unless the Thread daemon is critically broken
+ return null;
+ }
+ }
+
+ private static LinkAddress newLinkAddress(
+ Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress) {
+ // Mesh-local addresses and OMR address have the same scope, to distinguish them we set
+ // mesh-local addresses as deprecated when there is an active OMR address.
+ // For OMR address and link-local address we only use the value isPreferred set by
+ // ot-daemon.
+ boolean isPreferred = addressInfo.isPreferred;
+ if (addressInfo.isMeshLocal && hasActiveOmrAddress) {
+ isPreferred = false;
+ }
+
+ final long deprecationTimeMillis =
+ isPreferred ? LinkAddress.LIFETIME_PERMANENT : SystemClock.elapsedRealtime();
+
+ final InetAddress address = addressInfoToInetAddress(addressInfo);
+
+ // flags and scope will be adjusted automatically depending on the address and
+ // its lifetimes.
+ return new LinkAddress(
+ address,
+ addressInfo.prefixLength,
+ 0 /* flags */,
+ 0 /* scope */,
+ deprecationTimeMillis,
+ LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
+ }
+
+ private MulticastSocket createMulticastSocket() {
+ try {
+ return new MulticastSocket();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create multicast socket ", e);
+ }
+ }
+
+ private void joinGroup(InetAddress address) {
+ InetSocketAddress socketAddress = new InetSocketAddress(address, 0);
+ try {
+ mMulticastSocket.joinGroup(socketAddress, mNetworkInterface);
+ } catch (IOException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ ErrnoException ee = (ErrnoException) e.getCause();
+ if (ee.errno == EADDRINUSE) {
+ Log.w(TAG, "Already joined group" + address.getHostAddress(), e);
+ return;
+ }
+ }
+ Log.e(TAG, "failed to join group " + address.getHostAddress(), e);
+ }
+ }
+
+ private void leaveGroup(InetAddress address) {
+ InetSocketAddress socketAddress = new InetSocketAddress(address, 0);
+ try {
+ mMulticastSocket.leaveGroup(socketAddress, mNetworkInterface);
+ } catch (IOException e) {
+ Log.e(TAG, "failed to leave group " + address.getHostAddress(), e);
+ }
+ }
+
+ /**
+ * Sets the address generation mode to {@code IN6_ADDR_GEN_MODE_NONE}.
+ *
+ * <p>So that the "thread-wpan" interface has only one IPv6 link local address which is
+ * generated by OpenThread.
+ */
+ private void setAddrGenModeToNone() {
+ StructNlMsgHdr header = new StructNlMsgHdr();
+ header.nlmsg_type = RTM_NEWLINK;
+ header.nlmsg_pid = 0;
+ header.nlmsg_seq = 0;
+ header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ StructIfinfoMsg ifInfo =
+ new StructIfinfoMsg(
+ (short) 0 /* family */,
+ 0 /* type */,
+ Os.if_nametoindex(mIfName),
+ (IFF_MULTICAST | IFF_NOARP) /* flags */,
+ 0xffffffff /* change */);
+
+ // Nested attributes
+ // IFLA_AF_SPEC
+ // AF_INET6
+ // IFLA_INET6_ADDR_GEN_MODE
+ StructNlAttr addrGenMode =
+ new StructNlAttr(IFLA_INET6_ADDR_GEN_MODE, (byte) IN6_ADDR_GEN_MODE_NONE);
+ StructNlAttr afInet6 = new StructNlAttr((short) AF_INET6, addrGenMode);
+ StructNlAttr afSpec = new StructNlAttr(IFLA_AF_SPEC, afInet6);
+
+ final int msgLength =
+ StructNlMsgHdr.STRUCT_SIZE
+ + StructIfinfoMsg.STRUCT_SIZE
+ + afSpec.getAlignedLength();
+ byte[] msg = new byte[msgLength];
+ ByteBuffer buf = ByteBuffer.wrap(msg);
+ buf.order(ByteOrder.nativeOrder());
+
+ header.nlmsg_len = msgLength;
+ header.pack(buf);
+ ifInfo.pack(buf);
+ afSpec.pack(buf);
+
+ if (buf.position() != msgLength) {
+ throw new AssertionError(
+ String.format(
+ "Unexpected netlink message size (actual = %d, expected = %d)",
+ buf.position(), msgLength));
+ }
+
+ if (DBG) {
+ Log.d(TAG, "ADDR_GEN_MODE message is:");
+ Log.d(TAG, HexDump.dumpHexString(msg));
+ }
+
+ try {
+ NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to set ADDR_GEN_MODE to NONE", e);
+ }
+ }
}
diff --git a/thread/tests/cts/AndroidTest.xml b/thread/tests/cts/AndroidTest.xml
index ffc181c..6eda1e9 100644
--- a/thread/tests/cts/AndroidTest.xml
+++ b/thread/tests/cts/AndroidTest.xml
@@ -38,6 +38,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<!-- Install test -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="CtsThreadNetworkTestCases.apk" />
diff --git a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
index 0e76930..996d22d 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
@@ -30,6 +30,8 @@
import android.net.thread.ActiveOperationalDataset.Builder;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +40,7 @@
import com.google.common.primitives.Bytes;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +49,7 @@
/** CTS tests for {@link ActiveOperationalDataset}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class ActiveOperationalDatasetTest {
private static final int TYPE_ACTIVE_TIMESTAMP = 14;
@@ -81,6 +85,8 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS);
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
private static byte[] removeTlv(byte[] dataset, int type) {
ByteArrayOutputStream os = new ByteArrayOutputStream(dataset.length);
int i = 0;
diff --git a/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java b/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
index 9be3d56..4d7c7f1 100644
--- a/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
@@ -21,12 +21,15 @@
import static org.junit.Assert.assertThrows;
import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,8 +37,11 @@
/** Tests for {@link OperationalDatasetTimestamp}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class OperationalDatasetTimestampTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
@Test
public void fromInstant_tooLargeInstant_throwsIllegalArgument() {
assertThrows(
diff --git a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
index 0bb18ce..76be054 100644
--- a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
@@ -28,6 +28,8 @@
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,6 +38,7 @@
import com.google.common.primitives.Bytes;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,8 +47,11 @@
/** Tests for {@link PendingOperationalDataset}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class PendingOperationalDatasetTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
private static ActiveOperationalDataset createActiveDataset() throws Exception {
SparseArray<byte[]> channelMask = new SparseArray<>(1);
channelMask.put(0, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0});
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index ba7392c..11c4819 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -43,7 +43,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
@@ -67,6 +66,7 @@
import android.net.thread.utils.TapTestNetworkTracker;
import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+import android.os.Build;
import android.os.HandlerThread;
import android.os.OutcomeReceiver;
@@ -79,6 +79,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -172,6 +173,17 @@
}
@Test
+ public void subscribeThreadEnableState_getActiveDataset_onThreadEnableStateChangedNotCalled()
+ throws Exception {
+ EnabledStateListener listener = new EnabledStateListener(mController);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+
+ getActiveOperationalDataset(mController);
+
+ listener.expectCallbackNotCalled();
+ }
+
+ @Test
public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
@@ -749,17 +761,6 @@
}
@Test
- public void setEnabled_toggleAfterJoin_joinsThreadNetworkAgain() throws Exception {
- joinRandomizedDatasetAndWait(mController);
-
- setEnabledAndWait(mController, false);
- assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
- setEnabledAndWait(mController, true);
-
- runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(mController));
- }
-
- @Test
public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception {
joinRandomizedDatasetAndWait(mController);
CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
@@ -792,10 +793,14 @@
public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
CompletableFuture<Network> networkFuture = new CompletableFuture<>();
ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
- NetworkRequest networkRequest =
- new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
- .build();
+ NetworkRequest.Builder networkRequestBuilder =
+ new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_THREAD);
+ // Before V, we need to explicitly set `NET_CAPABILITY_LOCAL_NETWORK` capability to request
+ // a Thread network.
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ networkRequestBuilder.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ }
+ NetworkRequest networkRequest = networkRequestBuilder.build();
ConnectivityManager.NetworkCallback networkCallback =
new ConnectivityManager.NetworkCallback() {
@Override
@@ -857,6 +862,7 @@
}
@Test
+ @Ignore("b/333649897: Enable this when it's not flaky at all")
public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
setUpTestNetwork();
@@ -1020,7 +1026,11 @@
}
public void expectThreadEnabledState(int enabled) {
- assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled)));
+ assertThat(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled))).isNotNull();
+ }
+
+ public void expectCallbackNotCalled() {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, e -> true)).isNull();
}
public void unregisterStateCallback() {
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
index 7d9ae81..4de2e13 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
@@ -25,17 +25,23 @@
import static org.junit.Assert.assertThrows;
import android.net.thread.ThreadNetworkException;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** CTS tests for {@link ThreadNetworkException}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class ThreadNetworkExceptionTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
@Test
public void constructor_validValues_valuesAreConnectlySet() throws Exception {
ThreadNetworkException errorThreadDisabled =
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 94985b1..71693af 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -34,6 +34,7 @@
"testables",
"ThreadNetworkTestUtils",
"truth",
+ "ot-daemon-aidl-java",
],
libs: [
"android.test.runner",
diff --git a/thread/tests/integration/AndroidTest.xml b/thread/tests/integration/AndroidTest.xml
index 152c1c3..8f98941 100644
--- a/thread/tests/integration/AndroidTest.xml
+++ b/thread/tests/integration/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- Install test -->
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 9b1c338..8c63d37 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
@@ -33,6 +34,7 @@
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -43,6 +45,8 @@
import android.content.Context;
import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.thread.utils.FullThreadDevice;
@@ -55,6 +59,7 @@
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
@@ -252,6 +257,21 @@
}
@Test
+ public void unicastRouting_meshLocalAddressesAreNotPreferred() throws Exception {
+ // When BR is enabled, there will be OMR address, so the mesh-local addresses are expected
+ // to be deprecated.
+ List<LinkAddress> linkAddresses = getIpv6LinkAddresses("thread-wpan");
+ IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
+
+ for (LinkAddress address : linkAddresses) {
+ if (meshLocalPrefix.contains(address.getAddress())) {
+ assertThat(address.getDeprecationTime()).isAtMost(SystemClock.elapsedRealtime());
+ assertThat(address.isPreferred()).isFalse();
+ }
+ }
+ }
+
+ @Test
@RequiresIpv6MulticastRouting
public void multicastRouting_ftdSubscribedMulticastAddress_infraLinkJoinsMulticastGroup()
throws Exception {
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index 5a8d21f..e10f134 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -265,6 +265,7 @@
}
@Test
+ @Ignore("TODO: b/333806992 - Enable when it's not flaky at all")
public void advertisingProxy_srpClientUnregistersService_serviceIsNotDiscoverableByMdns()
throws Exception {
/*
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index c70f3af..61b6eac 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -21,14 +21,27 @@
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
+import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
+import static android.net.thread.utils.IntegrationTestUtils.getPrefixesFromNetData;
+import static android.net.thread.utils.IntegrationTestUtils.getThreadNetwork;
+import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
+import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.OtDaemonController;
import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
@@ -45,14 +58,31 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.time.Duration;
+import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
@LargeTest
@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public class ThreadIntegrationTest {
+ // The byte[] buffer size for UDP tests
+ private static final int UDP_BUFFER_SIZE = 1024;
+
+ // The maximum time for OT addresses to be propagated to the TUN interface "thread-wpan"
+ private static final Duration TUN_ADDR_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
+ // The maximum time for changes to be propagated to netdata.
+ private static final Duration NET_DATA_UPDATE_TIMEOUT = Duration.ofSeconds(1);
+
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
private static final byte[] DEFAULT_DATASET_TLVS =
base16().decode(
@@ -64,26 +94,41 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ private static final Inet6Address GROUP_ADDR_ALL_ROUTERS =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
+
+ private static final String TEST_NO_SLAAC_PREFIX = "9101:dead:beef:cafe::/64";
+ private static final InetAddress TEST_NO_SLAAC_PREFIX_ADDRESS =
+ InetAddresses.parseNumericAddress("9101:dead:beef:cafe::");
+
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+ private ExecutorService mExecutor;
private final Context mContext = ApplicationProvider.getApplicationContext();
private final ThreadNetworkControllerWrapper mController =
ThreadNetworkControllerWrapper.newInstance(mContext);
private OtDaemonController mOtCtl;
+ private FullThreadDevice mFtd;
@Before
public void setUp() throws Exception {
+ mExecutor = Executors.newSingleThreadExecutor();
mOtCtl = new OtDaemonController();
mController.leaveAndWait();
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
mOtCtl.factoryReset();
+
+ mFtd = new FullThreadDevice(10 /* nodeId */);
}
@After
public void tearDown() throws Exception {
mController.setTestNetworkAsUpstreamAndWait(null);
mController.leaveAndWait();
+
+ mFtd.destroy();
+ mExecutor.shutdownNow();
}
@Test
@@ -129,16 +174,25 @@
assertThat(ifconfig).doesNotContain("inet6 addr");
}
+ // TODO (b/323300829): add test for removing an OT address
@Test
- public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
+ public void tunInterface_joinedNetwork_otAndTunAddressesMatch() throws Exception {
mController.joinAndWait(DEFAULT_DATASET);
- String ifconfig = runShellCommand("ifconfig thread-wpan");
List<Inet6Address> otAddresses = mOtCtl.getAddresses();
assertThat(otAddresses).isNotEmpty();
- for (Inet6Address otAddress : otAddresses) {
- assertThat(ifconfig).contains(otAddress.getHostAddress());
- }
+ // TODO: it's cleaner to have a retry() method to retry failed asserts in given delay so
+ // that we can write assertThat() in the Predicate
+ waitFor(
+ () -> {
+ List<Inet6Address> tunAddresses =
+ getIpv6LinkAddresses("thread-wpan").stream()
+ .map(linkAddr -> (Inet6Address) linkAddr.getAddress())
+ .toList();
+ return otAddresses.containsAll(tunAddresses)
+ && tunAddresses.containsAll(otAddresses);
+ },
+ TUN_ADDR_UPDATE_TIMEOUT);
}
@Test
@@ -153,10 +207,163 @@
assertThat(mOtCtl.getCountryCode()).isEqualTo("CN");
}
- private static String runThreadCommand(String cmd) {
- return runShellCommandOrThrow("cmd thread_network " + cmd);
+ @Test
+ public void udp_appStartEchoServer_endDeviceUdpEchoSuccess() throws Exception {
+ // Topology:
+ // Test App ------ thread-wpan ------ End Device
+
+ mController.joinAndWait(DEFAULT_DATASET);
+ startFtdChild(mFtd, DEFAULT_DATASET);
+ final Inet6Address serverAddress = mOtCtl.getMeshLocalAddresses().get(0);
+ final int serverPort = 9527;
+
+ mExecutor.execute(() -> startUdpEchoServerAndWait(serverAddress, serverPort));
+ mFtd.udpOpen();
+ mFtd.udpSend("Hello,Thread", serverAddress, serverPort);
+ String udpReply = mFtd.udpReceive();
+
+ assertThat(udpReply).isEqualTo("Hello,Thread");
+ }
+
+ @Test
+ public void joinNetworkWithBrDisabled_meshLocalAddressesArePreferred() throws Exception {
+ // When BR feature is disabled, there is no OMR address, so the mesh-local addresses are
+ // expected to be preferred.
+ mOtCtl.executeCommand("br disable");
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ IpPrefix meshLocalPrefix = DEFAULT_DATASET.getMeshLocalPrefix();
+ List<LinkAddress> linkAddresses = getIpv6LinkAddresses("thread-wpan");
+ for (LinkAddress address : linkAddresses) {
+ if (meshLocalPrefix.contains(address.getAddress())) {
+ assertThat(address.getDeprecationTime())
+ .isGreaterThan(SystemClock.elapsedRealtime());
+ assertThat(address.isPreferred()).isTrue();
+ }
+ }
+
+ mOtCtl.executeCommand("br enable");
+ }
+
+ @Test
+ public void joinNetwork_tunInterfaceJoinsAllRouterMulticastGroup() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ assertTunInterfaceMemberOfGroup(GROUP_ADDR_ALL_ROUTERS);
+ }
+
+ @Test
+ public void edPingsMeshLocalAddresses_oneReplyPerRequest() throws Exception {
+ mController.joinAndWait(DEFAULT_DATASET);
+ startFtdChild(mFtd, DEFAULT_DATASET);
+ List<Inet6Address> meshLocalAddresses = mOtCtl.getMeshLocalAddresses();
+
+ for (Inet6Address address : meshLocalAddresses) {
+ assertWithMessage(
+ "There may be duplicated replies of ping request to "
+ + address.getHostAddress())
+ .that(mFtd.ping(address, 2))
+ .isEqualTo(2);
+ }
+ }
+
+ @Test
+ public void addPrefixToNetData_routeIsAddedToTunInterface() throws Exception {
+ ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ // Ftd child doesn't have the ability to add a prefix, so let BR itself add a prefix.
+ mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+ mOtCtl.executeCommand("netdata register");
+ waitFor(
+ () -> {
+ String netData = mOtCtl.executeCommand("netdata show");
+ return getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
+ },
+ NET_DATA_UPDATE_TIMEOUT);
+
+ LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+ assertThat(lp).isNotNull();
+ assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+ .isTrue();
+ }
+
+ @Test
+ public void removePrefixFromNetData_routeIsRemovedFromTunInterface() throws Exception {
+ ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ mController.joinAndWait(DEFAULT_DATASET);
+ mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+ mOtCtl.executeCommand("netdata register");
+
+ mOtCtl.executeCommand("prefix remove " + TEST_NO_SLAAC_PREFIX);
+ mOtCtl.executeCommand("netdata register");
+ waitFor(
+ () -> {
+ String netData = mOtCtl.executeCommand("netdata show");
+ return !getPrefixesFromNetData(netData).contains(TEST_NO_SLAAC_PREFIX);
+ },
+ NET_DATA_UPDATE_TIMEOUT);
+
+ LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+ assertThat(lp).isNotNull();
+ assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+ .isFalse();
+ }
+
+ @Test
+ public void toggleThreadNetwork_routeFromPreviousNetDataIsRemoved() throws Exception {
+ ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ mController.joinAndWait(DEFAULT_DATASET);
+ mOtCtl.executeCommand("prefix add " + TEST_NO_SLAAC_PREFIX + " pros med");
+ mOtCtl.executeCommand("netdata register");
+
+ mController.leaveAndWait();
+ mOtCtl.factoryReset();
+ mController.joinAndWait(DEFAULT_DATASET);
+
+ LinkProperties lp = cm.getLinkProperties(getThreadNetwork(CALLBACK_TIMEOUT));
+ assertThat(lp).isNotNull();
+ assertThat(lp.getRoutes().stream().anyMatch(r -> r.matches(TEST_NO_SLAAC_PREFIX_ADDRESS)))
+ .isFalse();
}
// TODO (b/323300829): add more tests for integration with linux platform and
// ConnectivityService
+
+ private static String runThreadCommand(String cmd) {
+ return runShellCommandOrThrow("cmd thread_network " + cmd);
+ }
+
+ private void startFtdChild(FullThreadDevice ftd, ActiveOperationalDataset activeDataset)
+ throws Exception {
+ ftd.factoryReset();
+ ftd.joinNetwork(activeDataset);
+ ftd.waitForStateAnyOf(List.of("router", "child"), Duration.ofSeconds(8));
+ }
+
+ /**
+ * Starts a UDP echo server and replies to the first UDP message.
+ *
+ * <p>This method exits when the first UDP message is received and the reply is sent
+ */
+ private void startUdpEchoServerAndWait(InetAddress serverAddress, int serverPort) {
+ try (var udpServerSocket = new DatagramSocket(serverPort, serverAddress)) {
+ DatagramPacket recvPacket =
+ new DatagramPacket(new byte[UDP_BUFFER_SIZE], UDP_BUFFER_SIZE);
+ udpServerSocket.receive(recvPacket);
+ byte[] sendBuffer = Arrays.copyOf(recvPacket.getData(), recvPacket.getData().length);
+ udpServerSocket.send(
+ new DatagramPacket(
+ sendBuffer,
+ sendBuffer.length,
+ (Inet6Address) recvPacket.getAddress(),
+ recvPacket.getPort()));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void assertTunInterfaceMemberOfGroup(Inet6Address address) throws Exception {
+ waitFor(() -> isInMulticastGroup(TUN_IF_NAME, address), TUN_ADDR_UPDATE_TIMEOUT);
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index f7bb9ff..d24059a 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -17,18 +17,16 @@
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
-
import static com.google.common.io.BaseEncoding.base16;
-
-import static org.junit.Assert.fail;
+import static java.util.concurrent.TimeUnit.SECONDS;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
-
+import android.os.Handler;
+import android.os.HandlerThread;
import com.google.errorprone.annotations.FormatMethod;
-
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -41,6 +39,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -59,11 +59,16 @@
private static final int PING_SIZE = 100;
// There may not be a response for the ping command, using a short timeout to keep the tests
// short.
- private static final float PING_TIMEOUT_SECONDS = 0.1f;
+ private static final float PING_TIMEOUT_0_1_SECOND = 0.1f;
+ // 1 second timeout should be used when response is expected.
+ private static final float PING_TIMEOUT_1_SECOND = 1f;
+ private static final int READ_LINE_TIMEOUT_SECONDS = 5;
private final Process mProcess;
private final BufferedReader mReader;
private final BufferedWriter mWriter;
+ private final HandlerThread mReaderHandlerThread;
+ private final Handler mReaderHandler;
private ActiveOperationalDataset mActiveOperationalDataset;
@@ -80,17 +85,22 @@
*/
public FullThreadDevice(int nodeId) {
try {
- mProcess = Runtime.getRuntime().exec("/system/bin/ot-cli-ftd " + nodeId);
+ mProcess = Runtime.getRuntime().exec("/system/bin/ot-cli-ftd -Leth1 " + nodeId);
} catch (IOException e) {
- throw new IllegalStateException("Failed to start ot-cli-ftd (id=" + nodeId + ")", e);
+ throw new IllegalStateException(
+ "Failed to start ot-cli-ftd -Leth1 (id=" + nodeId + ")", e);
}
mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
+ mReaderHandlerThread = new HandlerThread("FullThreadDeviceReader");
+ mReaderHandlerThread.start();
+ mReaderHandler = new Handler(mReaderHandlerThread.getLooper());
mActiveOperationalDataset = null;
}
public void destroy() {
mProcess.destroy();
+ mReaderHandlerThread.quit();
}
/**
@@ -212,12 +222,17 @@
public String udpReceive() throws IOException {
Pattern pattern =
Pattern.compile("> (\\d+) bytes from ([\\da-f:]+) (\\d+) ([\\x00-\\x7F]+)");
- Matcher matcher = pattern.matcher(mReader.readLine());
+ Matcher matcher = pattern.matcher(readLine());
matcher.matches();
return matcher.group(4);
}
+ /** Sends a UDP message to given IPv6 address and port. */
+ public void udpSend(String message, Inet6Address serverAddr, int serverPort) {
+ executeCommand("udp send %s %d %s", serverAddr.getHostAddress(), serverPort, message);
+ }
+
/** Enables the SRP client and run in autostart mode. */
public void autoStartSrpClient() {
executeCommand("srp client autostart enable");
@@ -405,7 +420,7 @@
1 /* count */,
PING_INTERVAL,
HOP_LIMIT,
- PING_TIMEOUT_SECONDS);
+ PING_TIMEOUT_0_1_SECOND);
}
public void ping(Inet6Address address) {
@@ -416,10 +431,24 @@
1 /* count */,
PING_INTERVAL,
HOP_LIMIT,
- PING_TIMEOUT_SECONDS);
+ PING_TIMEOUT_0_1_SECOND);
}
- private void ping(
+ /** Returns the number of ping reply packets received. */
+ public int ping(Inet6Address address, int count) {
+ List<String> output =
+ ping(
+ address,
+ null,
+ PING_SIZE,
+ count,
+ PING_INTERVAL,
+ HOP_LIMIT,
+ PING_TIMEOUT_1_SECOND);
+ return getReceivedPacketsCount(output);
+ }
+
+ private List<String> ping(
Inet6Address address,
Inet6Address source,
int size,
@@ -442,7 +471,21 @@
+ hopLimit
+ " "
+ timeout;
- executeCommand(cmd);
+ return executeCommand(cmd);
+ }
+
+ private int getReceivedPacketsCount(List<String> stringList) {
+ Pattern pattern = Pattern.compile("([\\d]+) packets received");
+
+ for (String message : stringList) {
+ Matcher matcher = pattern.matcher(message);
+ if (matcher.find()) {
+ String packetCountStr = matcher.group(1);
+ return Integer.parseInt(packetCountStr);
+ }
+ }
+ // No match found
+ return -1;
}
@FormatMethod
@@ -466,15 +509,32 @@
}
}
+ private String readLine() throws IOException {
+ final CompletableFuture<String> future = new CompletableFuture<>();
+ mReaderHandler.post(
+ () -> {
+ try {
+ future.complete(mReader.readLine());
+ } catch (IOException e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ return future.get(READ_LINE_TIMEOUT_SECONDS, SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new IOException("Failed to read a line from ot-cli-ftd");
+ }
+ }
+
private List<String> readUntilDone() throws IOException {
ArrayList<String> result = new ArrayList<>();
String line;
- while ((line = mReader.readLine()) != null) {
+ while ((line = readLine()) != null) {
if (line.equals("Done")) {
break;
}
if (line.startsWith("Error")) {
- fail("ot-cli-ftd reported an error: " + line);
+ throw new IOException("ot-cli-ftd reported an error: " + line);
}
if (!line.startsWith("> ")) {
result.add(line);
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index 2237e65..ada46c8 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -15,6 +15,7 @@
*/
package android.net.thread.utils;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
@@ -24,15 +25,24 @@
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import android.net.ConnectivityManager;
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.net.TestNetworkInterface;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.net.thread.ThreadNetworkController;
+import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.Icmpv6Header;
@@ -85,7 +95,7 @@
*/
public static void waitFor(Supplier<Boolean> condition, Duration timeout)
throws TimeoutException {
- final long intervalMills = 1000;
+ final long intervalMills = 500;
final long timeoutMills = timeout.toMillis();
for (long i = 0; i < timeoutMills; i += intervalMills) {
@@ -291,6 +301,20 @@
return false;
}
+ public static List<LinkAddress> getIpv6LinkAddresses(String interfaceName) {
+ List<LinkAddress> addresses = new ArrayList<>();
+ final String cmd = " ip -6 addr show dev " + interfaceName;
+ final String output = runShellCommandOrThrow(cmd);
+
+ for (final String line : output.split("\\n")) {
+ if (line.contains("inet6")) {
+ addresses.add(parseAddressLine(line));
+ }
+ }
+
+ return addresses;
+ }
+
/** Return the first discovered service of {@code serviceType}. */
public static NsdServiceInfo discoverService(NsdManager nsdManager, String serviceType)
throws Exception {
@@ -359,6 +383,36 @@
}
}
+ public static String getPrefixesFromNetData(String netData) {
+ int startIdx = netData.indexOf("Prefixes:");
+ int endIdx = netData.indexOf("Routes:");
+ return netData.substring(startIdx, endIdx);
+ }
+
+ public static Network getThreadNetwork(Duration timeout) throws Exception {
+ CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+ ConnectivityManager cm =
+ ApplicationProvider.getApplicationContext()
+ .getSystemService(ConnectivityManager.class);
+ NetworkRequest.Builder networkRequestBuilder =
+ new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_THREAD);
+ // Before V, we need to explicitly set `NET_CAPABILITY_LOCAL_NETWORK` capability to request
+ // a Thread network.
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ networkRequestBuilder.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ }
+ NetworkRequest networkRequest = networkRequestBuilder.build();
+ ConnectivityManager.NetworkCallback networkCallback =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ networkFuture.complete(network);
+ }
+ };
+ cm.registerNetworkCallback(networkRequest, networkCallback);
+ return networkFuture.get(timeout.toSeconds(), SECONDS);
+ }
+
private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
@@ -392,4 +446,29 @@
@Override
public void onServiceInfoCallbackUnregistered() {}
}
+
+ /**
+ * Parses a line of output from "ip -6 addr show" into a {@link LinkAddress}.
+ *
+ * <p>Example line: "inet6 2001:db8:1:1::1/64 scope global deprecated"
+ */
+ private static LinkAddress parseAddressLine(String line) {
+ String[] parts = line.trim().split("\\s+");
+ String addressString = parts[1];
+ String[] pieces = addressString.split("/", 2);
+ int prefixLength = Integer.parseInt(pieces[1]);
+ final InetAddress address = InetAddresses.parseNumericAddress(pieces[0]);
+ long deprecationTimeMillis =
+ line.contains("deprecated")
+ ? SystemClock.elapsedRealtime()
+ : LinkAddress.LIFETIME_PERMANENT;
+
+ return new LinkAddress(
+ address,
+ prefixLength,
+ 0 /* flags */,
+ 0 /* scope */,
+ deprecationTimeMillis,
+ LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index f39a064..b3175fd 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -16,13 +16,16 @@
package android.net.thread.utils;
+import android.annotation.Nullable;
import android.net.InetAddresses;
+import android.net.IpPrefix;
import android.os.SystemClock;
import com.android.compatibility.common.util.SystemUtil;
import java.net.Inet6Address;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -53,10 +56,7 @@
/** Returns the list of IPv6 addresses on ot-daemon. */
public List<Inet6Address> getAddresses() {
- String output = executeCommand("ipaddr");
- return Arrays.asList(output.split("\n")).stream()
- .map(String::trim)
- .filter(str -> !str.equals("Done"))
+ return executeCommandAndParse("ipaddr").stream()
.map(addr -> InetAddresses.parseNumericAddress(addr))
.map(inetAddr -> (Inet6Address) inetAddr)
.toList();
@@ -70,17 +70,54 @@
/** Returns the ML-EID of the device. */
public Inet6Address getMlEid() {
- String addressStr = executeCommand("ipaddr mleid").split("\n")[0].trim();
+ String addressStr = executeCommandAndParse("ipaddr mleid").get(0);
return (Inet6Address) InetAddresses.parseNumericAddress(addressStr);
}
/** Returns the country code on ot-daemon. */
public String getCountryCode() {
- String countryCodeStr = executeCommand("region").split("\n")[0].trim();
- return countryCodeStr;
+ return executeCommandAndParse("region").get(0);
+ }
+
+ /**
+ * Returns the list of IPv6 Mesh-Local addresses on ot-daemon.
+ *
+ * <p>The return List can be empty if no Mesh-Local prefix exists.
+ */
+ public List<Inet6Address> getMeshLocalAddresses() {
+ IpPrefix meshLocalPrefix = getMeshLocalPrefix();
+ if (meshLocalPrefix == null) {
+ return Collections.emptyList();
+ }
+ return getAddresses().stream().filter(addr -> meshLocalPrefix.contains(addr)).toList();
+ }
+
+ /**
+ * Returns the Mesh-Local prefix or {@code null} if none exists (e.g. the Active Dataset is not
+ * set).
+ */
+ @Nullable
+ public IpPrefix getMeshLocalPrefix() {
+ List<IpPrefix> prefixes =
+ executeCommandAndParse("prefix meshlocal").stream()
+ .map(prefix -> new IpPrefix(prefix))
+ .toList();
+ return prefixes.isEmpty() ? null : prefixes.get(0);
}
public String executeCommand(String cmd) {
return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
}
+
+ /**
+ * Executes a ot-ctl command and parse the output to a list of strings.
+ *
+ * <p>The trailing "Done" in the command output will be dropped.
+ */
+ public List<String> executeCommandAndParse(String cmd) {
+ return Arrays.asList(executeCommand(cmd).split("\n")).stream()
+ .map(String::trim)
+ .filter(str -> !str.equals("Done"))
+ .toList();
+ }
}
diff --git a/thread/tests/multidevices/Android.bp b/thread/tests/multidevices/Android.bp
new file mode 100644
index 0000000..050caa8
--- /dev/null
+++ b/thread/tests/multidevices/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2024 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 {
+ default_team: "trendy_team_fwk_thread_network",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test_host {
+ name: "ThreadNetworkMultiDeviceTests",
+ main: "thread_network_multi_device_test.py",
+ srcs: ["thread_network_multi_device_test.py"],
+ test_config: "AndroidTest.xml",
+ libs: [
+ "mobly",
+ ],
+ test_options: {
+ unit_test: false,
+ tags: ["mobly"],
+ },
+ test_suites: [
+ "mts-tethering",
+ "general-tests",
+ ],
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
+}
diff --git a/thread/tests/multidevices/AndroidTest.xml b/thread/tests/multidevices/AndroidTest.xml
new file mode 100644
index 0000000..8b2bed3
--- /dev/null
+++ b/thread/tests/multidevices/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+ -->
+
+<configuration description="Config for Thread Multi-device test cases">
+ <option name="config-descriptor:metadata" key="component" value="threadnetwork" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
+
+ <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController"
+ type="module_controller">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+ <!--
+ Only run tests if the device under test is SDK version 34 (Android 14) or above.
+ -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+
+ <device name="device1">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ </device>
+ <device name="device2">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ </device>
+
+ <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+ <!-- The mobly-par-file-name should match the module name -->
+ <option name="mobly-par-file-name" value="ThreadNetworkMultiDeviceTests" />
+ <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+ <option name="mobly-test-timeout" value="180000" />
+ </test>
+</configuration>
diff --git a/thread/tests/multidevices/thread_network_multi_device_test.py b/thread/tests/multidevices/thread_network_multi_device_test.py
new file mode 100644
index 0000000..652576b
--- /dev/null
+++ b/thread/tests/multidevices/thread_network_multi_device_test.py
@@ -0,0 +1,67 @@
+# Copyright (C) 2024 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.
+
+# Lint as: python3
+
+import logging
+import time
+
+from mobly import asserts
+from mobly import base_test
+from mobly import test_runner
+from mobly.controllers import android_device
+
+class ThreadNetworkMultiDeviceTest(base_test.BaseTestClass):
+ def setup_class(self):
+ self.node_a, self.node_b = self.register_controller(
+ android_device, min_number=2)
+ self.node_a.adb.shell([
+ 'ot-ctl', 'factoryreset',
+ ])
+ self.node_b.adb.shell([
+ 'ot-ctl', 'factoryreset',
+ ])
+ time.sleep(1)
+
+ def ot_ctl(self, node, cmd, expect_done=True):
+ args = cmd.split(' ')
+ args = ['ot-ctl'] + args
+ stdout = node.adb.shell(args).decode('utf-8')
+ if expect_done:
+ asserts.assert_in('Done', stdout)
+ return stdout
+
+ def test_b_should_be_able_to_discover_a(self):
+ self.ot_ctl(self.node_a, 'dataset init new')
+ self.ot_ctl(self.node_a, 'dataset commit active')
+ self.ot_ctl(self.node_a, 'ifconfig up')
+ self.ot_ctl(self.node_a, 'thread start')
+ self.ot_ctl(self.node_a, 'state leader')
+ stdout = self.ot_ctl(self.node_a, 'extaddr')
+ extaddr = stdout.splitlines()[0]
+ logging.info('node a extaddr: %s', extaddr)
+ asserts.assert_equal(len(extaddr), 16)
+
+ stdout = self.ot_ctl(self.node_b, 'scan')
+ asserts.assert_in(extaddr, stdout)
+ logging.info('discovered node a')
+
+
+if __name__ == '__main__':
+ # Take test args
+ if '--' in sys.argv:
+ index = sys.argv.index('--')
+ sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+
+ test_runner.main()
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 3365cd0..9404d1b 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -33,6 +33,7 @@
"mts-tethering",
],
static_libs: [
+ "androidx.test.rules",
"frameworks-base-testutils",
"framework-connectivity-pre-jarjar",
"framework-connectivity-t-pre-jarjar",
diff --git a/thread/tests/unit/AndroidManifest.xml b/thread/tests/unit/AndroidManifest.xml
index ace7c52..8442e80 100644
--- a/thread/tests/unit/AndroidManifest.xml
+++ b/thread/tests/unit/AndroidManifest.xml
@@ -19,6 +19,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.net.thread.unittests">
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index d16e423..58e9bdd 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
<option name="check-min-sdk" value="true" />
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index 8886c73..3cae84f 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -16,6 +16,7 @@
package com.android.server.thread;
+import static android.net.DnsResolver.ERROR_SYSTEM;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
@@ -30,15 +31,19 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.test.TestLooper;
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
@@ -61,11 +66,14 @@
/** Unit tests for {@link NsdPublisher}. */
public final class NsdPublisherTest {
@Mock private NsdManager mMockNsdManager;
+ @Mock private DnsResolver mMockDnsResolver;
@Mock private INsdStatusReceiver mRegistrationReceiver;
@Mock private INsdStatusReceiver mUnregistrationReceiver;
@Mock private INsdDiscoverServiceCallback mDiscoverServiceCallback;
@Mock private INsdResolveServiceCallback mResolveServiceCallback;
+ @Mock private INsdResolveHostCallback mResolveHostCallback;
+ @Mock private Network mNetwork;
private TestLooper mTestLooper;
private NsdPublisher mNsdPublisher;
@@ -637,6 +645,84 @@
}
@Test
+ public void resolveHost_hostResolved() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onAnswer(
+ List.of(
+ InetAddresses.parseNumericAddress("2001::1"),
+ InetAddresses.parseNumericAddress("2001::2")),
+ 0);
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1))
+ .onHostResolved("test", List.of("2001::1", "2001::2"));
+ }
+
+ @Test
+ public void resolveHost_errorReported() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onError(new DnsResolver.DnsException(ERROR_SYSTEM, null /* cause */));
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1)).onHostResolved("test", Collections.emptyList());
+ }
+
+ @Test
+ public void stopHostResolution() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<CancellationSignal> cancellationSignalArgumentCaptor =
+ ArgumentCaptor.forClass(CancellationSignal.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ cancellationSignalArgumentCaptor.capture(),
+ any(DnsResolver.Callback.class));
+
+ mNsdPublisher.stopHostResolution(10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ assertThat(cancellationSignalArgumentCaptor.getValue().isCanceled()).isTrue();
+ }
+
+ @Test
public void reset_unregisterAll() {
prepareTest();
@@ -780,6 +866,7 @@
private void prepareTest() {
mTestLooper = new TestLooper();
Handler handler = new Handler(mTestLooper.getLooper());
- mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+ mNsdPublisher = new NsdPublisher(mMockNsdManager, mMockDnsResolver, handler);
+ mNsdPublisher.setNetworkForHostResolution(mNetwork);
}
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 85b6873..8f60783 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -26,6 +26,7 @@
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED_IN_AIRPLANE_MODE;
import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
import static com.google.common.io.BaseEncoding.base16;
@@ -35,6 +36,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
@@ -62,20 +64,27 @@
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserManager;
import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.util.AtomicFile;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.connectivity.resources.R;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.MeshcopTxtAttributes;
import com.android.server.thread.openthread.testing.FakeOtDaemon;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
@@ -83,7 +92,10 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import java.time.Clock;
+import java.time.DateTimeException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -91,6 +103,12 @@
/** Unit tests for {@link ThreadNetworkControllerService}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+// This test doesn't really need to run on the UI thread, but @Before and @Test annotated methods
+// need to run in the same thread because there are code in {@code ThreadNetworkControllerService}
+// checking that all its methods are running in the thread of the handler it's using. This is due
+// to a bug in TestLooper that it executes all tasks on the current thread rather than the thread
+// associated to the backed Looper object.
+@UiThreadTest
public final class ThreadNetworkControllerServiceTest {
// A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
// Active Timestamp: 1
@@ -115,7 +133,11 @@
private static final String DEFAULT_NETWORK_NAME = "thread-wpan0";
private static final int OT_ERROR_NONE = 0;
private static final int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26
- private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0x00000800; // channel 11
+
+ // The DEFAULT_PREFERRED_CHANNEL_MASK is the ot-daemon preferred channel mask. Channel 25 and
+ // 26 are not preferred by dataset. The ThreadNetworkControllerService will only select channel
+ // 11 when it creates randomized dataset.
+ private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0x06000800; // channel 11, 25 and 26
private static final int DEFAULT_SELECTED_CHANNEL = 11;
private static final byte[] DEFAULT_SUPPORTED_CHANNEL_MASK_ARRAY = base16().decode("001FFFE0");
@@ -129,7 +151,6 @@
@Mock private TunInterfaceController mMockTunIfController;
@Mock private ParcelFileDescriptor mMockTunFd;
@Mock private InfraInterfaceController mMockInfraIfController;
- @Mock private ThreadPersistentSettings mMockPersistentSettings;
@Mock private NsdPublisher mMockNsdPublisher;
@Mock private UserManager mMockUserManager;
@Mock private IBinder mIBinder;
@@ -139,11 +160,15 @@
private Context mContext;
private TestLooper mTestLooper;
private FakeOtDaemon mFakeOtDaemon;
+ private ThreadPersistentSettings mPersistentSettings;
private ThreadNetworkControllerService mService;
@Captor private ArgumentCaptor<ActiveOperationalDataset> mActiveDatasetCaptor;
+ @Rule(order = 1)
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
@@ -160,10 +185,12 @@
mFakeOtDaemon = new FakeOtDaemon(handler);
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
- when(mMockPersistentSettings.get(any())).thenReturn(true);
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+
when(mConnectivityResources.get()).thenReturn(mResources);
+ when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
when(mResources.getString(eq(R.string.config_thread_vendor_name)))
.thenReturn(TEST_VENDOR_NAME);
when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
@@ -171,6 +198,10 @@
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
+ final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
+ mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
+ mPersistentSettings.initialize();
+
mService =
new ThreadNetworkControllerService(
mContext,
@@ -180,7 +211,7 @@
mMockConnectivityManager,
mMockTunIfController,
mMockInfraIfController,
- mMockPersistentSettings,
+ mPersistentSettings,
mMockNsdPublisher,
mMockUserManager,
mConnectivityResources,
@@ -339,15 +370,9 @@
@Test
public void userRestriction_userBecomesRestricted_stateIsDisabledButNotPersisted() {
- AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(false);
- doAnswer(
- invocation -> {
- receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
- return null;
- })
- .when(mContext)
- .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+ AtomicReference<BroadcastReceiver> receiverRef =
+ captureBroadcastReceiver(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
mService.initialize();
mTestLooper.dispatchAll();
@@ -356,21 +381,14 @@
mTestLooper.dispatchAll();
assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
- verify(mMockPersistentSettings, never())
- .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(false));
+ assertThat(mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED)).isTrue();
}
@Test
- public void userRestriction_userBecomesNotRestricted_stateIsEnabledButNotPersisted() {
- AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+ public void userRestriction_userBecomesNotRestricted_stateIsEnabled() {
when(mMockUserManager.hasUserRestriction(eq(DISALLOW_THREAD_NETWORK))).thenReturn(true);
- doAnswer(
- invocation -> {
- receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
- return null;
- })
- .when(mContext)
- .registerReceiver(any(BroadcastReceiver.class), any(), any(), any());
+ AtomicReference<BroadcastReceiver> receiverRef =
+ captureBroadcastReceiver(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
mService.initialize();
mTestLooper.dispatchAll();
@@ -379,8 +397,6 @@
mTestLooper.dispatchAll();
assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
- verify(mMockPersistentSettings, never())
- .put(eq(ThreadPersistentSettings.THREAD_ENABLED.key), eq(true));
}
@Test
@@ -397,6 +413,118 @@
assertThat(failure.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
}
+ @Test
+ public void airplaneMode_initWithAirplaneModeOn_otDaemonNotStarted() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.isInitialized()).isFalse();
+ }
+
+ @Test
+ public void airplaneMode_initWithAirplaneModeOff_threadIsEnabled() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+ }
+
+ @Test
+ public void airplaneMode_changesFromOffToOn_stateIsDisabled() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+ AtomicReference<BroadcastReceiver> receiverRef =
+ captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+ }
+
+ @Test
+ public void airplaneMode_changesFromOnToOff_stateIsEnabled() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ AtomicReference<BroadcastReceiver> receiverRef =
+ captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+ }
+
+ @Test
+ public void airplaneMode_setEnabledWhenAirplaneModeIsOn_WillNotAutoDisableSecondTime() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ AtomicReference<BroadcastReceiver> receiverRef =
+ captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+ mService.initialize();
+
+ mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
+ mTestLooper.dispatchAll();
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_ENABLED);
+ assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isTrue();
+ }
+
+ @Test
+ public void airplaneMode_setDisabledWhenAirplaneModeIsOn_WillAutoDisableSecondTime() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ AtomicReference<BroadcastReceiver> receiverRef =
+ captureBroadcastReceiver(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
+ mService.initialize();
+ mService.setEnabled(true, newOperationReceiver(setEnabledFuture));
+ mTestLooper.dispatchAll();
+
+ mService.setEnabled(false, newOperationReceiver(setEnabledFuture));
+ mTestLooper.dispatchAll();
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ receiverRef.get().onReceive(mContext, new Intent());
+ mTestLooper.dispatchAll();
+
+ assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
+ assertThat(mPersistentSettings.get(THREAD_ENABLED_IN_AIRPLANE_MODE)).isFalse();
+ }
+
+ private AtomicReference<BroadcastReceiver> captureBroadcastReceiver(String action) {
+ AtomicReference<BroadcastReceiver> receiverRef = new AtomicReference<>();
+
+ doAnswer(
+ invocation -> {
+ receiverRef.set((BroadcastReceiver) invocation.getArguments()[0]);
+ return null;
+ })
+ .when(mContext)
+ .registerReceiver(
+ any(BroadcastReceiver.class),
+ argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
+ any(),
+ any());
+
+ return receiverRef;
+ }
+
private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
return new IOperationReceiver.Stub() {
@Override
@@ -412,6 +540,53 @@
}
@Test
+ public void
+ createRandomizedDataset_noNetworkTimeClock_datasetActiveTimestampIsNotAuthoritative()
+ throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock())
+ .thenThrow(new DateTimeException("fake throw"));
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isFalse();
+ }
+
+ @Test
+ public void createRandomizedDataset_hasNetworkTimeClock_datasetActiveTimestampIsAuthoritative()
+ throws Exception {
+ MockitoSession session =
+ ExtendedMockito.mockitoSession().mockStatic(SystemClock.class).startMocking();
+ final IActiveOperationalDatasetReceiver mockReceiver =
+ ExtendedMockito.mock(IActiveOperationalDatasetReceiver.class);
+
+ try {
+ ExtendedMockito.when(SystemClock.currentNetworkTimeClock())
+ .thenReturn(Clock.systemUTC());
+ mService.createRandomizedDataset(DEFAULT_NETWORK_NAME, mockReceiver);
+ mTestLooper.dispatchAll();
+ } finally {
+ session.finishMocking();
+ }
+
+ verify(mockReceiver, never()).onError(anyInt(), anyString());
+ verify(mockReceiver, times(1)).onSuccess(mActiveDatasetCaptor.capture());
+ ActiveOperationalDataset activeDataset = mActiveDatasetCaptor.getValue();
+ assertThat(activeDataset.getActiveTimestamp().isAuthoritativeSource()).isTrue();
+ }
+
+ @Test
public void createRandomizedDataset_succeed_activeDatasetCreated() throws Exception {
final IActiveOperationalDatasetReceiver mockReceiver =
mock(IActiveOperationalDatasetReceiver.class);
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
index 9f2d0cb..dfb3129 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkShellCommandTest.java
@@ -16,22 +16,29 @@
package com.android.server.thread;
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.contains;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.validateMockitoUsage;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.PendingOperationalDataset;
import android.os.Binder;
-import android.os.Process;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,6 +46,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -49,19 +57,43 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ThreadNetworkShellCommandTest {
- private static final String TAG = "ThreadNetworkShellCommandTTest";
- @Mock ThreadNetworkControllerService mControllerService;
- @Mock ThreadNetworkCountryCode mCountryCode;
- @Mock PrintWriter mErrorWriter;
- @Mock PrintWriter mOutputWriter;
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
+ // Active Timestamp: 1
+ // Channel: 19
+ // Channel Mask: 0x07FFF800
+ // Ext PAN ID: ACC214689BC40BDF
+ // Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
+ // Network Key: F26B3153760F519A63BAFDDFFC80D2AF
+ // Network Name: OpenThread-d9a0
+ // PAN ID: 0xD9A0
+ // PSKc: A245479C836D551B9CA557F7B9D351B4
+ // Security Policy: 672 onrcb
+ private static final String DEFAULT_ACTIVE_DATASET_TLVS =
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8";
- ThreadNetworkShellCommand mShellCommand;
+ @Mock private ThreadNetworkControllerService mControllerService;
+ @Mock private ThreadNetworkCountryCode mCountryCode;
+ @Mock private PrintWriter mErrorWriter;
+ @Mock private PrintWriter mOutputWriter;
+
+ private Context mContext;
+ private ThreadNetworkShellCommand mShellCommand;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mShellCommand = new ThreadNetworkShellCommand(mControllerService, mCountryCode);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ doNothing()
+ .when(mContext)
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+
+ mShellCommand = new ThreadNetworkShellCommand(mContext, mControllerService, mCountryCode);
mShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
}
@@ -71,8 +103,23 @@
}
@Test
- public void getCountryCode_executeInUnrootedShell_allowed() {
- BinderUtil.setUid(Process.SHELL_UID);
+ public void getCountryCode_testingPermissionIsChecked() {
+ when(mCountryCode.getCountryCode()).thenReturn("US");
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"get-country-code"});
+
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
+ }
+
+ @Test
+ public void getCountryCode_currentCountryCodePrinted() {
when(mCountryCode.getCountryCode()).thenReturn("US");
mShellCommand.exec(
@@ -86,9 +133,7 @@
}
@Test
- public void forceSetCountryCodeEnabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
+ public void forceSetCountryCodeEnabled_testingPermissionIsChecked() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -96,14 +141,13 @@
new FileDescriptor(),
new String[] {"force-country-code", "enabled", "US"});
- verify(mCountryCode, never()).setOverrideCountryCode(eq("US"));
- verify(mErrorWriter).println(contains("force-country-code"));
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
}
@Test
- public void forceSetCountryCodeEnabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
+ public void forceSetCountryCodeEnabled_countryCodeIsOverridden() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -115,24 +159,7 @@
}
@Test
- public void forceSetCountryCodeDisabled_executeInUnrootedShell_notAllowed() {
- BinderUtil.setUid(Process.SHELL_UID);
-
- mShellCommand.exec(
- new Binder(),
- new FileDescriptor(),
- new FileDescriptor(),
- new FileDescriptor(),
- new String[] {"force-country-code", "disabled"});
-
- verify(mCountryCode, never()).setOverrideCountryCode(any());
- verify(mErrorWriter).println(contains("force-country-code"));
- }
-
- @Test
- public void forceSetCountryCodeDisabled_executeInRootedShell_allowed() {
- BinderUtil.setUid(Process.ROOT_UID);
-
+ public void forceSetCountryCodeDisabled_overriddenCountryCodeIsCleared() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -144,9 +171,7 @@
}
@Test
- public void forceStopOtDaemon_executeInUnrootedShell_failedAndServiceApiNotCalled() {
- BinderUtil.setUid(Process.SHELL_UID);
-
+ public void forceStopOtDaemon_testingPermissionIsChecked() {
mShellCommand.exec(
new Binder(),
new FileDescriptor(),
@@ -154,14 +179,13 @@
new FileDescriptor(),
new String[] {"force-stop-ot-daemon", "enabled"});
- verify(mControllerService, never()).forceStopOtDaemonForTest(anyBoolean(), any());
- verify(mErrorWriter, atLeastOnce()).println(contains("force-stop-ot-daemon"));
- verify(mOutputWriter, never()).println();
+ verify(mContext, times(1))
+ .enforceCallingOrSelfPermission(
+ eq("android.permission.THREAD_NETWORK_TESTING"), anyString());
}
@Test
public void forceStopOtDaemon_serviceThrows_failed() {
- BinderUtil.setUid(Process.ROOT_UID);
doThrow(new SecurityException(""))
.when(mControllerService)
.forceStopOtDaemonForTest(eq(true), any());
@@ -179,7 +203,6 @@
@Test
public void forceStopOtDaemon_serviceApiTimeout_failedWithTimeoutError() {
- BinderUtil.setUid(Process.ROOT_UID);
doNothing().when(mControllerService).forceStopOtDaemonForTest(eq(true), any());
mShellCommand.exec(
@@ -193,4 +216,89 @@
verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
verify(mOutputWriter, never()).println();
}
+
+ @Test
+ public void join_controllerServiceJoinIsCalled() {
+ doNothing().when(mControllerService).join(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"join", DEFAULT_ACTIVE_DATASET_TLVS});
+
+ var activeDataset =
+ ActiveOperationalDataset.fromThreadTlvs(
+ base16().decode(DEFAULT_ACTIVE_DATASET_TLVS));
+ verify(mControllerService, times(1)).join(eq(activeDataset), any());
+ verify(mErrorWriter, never()).println();
+ }
+
+ @Test
+ public void join_invalidDataset_controllerServiceJoinIsNotCalled() {
+ doNothing().when(mControllerService).join(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"join", "000102"});
+
+ verify(mControllerService, never()).join(any(), any());
+ verify(mErrorWriter, times(1)).println(contains("Invalid dataset argument"));
+ }
+
+ @Test
+ public void migrate_controllerServiceMigrateIsCalled() {
+ doNothing().when(mControllerService).scheduleMigration(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"migrate", DEFAULT_ACTIVE_DATASET_TLVS, "300"});
+
+ ArgumentCaptor<PendingOperationalDataset> captor =
+ ArgumentCaptor.forClass(PendingOperationalDataset.class);
+ verify(mControllerService, times(1)).scheduleMigration(captor.capture(), any());
+ assertThat(captor.getValue().getActiveOperationalDataset())
+ .isEqualTo(
+ ActiveOperationalDataset.fromThreadTlvs(
+ base16().decode(DEFAULT_ACTIVE_DATASET_TLVS)));
+ assertThat(captor.getValue().getDelayTimer().toSeconds()).isEqualTo(300);
+ verify(mErrorWriter, never()).println();
+ }
+
+ @Test
+ public void migrate_invalidDataset_controllerServiceMigrateIsNotCalled() {
+ doNothing().when(mControllerService).scheduleMigration(any(), any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"migrate", "000102", "300"});
+
+ verify(mControllerService, never()).scheduleMigration(any(), any());
+ verify(mErrorWriter, times(1)).println(contains("Invalid dataset argument"));
+ }
+
+ @Test
+ public void leave_controllerServiceLeaveIsCalled() {
+ doNothing().when(mControllerService).leave(any());
+
+ mShellCommand.exec(
+ new Binder(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new FileDescriptor(),
+ new String[] {"leave"});
+
+ verify(mControllerService, times(1)).leave(any());
+ verify(mErrorWriter, never()).println();
+ }
}
diff --git a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
index bee9ceb..38a6e90 100644
--- a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
+++ b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
@@ -21,7 +21,6 @@
import static org.junit.Assume.assumeTrue;
import android.content.Context;
-import android.net.thread.ThreadNetworkManager;
import android.os.SystemProperties;
import android.os.VintfRuntimeInfo;
@@ -122,7 +121,10 @@
/** Returns {@code true} if this device has the Thread feature supported. */
private static boolean hasThreadFeature() {
final Context context = ApplicationProvider.getApplicationContext();
- return context.getSystemService(ThreadNetworkManager.class) != null;
+
+ // Use service name rather than `ThreadNetworkManager.class` to avoid
+ // `ClassNotFoundException` on U- devices.
+ return context.getSystemService("thread_network") != null;
}
/**
diff --git a/tools/aospify_device.sh b/tools/aospify_device.sh
new file mode 100755
index 0000000..0176093
--- /dev/null
+++ b/tools/aospify_device.sh
@@ -0,0 +1,176 @@
+#!/bin/bash
+
+# Script to swap core networking modules in a GMS userdebug device to AOSP modules, by remounting
+# the system partition and replacing module prebuilts. This is only to be used for local testing,
+# and should only be used on userdebug devices that support "adb root" and remounting the system
+# partition using overlayfs. The setup wizard should be cleared before running the script.
+#
+# Usage: aospify_device.sh [device_serial]
+#
+# Reset with "adb enable-verity", then wiping data (from Settings, or:
+# "adb reboot bootloader && fastboot erase userdata && fastboot reboot").
+# Some devices output errors like "Overlayfs teardown failed" on "enable-verity" but it still works
+# (/mnt/scratch should be deleted).
+#
+# This applies to NetworkStack, CaptivePortalLogin, dnsresolver, tethering, cellbroadcast modules,
+# which generally need to be preloaded together (core networking modules + cellbroadcast which
+# shares its certificates with NetworkStack and CaptivePortalLogin)
+#
+# This allows device manufacturers to test their changes in AOSP modules, running them on their
+# own device builds, before contributing contributing the patches to AOSP. After running this script
+# once AOSP modules can be quickly built and updated on the prepared device with:
+# m NetworkStack
+# adb install --staged $ANDROID_PRODUCT_OUT/system/priv-app/NetworkStack/NetworkStack.apk \
+# adb reboot
+# or for APEX modules:
+# m com.android.tethering deapexer
+# $ANDROID_HOST_OUT/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/com.android.tethering.capex --output /tmp/decompressed.apex
+# adb install /tmp/decompressed.apex && adb reboot
+#
+# This has been tested on Android T and Android U Pixel devices. On recent (U+) devices, it requires
+# setting a released target SDK (for example target_sdk_version: "34") in
+# packages/modules/Connectivity/service/ServiceConnectivityResources/Android.bp before building.
+set -e
+
+function push_apex {
+ local original_apex_name=$1
+ local aosp_apex_name=$2
+ if $ADB_CMD shell ls /system/apex/$original_apex_name.capex 1>/dev/null 2>/dev/null; then
+ $ADB_CMD shell rm /system/apex/$original_apex_name.capex
+ $ADB_CMD push $ANDROID_PRODUCT_OUT/system/apex/$aosp_apex_name.capex /system/apex/
+ else
+ rm -f /tmp/decompressed_$aosp_apex_name.apex
+ $ANDROID_HOST_OUT/bin/deapexer decompress --input $ANDROID_PRODUCT_OUT/system/apex/$aosp_apex_name.capex --output /tmp/decompressed_$aosp_apex_name.apex
+ if ! $ADB_CMD shell ls /system/apex/$original_apex_name.apex 1>/dev/null 2>/dev/null; then
+ # Filename observed on some phones, even though it is not actually compressed
+ original_apex_name=${original_apex_name}_compressed
+ fi
+ $ADB_CMD shell rm /system/apex/$original_apex_name.apex
+ $ADB_CMD push /tmp/decompressed_$aosp_apex_name.apex /system/apex/$aosp_apex_name.apex
+ rm /tmp/decompressed_$aosp_apex_name.apex
+ fi
+}
+
+function push_apk {
+ local app_type=$1
+ local original_apk_name=$2
+ local aosp_apk_name=$3
+ $ADB_CMD shell rm /system/$app_type/$original_apk_name/$original_apk_name*.apk
+ $ADB_CMD push $ANDROID_PRODUCT_OUT/system/$app_type/$aosp_apk_name/$aosp_apk_name.apk /system/$app_type/$original_apk_name/
+}
+
+NETWORKSTACK_AOSP_SEPOLICY_KEY="<signer signature=\"308205dc308203c4a003020102020900fc6cb0d8a6fdd16\
+8300d06092a864886f70d01010b0500308181310b30090603550406130255533113301106035504080c0a43616c69666f72\
+6e69613116301406035504070c0d4d6f756e7461696e20566965773110300e060355040a0c07416e64726f69643110300e0\
+60355040b0c07416e64726f69643121301f06035504030c18636f6d2e616e64726f69642e6e6574776f726b737461636b30\
+20170d3139303231323031343632305a180f34373537303130383031343632305a308181310b30090603550406130255533\
+113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e20566965773110300e06\
+0355040a0c07416e64726f69643110300e060355040b0c07416e64726f69643121301f06035504030c18636f6d2e616e647\
+26f69642e6e6574776f726b737461636b30820222300d06092a864886f70d01010105000382020f003082020a0282020100\
+bb71f5137ff0b2d757acc2ca3d378e0f8de11090d5caf3d49e314d35c283b778b02d792d8eba440364ca970985441660f0b\
+c00afbc63dd611b1bf51ad28a1edd21e0048f548b80f8bd113e25682822f57dab8273afaf12c64d19a0c6be238f3e66ddc7\
+9b10fd926931e3ee60a7bf618644da3c2c4fc428139d45d27beda7fe45e30075b493ead6ec01cdd55d931c0a657e2e59742\
+ca632b6dc3842a2deb7d22443c809291d7a549203ae6ae356582a4ca23f30f0549c4ec8408a75278e95c69e8390ad5280bc\
+efaef6f1309a41bd9f3bfb5d12dca7e79ec6fd6848193fa9ab728224887b4f93e985ec7cbf6401b0e863a4b91c05d046f04\
+0fe954004b1645954fcb4114cee1e8b64b47d719a19ef4c001cb183f7f3e166e43f56d68047c3440da34fdf529d44274b8b\
+2f6afb345091ad8ad4b93bd5c55d52286a5d3c157465db8ddf62e7cdb6b10fb18888046afdd263ae6f2125d9065759c7e42\
+f8610a6746edbdc547d4301612eeec3c3cbd124dececc8d38b20e73b13f24ee7ca13a98c5f61f0c81b07d2b519749bc2bcb\
+9e0949aef6c118a3e8125e6ab57fce46bb091a66740e10b31c740b891900c0ecda9cc69ecb4f3369998b175106dd0a4ffd7\
+024eb7e75fedd1a5b131d0bb2b40c63491e3cf86b8957b21521b3a96ed1376a51a6ac697866b0256dee1bcd9ab9a188bf4c\
+ed80b59a5f24c2da9a55eb7b0e502116e30203010001a3533051301d0603551d0e041604149383c92cfbf099d5c47b0c365\
+7d8622a084b72e1301f0603551d230418301680149383c92cfbf099d5c47b0c3657d8622a084b72e1300f0603551d130101\
+ff040530030101ff300d06092a864886f70d01010b050003820201006a0501382fde2a6b8f70c60cd1b8ee4f788718c288b\
+170258ef3a96230b65005650d6a4c42a59a97b2ddec502413e7b438fbd060363d74b74a232382a7f77fd3da34e38f79fad0\
+35a8b472c5cff365818a0118d87fa1e31cc7ed4befd27628760c290980c3cc3b7ff0cfd01b75ff1fcc83e981b5b25a54d85\
+b68a80424ac26015fb3a4c754969a71174c0bc283f6c88191dced609e245f5938ffd0ad799198e2d0bf6342221c1b0a5d33\
+2ed2fffc668982cabbcb7d3b630ff8476e5c84ac0ad37adf9224035200039f95ec1fa95bf83796c0e8986135cee2dcaef19\
+0b249855a7e7397d4a0bf17ea63d978589c6b48118a381fffbd790c44d80233e2e35292a3b5533ca3f2cc173f85cf904adf\
+e2e4e2183dc1eba0ebae07b839a81ff1bc92e292550957c8599af21e9c0497b9234ce345f3f508b1cc872aa55ddb5e773c5\
+c7dd6577b9a8b6daed20ae1ff4b8206fd9f5c8f5a22ba1980bef01ae6fcb2659b97ad5b985fa81c019ffe008ddd9c8130c0\
+6fc6032b2149c2209fc438a7e8c3b20ce03650ad31c4ee48f169777a0ae182b72ca31b81540f61f167d8d7adf4f6bb2330f\
+f5c24037245000d8172c12ab5d5aa5890b8b12db0f0e7296264eb66e7f9714c31004649fb4b864005f9c43c80db3f6de52f\
+d44d6e2036bfe7f5807156ed5ab591d06fd6bb93ba4334ea2739af8b41ed2686454e60b666d10738bb7ba88001\">\
+<seinfo value=\"network_stack\"\/><\/signer>"
+
+DEVICE=$1
+ADB_CMD="adb -s $DEVICE"
+
+if [ -z "$DEVICE" ]; then
+ echo "Usage: aospify_device.sh [device_serial]"
+ exit 1
+fi
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Run build/envsetup.sh first to set ANDROID_BUILD_TOP"
+ exit 1
+fi
+
+if ! $ADB_CMD wait-for-device shell pm path com.google.android.networkstack 1>/dev/null 2>/dev/null; then
+ echo "This device is already not using GMS modules"
+ exit 1
+fi
+
+read -p "This script is only for test purposes and highly likely to make your device unusable. \
+Continue ? <y/N>" prompt
+if [[ $prompt != "y" ]]
+then
+ exit 0
+fi
+
+cd $ANDROID_BUILD_TOP
+source build/envsetup.sh
+lunch aosp_arm64-trunk_staging-userdebug
+m NetworkStack CaptivePortalLogin com.android.tethering com.android.cellbroadcast \
+ com.android.resolv deapexer \
+ out/target/product/generic_arm64/system/etc/selinux/plat_mac_permissions.xml \
+ out/target/product/generic_arm64/system/etc/permissions/com.android.networkstack.xml
+
+$ADB_CMD root
+$ADB_CMD remount
+$ADB_CMD reboot
+
+echo "Waiting for boot..."
+until [[ $($ADB_CMD wait-for-device shell getprop sys.boot_completed) == 1 ]]; do
+ sleep 1;
+done
+
+$ADB_CMD root
+$ADB_CMD remount
+
+push_apk priv-app NetworkStackGoogle NetworkStack
+push_apk app CaptivePortalLoginGoogle CaptivePortalLogin
+push_apex com.google.android.tethering com.android.tethering
+push_apex com.google.android.cellbroadcast com.android.cellbroadcast
+push_apex com.google.android.resolv com.android.resolv
+
+# Replace the network_stack key used to set its sepolicy context
+rm -f /tmp/pulled_plat_mac_permissions.xml
+$ADB_CMD pull /system/etc/selinux/plat_mac_permissions.xml /tmp/pulled_plat_mac_permissions.xml
+sed_replace='s/<signer signature="[0-9a-fA-F]+"><seinfo value="network_stack"\/><\/signer>/'$NETWORKSTACK_AOSP_SEPOLICY_KEY'/'
+sed -E "$sed_replace" /tmp/pulled_plat_mac_permissions.xml |
+ $ADB_CMD shell 'cat > /system/etc/selinux/plat_mac_permissions.xml'
+rm /tmp/pulled_plat_mac_permissions.xml
+
+# Update the networkstack privapp-permissions allowlist
+rm -f /tmp/pulled_privapp-permissions.xml
+networkstack_permissions=/system/etc/permissions/GoogleNetworkStack_permissions.xml
+if ! $ADB_CMD shell ls $networkstack_permissions 1>/dev/null 2>/dev/null; then
+ networkstack_permissions=/system/etc/permissions/privapp-permissions-google.xml
+fi
+
+$ADB_CMD pull $networkstack_permissions /tmp/pulled_privapp-permissions.xml
+
+# Remove last </permission> line, and the permissions for com.google.android.networkstack
+sed -nE '1,/<\/permissions>/p' /tmp/pulled_privapp-permissions.xml \
+ | sed -E '/com.google.android.networkstack/,/privapp-permissions/d' > /tmp/modified_privapp-permissions.xml
+# Add the AOSP permissions and re-add the </permissions> line
+sed -nE '/com.android.networkstack/,/privapp-permissions/p' $ANDROID_PRODUCT_OUT/system/etc/permissions/com.android.networkstack.xml \
+ >> /tmp/modified_privapp-permissions.xml
+echo '</permissions>' >> /tmp/modified_privapp-permissions.xml
+
+$ADB_CMD push /tmp/modified_privapp-permissions.xml $networkstack_permissions
+
+rm /tmp/pulled_privapp-permissions.xml /tmp/modified_privapp-permissions.xml
+
+echo "Done modifying, rebooting"
+$ADB_CMD reboot
\ No newline at end of file